TM2020-Gamemodes/TM_LapsKnockout.Script.txt
2022-05-20 13:46:53 +02:00

450 lines
13 KiB
Plaintext

/**
* Laps Knockout mode
*/
// #RequireContext CSmMode
#Extends "Libs/Nadeo/TMNext/TrackMania/Modes/TMNextBase.Script.txt"
#Const CompatibleMapTypes "TrackMania\\TM_Race,TM_Race"
#Const Version "2022-05-20"
#Const ScriptName "Modes/TM2020-Gamemodes/TM_LapsKnockout.Script.txt"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Libraries
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Include "TextLib" as TL
#Include "MathLib" as ML
#Include "Libs/Nadeo/TMNext/TrackMania/Menu/Constants.Script.txt" as MenuConsts
#Include "Libs/Nadeo/TMNext/TrackMania/Modes/Laps/StateManager.Script.txt" as StateMgr
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/ScoresTable_Server.Script.txt" as UIModules_ScoresTable
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/TimeGap_Server.Script.txt" as UIModules_TimeGap
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/Checkpoint_Server.Script.txt" as UIModules_Checkpoint
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/PauseMenuOnline_Server.Script.txt" as UIModules_PauseMenu_Online
#Include "ManiaApps/Nadeo/TMNext/TrackMania/LapsCommon/Libs/Constants.Script.txt" as LibLaps_Constants
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/BigMessage_Server.Script.txt" as UIModules_BigMessage
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Settings
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Setting S_DisableGiveUp True as _("Disable give up")
#Setting S_WarmUpNb 0 as _("Number of warm up")
#Setting S_WarmUpDuration 0 as _("Duration of one warm up")
#Setting S_WarmUpTimeout -1 as _("Warm up timeout")
#Setting S_NbLapsWithoutKO 0 as "Number of laps without elimination"
#Setting S_EliminatedPlayersNbRanks "4,16,16" as _("Nb of players above which one extra elim. /lap. Same setting of Knock")
/* About S_EliminatedPlayersNbRanks
* Example : "8,16"
* 1 to 8 players -> 1 elimination per lap
* 9 to 16 players -> 2 eliminations per lap
* 17 or more players -> 3 eliminations per lap
*
* Example : "8,16,16"
* 1 to 8 players -> 1 eliminations per lap
* 9 to 16 players -> 2 eliminations per lap
* 17 or more players -> 4 eliminations per lap
*
* Example : "0,8"
* 1 to 8 players -> 2 eliminations per lap
* 9 or more players -> 3 eliminations per lap
*
* Example : ""
* 1 or more players -> 1 elimination per lap
*/
#Struct K_LapState {
Integer NbAliveAfter;
Integer NbEliminations;
Integer NbLapFinishers;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Constants
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Const C_ModeName "Laps"
//L16N [Laps] Description of the mode rules
#Const Description _("$zIn $<$t$6F9Laps$> mode, the goal is to drive as far as possible by passing $<$t$6F9checkpoints$>.\n\nThe laps mode takes place on multilap (cyclical) maps, and is played in one go for every map.\n\nWhen the time is up, the $<$t$6F9winner$> is the player who passed the most $<$t$6F9checkpoints$>. In case of draws, the winner is the player who passed the last checkpoint first.")
#Const C_HudModulePath "" //< Path to the hud module
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/TMNext/TrackMania/Laps/Laps.Script.txt" //< Url of the mania app
#Const C_FakeUsersNb 0
#Const C_UploadRecord True
#Const C_DisplayRecordGhost False
#Const C_DisplayRecordMedal False
#Const C_CelebrateRecordGhost True
#Const C_CelebrateRecordMedal True
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Extends
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
***Match_LogVersions***
***
Log::RegisterScript(ScriptName, Version);
Log::RegisterScript(StateMgr::ScriptName, StateMgr::Version);
***
***Match_LoadLibraries***
***
StateMgr::Load();
***
***Match_UnloadLibraries***
***
StateMgr::Unload();
***
***Match_Settings***
***
MB_Settings_UseDefaultHud = (C_HudModulePath == "");
MB_Settings_UseDefaultTimer = False;
***
***Match_Rules***
***
ModeInfo::SetName(C_ModeName);
ModeInfo::SetType(ModeInfo::C_Type_FreeForAll);
ModeInfo::SetRules(Description);
ModeInfo::SetStatusMessage(_("TYPE: Free for all\nOBJECTIVE: Set the best time on the track."));
***
***Match_LoadHud***
***
if (C_HudModulePath != "") Hud_Load(C_HudModulePath);
***
***Match_AfterLoadHud***
***
ClientManiaAppUrl = C_ManiaAppUrl;
Race::SortScores(Race::C_Sort_BestRaceCheckpointsProgress);
UIModules_TimeGap::SetTimeGapMode(UIModules_TimeGap::C_TimeGapMode_BestRace);
UIModules_Checkpoint::SetRankMode(UIModules_Checkpoint::C_RankMode_BestRace);
UIModules_Checkpoint::SetVisibilityTimeDiff(False, True);
UIModules_PauseMenu_Online::SetHelp(Description);
UIModules_ScoresTable::SetScoreMode(UIModules_ScoresTable::C_Mode_Laps);
UIModules_ScoresTable::SetHideSpectators(True);
***
***Match_Yield***
***
foreach (Event in PendingEvents) {
switch (Event.Type) {
// Initialize players when they join the server
case CSmModeEvent::EType::OnPlayerAdded: {
StateMgr::InitializePlayer(Event.Player);
CarRank::InitializePlayer(Event.Player);
}
}
}
StateMgr::Yield();
***
***Match_StartServer***
***
// Initialize mode
Clans::SetClansNb(0);
StateMgr::ForcePlayersStates([LibLaps_Constants::C_State_Waiting]);
WarmUp::SetAvailability(True);
Race::SetRespawnBehaviour(Race::C_RespawnBehaviour_Normal);
Race::SetupRecord(
MenuConsts::C_ScopeType_Season,
MenuConsts::C_ScopeType_PersonalBest,
MenuConsts::C_GameMode_Laps,
"",
C_UploadRecord,
C_DisplayRecordGhost,
C_DisplayRecordMedal,
C_CelebrateRecordGhost,
C_CelebrateRecordMedal
);
Race::UseAutomaticDossardColor(False);
***
***Match_InitMap***
***
declare Integer Last_NbLapsWithoutKO;
declare Integer Last_NumberOfPlayers;
declare Text Last_EliminatedPlayersNbRanks;
declare K_LapState[Integer] MatchState;
declare Integer Last_CooldownDossardUpdate = -1;
declare Boolean Last_InCooldownDossardUpdate = False;
Race::SetLapsSettings(True, -1); // force infinite lap
***
***Match_StartMap***
***
CarRank::Reset();
// Warm up
---Laps_Warmup---
StartTime = Now + Race::C_SpawnDuration;
if (S_DisableGiveUp) {
Race::SetRespawnBehaviour(Race::C_RespawnBehaviour_NeverGiveUp);
} else {
Race::SetRespawnBehaviour(Race::C_RespawnBehaviour_Normal);
}
// Spawn players for the race
foreach (Score in Scores) {
declare Boolean Laps_CanSpawn for Score = True;
Laps_CanSpawn = True;
}
foreach (Player in Players) {
if (Player.Score != Null && Race::IsReadyToStart(Player)) {
declare Boolean Laps_CanSpawn for Player.Score = True;
Race::Start(Player, StartTime);
Laps_CanSpawn = False;
}
}
CarRank::Update(CarRank::C_SortCriteria_BestRace);
StateMgr::ForcePlayersStates([LibLaps_Constants::C_State_Playing]);
***
***Laps_Warmup***
***
Race::SetRespawnBehaviour(Race::C_RespawnBehaviour_Normal);
MB_WarmUp(S_WarmUpNb, S_WarmUpDuration * 1000, S_WarmUpTimeout * 1000);
***
***Match_StartRound***
***
// Once the round started, no one can join to play
Last_NumberOfPlayers = Players.count;
Last_NbLapsWithoutKO = S_NbLapsWithoutKO;
Last_EliminatedPlayersNbRanks = S_EliminatedPlayersNbRanks;
MatchState = ComputeMatchState(Last_NumberOfPlayers);
// Update UI lap number
foreach (LapNb => State in MatchState.sortkeyreverse()) {
Race::SetLapsSettings(False, LapNb); // Set number of laps with number of
break;
}
// Reset Dossard Color
foreach (Player in Players) {
Player.Dossard_Color = <1., 1., 1.>;
}
Last_CooldownDossardUpdate = -1;
Last_InCooldownDossardUpdate = False;
if (Last_NbLapsWithoutKO == 1) {
UIManager.UIAll.SendChat(Last_NbLapsWithoutKO ^ " lap without eliminations");
} else if (Last_NbLapsWithoutKO > 1){
UIManager.UIAll.SendChat(Last_NbLapsWithoutKO ^ " laps without eliminations");
}
***
***Match_PlayLoop***
***
// Manage race events
declare RacePendingEvents = Race::GetPendingEvents();
foreach (Event in RacePendingEvents) {
Race::ValidEvent(Event);
// Waypoint
if (Event.Type == Events::C_Type_Waypoint) {
if (Event.Player != Null) {
if (Event.IsEndLap) {
Scores::UpdatePlayerBestLapIfBetter(Event.Player);
if (MatchState.existskey(Event.Player.CurrentLapNumber)) {
MatchState[Event.Player.CurrentLapNumber].NbLapFinishers += 1;
// Proceed kick
if (MatchState[Event.Player.CurrentLapNumber].NbLapFinishers == MatchState[Event.Player.CurrentLapNumber].NbAliveAfter) {
foreach (Player in Players) {
if (Player.SpawnStatus != CSmPlayer::ESpawnStatus::Spawned) continue;
if (Player.CurrentLapNumber < Event.Player.CurrentLapNumber) {
EliminatePlayer(Player);
}
}
foreach (LapNb => State in MatchState) {
if (LapNb > Event.Player.CurrentLapNumber) {
break;
}
MatchState.removekey(LapNb);
}
}
}
}
if (Event.IsEndRace) {
UIManager.UIAll.SendChat("Player $<$ff6" ^ Event.Player.User.Name ^ "$> $<$6f6wins the match!$>");
MB_StopMatch();
}
// Update best race at each checkpoint to sort scores with C_Sort_BestRaceCheckpointsProgress
Scores::UpdatePlayerBestRace(Event.Player);
CarRank::ThrottleUpdate(CarRank::C_SortCriteria_BestRace);
if (Last_CooldownDossardUpdate == -1) {
UpdateDossardColors(MatchState);
Last_CooldownDossardUpdate = Now + 1000;
} else {
Last_InCooldownDossardUpdate = True;
}
}
}
}
// Manage mode events
foreach (Event in PendingEvents) {
if (Event.HasBeenPassed || Event.HasBeenDiscarded) continue;
Events::Invalid(Event);
}
if (Last_CooldownDossardUpdate != -1 && Now > Last_CooldownDossardUpdate) {
Last_CooldownDossardUpdate = -1;
if (Last_InCooldownDossardUpdate) {
Last_InCooldownDossardUpdate = False;
UpdateDossardColors(MatchState);
}
}
if (Last_NbLapsWithoutKO != S_NbLapsWithoutKO || Last_EliminatedPlayersNbRanks != S_EliminatedPlayersNbRanks) {
Last_NbLapsWithoutKO = S_NbLapsWithoutKO;
Last_EliminatedPlayersNbRanks = S_EliminatedPlayersNbRanks;
MatchState = ComputeMatchState(Last_NumberOfPlayers);
foreach (LapNb => State in MatchState.sortkeyreverse()) {
Race::SetLapsSettings(False, LapNb);
break;
}
}
if (Players.count > 0 && PlayersNbAlive <= 0) {
MB_StopMatch();
}
***
***Match_EndMap***
***
// Ensure that we stop the match (after a vote for the next map, ...)
MB_StopMatch();
EndTime = -1;
StateMgr::ForcePlayersStates([LibLaps_Constants::C_State_Waiting]);
CarRank::Update(CarRank::C_SortCriteria_BestRace);
Race::SortScores(Race::C_Sort_BestRaceCheckpointsProgress);
Scores::SetPlayerWinner(Scores::GetBestPlayer(Scores::C_Sort_BestRaceCheckpointsProgress));
Race::StopSkipOutroAll();
***
/*
*
* Functions
*
*/
/** Compute Match State based on S_EliminatedPlayersNbRanks and number of Players
*
* @return K_LapState[Integer]
*/
K_LapState[Integer] ComputeMatchState(Integer _NumberOfPlayers) {
declare K_LapState[Integer] MatchState;
declare Integer[] Eliminations = [0];
foreach (EliminationText in TL::Split(",", S_EliminatedPlayersNbRanks)) {
declare Integer Elimination = TL::ToInteger(EliminationText);
if (Elimination > 0) { // -1 if error
Eliminations.add(Elimination);
}
}
declare Integer KickedPlayers = 0;
declare Integer LapNumber = S_NbLapsWithoutKO + 1;
while (KickedPlayers <= _NumberOfPlayers) {
while (_NumberOfPlayers - KickedPlayers < Eliminations[Eliminations.count - 1]) {
Eliminations.removekey(Eliminations.count - 1);
}
if (_NumberOfPlayers - KickedPlayers - Eliminations.count < Eliminations[Eliminations.count - 1]) {
Eliminations.removekey(Eliminations.count - 1);
}
declare Integer Alive = _NumberOfPlayers - KickedPlayers - Eliminations.count;
if (Alive <= 0) break;
MatchState[LapNumber] = K_LapState {
NbAliveAfter = Alive,
NbEliminations = Eliminations.count
};
KickedPlayers += Eliminations.count;
LapNumber += 1;
if (LapNumber > 255) break; // Anti crash
}
return MatchState;
}
/** Update Dossard Color of Players depending of the Lap and the Rank
*
* @return Void
*/
Void UpdateDossardColors(K_LapState[Integer] _MatchState) {
Log::Log("UpdateDossardColors");
declare Integer Rank = 1;
foreach (Score in Scores) {
if (Score == Null) continue;
if (Score.User == Null) continue;
declare CSmPlayer Player = GetPlayer(Score.User.Login);
if (Player == Null) continue;
if (Player.SpawnStatus == CSmPlayer::ESpawnStatus::NotSpawned) continue;
declare Integer NbAlive;
if (_MatchState.existskey(Player.CurrentLapNumber + 1)) {
NbAlive = _MatchState[Player.CurrentLapNumber + 1].NbAliveAfter;
} else {
// get first lap
foreach (Value in _MatchState) {
NbAlive = Value.NbAliveAfter;
break;
}
}
if (Rank > NbAlive) {
Player.Dossard_Color = <1., 0., 0.>;
} else {
Player.Dossard_Color = <1., 1., 1.>;
}
Rank += 1;
}
}
/** Eliminate Player and send a message in a Chat
*
* @return Void
*/
Void EliminatePlayer(CSmPlayer _Player) {
if (_Player == Null) return;
if (_Player.SpawnStatus == CSmPlayer::ESpawnStatus::NotSpawned) return;
log("EliminatePlayer: " ^ _Player.User.Name ^ " (" ^ _Player.User.Login ^ ")");
Race::StopSkipOutro(_Player);
UIManager.UIAll.SendChat("Player $<$ff6" ^ _Player.User.Name ^ "$> is $<$f00eliminated$>");
}