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