Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
957 lines (825 sloc) 29.2 KB
// -------------------------------------- //
// KNOCKOUT by domino54 //
// script version: 1.3.0 //
// -------------------------------------- //
#Extends "Modes/TrackMania/RoundsBase.Script.txt"
#Const Version "2015-06-27"
#Const ScriptName "Knockout.Script.txt"
// Custom libs
#Include "Libs/domino54/SentenceBank.Script.txt" as SentenceBank
#Include "Libs/domino54/Translations.Script.txt" as Translations
#Include "Libs/domino54/UpdateChecker.Script.txt" as UpdateChecker
// ---------------------------------- //
// Settings
// ---------------------------------- //
#Setting S_RoundsPerMap 0 as _("Number of rounds per map (0 = infinite):")
#Setting S_DoubleKnockUntil 20 as _("DoubleKO - knock 2 players until (0 = disabled):")
#Setting S_DebugBotsCount 0 as _("Number of fake players (debug only):")
#Setting S_AdminHoldStart False as "<hidden>" ///< Allows event admin to pause start of the next Knockout
#Setting S_AdminSetPause False as "<hidden>" ///< Allows event admin to pause match before next round
#Setting S_ShowMultilapInfo True as "<hidden>" ///< Laps info visible if laps count > 0
#Setting S_CustomLayerPath "" as "<hidden>" ///< Try to load custom layer from an XML file
// Change default values
#Setting S_ForceLapsNb 0
#Setting S_FinishTimeout 20
// ---------------------------------- //
// Constants
// ---------------------------------- //
#Const C_RequiredPlayersNb 2 ///< Minimum players amount to start the game
#Const Description _("""$FFFIn $<$F00Knockout$> mode, slowest players are knock out.
In $<$F00Rounds$> mode, each round the slowest player or all players that not finished are $<$F00knock out$>.
In $<$F00Laps$> mode, every time first player begins next lap, the player with least passed checkpoints is $<$F00knock out$>.
In both modes, if someone $<$F00give up$> all players that finished in Rounds or everyone in Laps mode are safe.
The $<$F00winner$> is the last player not knocked out.""")
#Const C_ModeStatusMessage _("""TYPE: Free for all
OBJECTIVE: Each round or lap slowest player is knock out.
STATUS: %1 players racing, %2 spectators
""")
/// Colors of player slots in scores table
#Const C_CardColors [
"Alive" => <.5, .5, .5>, ///< Players still racing
"Dead" => <0., 0., 0.>, ///< Spectators and players not racing
"Safe" => <0., 1., 0.>, ///< Round end: player survived
"Knock" => <1., 0., 0.> ///< Round end: player eliminated
]
#Const C_UI_RequestTimeout 10000 ///< Time before UI layer request is canceled
// ---------------------------------- //
// Globals
// ---------------------------------- //
declare Text[] G_PlayersRacing; ///< Logins of racing players
// Xml load
declare Ident G_UI_RequestId; ///< Id of the request to the xml file
declare Integer G_UI_RequestEndTime; ///< Timeout for the request
// ---------------------------------- //
// Extend
// ---------------------------------- //
***LogVersion***
***
MB_LogVersion(ScriptName, Version);
***
// ---------------------------------- //
// Update Checker
// ---------------------------------- //
***UpdateChecker***
***
UpdateChecker::SetAndLogScriptVersions([
ScriptName => Version,
SentenceBank::GetScriptName() => SentenceBank::GetScriptVersion(),
Translations::GetScriptName() => Translations::GetScriptVersion(),
UpdateChecker::GetScriptName() => UpdateChecker::GetScriptVersion()
]);
***
// ---------------------------------- //
// Start server
// ---------------------------------- //
***StartServer***
***
// Variables
declare Integer[] RoundFinishTimes; ///< Players finish times order
declare Integer RoundGiveUpsAmount; ///< Number of players that gave up in current round/lap
declare Integer[] LapPlayersNbDoubled; ///< Amount of players doubled in a lap
declare Integer PrevMostLapsNb; ///< Previous maximum reached laps count
declare Integer PrevPlayersCount; ///< Previous players count
// ---------------------------------- //
// XmlRpc
XmlRpc::RegisterCallback("KOPlayerAdded", """
* Data : An array with the login of the player added to the match
* Example : ["Login"]
* Note : This callback is sent when Knockout begins.
""");
XmlRpc::RegisterCallback("KOPlayerRemoved", """
* Data : An array with the login of the player removed from the match
* Example : ["Login"]
* Note : This callback is sent when a player is knocked out.
""");
XmlRpc::RegisterCallback("KOSendWinner", """
* Data : An array with the login of the match winner
* Example : ["Login"]
* Note : This callback is sent when match is over.
""");
// Interface
UI::UnloadModule("PrevBestTime");
UIManager.UIAll.OverlayHidePersonnalBestAndRank = True;
// Custom layer
Layers::Create("Custom");
Layers::Attach("Custom");
SetCustomLayerFromXml(S_CustomLayerPath);
// ---------------------------------- //
// Scores Table
ST2::SetStyle("LibST_TMBaseSolo");
SetScoresTableLayout();
ST2::CreateCol("KO_Checkpoints", "CP", "", 3., 80.);
ST2::CreateCol("KO_Time", _("Time"), "", 7., 90.);
MB_SetScoresTableStyleFromXml(S_ScoresTableStylePath);
ST2::Build("TM");
// Check available updates
UpdateChecker::Load();
+++UpdateChecker+++
***
// ---------------------------------- //
// Yield
// ---------------------------------- //
***Yield***
***
Users_SetNbFakeUsers(S_DebugBotsCount, 0);
Message::Loop();
***
// ---------------------------------- //
// Init map
// ---------------------------------- //
***InitMap***
***
if (GetRacingPlayersAmount() <= 1) {
Scores_Clear();
G_PlayersRacing.clear();
}
UIManager.UIAll.OverlayHideMultilapInfos = (S_ShowMultilapInfo && S_ForceLapsNb <= 0);
ST2::ClearScores();
foreach (Score in Scores) Score.BestRace = Null;
***
// ---------------------------------- //
// Start map
// ---------------------------------- //
***StartMap***
***
UpdatePlayerColors();
UpdateChecker::CheckUpdate();
// ---------------------------------- //
// Wait for enough players
WaitForPlayers(C_RequiredPlayersNb);
// ---------------------------------- //
// Warm up
declare WarmUpTimeLimit = -1;
if (S_UseAlternateRules) {
G_RoundStartTime = Now + 3000;
WarmUpTimeLimit = (GetFinishTimeout() - Now) / 1000;
}
MB_WarmUp(WarmUpTimeLimit);
UpdatePlayerColors();
// ---------------------------------- //
// Start new Knockout
if (GetRacingPlayersAmount() <= 1) {
StartKnockout();
Message::SendBigMessage(_("New match"), 3000, 0);
}
if (!ServerShutdownRequested && !MatchEndRequested) MB_Sleep(3000);
***
// ---------------------------------- //
// Start round
// ---------------------------------- //
***StartRound***
***
RemoveDisconnected();
UpdateScoresTableFooter();
ST2::ClearScores();
Scores_Sort(CTmMode::ETmScoreSortOrder::TotalPoints);
UpdatePlayerColors();
// Clear variables
RoundFinishTimes.clear();
RoundGiveUpsAmount = 0;
LapPlayersNbDoubled.clear();
PrevMostLapsNb = 0;
PrevPlayersCount = -1;
// ---------------------------------- //
// Clear scores
foreach (Score in Scores) {
Score.PrevRace = Null;
Score.BestRace = Null;
}
// ---------------------------------- //
// Pause
if (S_AdminSetPause) DoPause();
// ---------------------------------- //
// Starts race only for racing players
foreach (Player in Players) {
declare Boolean HasFinishedTrack for Player;
HasFinishedTrack = False;
// Spawn them
if (IsRacing(Player.User)) TM2::StartRace(Player);
else TM2::WaitRace(Player);
}
// Set laps amount
UIManager.UIAll.OverlayHideMultilapInfos = (S_ShowMultilapInfo && S_ForceLapsNb <= 0);
if (!MapIsLapRace) NbLaps = 1;
else if (S_ForceLapsNb > 0) NbLaps = S_ForceLapsNb;
else NbLaps = 1000;
// ---------------------------------- //
// Message
declare TotalRoundsNb = S_RoundsPerMap;
if (MapIsLapRace || S_RoundsPerMap <= 0) TotalRoundsNb = 1;
declare RoundsMessage = MB_SectionRoundNb^" / "^TotalRoundsNb;
if (S_RoundsPerMap <= 0 || MapIsLapRace) RoundsMessage = TL::ToText(MB_SectionRoundNb);
Message::SendBigMessage(TL::Compose(_("Round %1"), RoundsMessage), 3000, 0);
// Sequence
UIManager.UIAll.UISequence = CUIConfig::EUISequence::Playing;
***
// ---------------------------------- //
// Play loop
// ---------------------------------- //
***PlayLoop***
***
// ---------------------------------- //
// Manage events
foreach (Event in PendingEvents) {
PassOn(Event);
XmlRpc::PassOn(Event);
// ---------------------------------- //
// Waypoint
if (Event.Type == CTmModeEvent::EType::WayPoint) {
// ---------------------------------- //
// Scores table
if (Event.Player.Score != Null) {
// ---------------------------------- //
// Sort scores by crossed checkpoints amount
ST2::SetColValue("KO_Checkpoints", Event.Player.Score, TL::ToText(Event.Player.CurRace.Checkpoints.count));
Event.Player.Score.BestRace = Event.Player.CurRace;
Scores_Sort(CTmMode::ETmScoreSortOrder::BestRace_CheckpointsProgress);
// ---------------------------------- //
// Laps mode
if (MapIsLapRace) ST2::SetColValue("KO_Time", Event.Player.Score, TimeToText(Event.Player.CurRace.Time));
}
// ---------------------------------- //
// Race finish
if (Event.IsEndRace) {
ST2::SetColValue("KO_Time", Event.Player.Score, TimeToText(Event.Player.CurRace.Time));
TM2::EndRace(Event.Player);
RoundFinishTimes.add(Event.Player.CurRace.Time);
// Rounds: Player finished track
declare Boolean HasFinishedTrack for Event.Player;
HasFinishedTrack = True;
// ---------------------------------- //
// Start the countdown if it's the first player to finish
if (CutOffTimeLimit <= 0) {
Message::SendStatusMessage(TL::Compose(_("$<%1$> wins the round!"), Event.Player.User.Name), 4000, 2);
CutOffTimeLimit = GetFinishTimeout();
}
}
// ---------------------------------- //
// Laps mode
if (MapIsLapRace && Event.IsEndLap && !MB_StopRound) {
// ---------------------------------- //
// New lap message
if (Event.Player.CurrentNbLaps > PrevMostLapsNb) {
PrevMostLapsNb = Event.Player.CurrentNbLaps;
Translations::SendStatusMessage(["$<%1$> has finished lap %2!", Event.Player.User.Name, TL::ToText(Event.Player.CurrentNbLaps)], 4000, 1);
}
// ---------------------------------- //
// Do the Laps Knockout function
---Laps---
// Stop the round when last player remains
if (GetRacingPlayersAmount() <= 1) MB_StopRound = True;
}
}
// ---------------------------------- //
// GiveUp
else if (Event.Type == CTmModeEvent::EType::GiveUp) {
if (Event.Player.IsSpawned && IsRacing(Event.Player.User) && Event.Player.CurrentNbLaps < NbLaps) {
// Knockout and unspawn
TM2::WaitRace(Event.Player);
KnockoutPlayer(Event.Player, False);
RoundGiveUpsAmount += 1;
// Send messages
Translations::SendStatusMessage(["$<%1$> gives up!", Event.Player.User.Name], 4000, 0);
Translations::SendChat(["$<%1$> gives up and is knocked out!", Event.Player.User.Name]);
ST2::SetColValue("KO_Time", Event.Player.Score, _("Give up"));
}
// Avoid players giving up after finish
else Event.Player.IsSpawned = False;
}
}
// ---------------------------------- //
// Unspawn players joining the game
foreach (Player in Players) {
if (Player.IsSpawned && !IsRacing(Player.User)) {
TM2::WaitRace(Player);
Player.IsSpawned = False;
}
}
// ---------------------------------- //
// Remove disconnected players
if (PrevPlayersCount != Players.count) {
PrevPlayersCount = Players.count;
RemoveDisconnected();
UpdateScoresTableFooter();
}
// Stop round
if (GetRacingPlayersAmount() <= 1 || RoundFinishTimes.count == GetRacingPlayersAmount()) MB_StopRound = True;
***
// ---------------------------------- //
// Laps: when player cross finish line
// ---------------------------------- //
***Laps***
***
// ---------------------------------- //
// Get highest and lowest amount of laps finished by players
declare Integer HighestLapsNb;
declare Integer LowestLapsNb;
HighestLapsNb = 0;
LowestLapsNb = 1000;
foreach (Player in Players) {
if (IsRacing(Player.User)) {
if (Player.CurrentNbLaps < LowestLapsNb) LowestLapsNb = Player.CurrentNbLaps;
if (Player.CurrentNbLaps > HighestLapsNb) HighestLapsNb = Player.CurrentNbLaps;
}
}
// ---------------------------------- //
// Get amount of doubled players
declare Integer PlayersNbDoubled;
PlayersNbDoubled = 0;
if (HighestLapsNb - LowestLapsNb >= 2) {
foreach (Player in Players) {
// Knock doubled player
if (Player.CurrentNbLaps == LowestLapsNb && IsRacing(Player.User)) {
KnockoutPlayer(Player, False);
Translations::SendChat(["$<%1$> knocked out, doubled by $<%2$>!", Player.User.Name, Event.Player.User.Name]);
PlayersNbDoubled += 1;
}
}
}
if (LapPlayersNbDoubled.count < Event.Player.CurrentNbLaps) LapPlayersNbDoubled.add(PlayersNbDoubled);
// ---------------------------------- //
// Knock last player if nobody was doubled
if (LapPlayersNbDoubled[Event.Player.CurrentNbLaps-1] == 0) {
// ---------------------------------- //
// Don't knock last player, if someone gave up
if (RoundGiveUpsAmount > 0) {
Translations::SendChat(["Noone is knocked out: %1 player(s) gave up!", TL::ToText(RoundGiveUpsAmount)]);
RoundGiveUpsAmount = 0;
}
// ---------------------------------- //
// Knockout the slowest player when someone cross finish before him
else {
declare CTmPlayer[] PlayersToCheck;
PlayersToCheck.clear();
foreach (Player in Players) {
if (IsRacing(Player.User) && Player.CurrentNbLaps == LowestLapsNb) PlayersToCheck.add(Player);
}
declare Boolean IsDoubleKO;
IsDoubleKO = (S_DoubleKnockUntil > 0 && GetRacingPlayersAmount() > S_DoubleKnockUntil && GetRacingPlayersAmount() >= 3);
// DoubleKO
if (IsDoubleKO && PlayersToCheck.count == 2) {
for (I, 0, 1) {
KnockoutPlayer(PlayersToCheck[0], True);
declare Removed = PlayersToCheck.removekey(0);
}
}
// Normal mode
else if (PlayersToCheck.count == 1) KnockoutPlayer(PlayersToCheck[0], True);
}
}
***
// ---------------------------------- //
// Time Attack minigame
// ---------------------------------- //
***TimeAttack***
***
// ---------------------------------- //
// Manage events
foreach (Event in PendingEvents) {
PassOn(Event);
XmlRpc::PassOn(Event);
// ---------------------------------- //
// Waypoint
if (Event.Type == CTmModeEvent::EType::WayPoint) {
if (Event.IsEndRace) {
if (Event.Player.Score != Null && Event.Player.Score.BestRace.Compare(Event.Player.CurRace, CTmResult::ETmRaceResultCriteria::Time) <= 0) {
Event.Player.Score.BestRace = Event.Player.CurRace;
ST2::SetColValue("KO_Time", Event.Player.Score, TimeToText(Event.Player.Score.BestRace.Time));
Scores_Sort(CTmMode::ETmScoreSortOrder::BestRace_Time);
}
TM2::WaitRace(Event.Player);
}
}
// ---------------------------------- //
// GiveUp
else if (Event.Type == CTmModeEvent::EType::GiveUp) {
TM2::WaitRace(Event.Player);
}
}
// ---------------------------------- //
// Spawn players
foreach (Player in Players) if (TM2::IsWaiting(Player)) TM2::StartRace(Player);
***
// ---------------------------------- //
// End round
// ---------------------------------- //
***EndRound***
***
TM2::WaitRaceAll();
CutOffTimeLimit = -1;
RemoveDisconnected();
UpdateScoresTableFooter();
// ---------------------------------- //
// Knock only in Rounds mode
if (!MapIsLapRace && !ServerShutdownRequested && !MatchEndRequested) {
declare Integer KnockedCount;
KnockedCount = 0;
// ---------------------------------- //
// Knock all DNF
foreach (Player in Players) {
declare Boolean HasFinishedTrack for Player;
if (GetRacingPlayersAmount() > 1 && !HasFinishedTrack && IsRacing(Player.User)) {
KnockedCount += 1;
ST2::SetColValue("KO_Time", Player.Score, "DNF");
KnockoutPlayer(Player, True);
}
}
if (KnockedCount == 0) {
// ---------------------------------- //
// Everyone finished, but someone gave up
if (RoundGiveUpsAmount > 0) {
Translations::SendChat(["Noone is knocked out: %1 player(s) gave up!", TL::ToText(RoundGiveUpsAmount)]);
}
// ---------------------------------- //
// Knock latest player if all finished and noone gave up
else if (GetRacingPlayersAmount() > 1 && RoundFinishTimes.count > 0) {
declare Integer Multiplier;
Multiplier = 1;
if (S_DoubleKnockUntil > 0 && GetRacingPlayersAmount() > S_DoubleKnockUntil && GetRacingPlayersAmount() >= 3) Multiplier = 2;
foreach (Player in Players) {
for (I, 0, Multiplier-1) {
if (Player.CurRace.Time == RoundFinishTimes[RoundFinishTimes.count - 1]) {
KnockoutPlayer(Player, True);
declare Removed = RoundFinishTimes.remove(Player.CurRace.Time);
}
}
}
}
}
}
// ---------------------------------- //
// Passed players color
foreach (Score in Scores) if (IsRacing(Score.User)) ST2::SetPlayerColor(Score, C_CardColors["Safe"]);
// Stop map
if (GetRacingPlayersAmount() <= 1 || (S_RoundsPerMap > 0 && MB_SectionRoundNb >= S_RoundsPerMap) || MapIsLapRace) MB_StopMap = True;
// End sequence
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
UIManager.UIAll.UISequence = CUIConfig::EUISequence::EndRound;
if (!ServerShutdownRequested && !MatchEndRequested) MB_Sleep(5000);
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::Normal;
UIManager.UIAll.UISequence = CUIConfig::EUISequence::None;
***
// ---------------------------------- //
// Map end
// ---------------------------------- //
***EndMap***
***
Scores_Sort(CTmMode::ETmScoreSortOrder::TotalPoints);
ST2::ClearScores();
XmlRpc::SendPlayersScores();
UpdatePlayerColors();
RemoveDisconnected();
// ---------------------------------- //
// Knockout going on...
if (GetRacingPlayersAmount() > 1) {
MB_VictoryMessage = _("Going to next map");
}
// ---------------------------------- //
// Close ladder
else {
Ladder_ComputeRank(CTmMode::ETmScoreSortOrder::TotalPoints);
MB_Ladder_CloseMatch();
// ---------------------------------- //
// Last player
if (GetRacingPlayersAmount() == 1) {
declare CTmPlayer Winner = TM2::GetPlayer(G_PlayersRacing[0]);
if (Winner != Null) MB_VictoryMessage = TL::Compose(_("$<%1$> wins the match!"), Winner.User.Name);
else MB_VictoryMessage = _("Winner ragequit!");
XmlRpc_KOSendWinner(Winner);
}
// No players?!
else MB_VictoryMessage = _("|Match|Draw");
}
***
// ---------------------------------- //
// End server
// ---------------------------------- //
***EndServer***
***
UpdateChecker::Unload();
Layers::Detach("Custom");
Layers::Destroy("Custom");
XmlRpc::UnregisterCallback("KOPlayerAdded");
XmlRpc::UnregisterCallback("KOPlayerRemoved");
XmlRpc::UnregisterCallback("KOSendWinner");
***
// ---------------------------------- //
// Functions
// ---------------------------------- //
// ---------------------------------- //
// XmlRpc
// ---------------------------------- //
// ---------------------------------- //
/** Callback sent when a player is added to match
* Data:
* [Player login]
*/
Void XmlRpc_KOPlayerAdded(CTmPlayer _Player) {
if (!XmlRpc::CallbackIsAllowed("KOPlayerAdded")) return;
declare PlayerLogin = "Null";
if (_Player != Null) PlayerLogin = _Player.User.Login;
declare Data = [PlayerLogin];
XmlRpc::SendCallbackArray("KOPlayerAdded", Data);
}
// ---------------------------------- //
/** Callback sent when a player is removed from match
* Data:
* [Player login]
*/
Void XmlRpc_KOPlayerRemoved(CTmPlayer _Player) {
if (!XmlRpc::CallbackIsAllowed("KOPlayerRemoved")) return;
declare PlayerLogin = "Null";
if (_Player != Null) PlayerLogin = _Player.User.Login;
declare Data = [PlayerLogin];
XmlRpc::SendCallbackArray("KOPlayerRemoved", Data);
}
// ---------------------------------- //
/** Callback sent when a player is removed from match
* Data:
* [Player login]
*/
Void XmlRpc_KOSendWinner(CTmPlayer _Player) {
if (!XmlRpc::CallbackIsAllowed("KOSendWinner")) return;
declare PlayerLogin = "Null";
if (_Player != Null) PlayerLogin = _Player.User.Login;
declare Data = [PlayerLogin];
XmlRpc::SendCallbackArray("KOSendWinner", Data);
}
// ---------------------------------- //
// Knockout
// ---------------------------------- //
// ---------------------------------- //
/// Start new Knockout (add players)
Void StartKnockout() {
G_PlayersRacing.clear();
foreach (Player in Players) {
G_PlayersRacing.add(Player.User.Login);
XmlRpc_KOPlayerAdded(Player);
}
}
// ---------------------------------- //
/** Check if player is still racing
*
* @param _User Player to check status
*
* @return True if player isn't knocked
*/
Boolean IsRacing(CUser _User) { return (_User != Null && G_PlayersRacing.exists(_User.Login)); }
// ---------------------------------- //
/** Get amount of racing players
*
* @return Amount of players still racing
*/
Integer GetRacingPlayersAmount() { return G_PlayersRacing.count; }
// ---------------------------------- //
/// Update the scores table footer text
Void UpdateScoresTableFooter() {
// Update rounds count
declare TotalRoundsNb = S_RoundsPerMap;
if (MapIsLapRace || S_RoundsPerMap <= 0) TotalRoundsNb = 1;
// Get rounds message
declare RoundsMessage = MB_SectionRoundNb^" / "^TotalRoundsNb;
if (TotalRoundsNb <= 1) RoundsMessage = TL::ToText(MB_SectionRoundNb);
// DoubleKO S_DoubleKnockUntil
declare DoubleKOMessage = "";
if (S_DoubleKnockUntil >= 3 && GetRacingPlayersAmount() > S_DoubleKnockUntil) DoubleKOMessage ^= TL::ToText(S_DoubleKnockUntil);
else DoubleKOMessage = _("|FxMotionBlur|Off");
// Compose footer message
ST2::SetFooterText(TL::Compose("%1 %2 %3"^GetRacingPlayersAmount()^" DoubleKO: %4", _("Round"), RoundsMessage, _("Remaining players: "), DoubleKOMessage));
// Update mode status message
declare SpectatorsAmount = Spectators.count + Players.count - GetRacingPlayersAmount();
ModeStatusMessage = TL::Compose(C_ModeStatusMessage, TL::ToText(GetRacingPlayersAmount()), TL::ToText(SpectatorsAmount));
}
// ---------------------------------- //
/** Knock player out from the game
*
* @param _Player The player to knock out
*/
Void KnockoutPlayer(CTmPlayer _Player, Boolean _UseMessage) {
if (_Player != Null && IsRacing(_Player.User)) {
declare Removed = G_PlayersRacing.remove(_Player.User.Login);
// Scores table
ST2::SetPlayerColor(_Player.Score, C_CardColors["Knock"]);
Scores_Sort(CTmMode::ETmScoreSortOrder::BestRace_CheckpointsProgress);
UpdateScoresTableFooter();
if (MapIsLapRace) ST2::SetColValue("KO_Time", _Player.Score, _("Kick"));
XmlRpc_KOPlayerRemoved(_Player);
if (_UseMessage) Translations::SendChat(["<%1$> knocked out!", _Player.User.Name]);
// +1 for other players
foreach (Score in Scores) if (G_PlayersRacing.exists(Score.User.Login)) Score.Points += 1;
}
else return;
}
// ---------------------------------- //
/// Remove all disconnected players from array
Void RemoveDisconnected() {
// Get logins of all connected players
declare Text[] ConnectedLogins;
ConnectedLogins.clear();
foreach (Player in Players) ConnectedLogins.add(Player.User.Login);
// Copy alive players logins
declare Text[] AliveLogins;
AliveLogins.clear();
foreach (Login in G_PlayersRacing) AliveLogins.add(Login);
// Remove login if doesn't exists in connected array
foreach (Login in AliveLogins)
if (!ConnectedLogins.exists(Login)) {
declare Removed = G_PlayersRacing.remove(Login);
}
}
// ---------------------------------- //
// Other
// ---------------------------------- //
// ---------------------------------- //
/// Update player colors
Void UpdatePlayerColors() {
foreach (Score in Scores) {
if (IsRacing(Score.User)) ST2::SetPlayerColor(Score, C_CardColors["Alive"]);
else ST2::SetPlayerColor(Score, C_CardColors["Dead"]);
}
}
// ---------------------------------- //
/** Convert time integer to text
*
* @param _Time Time to convert
*/
Text TimeToText(Integer _Time) {
declare Time = TL::TimeToText(_Time, True);
if (_Time >= 0) Time ^= _Time%10;
return Time;
}
// ---------------------------------- //
/** Wait for enough players to launch a round
*
* @param _MinimumNbPlayers Minimum players required to start
*/
Void WaitForPlayers(Integer _MinimumNbPlayers) {
UIManager.UIAll.UISequence = CUIConfig::EUISequence::None;
UIManager.UIAll.BigMessage = _("Waiting for players...");
declare HasWaited = False;
declare WasHold = False;
// Hold start message
if (S_AdminHoldStart) {
UIManager.UIAll.BigMessage = "";
UIManager.UIAll.StatusMessage = _("Waiting for start confirmation by admin");
}
// Time Attack minigame
while (!ServerShutdownRequested && !MatchEndRequested && (Players.count < _MinimumNbPlayers || S_AdminHoldStart)) {
MB_Yield();
HasWaited = True;
WasHold = S_AdminHoldStart;
---TimeAttack---
}
if (HasWaited) {
TM2::WaitRaceAll();
UIManager.UIAll.BigMessage = _("A new player joins the game.");
// Ignore waiting if map is skipped
if (!ServerShutdownRequested && !MatchEndRequested) MB_Sleep(5000);
}
// Resumed by admin
if (WasHold) UIManager.UIAll.BigMessage = _("Competition is starting!");
UIManager.UIAll.StatusMessage = "";
UIManager.UIAll.BigMessage = "";
}
// ---------------------------------- //
/// Set mode pause (FFA Time Attack minigame)
Void DoPause() {
UIManager.UIAll.UISequence = CUIConfig::EUISequence::None;
UIManager.UIAll.BigMessage = TL::Compose("$f90%1", _("Pause"));
UIManager.UIAll.StatusMessage = _("Competition has been paused for a while");
UpdatePlayerColors();
declare HasWaited = False;
// Time Attack minigame
while (!ServerShutdownRequested && !MatchEndRequested && S_AdminSetPause) {
MB_Yield();
HasWaited = True;
---TimeAttack---
}
if (HasWaited) {
TM2::WaitRaceAll();
UIManager.UIAll.StatusMessage = "";
UIManager.UIAll.BigMessage = _("Competition resumed!");
// Ignore waiting if map is skipped
if (!ServerShutdownRequested && !MatchEndRequested) MB_Sleep(5000);
ST2::ClearScores();
}
UpdatePlayerColors();
Scores_Sort(CTmMode::ETmScoreSortOrder::TotalPoints);
UIManager.UIAll.BigMessage = "";
UIManager.UIAll.StatusMessage = "";
}
// ---------------------------------- //
/** Get the time left to the players to finish the round after the first player
*
* @return The time left in ms
*/
Integer GetFinishTimeout() {
declare FinishTimeout = 0;
if (S_FinishTimeout >= 0) {
FinishTimeout = S_FinishTimeout * 1000;
} else {
FinishTimeout = 5000;
if (Map.TMObjective_IsLapRace && NbLaps > 0 && Map.TMObjective_NbLaps > 0) {
FinishTimeout += ((Map.TMObjective_AuthorTime / Map.TMObjective_NbLaps) * NbLaps) / 6;
} else {
FinishTimeout += Map.TMObjective_AuthorTime / 6;
}
}
if (S_UseAlternateRules) {
if (Map.TMObjective_IsLapRace && NbLaps > 0 && Map.TMObjective_NbLaps > 0) {
return G_RoundStartTime + ((Map.TMObjective_AuthorTime / Map.TMObjective_NbLaps) * NbLaps) + FinishTimeout;
} else {
return G_RoundStartTime + Map.TMObjective_AuthorTime + FinishTimeout;
}
} else {
return Now + FinishTimeout;
}
// Default value from TMO, TMS (not used)
return Now + 15000;
}
// ---------------------------------- //
/// Setup scores table default style
Void SetScoresTableLayout() {
// Global settings
ST2::SetPos(<0., 39.2, 20.>);
ST2::SetSize(<68., 10.5>, <176., 58.5>, <176., 15.5>);
ST2::SetFormat(2, 8);
ST2::SetTextScale(7/8.);
ST2::SetModeIcon("Icons128x32_1|RT_Rounds");
ST2::SetBackgroundProperties(<0., 5.>, <218., 98.>);
// Columns width
ST2::SetColWidth("LibST_Avatar", 2.5);
ST2::SetColWidth("LibST_Tags", 0.);
ST2::SetColWidth("LibST_ManiaStars", 2.5);
ST2::SetColWidth("LibST_Tools", 1.5);
// Score points (SM-like)
ST2::CreateCol("LibST_TMPoints", _("Score"), "0", 4., 110.);
ST2::SetColTextAlign("LibST_TMPoints", CMlControl::AlignHorizontal::Right);
ST2::SetColTextStyle("LibST_TMPoints", "TextValueSmallSm");
ST2::SetColTextSize("LibST_TMPoints", 3.);
ST2::SetColScript("LibST_TMPoints", """
declare Score <=> (_Score as CTmScore);
Label_Col.Value = TL::ToText(Score.Points);""");
}
// ---------------------------------- //
// Custom layer
// ---------------------------------- //
// ---------------------------------- //
/** Create a request for the XML style file
*
* @param _Style The path to the XML file to use
*
* @return True if the request was created, false otherwise
*/
Boolean RequestLayerFromXml(Text _XmlPath) {
// Destroy an ongoing request
if (G_UI_RequestId != NullId && Http.Requests.existskey(G_UI_RequestId)) Http.Destroy(Http.Requests[G_UI_RequestId]);
G_UI_RequestId = NullId;
G_UI_RequestEndTime = -1;
if (!Http.IsValidUrl(_XmlPath)) return False;
declare Req <=> Http.CreateGet(_XmlPath);
if (Req == Null) return False;
G_UI_RequestId = Req.Id;
G_UI_RequestEndTime = Now + C_UI_RequestTimeout;
return True;
}
// ---------------------------------- //
/** Wait for a response to the request
*
* @return True if the request is not completed, false otherwise
*/
Boolean WaitLayerFromXml() {
if (Now >= G_UI_RequestEndTime) return False;
if (G_UI_RequestId == NullId) return False;
if (!Http.Requests.existskey(G_UI_RequestId)) return False;
if (Http.Requests[G_UI_RequestId].IsCompleted) return False;
return True;
}
// ---------------------------------- //
/** Read the XML file and create layer from it
*
* @return True if the layer was applied, false otherwise
*/
Boolean SetLayerFromXml() {
if (!Http.Requests.existskey(G_UI_RequestId)) return False;
declare Req <=> Http.Requests[G_UI_RequestId];
if (!Req.IsCompleted || Req.StatusCode != 200) return False;
Layers::Update("Custom", Req.Result);
Http.Destroy(Req);
G_UI_RequestId = NullId;
G_UI_RequestEndTime = -1;
return True;
}
// ---------------------------------- //
/** Load a custom layer from an XML file
*
* @param _Path Path to the XML file
*/
Void SetCustomLayerFromXml(Text _Path) {
if (_Path == "") return;
declare Loaded = RequestLayerFromXml(_Path);
if (Loaded) {
while (WaitLayerFromXml()) yield;
Loaded = SetLayerFromXml();
}
}
You can’t perform that action at this time.