beu
deea33e0bd
- Add a setting to display same time message, disabled by default - Add a message when player is at 001 of the perfect time - Display the perfect time message instantly
640 lines
20 KiB
Plaintext
640 lines
20 KiB
Plaintext
/**
|
|
* Rounds Nearest.
|
|
* Nearest you are of the S_TargetTime, more points you win
|
|
*/
|
|
#Extends "Modes/Nadeo/Trackmania/Base/TrackmaniaRoundsBase.Script.txt"
|
|
|
|
#Const CompatibleMapTypes "TrackMania\\TM_Race,TM_Race"
|
|
#Const Version "2024-11-29"
|
|
#Const ScriptName "Modes/TM2020-Gamemodes/TM_RoundsNearest.Script.txt"
|
|
|
|
// #RequireContext CSmMode
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
// Libraries
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
#Include "TextLib" as TL
|
|
#Include "MathLib" as ML
|
|
#Include "Libs/Nadeo/CMGame/Utils/Semver.Script.txt" as Semver
|
|
#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/Utils/Tracking.Script.txt" as Tracking
|
|
#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
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
// Settings
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
#Setting S_BonusForPerfect 50
|
|
#Setting S_FinishTimeout 10 as _("Finish timeout")
|
|
#Setting S_MapsPerMatch 0 as _("Number of tracks per match") ///< Number of maps to play before finishing the match
|
|
#Setting S_NbOfWinners 4 as _("Number of winners")
|
|
#Setting S_PointsLimit 50 as _("Points limit")
|
|
#Setting S_PointsRepartition "5,4,3,2,1,0"
|
|
#Setting S_RoundsPerMap 8 as _("Number of rounds per track") ///< Number of round to play on one map before going to the next one
|
|
#Setting S_TargetTime 20000
|
|
#Setting S_SendSameTimeMessage False
|
|
|
|
#Setting S_WarmUpDuration 30 as _("Duration of one warm up")
|
|
#Setting S_WarmUpNb 1 as _("Number of warm up")
|
|
#Setting S_WarmUpTimeout -1 as _("Warm up timeout")
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
// Constants
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
#Const C_ModeName "Rounds"
|
|
//L16N [Rounds] Description of the mode rules
|
|
#Const Description _("$zIn $<$t$6F9Rounds$z$z$> mode, the goal is to win a maximum number of $<$t$6F9points.\n\n$z$>The rounds mode consists of $<$t$6F9a series of races$z$>.\nWhen you finish a race in a good $<$t$6F9position$z$>, you get $<$t$6F9points$z$>, added to your total.\n\nThe $<$t$6F9winner$z$> is the first player whose total reaches the $<$t$6F9point limit$z$> (30 for example).")
|
|
|
|
#Const C_HudModulePath "" //< Path to the hud module
|
|
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/Trackmania/Modes/Rounds.Script.txt" //< Url of the mania app
|
|
|
|
#Const C_PointsLimit_NotReached 0
|
|
#Const C_PointsLimit_Reached 1
|
|
#Const C_PointsLimit_Tie 2
|
|
|
|
#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(Semver::ScriptName, Semver::Version);
|
|
Log::RegisterScript(ModeUtils::ScriptName, ModeUtils::Version);
|
|
Log::RegisterScript(StateMgr::ScriptName, StateMgr::Version);
|
|
***
|
|
|
|
***Match_LoadLibraries***
|
|
***
|
|
StateMgr::Load();
|
|
***
|
|
|
|
***Match_UnloadLibraries***
|
|
***
|
|
StateMgr::Unload();
|
|
***
|
|
|
|
***Match_Settings***
|
|
***
|
|
MB_Settings_UseDefaultHud = (C_HudModulePath == "");
|
|
***
|
|
|
|
***Match_Rules***
|
|
***
|
|
ModeInfo::SetName(C_ModeName);
|
|
ModeInfo::SetType(ModeInfo::C_Type_FreeForAll);
|
|
ModeInfo::SetRules(Description);
|
|
ModeInfo::SetStatusMessage("");
|
|
***
|
|
|
|
***Match_LoadHud***
|
|
***
|
|
if (C_HudModulePath != "") Hud_Load(C_HudModulePath);
|
|
***
|
|
|
|
***Match_AfterLoadHud***
|
|
***
|
|
ClientManiaAppUrl = C_ManiaAppUrl;
|
|
Race::SortScores(Race::C_Sort_TotalPoints);
|
|
UIModules_ScoresTable::SetScoreMode(UIModules_ScoresTable::C_Mode_Points);
|
|
UIModules_Checkpoint::SetVisibilityTimeDiff(False);
|
|
UIModules_Checkpoint::SetRankMode(UIModules_Checkpoint::C_RankMode_CurrentRace);
|
|
UIModules_PauseMenu_Online::SetHelp(Description);
|
|
UIModules_Sign16x9Small::SetScoreMode(UIModules_Sign16x9Small::C_ScoreMode_Points);
|
|
// Hide SM Overlay
|
|
UIManager.UIAll.OverlayHideSpectatorInfos = True;
|
|
UIManager.UIAll.OverlayHideCountdown = True;
|
|
|
|
UIModules_ScoresTable::DisplayRoundPoints(True);
|
|
UIModules::UnloadModules(["UIModule_Race_TimeGap", "UIModule_Rounds_SmallScoresTable"]);
|
|
***
|
|
|
|
***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_InitServer***
|
|
***
|
|
declare Integer Server_PointsLimit;
|
|
declare Integer Server_RoundsPerMap;
|
|
declare Integer Server_MapsPerMatch;
|
|
***
|
|
|
|
***Match_StartServer***
|
|
***
|
|
// Initialize mode
|
|
Clans::SetClansNb(0);
|
|
Scores::SaveInScore(Scores::C_Points_Match);
|
|
StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]);
|
|
WarmUp::SetAvailability(True);
|
|
Race::SetupRecord(
|
|
MenuConsts::C_ScopeType_Season,
|
|
MenuConsts::C_ScopeType_PersonalBest,
|
|
MenuConsts::C_GameMode_Rounds,
|
|
"",
|
|
C_UploadRecord,
|
|
C_DisplayRecordGhost,
|
|
C_DisplayRecordMedal,
|
|
C_CelebrateRecordGhost,
|
|
C_CelebrateRecordMedal
|
|
);
|
|
|
|
Server_PointsLimit = S_PointsLimit - 1;
|
|
Server_RoundsPerMap = S_RoundsPerMap - 1;
|
|
Server_MapsPerMatch = S_MapsPerMatch - 1;
|
|
***
|
|
|
|
***Match_StartMatch***
|
|
***
|
|
UIModules_ScoresTable::SetCustomPoints([]);
|
|
***
|
|
|
|
***Match_InitMap***
|
|
***
|
|
declare Integer Map_ValidRoundsNb;
|
|
declare Boolean Map_Skipped;
|
|
|
|
UpdateScoresTableFooter(S_PointsLimit, S_RoundsPerMap, S_MapsPerMatch, Map_ValidRoundsNb);
|
|
***
|
|
|
|
***Match_StartMap***
|
|
***
|
|
Map_Skipped = True;
|
|
CarRank::Reset();
|
|
UIModules_ScoresTable::SetCustomPoints(GetWinnersCustomPoints());
|
|
|
|
// Warm up
|
|
foreach (Score in Scores) {
|
|
WarmUp::CanPlay(Score, CanSpawn(Score));
|
|
}
|
|
|
|
UIModules_ScoresTable::SetFooterInfo(_("Warm up"));
|
|
MB_WarmUp(S_WarmUpNb, S_WarmUpDuration * 1000, S_WarmUpTimeout * 1000);
|
|
***
|
|
|
|
***Rounds_CanSpawn***
|
|
***
|
|
foreach (Score in Scores) {
|
|
declare Boolean ModeRounds_CanSpawn for Score = True;
|
|
ModeRounds_CanSpawn = CanSpawn(Score);
|
|
}
|
|
***
|
|
|
|
***Match_StartRound***
|
|
***
|
|
UpdateScoresTableFooter(S_PointsLimit, S_RoundsPerMap, S_MapsPerMatch, Map_ValidRoundsNb);
|
|
StateMgr::ForcePlayersStates([StateMgr::C_State_Playing]);
|
|
UIModules_ScoresTable::SetCustomPoints(GetWinnersCustomPoints());
|
|
|
|
foreach (Score in Scores) {
|
|
declare Boolean RoundsNearest_AlmostPerfectTime_MessageSent for Score;
|
|
RoundsNearest_AlmostPerfectTime_MessageSent = False;
|
|
declare Boolean RoundsNearest_DidPerfectTime_MessageSent for Score;
|
|
RoundsNearest_DidPerfectTime_MessageSent = False;
|
|
}
|
|
***
|
|
|
|
***Rounds_PlayerSpawned***
|
|
***
|
|
CarRank::ThrottleUpdate(CarRank::C_SortCriteria_CurrentRace);
|
|
***
|
|
|
|
***Match_PlayLoop***
|
|
***
|
|
// Manage race events
|
|
declare Events::K_RaceEvent[] RacePendingEvents = Race::GetPendingEvents();
|
|
foreach (Event in RacePendingEvents) {
|
|
Race::ValidEvent(Event);
|
|
|
|
// Waypoint
|
|
if (Event.Type == Events::C_Type_Waypoint) {
|
|
CarRank::ThrottleUpdate(CarRank::C_SortCriteria_CurrentRace);
|
|
if (Event.Player != Null) {
|
|
if (Event.IsEndRace) {
|
|
Scores::UpdatePlayerBestRaceIfBetter(Event.Player);
|
|
Scores::UpdatePlayerBestLapIfBetter(Event.Player);
|
|
Scores::UpdatePlayerPrevRace(Event.Player);
|
|
ComputeLatestRaceScores(False);
|
|
Race::SortScores(Race::C_Sort_TotalPoints);
|
|
|
|
// Start the countdown if it's the first player to finish
|
|
if (EndTime <= 0) {
|
|
EndTime = GetFinishTimeout(S_FinishTimeout);
|
|
}
|
|
}
|
|
if (Event.IsEndLap) {
|
|
Scores::UpdatePlayerBestLapIfBetter(Event.Player);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Manage mode events
|
|
foreach (Event in PendingEvents) {
|
|
if (Event.HasBeenPassed || Event.HasBeenDiscarded) continue;
|
|
Events::Invalid(Event);
|
|
}
|
|
|
|
// Server info change
|
|
if (
|
|
Server_PointsLimit != S_PointsLimit ||
|
|
Server_RoundsPerMap != S_RoundsPerMap ||
|
|
Server_MapsPerMatch != S_MapsPerMatch
|
|
) {
|
|
Server_PointsLimit = S_PointsLimit;
|
|
Server_RoundsPerMap = S_RoundsPerMap;
|
|
Server_MapsPerMatch = S_MapsPerMatch;
|
|
|
|
UpdateScoresTableFooter(S_PointsLimit, S_RoundsPerMap, S_MapsPerMatch, Map_ValidRoundsNb);
|
|
}
|
|
***
|
|
|
|
***Match_EndRound***
|
|
***
|
|
Race::StopSkipOutroAll();
|
|
EndTime = -1;
|
|
StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]);
|
|
CarRank::Update(CarRank::C_SortCriteria_CurrentRace);
|
|
|
|
if (Semver::Compare(XmlRpc::GetApiVersion(), ">=", "2.1.1")) {
|
|
Scores::XmlRpc_SendScores(Scores::C_Section_PreEndRound, "");
|
|
}
|
|
|
|
if (Round_ForceEndRound || Round_SkipPauseRound || Round_Skipped) {
|
|
// 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 {
|
|
Map_ValidRoundsNb += 1;
|
|
// Get the last round points
|
|
ComputeLatestRaceScores(True);
|
|
Race::SortScores(Race::C_Sort_TotalPoints);
|
|
UIManager.UIAll.UISequence = CUIConfig::EUISequence::EndRound;
|
|
MB_Sleep(S_ChatTime * 1000 / 3);
|
|
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
|
|
MB_Sleep(S_ChatTime * 1000 / 3);
|
|
// Add them to the total scores
|
|
ComputeScores();
|
|
UIModules_ScoresTable::SetCustomPoints(GetWinnersCustomPoints());
|
|
Race::SortScores(Race::C_Sort_TotalPoints);
|
|
MB_Sleep(S_ChatTime * 1000 / 3);
|
|
UIModules_BigMessage::SetMessage("");
|
|
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::Normal;
|
|
UIManager.UIAll.UISequence = CUIConfig::EUISequence::Playing;
|
|
|
|
// Match is over, we have all the winners
|
|
if (MatchIsOver(MB_GetMapCount(), Map_Skipped)) {
|
|
MB_StopMatch();
|
|
}
|
|
// Map is over, we played all the rounds
|
|
else if (MapIsOver(Map_ValidRoundsNb)) {
|
|
MB_StopMap();
|
|
}
|
|
}
|
|
***
|
|
|
|
***Match_EndMap***
|
|
***
|
|
if (!MB_MapIsRunning() && MB_MatchIsRunning()) MB_SkipPodiumSequence();
|
|
|
|
Race::SortScores(Race::C_Sort_TotalPoints);
|
|
declare CSmScore Winner <=> Scores::GetBestPlayer(Scores::C_Sort_MatchPoints);
|
|
Scores::SetPlayerWinner(Winner);
|
|
|
|
if (!MB_MatchIsRunning()) {
|
|
// Compute ranking for tracking
|
|
declare Integer PreviousPoints = 0;
|
|
declare Integer Rank = 0;
|
|
foreach (Key => Score in Scores) {
|
|
if (Key == 0 || Scores::GetPlayerMatchPoints(Score) < PreviousPoints) {
|
|
PreviousPoints = Scores::GetPlayerMatchPoints(Score);
|
|
Rank = Key + 1;
|
|
}
|
|
Tracking::SendPlayerMatchResult(UIManager, Score.User, Rank, Winner == Score && Scores.count > 1);
|
|
}
|
|
}
|
|
***
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
// Functions
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
/** Update the scores table footer text
|
|
*
|
|
* @param _PointsLimit The points limit
|
|
* @param _RoundsPerMap The number of rounds per map
|
|
* @param _MapsPerMatch The number of maps per match
|
|
* @param _ValidRoundsNb Number of valid rounds played
|
|
*/
|
|
Void UpdateScoresTableFooter(Integer _PointsLimit, Integer _RoundsPerMap, Integer _MapsPerMatch, Integer _ValidRoundsNb) {
|
|
declare Text[] Parts;
|
|
declare Text Message = "";
|
|
if (_PointsLimit > 0) {
|
|
if (Parts.count > 0) Message ^= "\n";
|
|
Message ^= """%{{{Parts.count + 1}}}{{{_PointsLimit}}}""";
|
|
//L16N [Rounds] Number of points to reach to win the match.
|
|
Parts.add(_("Points limit : "));
|
|
}
|
|
if (_RoundsPerMap > 0) {
|
|
if (Parts.count > 0) Message ^= "\n";
|
|
Message ^= """%{{{Parts.count + 1}}}{{{ML::Min(_ValidRoundsNb+1, _RoundsPerMap)}}}/{{{_RoundsPerMap}}}""";
|
|
//L16N [Rounds] Number of rounds played during the track.
|
|
Parts.add(_("Rounds : "));
|
|
}
|
|
if (_MapsPerMatch > 0) {
|
|
if (Parts.count > 0) Message ^= "\n";
|
|
Message ^= """%{{{Parts.count + 1}}}{{{MB_GetMapCount()}}}/{{{_MapsPerMatch}}}""";
|
|
//L16N [Rounds] Number of tracks played during the match.
|
|
Parts.add(_("Tracks : "));
|
|
}
|
|
|
|
switch (Parts.count) {
|
|
case 0: UIModules_ScoresTable::SetFooterInfo(Message);
|
|
case 1: UIModules_ScoresTable::SetFooterInfo(TL::Compose(Message, Parts[0]));
|
|
case 2: UIModules_ScoresTable::SetFooterInfo(TL::Compose(Message, Parts[0], Parts[1]));
|
|
case 3: UIModules_ScoresTable::SetFooterInfo(TL::Compose(Message, Parts[0], Parts[1], Parts[2]));
|
|
}
|
|
}
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
/** Check if a player can spawn
|
|
*
|
|
* @param _Score The player's score
|
|
*
|
|
* @return True if the player can spawn,
|
|
* False otherwise
|
|
*/
|
|
Boolean CanSpawn(CSmScore _Score) {
|
|
if (_Score == Null) return False;
|
|
|
|
if (Scores::GetPlayerMatchPoints(_Score) >= S_PointsLimit) {
|
|
return False;
|
|
}
|
|
|
|
return True;
|
|
}
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
/** Get the time left to the players to finish the round after the first player
|
|
*
|
|
* @return The time left in ms
|
|
*/
|
|
Integer GetFinishTimeout(Integer _FinishTimeout) {
|
|
declare Integer FinishTimeout = 0;
|
|
|
|
if (_FinishTimeout >= 0) {
|
|
FinishTimeout = _FinishTimeout * 1000;
|
|
} else {
|
|
FinishTimeout = 5000;
|
|
if (Map.TMObjective_IsLapRace && Race::GetLapsNb() > 0 && Map.TMObjective_NbLaps > 0) {
|
|
FinishTimeout += ((Map.TMObjective_AuthorTime / Map.TMObjective_NbLaps) * Race::GetLapsNb()) / 6;
|
|
} else {
|
|
FinishTimeout += Map.TMObjective_AuthorTime / 6;
|
|
}
|
|
}
|
|
|
|
return Now + FinishTimeout;
|
|
}
|
|
|
|
Text DeltaTimeToText(Integer _Time) {
|
|
if (_Time < 1000) {
|
|
return TL::FormatReal(_Time / 1000., 3, False, False);
|
|
}
|
|
|
|
declare Real TimeWithoutMs = _Time / 10.;
|
|
declare Real TimeInSeconds = TimeWithoutMs / 100.;
|
|
if (TimeInSeconds <= 10.) {
|
|
return TL::FormatReal(TimeInSeconds, 2, False, False);
|
|
} else if (TimeInSeconds <= 100.) {
|
|
return TL::FormatReal(TimeInSeconds, 1, False, False);
|
|
}
|
|
return TL::ToText(ML::FloorInteger(TimeInSeconds));
|
|
}
|
|
|
|
Text FormatPlayerName(Text _Name) {
|
|
return "$<$fff" ^ _Name ^ "$>";
|
|
}
|
|
|
|
|
|
Text[][Text] GetWinnersCustomPoints() {
|
|
if (S_PointsLimit <= 0) return [];
|
|
|
|
declare Text[][Text] CustomPoints = [];
|
|
|
|
foreach (Score in Scores) {
|
|
if (Score.User == Null) continue;
|
|
if (Scores::GetPlayerMatchPoints(Score) < S_PointsLimit) continue;
|
|
|
|
declare Integer Rank = S_PointsLimit + 1 + S_NbOfWinners + 1 - Scores::GetPlayerMatchPoints(Score);
|
|
CustomPoints[Score.User.WebServicesUserId] = [TL::FormatRank(Rank, True)];
|
|
}
|
|
return CustomPoints;
|
|
}
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
/// Compute the latest race scores
|
|
Void ComputeLatestRaceScores(Boolean _IsEndRound) {
|
|
Race::SortScores(Race::C_Sort_PrevRaceTime);
|
|
|
|
// Points distributed between all players
|
|
|
|
declare Text[][Text] CustomPoints = GetWinnersCustomPoints();
|
|
declare CSmScore[][Integer] ScoresPerAbsoluteDelta;
|
|
foreach (Score in Scores) {
|
|
if (Score.User == Null) continue;
|
|
if (Scores::GetPlayerPrevRaceTime(Score) <= 0) continue;
|
|
declare Integer Delta = Scores::GetPlayerPrevRaceTime(Score) - S_TargetTime;
|
|
declare Integer AbsoluteDelta = ML::Abs(Delta);
|
|
if (!ScoresPerAbsoluteDelta.existskey(AbsoluteDelta)) ScoresPerAbsoluteDelta[AbsoluteDelta] = [];
|
|
ScoresPerAbsoluteDelta[AbsoluteDelta].add(Score);
|
|
|
|
declare Text TextDelta;
|
|
if (Delta >= 0) TextDelta = "+";
|
|
else TextDelta = "-";
|
|
|
|
TextDelta ^= DeltaTimeToText(AbsoluteDelta);
|
|
if (S_PointsLimit < 0 || Scores::GetPlayerMatchPoints(Score) < S_PointsLimit) {
|
|
CustomPoints[Score.User.WebServicesUserId] = [Scores::GetPlayerMatchPoints(Score) ^ " (" ^ TextDelta ^ ")"];
|
|
}
|
|
}
|
|
UIModules_ScoresTable::SetCustomPoints(CustomPoints);
|
|
|
|
ScoresPerAbsoluteDelta = ScoresPerAbsoluteDelta.sortkey();
|
|
|
|
declare Integer I = 0;
|
|
declare Integer[] PointsRepartition = PointsRepartition::GetPointsRepartition();
|
|
declare Text Names;
|
|
|
|
foreach (Delta => CustomScores in ScoresPerAbsoluteDelta) {
|
|
// Attribute less points if they have the same time
|
|
if (S_SendSameTimeMessage && _IsEndRound && CustomScores.count > 1) {
|
|
I += CustomScores.count - 1;
|
|
foreach (Key => Score in CustomScores) {
|
|
if (Key == 0) {
|
|
Names = FormatPlayerName(Score.User.Name);
|
|
} else if (Key == CustomScores.count - 1) {
|
|
Names ^= " and " ^ FormatPlayerName(Score.User.Name);
|
|
} else {
|
|
Names ^= ", " ^ FormatPlayerName(Score.User.Name);
|
|
}
|
|
}
|
|
UIManager.UIAll.SendChat("$ff3" ^ Names^ " have the same time");
|
|
}
|
|
|
|
Names = "";
|
|
declare Boolean UpdateBigMessage;
|
|
|
|
foreach (Key => Score in CustomScores) {
|
|
declare Integer Points;
|
|
|
|
if (PointsRepartition.existskey(I)) {
|
|
Points = PointsRepartition[I];
|
|
} else if (PointsRepartition.count > 0) {
|
|
Points = PointsRepartition[PointsRepartition.count - 1];
|
|
}
|
|
|
|
if (Delta == 0) {
|
|
declare Boolean RoundsNearest_DidPerfectTime_MessageSent for Score;
|
|
if (!RoundsNearest_DidPerfectTime_MessageSent) {
|
|
RoundsNearest_DidPerfectTime_MessageSent = True;
|
|
UpdateBigMessage = True;
|
|
UIManager.UIAll.SendChat("$ff3" ^ FormatPlayerName(Score.User.Name) ^ " did the perfect time");
|
|
ModeUtils::PlaySound(CUIConfig::EUISound::TieBreakPoint, 0);
|
|
}
|
|
|
|
if (Key == 0) {
|
|
Names = FormatPlayerName(Score.User.Name);
|
|
} else if (Key == CustomScores.count - 1) {
|
|
Names ^= " and " ^ FormatPlayerName(Score.User.Name);
|
|
} else {
|
|
Names ^= ", " ^ FormatPlayerName(Score.User.Name);
|
|
}
|
|
|
|
if (CustomScores.count == 1) {
|
|
Points += S_BonusForPerfect;
|
|
}
|
|
} else if (Delta == 1) {
|
|
declare Boolean RoundsNearest_AlmostPerfectTime_MessageSent for Score;
|
|
if (!RoundsNearest_AlmostPerfectTime_MessageSent) {
|
|
RoundsNearest_AlmostPerfectTime_MessageSent = True;
|
|
UIManager.UIAll.SendChat("$ff3" ^ FormatPlayerName(Score.User.Name) ^ " HAH");
|
|
}
|
|
}
|
|
Scores::SetPlayerRoundPoints(Score, Points);
|
|
}
|
|
|
|
if (UpdateBigMessage) {
|
|
UIModules_BigMessage::SetMessage(Names ^ " did the perfect time");
|
|
}
|
|
|
|
I += 1;
|
|
}
|
|
}
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
/// Compute the map scores
|
|
Void ComputeScores() {
|
|
if (S_PointsLimit <= 0) {
|
|
Scores::EndRound();
|
|
return;
|
|
}
|
|
|
|
declare Integer NbOfWinners = 0;
|
|
Race::SortScores(Race::C_Sort_TotalPoints);
|
|
|
|
foreach (Score in Scores) {
|
|
// Already won
|
|
if (Scores::GetPlayerMatchPoints(Score) >= S_PointsLimit) {
|
|
Scores::SetPlayerMatchPoints(Score, S_PointsLimit + 1 + S_NbOfWinners - NbOfWinners);
|
|
NbOfWinners += 1;
|
|
}
|
|
// New winner
|
|
else if (Scores::GetPlayerMatchPoints(Score) + Scores::GetPlayerRoundPoints(Score) >= S_PointsLimit) {
|
|
Scores::SetPlayerMatchPoints(Score, S_PointsLimit + 1 + S_NbOfWinners - NbOfWinners);
|
|
NbOfWinners += 1;
|
|
}
|
|
// Standard round finish
|
|
else {
|
|
Scores::AddPlayerMatchPoints(Score, Scores::GetPlayerRoundPoints(Score));
|
|
}
|
|
|
|
Scores::AddPlayerMapPoints(Score, Scores::GetPlayerRoundPoints(Score));
|
|
Scores::SetPlayerRoundPoints(Score, 0);
|
|
}
|
|
}
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
/** Check if we should go to the next map
|
|
*
|
|
* @return True if it is the case, false otherwise
|
|
*/
|
|
Boolean MapIsOver(Integer _ValidRoundsNb) {
|
|
if (_ValidRoundsNb >= S_RoundsPerMap) return True;
|
|
return False;
|
|
}
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
/** Check if we should go to the next match
|
|
*
|
|
* @param _MapsPerMatch Number of maps to play to complete a match
|
|
* @param _MapSkipped if map was skipped
|
|
*
|
|
* @return True if it is the case, false otherwise
|
|
*/
|
|
Boolean MatchIsOver(Integer _MapCount, Boolean _MapSkipped) {
|
|
Log::Log("[Cup] MatchIsOver() check | S_PointsLimit : "^S_PointsLimit);
|
|
if (S_PointsLimit > 0) {
|
|
declare Integer NbOfScoreWinners = 0;
|
|
foreach (Score in Scores) {
|
|
if (Scores::GetPlayerMatchPoints(Score) > S_PointsLimit) NbOfScoreWinners += 1;
|
|
}
|
|
declare Integer NbOfPlayerWinners = 0;
|
|
foreach (Player in Players) {
|
|
if (Scores::GetPlayerMatchPoints(Player.Score) > S_PointsLimit) NbOfPlayerWinners += 1;
|
|
}
|
|
|
|
// If there's only one player they need to reach the points limit to win
|
|
// If there's more than one player then all players except one must reach the points limit
|
|
declare Integer PlayerWinnersLimit = ML::Max(Players.count - 1, 1);
|
|
Log::Log("""[Cup] Match is over ? {{{(NbOfScoreWinners >= S_NbOfWinners || NbOfPlayerWinners >= PlayerWinnersLimit)}}} | ({{{NbOfScoreWinners}}} >= {{{S_NbOfWinners}}} || {{{NbOfPlayerWinners}}} >= {{{PlayerWinnersLimit}}})""");
|
|
if (NbOfScoreWinners >= S_NbOfWinners || NbOfPlayerWinners >= PlayerWinnersLimit) return True;
|
|
}
|
|
|
|
if (S_MapsPerMatch >= 1) {
|
|
if (
|
|
_MapCount >= S_MapsPerMatch || //< ... stop the match if the maps limit is reached and the match is not a tie
|
|
(_MapSkipped && S_MapsPerMatch == 1 && _MapCount >= S_MapsPerMatch) //< ... stop the match if the map was skipped and the match is played on only one map
|
|
) {
|
|
return True;
|
|
}
|
|
}
|
|
// If there is a rounds limit but no maps limit, continue to play until another limit is reached
|
|
else if (S_RoundsPerMap >= 1) {
|
|
return False;
|
|
}
|
|
|
|
return False;
|
|
} |