Add CP Knockout V2
This commit is contained in:
parent
dec90303b9
commit
9ca01735b4
476
TM_CPKnockout_V2.Script.txt
Normal file
476
TM_CPKnockout_V2.Script.txt
Normal file
@ -0,0 +1,476 @@
|
||||
/**
|
||||
* CP Knockout V2 mode
|
||||
* Similar to TM_CPKnockout.Script.txt but allow multiple rounds, and can finish before the finish if not enough players
|
||||
*/
|
||||
|
||||
// #RequireContext CSmMode
|
||||
#Extends "Modes/Nadeo/Trackmania/Base/TrackmaniaRoundsBase.Script.txt"
|
||||
|
||||
#Const CompatibleMapTypes "TrackMania\\TM_Race,TM_Race"
|
||||
#Const Version "2023-11-27"
|
||||
#Const ScriptName "Modes/TM2020-Gamemodes/TM_CPKnockout.Script.txt"
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
||||
// Libraries
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
||||
#Include "TextLib" as TL
|
||||
#Include "MathLib" as ML
|
||||
#Include "Libs/Nadeo/Trackmania/MainMenu/Constants.Script.txt" as MenuConsts
|
||||
#Include "Libs/Nadeo/Trackmania/Modes/Rounds/StateManager.Script.txt" as StateMgr
|
||||
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/ScoresTable_Server.Script.txt" as UIModules_ScoresTable
|
||||
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/TimeGap_Server.Script.txt" as UIModules_TimeGap
|
||||
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/Checkpoint_Server.Script.txt" as UIModules_Checkpoint
|
||||
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/PauseMenuOnline_Server.Script.txt" as UIModules_PauseMenu_Online
|
||||
|
||||
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/BigMessage_Server.Script.txt" as UIModules_BigMessage
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
||||
// Settings
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
||||
#Setting S_DisableGiveUp True as _("Disable give up")
|
||||
#Setting S_NumberOfFinishers 8
|
||||
#Setting S_FinishTimeout -1 as _("Finish timeout")
|
||||
#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_EliminatedPlayersNbRanks "2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,2" as _("Nb of players above which one extra elim. /CP. Same setting of Knock")
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
||||
// Constants
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
||||
#Const C_ModeName "CP Knockout"
|
||||
//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/Trackmania/Modes/Laps.Script.txt" //< Url of the mania app
|
||||
|
||||
#Const C_UploadRecord True
|
||||
#Const C_DisplayRecordGhost False
|
||||
#Const C_DisplayRecordMedal False
|
||||
#Const C_CelebrateRecordGhost True
|
||||
#Const C_CelebrateRecordMedal True
|
||||
|
||||
#Struct K_State {
|
||||
Integer NbAliveAfter;
|
||||
Integer NbFinishers;
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
||||
// 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_TotalPoints);
|
||||
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);
|
||||
Scores::SaveInScore(Scores::C_Points_Match);
|
||||
UIModules_ScoresTable::SetScoreMode(UIModules_ScoresTable::C_Mode_PrevTime);
|
||||
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([StateMgr::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_InitMatch***
|
||||
***
|
||||
foreach (Score in Scores) {
|
||||
declare Boolean IsAlive for Score = False;
|
||||
IsAlive = False;
|
||||
}
|
||||
UIModules_ScoresTable::SetCustomPoints([]);
|
||||
UIModules_ScoresTable::SetCustomTimes([]);
|
||||
|
||||
declare Boolean Match_InitPlayers = True;
|
||||
declare Integer Match_PlayersEliminated;
|
||||
declare Integer Match_Players;
|
||||
***
|
||||
|
||||
***Match_StartMap***
|
||||
***
|
||||
CarRank::Reset();
|
||||
|
||||
if (S_WarmUpNb > 0) {
|
||||
foreach (Score in Scores) {
|
||||
WarmUp::CanPlay(Score, True);
|
||||
}
|
||||
|
||||
MB_WarmUp(S_WarmUpNb, S_WarmUpDuration * 1000, S_WarmUpTimeout * 1000);
|
||||
}
|
||||
|
||||
if (Match_InitPlayers) {
|
||||
Match_InitPlayers = False;
|
||||
|
||||
foreach (Score in Scores) {
|
||||
declare Boolean IsAlive for Score = False;
|
||||
IsAlive = True;
|
||||
Match_Players += 1;
|
||||
|
||||
if (Score.User == Null) continue;
|
||||
}
|
||||
}
|
||||
|
||||
StartTime = Now + Race::C_SpawnDuration;
|
||||
if (S_DisableGiveUp) {
|
||||
Race::SetRespawnBehaviour(Race::C_RespawnBehaviour_NeverGiveUp);
|
||||
} else {
|
||||
Race::SetRespawnBehaviour(Race::C_RespawnBehaviour_Normal);
|
||||
}
|
||||
***
|
||||
|
||||
***Match_InitRound***
|
||||
***
|
||||
declare Integer Round_NumberOfPlayers = 0;
|
||||
declare Integer Round_DossardsUpdateCooldown = 0;
|
||||
|
||||
declare Text Round_EliminatedPlayersNbRanks = "";
|
||||
declare K_State[Integer] Round_State;
|
||||
|
||||
declare Ident[] Round_EliminatedScores;
|
||||
***
|
||||
|
||||
***Match_StartRound***
|
||||
***
|
||||
Round_EliminatedPlayersNbRanks = S_EliminatedPlayersNbRanks;
|
||||
|
||||
declare Integer[Text] CustomTimes = [];
|
||||
|
||||
foreach (Player in Players) {
|
||||
Player.Dossard_Color = <1., 1., 1.>;
|
||||
if (Player.Score == Null || Player.User == Null) continue;
|
||||
declare Boolean IsAlive for Player.Score = False;
|
||||
if (IsAlive) {
|
||||
Scores::SetPlayerMatchPoints(Player.Score, Match_Players);
|
||||
CustomTimes[Player.User.WebServicesUserId] = 0;
|
||||
Round_NumberOfPlayers += 1;
|
||||
}
|
||||
}
|
||||
UIModules_ScoresTable::SetCustomTimes(CustomTimes);
|
||||
|
||||
Round_State = ComputeState(Round_NumberOfPlayers);
|
||||
log("Round_State: " ^ Round_State);
|
||||
|
||||
EndTime = -1;
|
||||
***
|
||||
|
||||
***Match_PlayLoop***
|
||||
***
|
||||
// Manage race events
|
||||
foreach (Event in Race::GetPendingEvents()) {
|
||||
Race::ValidEvent(Event);
|
||||
|
||||
// Waypoint
|
||||
if (Event.Type == Events::C_Type_Waypoint) {
|
||||
if (Event.Player != Null) {
|
||||
declare Integer NBOfCP = Event.Player.RaceWaypointTimes.count;
|
||||
|
||||
if (Round_State.existskey(NBOfCP)) {
|
||||
Round_State[NBOfCP].NbFinishers += 1;
|
||||
|
||||
// Proceed kick
|
||||
if (Round_State[NBOfCP].NbFinishers >= Round_State[NBOfCP].NbAliveAfter) {
|
||||
foreach (Player in Players) {
|
||||
if (Player.SpawnStatus != CSmPlayer::ESpawnStatus::Spawned) continue;
|
||||
|
||||
if (Player.RaceWaypointTimes.count < NBOfCP) {
|
||||
EliminatePlayer(Player, Match_PlayersEliminated);
|
||||
Match_PlayersEliminated += 1;
|
||||
Round_EliminatedScores.add(Player.Score.Id);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (CPNb => State in Round_State) {
|
||||
if (CPNb > Event.Player.RaceWaypointTimes.count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Event.IsEndRace) {
|
||||
Scores::UpdatePlayerPrevRace(Event.Player);
|
||||
|
||||
declare Integer[Text] CustomTimes = UIModules_ScoresTable::GetCustomTimes();
|
||||
CustomTimes.removekey(Event.Player.User.WebServicesUserId);
|
||||
UIModules_ScoresTable::SetCustomTimes(CustomTimes);
|
||||
|
||||
if (EndTime <= 0) {
|
||||
EndTime = Race::GetFinishTimeout(S_FinishTimeout, Race::GetLapsNb(), Map);
|
||||
}
|
||||
}
|
||||
|
||||
// Update best race at each checkpoint to sort scores with C_Sort_BestRaceCheckpointsProgress
|
||||
Scores::UpdatePlayerBestRace(Event.Player);
|
||||
|
||||
CarRank::ThrottleUpdate(CarRank::C_SortCriteria_CurrentRace);
|
||||
|
||||
if (Round_DossardsUpdateCooldown == 0) {
|
||||
UpdateDossardColors(Round_State);
|
||||
Round_DossardsUpdateCooldown = Now + 1000;
|
||||
}
|
||||
}
|
||||
} else if (Event.Type == Events::C_Type_SkipOutro) {
|
||||
if (Event.Player != Null) {
|
||||
Race::StopSkipOutro(Event.Player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Manage mode events
|
||||
foreach (Event in PendingEvents) {
|
||||
if (Event.HasBeenPassed || Event.HasBeenDiscarded) continue;
|
||||
Events::Invalid(Event);
|
||||
}
|
||||
|
||||
if (Round_DossardsUpdateCooldown > 0 && Round_DossardsUpdateCooldown < Now) {
|
||||
Round_DossardsUpdateCooldown = 0;
|
||||
UpdateDossardColors(Round_State);
|
||||
}
|
||||
|
||||
if (Round_EliminatedPlayersNbRanks != S_EliminatedPlayersNbRanks) {
|
||||
Round_EliminatedPlayersNbRanks = S_EliminatedPlayersNbRanks;
|
||||
|
||||
Round_State = ComputeState(Round_NumberOfPlayers);
|
||||
}
|
||||
|
||||
if (Players.count > 0 && Round_NumberOfPlayers > 0 && PlayersNbAlive <= S_NumberOfFinishers) {
|
||||
MB_StopMatch();
|
||||
}
|
||||
***
|
||||
|
||||
***Match_EndRound***
|
||||
***
|
||||
Race::StopSkipOutroAll();
|
||||
EndTime = -1;
|
||||
StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]);
|
||||
CarRank::Update(CarRank::C_SortCriteria_CurrentRace);
|
||||
|
||||
if (Round_ForceEndRound || Round_SkipPauseRound) {
|
||||
declare Text[][Text] CustomPoints = UIModules_ScoresTable::GetCustomPoints();
|
||||
foreach (Score in Scores) {
|
||||
if (!Round_EliminatedScores.exists(Score.Id)) continue;
|
||||
|
||||
declare Boolean IsAlive for Score = False;
|
||||
IsAlive = True;
|
||||
|
||||
if (Score.User == Null || !CustomPoints.existskey(Score.User.WebServicesUserId)) continue;
|
||||
CustomPoints.removekey(Score.User.WebServicesUserId);
|
||||
}
|
||||
UIModules_ScoresTable::SetCustomPoints(CustomPoints);
|
||||
|
||||
// Cancel points
|
||||
foreach (Score in Scores) {
|
||||
Scores::SetPlayerRoundPoints(Score, 0);
|
||||
}
|
||||
// Do not launch the forced end round sequence after a pause
|
||||
if (!Round_SkipPauseRound) {
|
||||
ForcedEndRoundSequence();
|
||||
}
|
||||
MB_SetValidRound(False);
|
||||
} else {
|
||||
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
|
||||
UIManager.UIAll.UISequence = CUIConfig::EUISequence::EndRound;
|
||||
MB_Sleep(S_ChatTime / 2);
|
||||
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::Normal;
|
||||
UIManager.UIAll.UISequence = CUIConfig::EUISequence::Playing;
|
||||
MB_Sleep(S_ChatTime / 2);
|
||||
}
|
||||
***
|
||||
|
||||
***Match_EndMap***
|
||||
***
|
||||
// Ensure that we stop the match (after a vote for the next map, ...)
|
||||
MB_StopMatch();
|
||||
|
||||
EndTime = -1;
|
||||
StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]);
|
||||
|
||||
CarRank::Update(CarRank::C_SortCriteria_CurrentRace);
|
||||
Race::SortScores(Race::C_Sort_TotalPoints);
|
||||
Scores::SetPlayerWinner(Scores::GetBestPlayer(Scores::C_Sort_MatchPoints));
|
||||
Race::StopSkipOutroAll();
|
||||
***
|
||||
|
||||
***Rounds_CanSpawn***
|
||||
***
|
||||
foreach (Score in Scores) {
|
||||
declare Boolean ModeRounds_CanSpawn for Score = True;
|
||||
ModeRounds_CanSpawn = CanSpawn(Score);
|
||||
}
|
||||
***
|
||||
|
||||
***Rounds_CheckCanSpawn***
|
||||
***
|
||||
// During PlayLoop Check
|
||||
declare Boolean IsAlive for _Player.Score = False;
|
||||
return IsAlive;
|
||||
---PouleParty_Rounds_CanSpawn---
|
||||
***
|
||||
|
||||
Boolean CanSpawn(CSmScore _Score) {
|
||||
// Before PlayLoop Check
|
||||
declare Boolean IsAlive for _Score = False;
|
||||
return IsAlive;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
*/
|
||||
|
||||
/** Compute Match State based on S_EliminatedPlayersNbRanks and number of Players
|
||||
*
|
||||
* @return K_State[Integer]
|
||||
*/
|
||||
K_State[Integer] ComputeState(Integer _NbOfPlayer) {
|
||||
declare K_State[Integer] State;
|
||||
|
||||
declare Text[] KORepartition = TL::Split(",", S_EliminatedPlayersNbRanks);
|
||||
|
||||
declare Integer NBOfCP = Map::GetCheckpointsCount() + 1;
|
||||
declare Integer NbAliveAfter = _NbOfPlayer;
|
||||
|
||||
for (I, 0 , NBOfCP) {
|
||||
declare Integer CPIndex = NBOfCP - I;
|
||||
if (KORepartition.existskey(CPIndex) && KORepartition[CPIndex] != "0") {
|
||||
NbAliveAfter -= TL::ToInteger(KORepartition[CPIndex]);
|
||||
State[I] = K_State{
|
||||
NbAliveAfter = NbAliveAfter
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return State;
|
||||
}
|
||||
|
||||
/** Update Dossard Color of Players depending of the CP and the Rank
|
||||
*
|
||||
* @return Void
|
||||
*/
|
||||
Void UpdateDossardColors(K_State[Integer] _Round_State) {
|
||||
Log::Log("UpdateDossardColors");
|
||||
declare Integer Rank = 1;
|
||||
|
||||
foreach (Score in Scores) {
|
||||
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 (_Round_State.existskey(Player.CurrentLapNumber + 1)) {
|
||||
NbAlive = _Round_State[Player.CurrentLapNumber + 1].NbAliveAfter;
|
||||
} else {
|
||||
// get first CP
|
||||
foreach (Value in _Round_State) {
|
||||
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, Integer _Eliminated) {
|
||||
if (_Player == Null || _Player.Score == 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$>");
|
||||
|
||||
declare Text[][Text] CustomPoints = UIModules_ScoresTable::GetCustomPoints();
|
||||
CustomPoints[_Player.User.WebServicesUserId] = [_("|Status|K.O."), "f00"];
|
||||
UIModules_ScoresTable::SetCustomPoints(CustomPoints);
|
||||
|
||||
Scores::SetPlayerMatchPoints(_Player.Score, _Eliminated);
|
||||
|
||||
declare Boolean IsAlive for _Player.Score = False;
|
||||
IsAlive = False;
|
||||
}
|
Loading…
Reference in New Issue
Block a user