initial commit of the mode LapsKnockout
This commit is contained in:
		
							
								
								
									
										420
									
								
								TM_LapsKnockout.Script.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										420
									
								
								TM_LapsKnockout.Script.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,420 @@ | ||||
| /** | ||||
|  *	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-11" | ||||
| #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; | ||||
|  | ||||
| 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.>; | ||||
| } | ||||
| *** | ||||
|  | ||||
| ***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) { | ||||
| 				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); | ||||
| 		} | ||||
| 		UpdateDossardColors(MatchState); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Manage mode events | ||||
| foreach (Event in PendingEvents) { | ||||
| 	if (Event.HasBeenPassed || Event.HasBeenDiscarded) continue; | ||||
| 	Events::Invalid(Event); | ||||
| } | ||||
|  | ||||
| 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) { | ||||
| 	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; | ||||
|  | ||||
| 		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; | ||||
| 	Race::StopSkipOutro(_Player); | ||||
| 	UIManager.UIAll.SendChat("Player $<$ff6" ^ _Player.User.Name ^ "$> is $<$f00eliminated$>"); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user