Compare commits

..

89 Commits

Author SHA1 Message Date
beu
dcd89d584d use SmallScoresTable to display delta 2025-05-08 09:36:08 +02:00
beu
da04fba158 fix round count with pause or skipped round 2025-02-16 01:20:17 +01:00
beu
a0496073da fix missing constant 2025-02-14 16:51:16 +01:00
beu
ab7dd77649 fix first eliminated score to be reseted 2025-02-09 10:49:51 +01:00
beu
b57aaa5179 fix useless update when cam 7 2025-01-30 16:56:31 +01:00
beu
26cc4f63d9 bump version 2025-01-18 14:03:59 +01:00
beu
80e4641eb9 prevent loosing life en skipping map 2025-01-18 14:03:37 +01:00
beu
e2dc0d5d73 improve wording 2025-01-18 14:03:20 +01:00
beu
b646019307 add support of eliminations based on number of remaining lives 2025-01-18 11:22:06 +01:00
beu
80003e1a39 do not increase round counter on skip 2025-01-18 10:36:06 +01:00
beu
deea33e0bd Various changes:
- 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
2024-11-29 17:36:35 +01:00
beu
4dea966448 fix sentences 2024-08-27 09:53:42 +02:00
beu
7891c33934 improve line visibility 2024-08-26 12:27:18 +02:00
beu
e6aa4049a1 Add alternative position 2024-08-25 19:29:52 +02:00
beu
5a9d8abc82 keep live ranking displayed longer after the round 2024-08-25 18:46:31 +02:00
beu
a3f042e7f7 fix possible live ranking crash 2024-08-25 18:42:23 +02:00
beu
fba93075b0 fix sorting of the Scorestable during the round 2024-08-25 18:42:05 +02:00
beu
a11e708c4d fix live ranking sorting 2024-08-25 17:03:24 +02:00
beu
cf33391e7f fix typo 2024-08-22 19:46:40 +02:00
beu
0945702fdd fix display of the live Ranking 2024-08-22 19:06:02 +02:00
beu
fead1aa840 Add MultiLivesKnockout mode 2024-08-21 23:14:29 +02:00
beu
2f3b20c322 Change respawn behaviour 2024-08-12 21:41:47 +02:00
beu
62fc7bbf83 fix best time update condition 2024-08-10 14:48:41 +02:00
beu
8b267a6978 improve font size 2024-08-03 15:58:39 +02:00
beu
786bbea656 Add Lobby Gamemode 2024-08-03 15:53:35 +02:00
beu
8abd261ef9 add setting for disable free cam 2024-07-29 15:49:50 +02:00
beu
4425fc257e Add RoundsThenTimeattack gamemode 2024-06-03 10:54:35 +02:00
beu
8b3a30e441 Add manialink ScriptName & Version 2024-05-08 15:58:00 +02:00
beu
2ca013890a update variable names 2024-05-08 10:13:35 +02:00
beu
654c2b4dde Add support of points for the future cup 2024-05-08 10:00:13 +02:00
Beu
685d6ad3fb update pointslimit setting 2024-04-11 17:40:56 +02:00
Beu
86c36f306d bump version and improve comment 2024-04-11 16:47:02 +02:00
Beu
4bafbc6de3 change default settings 2024-04-11 16:46:47 +02:00
Beu
78519b8044 Add S_NbOfWinners setting 2024-04-11 16:46:35 +02:00
Beu
04b4137078 add Big Message when someone have a perfect time 2024-04-10 11:07:21 +02:00
Beu
1e9c428fb0 Fix chat spamming bug 2024-04-10 10:42:23 +02:00
Beu
1962e99ddf add custom gamemode event for Custom Chat 2024-04-08 16:56:42 +02:00
Beu
4b2f911c2c fix display of the SmallScoresTable Module + improve timings 2024-04-05 15:29:11 +02:00
Beu
bade6e62c0 Only apply bonus to players who finished 2024-04-05 15:12:29 +02:00
Beu
51273a46ef fixes for linter 2024-04-05 14:49:37 +02:00
Beu
5076df7f37 add mslint-disable-line to prevent warning 2024-03-12 16:40:30 +01:00
Beu
7b939d0a62 update assets from discord 2024-02-25 21:13:36 +01:00
Beu
122c864044 Add EnduroCup script 2024-02-23 10:18:50 +01:00
Beu
02b67fb469 add Animation easings example mode 2024-02-19 16:24:24 +01:00
Beu
aa0cc1b094 Make all teams play when one player is finalist 2024-02-18 16:27:44 +01:00
Beu
d22c8c7147 Use different names for included libs to prevent missing libs on parent script 2024-02-09 17:11:56 +01:00
Beu
7843a689b2 Add ControllerBenchmark script 2024-01-22 12:06:33 +01:00
Beu
7ae7e3f277 prevent to be stuck waiting players 2023-12-28 17:40:09 +01:00
Beu
6077657acd fix missing team in the UI 2023-12-28 17:38:07 +01:00
Beu
a272d789d2 remove development things 2023-12-28 17:35:02 +01:00
Beu
f963d81001 fix crash when player spawn before being added 2023-12-02 20:34:22 +01:00
Beu
1e017b925d allow to update teams when playing 2023-12-02 18:13:23 +01:00
Beu
dac6f3916f prevent DNF 2023-12-02 18:13:11 +01:00
Beu
0daabfdb8f change default settings 2023-11-30 17:46:01 +01:00
Beu
fb05e863b4 fix comments 2023-11-30 17:31:20 +01:00
Beu
10465dbf12 add values to improve visibility 2023-11-30 17:24:41 +01:00
Beu
9ca01735b4 Add CP Knockout V2 2023-11-27 17:41:53 +01:00
Beu
dec90303b9 compute latest race scores before the callback 2023-10-16 23:54:26 +02:00
Beu
63b200e8c4 add new required function to have the good round count in the XmlRpc callback 2023-10-16 14:26:48 +02:00
Beu
4ca1c7cb8b add missing lib in Cup mode and maybe other 2023-10-06 22:48:15 +02:00
Beu
eff6c28839 add wrongly deleted setting 2023-10-05 22:10:24 +02:00
Beu
796c59c8f4 minor fixes 2023-10-03 22:28:02 +02:00
Beu
ba8426c0ae remove bot 2023-10-03 21:45:42 +02:00
Beu
77daf13e58 add WaitYourMate mode 2023-10-03 21:41:41 +02:00
Beu
5956325fea Merge branch '2023-10-01' 2023-09-29 13:03:16 +02:00
Beu
6c4ecfb9c0 fix changed variable names 2023-09-27 17:40:49 +02:00
Beu
10a403bec7 fix update when the player count change 2023-09-25 22:04:20 +02:00
Beu
d84b22f0ef hide marker when GUIPlayer is null 2023-09-25 21:17:04 +02:00
Beu
1fb905399d change images path URL 2023-09-25 19:13:06 +02:00
Beu
9af0cb4efa Fix ManiaApps URL 2023-09-25 18:59:38 +02:00
Beu
671f1fe8a8 Update for the 2023-10-01 game update 2023-09-25 18:24:25 +02:00
Beu
48421ed7cb fix display of the altitude + method of the XmlRpc 2023-09-24 21:17:52 +02:00
Beu
7e985b55b4 fix relative altitude from xmlrpc plugin 2023-09-24 20:36:34 +02:00
Beu
7878af83e1 fix variable declaration 2023-09-24 19:00:46 +02:00
Beu
5938c7e79f fix xmlrpc altitude sent 2023-09-24 18:57:42 +02:00
Beu
b9fcc221b4 prevent to display winner message if match is not ended 2023-09-09 00:43:51 +02:00
Beu
81f062d54c add sleep when changing map 2023-09-08 19:56:43 +02:00
Beu
8f0bc2de59 manage when player join the server 2023-09-08 19:56:11 +02:00
Beu
3a9c12cd0e manage WR with a time 2023-09-08 19:55:45 +02:00
Beu
fad3a3dc8e add TriggerAction to open the UI 2023-09-08 19:02:44 +02:00
Beu
36ee7fc531 flush queue before end map 2023-09-08 17:59:15 +02:00
Beu
11f2683a3e Add XmlRpc callbacks 2023-09-08 17:21:24 +02:00
Beu
90a2f893da fix wrong frame 2023-09-07 20:31:08 +02:00
Beu
d17b249c4f add PB and WR 2023-09-07 17:24:00 +02:00
Beu
ca555c8cbe add ClimbTheMap Base 2023-09-06 18:24:03 +02:00
Beu
fb326d725b Add Rounds Nearest script 2023-09-04 18:09:21 +02:00
Beu
310e30c311 Merge branch 'master' of ssh://ssh-gitea.virtit.fr:2222/beu/TM2020-Gamemodes 2023-09-02 13:52:36 +02:00
Beu
c60724faba add logging of the DebugMode 2023-09-02 13:51:55 +02:00
Beu
a3b1eddafb change default mode 2023-09-02 13:50:20 +02:00
26 changed files with 6165 additions and 395 deletions

View File

@ -381,11 +381,11 @@ Void SetHolidayShowdownControlML() {
declare Real GlobalEndPosY;
if (Frame_UI.Visible) {
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_TOP.dds");
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_TOP.dds");
GlobalEndPosY = -28.;
AnimMgr.Add(Frame_UI, "<frame hidden=\"1\" />", Now, 250, CAnimManager::EAnimManagerEasing::Linear);
} else {
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_BOTTOM.dds");
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_BOTTOM.dds");
GlobalEndPosY = 0.;
Frame_UI.Visible = True;
}
@ -761,13 +761,13 @@ Void SetHolidayShowdownControlML() {
<frame id="frame-global" hidden="1" pos="-60 -63">
<frame pos="-2 -25" id="frame-toggle">
<quad id="Toggle_Bg_SettingButton" size="4 4" class="quad-base" opacity="0.5" halign="center" valign="center" bgcolor="000" scriptevents="1"/>
<quad id="Toggle_Icon_SettingButton" size="4 4" class="quad-base" z-index="3" opacity="0.9" halign="center" valign="center" image="file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_BOTTOM.dds" colorize="fff"/>
<quad id="Toggle_Icon_SettingButton" size="4 4" class="quad-base" z-index="3" opacity="0.9" halign="center" valign="center" image="file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_BOTTOM.dds" colorize="fff"/>
</frame>
<frame id="frame-UI">
<quad id="quad-bg" z-index="-1" size="27 27" bgcolor="000" opacity="0.5" pos="0 0"/>
<frame pos="-2 -21">
<quad id="BindKey_SettingButton" size="4 4" class="quad-base" opacity="0.5" halign="center" valign="center" bgcolor="000" scriptevents="1"/>
<quad size="4 4" class="quad-base" z-index="3" opacity="0.9" halign="center" valign="center" image="file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/Icones_128_icon_settings_key_02.dds" colorize="fff"/>
<quad size="4 4" class="quad-base" z-index="3" opacity="0.9" halign="center" valign="center" image="file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/Icones_128_icon_settings_key_02.dds" colorize="fff"/>
</frame>
<frame id="Admin_Frame" pos="-2 -13">
<frame pos="0 0">
@ -873,12 +873,12 @@ Void SetHolidayShowdownLiveRaceML() {
declare Real GlobalEndPosX;
if (Frame_UI.Visible) {
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_RIGHT_OBLIQUE.dds");
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_RIGHT_OBLIQUE.dds");
Quad_Toggle.RelativePosition_V3.X = 5.;
GlobalEndPosX = -220.;
AnimMgr.Add(Frame_UI, "<frame hidden=\"1\" />", Now, 250, CAnimManager::EAnimManagerEasing::Linear);
} else {
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds");
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds");
Quad_Toggle.RelativePosition_V3.X = 0.;
GlobalEndPosX = -160.;
Frame_UI.Visible = True;
@ -911,15 +911,15 @@ Void SetHolidayShowdownLiveRaceML() {
if (_PlayerInfo.RoundPoints > 0) {
Label_Player_PointsValue.Value = "+" ^ _PlayerInfo.RoundPoints;
Quad_Player_PointsIcon.ChangeImageUrl("file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/256x256/Icones_256_icon_damier_02.dds");
Quad_Player_PointsIcon.ChangeImageUrl("file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/256x256/Icones_256_icon_damier_02.dds");
Quad_Player_PointsBg.BgColor = <0.839,0.098,0.098>;
} else if (_PlayerInfo.RaceTime > 0) {
Label_Player_PointsValue.Value = TL::TimeToText(_PlayerInfo.RaceTime, True, True) ;
Quad_Player_PointsIcon.ChangeImageUrl("file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/256x256/Icones_256_icon_damier_02.dds");
Quad_Player_PointsIcon.ChangeImageUrl("file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/256x256/Icones_256_icon_damier_02.dds");
Quad_Player_PointsBg.BgColor = <0.839,0.098,0.098>;
} else if (_PlayerInfo.Eliminated) {
Label_Player_PointsValue.Value = _("|DidNotFinish|DNF");
Quad_Player_PointsIcon.ChangeImageUrl("file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/256x256/Icones_256_icon_cross_01.dds");
Quad_Player_PointsIcon.ChangeImageUrl("file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/256x256/Icones_256_icon_cross_01.dds");
Quad_Player_PointsBg.BgColor = <0.588,0.588,0.588>;
} else {
Label_Player_PointsValue.Value = TL::ToText(_PlayerInfo.Altitude);
@ -1096,7 +1096,7 @@ Void SetHolidayShowdownLiveRaceML() {
</framemodel>
<frame id="frame-global" pos="-160 50">
<frame pos="58 -2.5" id="frame-toggle">
<quad id="Toggle_SettingButton" pos="0 0" size="4 4" class="quad-base" z-index="3" opacity="0.9" scriptevents="1" halign="center" valign="center" image="file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds" colorize="fff"/>
<quad id="Toggle_SettingButton" pos="0 0" size="4 4" class="quad-base" z-index="3" opacity="0.9" scriptevents="1" halign="center" valign="center" image="file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds" colorize="fff"/>
</frame>
<frame id="frame-UI">
<quad id="quad-bg" pos="0 0" z-index="-1" size="60 14" bgcolor="000" opacity="0.5" /><!-- 16 + (ML::Max(NB_Players,16) * 6) -->

View File

@ -1,10 +1,10 @@
/**
* LastManStanding mode
*/
#Extends "Libs/Nadeo/TMNext/TrackMania/Modes/TMNextRoundsBase.Script.txt"
#Extends "Modes/Nadeo/Trackmania/Base/TrackmaniaRoundsBase.Script.txt"
#Const CompatibleMapTypes "TrackMania\\TM_Race,TM_Race"
#Const Version "2023-07-14"
#Const Version "2024-05-08"
#Const ScriptName "Modes/TM2020-Gamemodes/LastManStanding.Script.txt"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
@ -12,21 +12,21 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Include "TextLib" as TL
#Include "MathLib" as ML
#Include "Libs/Nadeo/TMNext/TrackMania/Modes/Rounds/StateManager.Script.txt" as StateMgr
#Include "Libs/Nadeo/TMNext/TrackMania/Menu/Constants.Script.txt" as MenuConsts
#Include "Libs/Nadeo/ModeLibs/Common/Utils.Script.txt" as ModeUtils
#Include "Libs/Nadeo/Trackmania/Modes/Rounds/StateManager.Script.txt" as StateMgr
#Include "Libs/Nadeo/Trackmania/MainMenu/Constants.Script.txt" as MenuConsts
#Include "Libs/Nadeo/CMGame/Modes/Utils.Script.txt" as ModeUtils
// UI from Race
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/ScoresTable_Server.Script.txt" as UIModules_ScoresTable
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/PauseMenuOnline_Server.Script.txt" as UIModules_PauseMenu_Online
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/BigMessage_Server.Script.txt" as UIModules_BigMessage
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/ScoresTable_Server.Script.txt" as UIModules_ScoresTable
#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_ForceLapsNb 1
#Setting S_RoundsPerMap 1 as _("Number of rounds per map") ///< Number of round to play on one map before going to the next one
#Setting S_MapsPerMatch 4 as "Only used if S_KeepScoresBetweenRounds = True"
#Setting S_AFKIdleTime 120000 as "Time before being an AFK player will be kicked"
@ -36,6 +36,7 @@
#Setting S_MalusEveryNSecs 10 as "Roll a new Malus every N Sec"
#Setting S_NextMalusPreparationTime 10 as "Time given to players to prepare them before a Malus"
#Setting S_MalusDuration 5 as "Malus Duration"
#Setting S_KeepScoresBetweenRounds False
#Setting S_TrustClientSimu False
#Setting S_UseCrudeExtrapolation False
@ -47,9 +48,11 @@
#Const Description "$zIn $<$t$6F9LastManStanding$> mode, The goal is to be the last player not to fall off the structure. Collisions are activated and you can push your opponents to win. From a certain time, malus are sent to all the players of the game to accelerate its end."
#Const C_HudModulePath "" //< Path to the hud module
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/TMNext/TrackMania/Rounds/Rounds.Script.txt" //< Url of the mania app
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/Trackmania/Modes/Rounds.Script.txt" //< Url of the mania app
#Const C_FakeUsersNb 0
#Const C_Callback_CustomChat_ChatMessage "CustomChat.GamemodeChatMessage"
#Const C_Malus_Reset 0
#Const C_Malus_ForceEngine 1
#Const C_Malus_BackwardOnly 2
@ -92,18 +95,31 @@ Log::RegisterScript(StateMgr::ScriptName, StateMgr::Version);
***Match_LoadLibraries***
***
StateMgr::Load();
XmlRpc::RegisterCallback(C_Callback_CustomChat_ChatMessage, """
* Name: {{{C_Callback_CustomChat_ChatMessage}}}
* Type: CallbackArray
* Description: Gamemode Chat Message for the Custom Chat plugin
* Data:
- Version >=2.0.0:
```
[
"This is a gamemode chat message"
]
```
""");
***
***Match_UnloadLibraries***
***
StateMgr::Unload();
XmlRpc::UnregisterCallback(C_Callback_CustomChat_ChatMessage);
***
***Match_Settings***
***
MB_Settings_UseDefaultTimer = False;
MB_Settings_UseDefaultHud = (C_HudModulePath == "");
MB_Settings_UseDefaultPodiumSequence = False;
Rounds_Settings_UseDefaultSpawnManagement = False;
MB_Settings_UseDefaultIntroSequence = False;
***
@ -127,9 +143,7 @@ UIManager.UIAll.ScoreTableOnlyManialink = True;
ClientManiaAppUrl = C_ManiaAppUrl;
Race::SortScores(Race::C_Sort_TotalPoints);
UIModules_PauseMenu_Online::SetHelp(Description);
UIManager.UIAll.OverlayHideSpectatorControllers = True;
UIManager.UIAll.OverlayHideSpectatorInfos = True;
UIManager.UIAll.OverlayHideChrono = True;
UIManager.UIAll.OverlayHideCountdown = True;
Markers::SetDefaultMarker_HudVisibility(CUIConfigMarker::EHudVisibility::Always);
@ -156,6 +170,7 @@ foreach (Event in UIManager.PendingEvents) {
if (Event.CustomEventType == "LMS_NotifyAFK") {
if (Event.CustomEventData.count > 0) {
UIManager.UIAll.SendChat("$ff9" ^ Event.CustomEventData[0] ^ " has been kicked for being AFK");
XmlRpc::SendCallback(C_Callback_CustomChat_ChatMessage, ["$ff9" ^ Event.CustomEventData[0] ^ " has been kicked for being AFK"]);
}
}
}
@ -169,6 +184,7 @@ StateMgr::Yield();
Clans::SetClansNb(0);
UsePvPCollisions = True;
UsePvECollisions = True;
Scores::SaveInScore(Scores::C_Points_Match);
StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]);
WarmUp::SetAvailability(True);
CarRank::Reset();
@ -177,35 +193,8 @@ ResetNetworkVariables();
***Match_InitMap***
***
declare Integer Map_ValidRoundsNb;
declare Integer Map_TimeBeforeMalus;
declare Integer Map_TimeBeforeNightmare;
declare Integer Map_MalusEveryNSecs;
declare Integer Map_NextMalusPreparationTime;
declare Integer Map_MalusDuration;
declare Integer Map_RoundsPerMap;
declare Text[] AccountIdsOfPlayers for This = [];
declare Integer LandmarkIndex for This = 0;
declare K_Malus[Text] MalusQueue;
declare Boolean ActiveMalus = False;
declare Boolean PendingMalus = False;
declare Integer NextStepMalusTime = 0;
declare Integer MalusIndex;
declare Integer MalusTime;
declare netwrite Boolean Net_DisplayUI for Teams[0] = False;
declare netwrite Integer Net_NBPlayers for Teams[0] = 0;
declare netwrite Integer Net_PlayersNbAlive for Teams[0] = 0;
declare netwrite Integer Net_NextMalus for Teams[0] = -1;
declare netwrite Integer Net_TimeBeforeMalus for Teams[0] = -1;
declare netwrite Integer Net_RoundsPerMap for Teams[0] = 0;
declare netwrite Integer Net_CurrentRoundNb for Teams[0] = 0;
ResetNetworkVariables();
UIModules_ScoresTable::SetCustomTimes([]);
ResetCustomPoints();
// Map Intro
declare Boolean MapIsCompatible;
@ -220,6 +209,7 @@ if (!MapIsCompatible) {
UIManager.UIAll.QueueMessage(3000, 1, CUIConfig::EMessageDisplay::Big, _("This map is not valid"));
MB_Sleep(3000);
MB_StopMap();
MB_SetValidMap(False);
} else if (S_IntroTime > 0) {
declare netwrite Boolean Net_LMS_IsIntro for Teams[0] = False;
Net_LMS_IsIntro = True;
@ -252,13 +242,39 @@ CarRank::Reset();
***Match_InitRound***
***
declare Boolean ThrottleUpdate;
declare Integer Round_TimeBeforeMalus;
declare Integer Round_TimeBeforeNightmare;
declare Integer Round_MalusEveryNSecs;
declare Integer Round_NextMalusPreparationTime;
declare Integer Round_MalusDuration;
declare Integer Round_RoundsPerMap;
declare Text[] Round_AccountIdsOfEliminated;
declare Text[] LMS_AccountIdsOfPlayers for This = [];
declare Integer LMS_LandmarkIndex for This = 0;
declare netwrite Boolean Net_DisplayUI for Teams[0] = False;
declare netwrite Integer Net_NBPlayers for Teams[0] = 0;
declare netwrite Integer Net_PlayersNbAlive for Teams[0] = 0;
declare netwrite Integer Net_NextMalus for Teams[0] = -1;
declare netwrite Integer Net_TimeBeforeMalus for Teams[0] = -1;
declare netwrite Integer Net_RoundsPerMap for Teams[0] = 0;
declare netwrite Integer Net_CurrentRoundNb for Teams[0] = 0;
declare Boolean Round_ThrottleUpdate = False;
declare Boolean Round_ActiveMalus = False;
declare Boolean Round_PendingMalus = False;
declare Integer Round_NextStepMalusTime = 0;
declare Integer Round_MalusIndex = 0;
declare Integer Round_MalusTime = 0;
declare K_Malus[Text] Round_MalusQueue;
***
***Match_StartRound***
***
Scores::Clear();
UIModules_ScoresTable::SetCustomTimes([]);
UIModules_ScoresTable::SetScoreMode(UIModules_ScoresTable::C_Mode_Points);
Race::SortScores(Race::C_Sort_RoundPoints);
declare netwrite Integer Net_LMS_AFKIdleTime for Teams[0] = 120000;
Net_LMS_AFKIdleTime = S_AFKIdleTime;
@ -271,29 +287,29 @@ while (Players.count < 2 && Now < (StartMapTime + 3000)) {
// Initialize race
StartTime = Now + Race::C_SpawnDuration;
Map_TimeBeforeMalus = S_TimeBeforeMalus;
Map_TimeBeforeNightmare = S_TimeBeforeNightmare;
Map_MalusEveryNSecs = S_MalusEveryNSecs;
Map_NextMalusPreparationTime = S_NextMalusPreparationTime;
Map_MalusDuration = S_MalusDuration;
Map_RoundsPerMap = S_RoundsPerMap;
Round_TimeBeforeMalus = S_TimeBeforeMalus;
Round_TimeBeforeNightmare = S_TimeBeforeNightmare;
Round_MalusEveryNSecs = S_MalusEveryNSecs;
Round_NextMalusPreparationTime = S_NextMalusPreparationTime;
Round_MalusDuration = S_MalusDuration;
Round_RoundsPerMap = S_RoundsPerMap;
UpdateScoresTableFooter();
MalusTime = GetTimeBeforeMalus(StartTime, S_TimeBeforeMalus, S_TimeBeforeNightmare);
Round_MalusTime = GetTimeBeforeMalus(StartTime, S_TimeBeforeMalus, S_TimeBeforeNightmare);
Net_DisplayUI = True;
Net_TimeBeforeMalus = MalusTime;
Net_TimeBeforeMalus = Round_MalusTime;
Net_NextMalus = -1;
Net_RoundsPerMap = Map_RoundsPerMap;
Net_CurrentRoundNb = Map_ValidRoundsNb + 1;
MalusQueue = [];
Net_RoundsPerMap = Round_RoundsPerMap;
Net_CurrentRoundNb = MB_GetValidRoundCount();
ResetCustomPoints();
// Spawn players for the race
---Rounds_CanSpawn---
declare Text[] AccountIdsOfPlayers for This = [];
declare Text[] LMS_AccountIdsOfPlayers for This = [];
declare CMapLandmark PlayerLM;
declare Integer LandmarkIndex for This = 0;
AccountIdsOfPlayers = [];
declare Integer LMS_LandmarkIndex for This = 0;
LMS_AccountIdsOfPlayers = [];
// Suffle Players list to randomise spawn
declare CSmPlayer[Integer] ShuffledPlayers;
@ -315,28 +331,29 @@ foreach (Player in ShuffledPlayers) {
if (Player == Null) continue;
PlayerLM = Null;
while (PlayerLM == Null) {
if (LandmarkIndex > Landmarks.count - 1 ) {
LandmarkIndex = 0;
if (LMS_LandmarkIndex > Landmarks.count - 1 ) {
LMS_LandmarkIndex = 0;
}
if (Map::IsMultilap(Landmarks[LandmarkIndex])) {
PlayerLM = Landmarks[LandmarkIndex];
if (Map::IsMultilap(Landmarks[LMS_LandmarkIndex])) {
PlayerLM = Landmarks[LMS_LandmarkIndex];
}
LandmarkIndex = LandmarkIndex + 1 ;
LMS_LandmarkIndex = LMS_LandmarkIndex + 1 ;
}
Race::Start(Player, PlayerLM , StartTime);
CarRank::SetRank(Player, Net_NBPlayers);
AccountIdsOfPlayers.add(Player.User.WebServicesUserId);
MalusQueue[Player.User.Login] = GetNewMalus(C_Malus_Reset, 1500);
LMS_AccountIdsOfPlayers.add(Player.User.WebServicesUserId);
Round_MalusQueue[Player.User.Login] = GetNewMalus(C_Malus_Reset, 1500);
}
UIModules_ScoresTable::DisplayOnly(AccountIdsOfPlayers);
UIModules_ScoresTable::DisplayOnly(LMS_AccountIdsOfPlayers);
StateMgr::ForcePlayersStates([StateMgr::C_State_Playing]);
Race::EnableIntroDuringMatch(False);
UIManager.UIAll.SendChat("$<$ff3$> Stay the most time on the structure. $<$ff9GL HF!$>");
XmlRpc::SendCallback(C_Callback_CustomChat_ChatMessage, ["$<$ff3$> Stay the most time on the structure. $<$ff9GL HF!$>"]);
***
// @mslint-disable-next-line max-statements
***Match_PlayLoop***
***
// Manage race events
@ -348,20 +365,23 @@ foreach (Event in RacePendingEvents) {
switch (Event.Type) {
case Events::C_Type_Waypoint: {
ThrottleUpdate = True;
Round_ThrottleUpdate = True;
Scores::UpdatePlayerBestRaceIfBetter(Event.Player);
Race::StopSkipOutro(Event.Player);
UpdateCustomRanking(Event.Player.User, False);
UpdateCustomRanking(Event.Player.User, Event.Player, False);
if (Event.Player.User != Null) Round_AccountIdsOfEliminated.add(Event.Player.User.WebServicesUserId);
}
case Events::C_Type_GiveUp: {
ThrottleUpdate = True;
UpdateCustomRanking(Event.Player.User, True);
Round_ThrottleUpdate = True;
UpdateCustomRanking(Event.Player.User, Event.Player, True);
if (Event.Player.User != Null) Round_AccountIdsOfEliminated.add(Event.Player.User.WebServicesUserId);
}
case Events::C_Type_Eliminated: {
ThrottleUpdate = True;
Round_ThrottleUpdate = True;
Race::StopSkipOutro(Event.Player);
UpdateCustomRanking(Event.Player.User, True);
UpdateCustomRanking(Event.Player.User, Event.Player, True);
if (Event.Player.User != Null) Round_AccountIdsOfEliminated.add(Event.Player.User.WebServicesUserId);
}
}
}
@ -373,28 +393,30 @@ foreach (Event in PendingEvents) {
if (Event.Type == CSmModeEvent::EType::OnPlayerRemoved) {
if (Event.User == Null ) continue;
if (!AccountIdsOfPlayers.exists(Event.User.WebServicesUserId)) continue;
if (IsEliminated(Event.User, Null)) continue;
ThrottleUpdate = True;
UpdateCustomRanking(Event.User, True);
if (!LMS_AccountIdsOfPlayers.exists(Event.User.WebServicesUserId)) continue;
if (Round_AccountIdsOfEliminated.exists(Event.User.WebServicesUserId)) continue;
Round_ThrottleUpdate = True;
UpdateCustomRanking(Event.User, Null, True);
Round_AccountIdsOfEliminated.add(Event.User.WebServicesUserId);
}
}
// Detect when a players count changed without having triggered any of Event (when becoming spectator for example)
if (!ThrottleUpdate && Net_PlayersNbAlive != PlayersNbAlive) {
if (!Round_ThrottleUpdate && Net_PlayersNbAlive != PlayersNbAlive) {
log("Trying to detect why the player count changed");
foreach (Player in AllPlayers) {
if (Player.User == Null || Player.Score == Null) continue;
if (!AccountIdsOfPlayers.exists(Player.User.WebServicesUserId)) continue;
if (!LMS_AccountIdsOfPlayers.exists(Player.User.WebServicesUserId)) continue;
if (Player.SpawnStatus != CSmPlayer::ESpawnStatus::NotSpawned) continue;
if (IsEliminated(Player.User, Player.Score)) continue;
UpdateCustomRanking(Player.User, True);
ThrottleUpdate = True;
if (Round_AccountIdsOfEliminated.exists(Player.User.WebServicesUserId)) continue;
UpdateCustomRanking(Player.User, Player, True);
Round_AccountIdsOfEliminated.add(Player.User.WebServicesUserId);
Round_ThrottleUpdate = True;
}
}
if (ThrottleUpdate) {
ThrottleUpdate = False;
if (Round_ThrottleUpdate) {
Round_ThrottleUpdate = False;
Net_PlayersNbAlive = PlayersNbAlive;
declare Integer Points = Net_NBPlayers - Net_PlayersNbAlive;
foreach (Player in Players) {
@ -405,97 +427,97 @@ if (ThrottleUpdate) {
}
}
if (PlayersNbAlive <= 1 && AccountIdsOfPlayers.count >= 2) { //TODO just respawn in case of 1 player
if (PlayersNbAlive <= 1 && LMS_AccountIdsOfPlayers.count >= 2) { //TODO just respawn in case of 1 player
Net_TimeBeforeMalus = -1;
MB_StopRound();
}
// Update the map duration setting
if (Map_TimeBeforeMalus != S_TimeBeforeMalus || Map_TimeBeforeMalus != S_TimeBeforeNightmare || Map_MalusEveryNSecs != S_MalusEveryNSecs || Map_NextMalusPreparationTime != S_NextMalusPreparationTime || Map_MalusDuration != S_MalusDuration || Map_RoundsPerMap != S_RoundsPerMap) {
Map_TimeBeforeMalus = S_TimeBeforeMalus;
Map_TimeBeforeNightmare = S_TimeBeforeNightmare;
Map_MalusEveryNSecs = S_MalusEveryNSecs;
Map_NextMalusPreparationTime = S_NextMalusPreparationTime;
Map_MalusDuration = S_MalusDuration;
Map_RoundsPerMap = S_RoundsPerMap;
Net_RoundsPerMap = Map_RoundsPerMap;
if (Round_TimeBeforeMalus != S_TimeBeforeMalus || Round_TimeBeforeMalus != S_TimeBeforeNightmare || Round_MalusEveryNSecs != S_MalusEveryNSecs || Round_NextMalusPreparationTime != S_NextMalusPreparationTime || Round_MalusDuration != S_MalusDuration || Round_RoundsPerMap != S_RoundsPerMap) {
Round_TimeBeforeMalus = S_TimeBeforeMalus;
Round_TimeBeforeNightmare = S_TimeBeforeNightmare;
Round_MalusEveryNSecs = S_MalusEveryNSecs;
Round_NextMalusPreparationTime = S_NextMalusPreparationTime;
Round_MalusDuration = S_MalusDuration;
Round_RoundsPerMap = S_RoundsPerMap;
Net_RoundsPerMap = Round_RoundsPerMap;
UpdateScoresTableFooter();
MalusTime = GetTimeBeforeMalus(StartTime, S_TimeBeforeMalus, S_TimeBeforeNightmare);
if (NextStepMalusTime == 0) {
Net_TimeBeforeMalus = MalusTime;
Round_MalusTime = GetTimeBeforeMalus(StartTime, S_TimeBeforeMalus, S_TimeBeforeNightmare);
if (Round_NextStepMalusTime == 0) {
Net_TimeBeforeMalus = Round_MalusTime;
}
if (Map_MalusDuration <= 0 || (Map_TimeBeforeMalus < 0 && Map_TimeBeforeNightmare < 0)) {
if (Round_MalusDuration <= 0 || (Round_TimeBeforeMalus < 0 && Round_TimeBeforeNightmare < 0)) {
Net_TimeBeforeMalus = -1;
Net_NextMalus = -1;
}
}
// Run Malus
if (Players.count > 0 && S_MalusDuration > 0 && MalusTime != -1 && Now > MalusTime) {
if (Now > NextStepMalusTime) {
if (!ActiveMalus && !PendingMalus) {
if (Players.count > 0 && S_MalusDuration > 0 && Round_MalusTime != -1 && Now > Round_MalusTime) {
if (Now > Round_NextStepMalusTime) {
if (!Round_ActiveMalus && !Round_PendingMalus) {
if (S_TimeBeforeNightmare >= 0 && Now > (StartTime + (S_TimeBeforeNightmare * 1000))) {
MalusIndex = C_Malus_Nightmare;
Round_MalusIndex = C_Malus_Nightmare;
} else if (AllPlayersAreInTurtle()) {
log("All players are in turtle");
MalusIndex = ML::Rand(7, 10); // Boost if all players in Turtle
Round_MalusIndex = ML::Rand(7, 10); // Boost if all players in Turtle
} else {
MalusIndex = ML::Rand(1, 15);
Round_MalusIndex = ML::Rand(1, 15);
}
PendingMalus = True;
ActiveMalus = False;
NextStepMalusTime = Now + (S_NextMalusPreparationTime*1000);
Round_PendingMalus = True;
Round_ActiveMalus = False;
Round_NextStepMalusTime = Now + (S_NextMalusPreparationTime*1000);
// Players UI update
Net_NextMalus = MalusIndex;
Net_TimeBeforeMalus = NextStepMalusTime;
} else if (PendingMalus && !ActiveMalus) {
Net_NextMalus = Round_MalusIndex;
Net_TimeBeforeMalus = Round_NextStepMalusTime;
} else if (Round_PendingMalus && !Round_ActiveMalus) {
foreach (Player in Players) {
MalusQueue[Player.User.Login] = GetNewMalus(MalusIndex);
Round_MalusQueue[Player.User.Login] = GetNewMalus(Round_MalusIndex);
}
PendingMalus = False;
ActiveMalus = True;
NextStepMalusTime = Now + (S_MalusDuration*1000);
Round_PendingMalus = False;
Round_ActiveMalus = True;
Round_NextStepMalusTime = Now + (S_MalusDuration*1000);
UIModules_BigMessage::SetMessage("Current Effect: "^C_Malus_Name[MalusIndex]);
UIModules_BigMessage::SetMessage("Current Effect: "^C_Malus_Name[Round_MalusIndex]);
// Players UI update
Net_NextMalus = 0;
Net_TimeBeforeMalus = NextStepMalusTime;
} else if (!PendingMalus && ActiveMalus) {
if (MalusIndex == 99) {
Net_TimeBeforeMalus = Round_NextStepMalusTime;
} else if (!Round_PendingMalus && Round_ActiveMalus) {
if (Round_MalusIndex == 99) {
foreach (Player in Players) {
MalusQueue[Player.User.Login] = GetNewMalus(C_Malus_Nightmare);
Round_MalusQueue[Player.User.Login] = GetNewMalus(C_Malus_Nightmare);
}
NextStepMalusTime = Now + (S_MalusDuration*1000);
Round_NextStepMalusTime = Now + (S_MalusDuration*1000);
} else {
foreach (Player in Players) {
MalusQueue[Player.User.Login] = GetNewMalus(C_Malus_Reset);
Round_MalusQueue[Player.User.Login] = GetNewMalus(C_Malus_Reset);
}
PendingMalus = False;
ActiveMalus = False;
Round_PendingMalus = False;
Round_ActiveMalus = False;
NextStepMalusTime = Now + (S_MalusEveryNSecs*1000);
Round_NextStepMalusTime = Now + (S_MalusEveryNSecs*1000);
UIModules_BigMessage::SetMessage("");
// Players UI update
Net_NextMalus = -1;
Net_TimeBeforeMalus = NextStepMalusTime;
Net_TimeBeforeMalus = Round_NextStepMalusTime;
}
}
}
}
foreach (Login => Malus in MalusQueue) {
foreach (Login => Malus in Round_MalusQueue) {
declare CSmPlayer Player = GetPlayer(Login);
if (Malus.Time + 1000 < Now) { // Clear old entry
MalusQueue.removekey(Login);
Round_MalusQueue.removekey(Login);
} else if (Player != Null && Malus.Time <= Now && (Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawned || Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawning)) {
Log::Log("[ApplyPhysics] Trying to set Event " ^ C_Malus_Name[Malus.MalusIndex] ^ " for " ^ Player.User.Name);
if (SetMalus(Player, Malus.MalusIndex)) {
MalusQueue.removekey(Login);
Round_MalusQueue.removekey(Login);
}
}
}
@ -503,8 +525,7 @@ foreach (Login => Malus in MalusQueue) {
***Match_EndRound***
***
PendingMalus = False;
ActiveMalus = False;
UIModules_BigMessage::SetMessage("");
Net_DisplayUI = False;
Net_TimeBeforeMalus = -1;
Net_NextMalus = -1;
@ -521,44 +542,75 @@ if (Round_ForceEndRound || Round_SkipPauseRound) {
if (!Round_SkipPauseRound) {
ForcedEndRoundSequence();
}
MB_SetValidRound(False);
} else {
Map_ValidRoundsNb += 1;
if (S_KeepScoresBetweenRounds) {
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
MB_Sleep((S_ChatTime*1000)/2);
declare CSmScore WinnerScore <=> Scores::GetBestPlayer(Scores::C_Sort_RoundPoints);
if (WinnerScore == Null) {
foreach (Score in Scores) {
if (Score.BestRaceTimes.count <= 0 && Score.User != Null && AccountIdsOfPlayers.exists(Score.User.WebServicesUserId)) {
declare CSmPlayer Player = GetPlayer(Score.User.Login);
if (Player != Null && !Player.RequestsSpectate) {
WinnerScore <=> Score;
break;
ComputeRoundPoints();
UIModules_ScoresTable::SetCustomPoints([]);
UIModules_ScoresTable::DisplayRoundPoints(True);
MB_Sleep((S_ChatTime*1000)/2);
Scores::EndRound();
Race::SortScores(Race::C_Sort_TotalPoints);
MB_Sleep((S_ChatTime*1000)/2);
} else {
declare CSmScore WinnerScore <=> Scores::GetBestPlayer(Scores::C_Sort_RoundPoints);
if (WinnerScore == Null) {
foreach (Score in Scores) {
if (Score.BestRaceTimes.count <= 0 && Score.User != Null && LMS_AccountIdsOfPlayers.exists(Score.User.WebServicesUserId)) {
declare CSmPlayer Player = GetPlayer(Score.User.Login);
if (Player != Null && !Player.RequestsSpectate) {
WinnerScore <=> Score;
break;
}
}
}
}
Scores::SetPlayerWinner(WinnerScore);
ModeUtils::PlaySound(CUIConfig::EUISound::EndRound, 0);
if (WinnerScore == Null) {
UIModules_BigMessage::SetMessage(_("|Match|Draw"));
} else {
UIModules_BigMessage::SetMessage(_("$<%1$> wins the match!"), WinnerScore.User.WebServicesUserId);
}
Scores::EndRound();
Race::SortScores(Race::C_Sort_TotalPoints);
MB_Sleep((S_ChatTime*1000)/2);
UIModules_BigMessage::SetMessage("");
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
MB_Sleep((S_ChatTime*1000)/2);
Scores::Clear();
}
Scores::SetPlayerWinner(WinnerScore);
ModeUtils::PlaySound(CUIConfig::EUISound::EndRound, 0);
if (WinnerScore == Null) {
UIModules_BigMessage::SetMessage(_("|Match|Draw"));
} else {
UIModules_BigMessage::SetMessage(_("$<%1$> wins the match!"), WinnerScore.User.WebServicesUserId);
}
Scores::EndRound();
Race::SortScores(Race::C_Sort_TotalPoints);
MB_Sleep(5000);
UIModules_BigMessage::SetMessage("");
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
MB_Sleep((S_ChatTime*1000)/2);
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::Normal;
UIManager.UIAll.UISequence = CUIConfig::EUISequence::Playing;
if (MapIsOver(Map_ValidRoundsNb)) MB_StopMatch();
}
UIModules_ScoresTable::DisplayRoundPoints(False);
UIModules_ScoresTable::SetCustomPoints([]);
if (MapIsOver()) MB_StopMap();
}
***
***Match_EndMap***
***
if (MatchIsOver()) {
MB_StopMatch();
if (!S_KeepScoresBetweenRounds) MB_SkipPodiumSequence();
declare CSmScore Winner <=> Scores::GetBestPlayer(Scores::C_Sort_MatchPoints);
Scores::SetPlayerWinner(Winner);
} else {
MB_SkipPodiumSequence();
}
***
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
@ -583,30 +635,13 @@ Void ResetNetworkVariables() {
Net_CurrentRoundNb = 0;
}
/** Check if a Player is already considered eliminated
*
* @param _User The User
* @param _Score The Score, can be Null but it will search the Score in Scores
*
* @return Return True if the player have a time
*/
Boolean IsEliminated(CUser _User, CSmScore _Score) {
if (UIModules_ScoresTable::GetCustomTimes().existskey(_User.WebServicesUserId)) return True;
if (_Score == Null) {
foreach (Score in Scores) {
if (Score.User == Null) continue;
if (Score.User == _User) {
if (Score.PrevRaceTimes.count > 0) return True;
else return False;
break;
}
}
} else if (_Score.PrevRaceTimes.count > 0) {
return True;
Void ResetCustomPoints() {
declare Text[][Text] CustomPoints = [];
foreach (Score in Scores) {
if (Score.User == Null) continue;
CustomPoints[Score.User.WebServicesUserId] = ["--:--.---"];
}
return False;
UIModules_ScoresTable::SetCustomPoints(CustomPoints);
}
/** Detect if all players are in Turtle
@ -630,7 +665,7 @@ Boolean AllPlayersAreInTurtle() {
*
* @return The Malus Time or -1 in no Malus
*/
Integer GetTimeBeforeMalus(Integer _StartTime, Integer _TimeBeforeMalus, Integer _TimeBeforeNightmare) {
Integer GetTimeBeforeMalus(Integer _StartTime, Integer _TimeBeforeMalus, Integer _TimeBeforeNightmare) {
declare Integer MalusTime;
if (_TimeBeforeMalus >= 0 && (_TimeBeforeMalus < _TimeBeforeNightmare || _TimeBeforeNightmare == -1)) {
MalusTime = _StartTime + (_TimeBeforeMalus * 1000);
@ -646,23 +681,46 @@ Boolean AllPlayersAreInTurtle() {
/** Update the Scores Table with hidden custom points
*
* @param _User The User who is eliminated
* @param _Player The Player who is eliminated. Can be Null
* @param _OverrideTime Compute time because it not ended the Map
*/
Void UpdateCustomRanking(CUser _User, Boolean _OverrideTime) {
Void UpdateCustomRanking(CUser _User, CSmPlayer _Player, Boolean _OverrideTime) {
if (_User == Null) return;
declare Text[][Text] CustomPoints = UIModules_ScoresTable::GetCustomPoints();
if (_OverrideTime) {
declare Integer[Text] CustomTimes = UIModules_ScoresTable::GetCustomTimes();
CustomTimes[_User.WebServicesUserId] = Now - StartTime;
UIModules_ScoresTable::SetCustomTimes(CustomTimes);
if (_OverrideTime || _Player == Null || _Player.RaceWaypointTimes.count == 0) {
CustomPoints[_User.WebServicesUserId] = [TL::TimeToText(Now - StartTime, True, True)];
} else {
CustomPoints[_User.WebServicesUserId] = [TL::TimeToText(_Player.RaceWaypointTimes[-1], True, True)];
}
UIModules_ScoresTable::SetCustomPoints(CustomPoints);
UIManager.UIAll.SendChat("""$<$ff3$> Player $<$ff9{{{_User.Name}}}$> is eliminated""");
XmlRpc::SendCallback(C_Callback_CustomChat_ChatMessage, ["""$<$ff3$> Player $<$ff9{{{_User.Name}}}$> is eliminated"""]);
}
/** Distribute real RoundPoints using Points Repartition
* Only used when S_KeepScoresBetweenRounds = True
*/
Void ComputeRoundPoints() {
declare Integer[] PointsRepartition = PointsRepartition::GetPointsRepartition();
foreach (Key => Score in Scores) {
declare Integer Points = 0;
if (PointsRepartition.count > 0) {
if (PointsRepartition.existskey(Key)) {
Points = PointsRepartition[Key];
} else {
Points = PointsRepartition[PointsRepartition.count - 1];
}
}
Scores::SetPlayerRoundPoints(Score, Points);
}
}
/** Update the scores table footer text
*
*/
Void UpdateScoresTableFooter() {
Void UpdateScoresTableFooter() {
declare Text Footer = "";
if (S_MalusDuration <= 0 || (S_TimeBeforeMalus < 0 && S_TimeBeforeNightmare < 0)) {
Footer ^= "Malus disabled";
@ -679,11 +737,18 @@ Boolean AllPlayersAreInTurtle() {
Message ^= """%{{{Parts.count + 1}}}{{{TL::TimeToText(S_TimeBeforeNightmare*1000)}}}""";
Parts.add("Time Before NM: ");
}
if (S_KeepScoresBetweenRounds) {
if (Parts.count > 0) Message ^= "\n";
Message ^= """%{{{Parts.count + 1}}}{{{MB_GetMapCount()}}}/{{{S_MapsPerMatch}}}""";
//L16N [Rounds] Number of maps played during the match.
Parts.add(_("Maps : "));
}
switch (Parts.count) {
case 0: Footer = Message;
case 1: Footer = TL::Compose(Message, Parts[0]);
case 2: Footer = TL::Compose(Message, Parts[0], Parts[1]);
case 3: Footer = TL::Compose(Message, Parts[0], Parts[1], Parts[2]);
}
}
UIModules_ScoresTable::SetFooterInfo(Footer);
@ -1034,12 +1099,20 @@ Void SetML() {
/** Check if we should go to the next map
*
* @param _ValidRoundsNb Number of valid rounds played
*
* @return True if it is the case, false otherwise
*/
Boolean MapIsOver(Integer _ValidRoundsNb) {
if (S_RoundsPerMap > 0 && _ValidRoundsNb >= S_RoundsPerMap) return True; //< There is a rounds limit and it is reached
Boolean MapIsOver() {
log("""MapIsOver> S_RoundsPerMap: {{{S_RoundsPerMap}}} / MB_GetValidRoundCount() {{{MB_GetValidRoundCount()}}}""");
if (S_RoundsPerMap > 0 && MB_GetValidRoundCount() >= S_RoundsPerMap) return True; //< There is a rounds limit and it is reached
return False;
}
/** Check if the match is over
* @return True if it is the case, false otherwise
*/
Boolean MatchIsOver() {
log("""MatchIsOver> S_KeepScoresBetweenRounds: {{{S_KeepScoresBetweenRounds}}} / MB_GetValidMapCount() {{{MB_GetValidMapCount()}}} / S_MapsPerMatch {{{S_MapsPerMatch}}}""");
if (!S_KeepScoresBetweenRounds) return True;
if (MB_GetValidMapCount() >= S_MapsPerMatch) return True;
return False;
}

View File

@ -1,9 +1,9 @@
// #RequireContext CSmMode
#Include "MathLib" as ML
#Include "Libs/Nadeo/CommonLibs/Common/Log.Script.txt" as Log
#Include "Libs/Nadeo/TMxSM/Race/Events.Script.txt" as Events
#Include "Libs/Nadeo/TMxSM/Race/Race.Script.txt" as Race
#Include "Libs/Nadeo/CMGame/Utils/Log.Script.txt" as Log
#Include "Libs/Nadeo/TMGame/Modes/Events.Script.txt" as Events
#Include "Libs/Nadeo/TMGame/Modes/Race.Script.txt" as Race
#Const C_Malus_Reset 0
#Const C_Malus_ForceEngine 1

View File

@ -532,12 +532,12 @@ Void SetML() {
declare Real GlobalEndPosX;
if (Frame_UI.Visible) {
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_RIGHT_OBLIQUE.dds");
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_RIGHT_OBLIQUE.dds");
Quad_Toggle.RelativePosition_V3.X = 5.;
GlobalEndPosX = -220.;
AnimMgr.Add(Frame_UI, "<frame hidden=\"1\" />", Now, 250, CAnimManager::EAnimManagerEasing::Linear);
} else {
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds");
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds");
Quad_Toggle.RelativePosition_V3.X = 0.;
GlobalEndPosX = -160.;
Frame_UI.Visible = True;
@ -1076,7 +1076,7 @@ Void SetML() {
</stylesheet>
<frame id="frame-global" hidden="1" pos="-160 40">
<frame pos="58 -2.5" id="frame-toggle" z-index=1>
<quad id="Toggle_SettingButton" pos="0 0" size="4 4" class="quad-base" z-index="3" opacity="0.9" scriptevents="1" halign="center" valign="center" image="file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds" colorize="fff"/>
<quad id="Toggle_SettingButton" pos="0 0" size="4 4" class="quad-base" z-index="3" opacity="0.9" scriptevents="1" halign="center" valign="center" image="file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds" colorize="fff"/>
</frame>
<frame id="frame-UI">
<quad id="quad-bg" pos="0 0" z-index="-1" size="60 74" bgcolor="000" opacity="0.5" /> <!-- 85 for regular, 74 without spec control-->
@ -1086,7 +1086,7 @@ Void SetML() {
<label class="text-suffix" pos="30 -9" textsize="1.5" z-index="0" size="50 10" id="SubTitle" text=""/>
<frame pos="58 -6">
<quad id="BindKey_SettingButton" size="4 4" z-index="1" class="quad-base" opacity="0" halign="center" valign="center" bgcolor="fff" scriptevents="1"/>
<quad size="4 4" class="quad-base" z-index="3" opacity="0.9" halign="center" valign="center" image="file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/Icones_128_icon_settings_key_02.dds" colorize="fff"/>
<quad size="4 4" class="quad-base" z-index="3" opacity="0.9" halign="center" valign="center" image="file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/Icones_128_icon_settings_key_02.dds" colorize="fff"/>
</frame>
<frame pos="0.5 -6">
<frame pos="30 0">

276
TM_Alfadream.Script.txt Normal file
View File

@ -0,0 +1,276 @@
#Extends "Modes/TrackMania/TM_Cup_Online.Script.txt"
#Setting S_PointsLimit 50
#Setting S_RoundsPerMap 4
#Setting S_PointsRepartition "10,6,4,3,0"
#Setting S_PlayersPerMatch 12
#Const C_ML_TeamPicker "Custom_TeamPicker"
***Match_StartServer***
***
Clans::SetClansNb(6);
Clans::SetUseForcedClans(True);
WarmUp::SetSpawnInRequestedClan(False); // Must be disabled because Clans are forced
***
***Match_StartMatch***
***
declare netwrite Integer[Text] Net_Custom_PlayersClan for Teams[0] ;
Net_Custom_PlayersClan = [];
Teams[2].Name = "Cyan";
Teams[3].Name = "Pink";
Teams[4].Name = "Yellow";
Teams[5].Name = "Green";
***
***Match_InitMap***
***
if (GetPlayersNb() < S_PlayersPerMatch) {
SendPickManialink();
declare netwrite Integer[Text] Net_Custom_PlayersClan for Teams[0] = [];
foreach (Player in AllPlayers) {
if (Player.IsFakePlayer) {
Net_Custom_PlayersClan[Player.User.WebServicesUserId] = ML::Rand(1,6);
Player.Score.LadderClan = Net_Custom_PlayersClan[Player.User.WebServicesUserId];
}
}
while (GetPlayersNb() < S_PlayersPerMatch && MB_MapIsRunning()) {
MB_Yield();
foreach (Event in UIManager.PendingEvents) {
if (Event.Type == CUIConfigEvent::EType::OnLayerCustomEvent && Event.CustomEventType == "Custom_Join_Clan") {
declare CSmPlayer Player = GetPlayer(Event.UI);
if (Player != Null) {
Net_Custom_PlayersClan[Player.User.WebServicesUserId] = TL::ToInteger(Event.CustomEventData[0]);
Player.Score.LadderClan = Net_Custom_PlayersClan[Player.User.WebServicesUserId];
SetPlayerClan(Player, Net_Custom_PlayersClan[Player.User.WebServicesUserId]);
}
}
}
}
Layers::Detach(C_ML_TeamPicker);
}
declare Int2[] Custom_RoundsVersus for This = [];
Custom_RoundsVersus = GenerateRoundVersus();
***
***Match_InitRound***
***
declare Integer Custom_NbOfValidRounds for This = 0;
Custom_NbOfValidRounds = G_NbOfValidRounds;
declare Integer[] ClansThatCanSpawn = GetClansThatCanSpawn();
if (ClansThatCanSpawn.count == 6) {
UIModules_BigMessage::SetMessage("All teams play");
} else {
declare CTeam Team1 <=> Teams[ClansThatCanSpawn[0] - 1];
declare CTeam Team2 <=> Teams[ClansThatCanSpawn[1] - 1];
UIModules_BigMessage::SetMessage(Team1.Name ^ " vs " ^ Team2.Name);
}
MB_Sleep(3000);
UIModules_BigMessage::SetMessage("");
***
***Match_Yield***
***
foreach (Event in PendingEvents) {
if (Event.Type == CSmModeEvent::EType::OnPlayerAdded) {
declare netwrite Integer[Text] Net_Custom_PlayersClan for Teams[0] = [];
if (Net_Custom_PlayersClan.existskey(Event.Player.User.WebServicesUserId)) {
Event.Player.Score.LadderClan = Net_Custom_PlayersClan[Event.Player.User.WebServicesUserId];
SetPlayerClan(Event.Player, Net_Custom_PlayersClan[Event.Player.User.WebServicesUserId]);
}
}
}
***
***Rounds_SpawnPlayer***
***
SetPlayerClan(Player, Player.Score.LadderClan);
Race::Start(Player, StartTime);
***
***Rounds_CheckCanSpawn***
***
declare netwrite Integer[Text] Net_Custom_PlayersClan for Teams[0] = [];
if (!Net_Custom_PlayersClan.existskey(_Player.Score.User.WebServicesUserId)) return False;
// Copy of GetClansThatCanSpawn() checks
declare Boolean OnePlayerIsFinalist = False;
foreach (Score in Scores) {
if (Scores::GetPlayerMatchPoints(Score) >= S_PointsLimit) {
OnePlayerIsFinalist = True;
break;
}
}
if (!OnePlayerIsFinalist) {
declare Int2[] Custom_RoundsVersus for This = [];
declare Integer Custom_NbOfValidRounds for This = 0;
if (Custom_RoundsVersus.existskey(Custom_NbOfValidRounds)) {
if (Custom_RoundsVersus[Custom_NbOfValidRounds].X != Net_Custom_PlayersClan[_Player.Score.User.WebServicesUserId]
&& Custom_RoundsVersus[Custom_NbOfValidRounds].Y != Net_Custom_PlayersClan[_Player.Score.User.WebServicesUserId]) {
return False;
}
}
}
***
Integer GetPlayersNb() {
declare netwrite Integer[Text] Net_Custom_PlayersClan for Teams[0] = [];
return Net_Custom_PlayersClan.count;
}
Int2[] GenerateRoundVersus() {
declare Integer[Integer] SortingTeam;
for (I, 1, 6) {
SortingTeam[I] = ML::Rand(0, 10000);
}
SortingTeam = SortingTeam.sort();
declare Integer[] SortedTeam;
foreach (ClanId => Rand in SortingTeam.sort()) {
SortedTeam.add(ClanId);
}
declare Int2[] RoundOrder;
for (I, 0, 2) {
RoundOrder.add(<SortedTeam[I * 2], SortedTeam[I * 2 + 1]>);
}
return RoundOrder;
}
Integer[] GetClansThatCanSpawn() {
declare Boolean OnePlayerIsFinalist = False;
foreach (Score in Scores) {
if (Scores::GetPlayerMatchPoints(Score) >= S_PointsLimit) {
OnePlayerIsFinalist = True;
break;
}
}
if (!OnePlayerIsFinalist) {
declare Int2[] Custom_RoundsVersus for This = [];
declare Integer Custom_NbOfValidRounds for This = 0;
if (Custom_RoundsVersus.existskey(Custom_NbOfValidRounds)) {
return [Custom_RoundsVersus[Custom_NbOfValidRounds].X, Custom_RoundsVersus[Custom_NbOfValidRounds].Y];
}
}
return [1, 2, 3, 4, 5, 6];
}
Void SendPickManialink() {
declare Text Manialink = """
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<manialink version="3" name="{{{C_ML_TeamPicker}}}">
<stylesheet>
<style class="center" halign="center" valign="center" />
</stylesheet>
<framemodel id="framemodel-team">
<label id="label-team-name" pos="0 -5" class="center" textsize=3 textfont="GameFontExtraBold"/>
<frame id="frame-players" pos="0 -15">
<label pos="0 0" class="center" textsize=2 textfont="GameFontSemiBold"/>
<label pos="0 -5" class="center" textsize=2 textfont="GameFontSemiBold"/>
<label pos="0 -10" class="center" textsize=2 textfont="GameFontSemiBold"/>
<label pos="0 -15" class="center" textsize=2 textfont="GameFontSemiBold"/>
</frame>
<label id="label-join-clan" pos="0 -76" class="center" style="CardButtonSmallS" text="Join" scriptevents=2/>
</framemodel>
<frame id="frame-global">
<quad id="background" class="center" z-index="-1" size="180 80" bgcolor="000" opacity="0.7" />
<frame id="frame-teams">
<frameinstance modelid="framemodel-team" pos="-75 40" data-clanid="1"/>
<frameinstance modelid="framemodel-team" pos="-45 40" data-clanid="2"/>
<frameinstance modelid="framemodel-team" pos="-15 40" data-clanid="3"/>
<frameinstance modelid="framemodel-team" pos="15 40" data-clanid="4"/>
<frameinstance modelid="framemodel-team" pos="45 40" data-clanid="5"/>
<frameinstance modelid="framemodel-team" pos="75 40" data-clanid="6"/>
</frame>
</frame>
<script><!--
#Include "TextLib" as TL
#Include "TimeLib" as TimeLib
#Const C_AttachId {{{dump(C_ML_TeamPicker)}}}
main() {
log("Init");
declare CMlFrame Frame_Teams <=> (Page.GetFirstChild("frame-teams") as CMlFrame);
foreach (Key => Control in Frame_Teams.Controls) {
if (!Teams.existskey(Key)) break;
declare CMlFrame Frame <=> Control as CMlFrame;
declare CMlLabel Label_TeamName <=> (Frame.GetFirstChild("label-team-name") as CMlLabel);
Label_TeamName.Value = Teams[Key].Name;
Label_TeamName.TextColor = Teams[Key].ColorPrimary;
}
// Wait C++ initialize the player
wait (InputPlayer != Null);
declare netread Integer[Text] Net_Custom_PlayersClan for Teams[0] = [];
while(True) {
yield;
declare Text[][Integer] PlayerPerTeams = [];
foreach (Player in Players) {
if (!Net_Custom_PlayersClan.existskey(Player.User.WebServicesUserId)) continue;
declare Integer TeamId = Net_Custom_PlayersClan[Player.User.WebServicesUserId] - 1;
if (!PlayerPerTeams.existskey(TeamId)) PlayerPerTeams[TeamId] = [];
PlayerPerTeams[TeamId].add(Player.User.Name);
}
foreach (TeamId => Control in Frame_Teams.Controls) {
declare CMlFrame Frame_Team <=> Control as CMlFrame;
declare CMlFrame Frame_Players <=> (Frame_Team.GetFirstChild("frame-players") as CMlFrame);
foreach (Key => Control in Frame_Players.Controls) {
declare CMlLabel Label <=> Control as CMlLabel ;
//declare CMlLabel Label <=> (Frame_Player.GetFirstChild("") as CMlLabel);
if (PlayerPerTeams.existskey(TeamId) && PlayerPerTeams[TeamId].existskey(Key)) {
Label.Value = PlayerPerTeams[TeamId][Key];
} else {
Label.Value = "";
}
}
}
foreach (Event in PendingEvents) {
if (Event.Type == CMlScriptEvent::Type::MouseClick) {
if (Event.ControlId == "label-join-clan") {
declare Text ClanId = Event.Control.Parent.DataAttributeGet("clanid");
SendCustomEvent("Custom_Join_Clan", [ClanId]);
}
}
}
}
}
--></script>
</manialink>
""";
Layers::Create(C_ML_TeamPicker, Manialink);
Layers::SetType(C_ML_TeamPicker, CUILayer::EUILayerType::Normal);
Layers::Attach(C_ML_TeamPicker);
}

View File

@ -0,0 +1,145 @@
/**
This gamemode is just an UI with all animations easings displayed.
*/
#Extends "Modes/TrackMania/TM_TimeAttack_Online.Script.txt"
#Const C_Anims [
"Linear",
"QuadIn", "QuadOut", "QuadInOut",
"CubicIn", "CubicOut", "CubicInOut",
"QuartIn", "QuartOut", "QuartInOut",
"QuintIn", "QuintOut", "QuintInOut",
"SineIn", "SineOut", "SineInOut",
"ExpIn", "ExpOut", "ExpInOut",
"CircIn", "CircOut", "CircInOut",
"BackIn", "BackOut", "BackInOut",
"ElasticIn", "ElasticOut", "ElasticInOut",
"ElasticIn2", "ElasticOut2", "ElasticInOut2",
"BounceIn", "BounceOut", "BounceInOut"
]
***Match_AfterLoadHud***
***
SetMl();
***
Void SetMl() {
declare Integer IndexX = 0;
declare Integer IndexY = 0;
declare Text FrameInstances;
for (I, 0, C_Anims.count - 1) {
FrameInstances ^= """<frameinstance modelid="framemodel" pos="{{{IndexX * 15.}}} {{{IndexY * -20.}}}"/>""";
if (IndexX == 4) {
IndexX = 0;
IndexY += 1;
} else {
IndexX += 1;
}
}
declare Text MLText = """
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<manialink version="3" name="AnimationEasingsExamples">
<framemodel id="framemodel">
<quad id="quad-background" size="15 18" halign="center" bgcolor="fff" opacity="0" scriptevents="1"/>
<frame pos="0 -0.5">
<quad id="quad-graph-brackground" size="14 14" halign="center" bgcolor="000"/>
<graph id="graph" pos="0.5 -0.5" size="13 13" halign="center" min="0 -0.3" max="105. 1.3"/>
</frame>
<label id="label-name" size="14 10" pos="0 -15" textsize="1" halign="center" textcolor="ffffff" textfont="GameFontRegular"/>
</framemodel>
<frame z-index=20>
<quad z-index="-2" halign="center" valign="center" size="250 150" bgcolor="222" />
<frame id="frame-anims" pos="-115 68">
{{{FrameInstances}}}
</frame>
<frame pos="-40 50">
<quad size="150 0.2" bgcolor="aaa"/>
<quad id="quad-point1" size="5 5" bgcolor="fff"/>
<quad pos="0 -80" size="150 0.2" bgcolor="aaa"/>
</frame>
<frame pos="-30 -50">
<quad pos="0 2.5" size="0.2 10" bgcolor="aaa"/>
<quad id="quad-point2" size="5 5" bgcolor="fff"/>
<quad pos="130 2.5" size="0.2 10" bgcolor="aaa"/>
</frame>
<label id="label-currentanimname" pos="40 -65" textsize="3" halign="center" textcolor="ffffff" textfont="GameFontSemiBold" text="Linear"/>
</frame>
<script><!--
#Include "AnimLib" as AL
#Include "TimeLib" as TiL
#Include "MathLib" as ML
#Const ScriptName "AnimationEasingsExamples"
#Const Version "2024-02-19"
#Const C_Anims {{{dump(C_Anims)}}}
#Const C_AnimationDuration 1500
Void InitFrameInstances() {
declare CMlFrame Frame_Anims <=> Page.GetFirstChild("frame-anims") as CMlFrame;
declare Integer Index = 0;
foreach (AnimName in C_Anims) {
declare CMlFrame Frame <=> Frame_Anims.Controls[Index] as CMlFrame;
declare Text AnimationName for Frame;
AnimationName = AnimName;
declare CMlLabel Label_Name <=> Frame.GetFirstChild("label-name") as CMlLabel;
Label_Name.Value = AnimName;
declare CMlGraph Graph = Frame.GetFirstChild("graph") as CMlGraph;
declare CMlGraphCurve Curve = Graph.AddCurve();
Curve.Width = 0.3;
for (I, 0, 100) {
Curve.Points.add(<I * 1., AL::Ease(AnimName, I * 1., 0., 1., 100.)>);
}
Index += 1;
}
}
main() {
InitFrameInstances();
declare CMlLabel Label_CurrentAnimName <=> Page.GetFirstChild("label-currentanimname") as CMlLabel;
declare CMlQuad Quad_Point1 <=> Page.GetFirstChild("quad-point1") as CMlQuad;
declare CMlQuad Quad_Point2 <=> Page.GetFirstChild("quad-point2") as CMlQuad;
declare Integer Last_PlayAnimationTime;
while (True) {
yield;
foreach (Event in PendingEvents) {
if (Event.Type == CMlScriptEvent::Type::MouseOver) {
(Event.Control as CMlQuad).Opacity = 0.2;
} else if (Event.Type == CMlScriptEvent::Type::MouseOut) {
(Event.Control as CMlQuad).Opacity = 0.;
} else if (Event.Type == CMlScriptEvent::Type::MouseClick) {
declare Text AnimationName for Event.Control.Parent;
Label_CurrentAnimName.Value = AnimationName;
Last_PlayAnimationTime = Now;
}
}
Quad_Point1.RelativePosition_V3.X = AL::Ease("Linear", Now - Last_PlayAnimationTime * 1., 0., 145., C_AnimationDuration * 1.);
Quad_Point1.RelativePosition_V3.Y = AL::Ease(Label_CurrentAnimName.Value, Now - Last_PlayAnimationTime * 1., 0., -75., C_AnimationDuration * 1.);
Quad_Point2.RelativePosition_V3.X = AL::Ease(Label_CurrentAnimName.Value, Now - Last_PlayAnimationTime * 1., 0., 125., C_AnimationDuration * 1.);
}
}
--></script>
</manialink>
""";
Layers::Create("AnimationEasingsExamples", MLText);
Layers::SetType("AnimationEasingsExamples", CUILayer::EUILayerType::Normal);
Layers::Attach("AnimationEasingsExamples");
}

View File

@ -3,10 +3,10 @@
*/
// #RequireContext CSmMode
#Extends "Libs/Nadeo/TMNext/TrackMania/Modes/TMNextBase.Script.txt"
#Extends "Modes/Nadeo/Trackmania/Base/TrackmaniaBase.Script.txt"
#Const CompatibleMapTypes "TrackMania\\TM_Race,TM_Race"
#Const Version "2022-05-20"
#Const Version "2023-09-25"
#Const ScriptName "Modes/TM2020-Gamemodes/TM_CPKnockout.Script.txt"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
@ -14,14 +14,14 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#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/Rounds/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 "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 "ManiaApps/Nadeo/TMxSM/Race/UIModules/BigMessage_Server.Script.txt" as UIModules_BigMessage
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/BigMessage_Server.Script.txt" as UIModules_BigMessage
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Settings
@ -51,7 +51,7 @@
#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_ManiaAppUrl "file://Media/ManiaApps/Nadeo/Trackmania/Modes/Laps.Script.txt" //< Url of the mania app
#Const C_UploadRecord True
#Const C_DisplayRecordGhost False
@ -387,6 +387,6 @@ Void EliminatePlayer(CSmPlayer _Player) {
UIManager.UIAll.SendChat("Player $<$ff6" ^ _Player.User.Name ^ "$> is $<$f00eliminated$>");
// waiting GetCustomPoints function in ScoresTable_Server
declare netwrite Text[][Text] Net_TMxSM_ScoresTable_CustomPoints for Teams[0];
Net_TMxSM_ScoresTable_CustomPoints[_Player.User.WebServicesUserId] = [_("|Status|K.O."), "f00"];
declare netwrite Text[][Text] Net_TMGame_ScoresTable_CustomPoints for Teams[0];
Net_TMGame_ScoresTable_CustomPoints[_Player.User.WebServicesUserId] = [_("|Status|K.O."), "f00"];
}

476
TM_CPKnockout_V2.Script.txt Normal file
View 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 1 as _("Number of warm up")
#Setting S_WarmUpDuration 30 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,0,0,0,0" as "Number of eliminated by checkpoint sorted from the finish to the start"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Constants
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Const C_ModeName "CP Knockout"
//L16N [Laps] Description of the mode rules
#Const Description "Knockout per checkpoint game mode"
#Const C_HudModulePath "" //< Path to the hud module
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/Trackmania/Modes/TimeAttack.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;
}

689
TM_ClimbTheMap.Script.txt Normal file
View File

@ -0,0 +1,689 @@
/**
* Time Attack mode
*/
#Extends "Modes/Nadeo/Trackmania/Base/TrackmaniaBase.Script.txt"
//#RequireContext CSmMode
#Const CompatibleMapTypes "TrackMania\\TM_Race,TM_Race"
#Const Version "2024-02-25"
#Const ScriptName "Modes/TM2020-Gamemodes/TM_ClimbTheMap.Script.txt"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Libraries
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Include "TextLib" as TL
#Include "MathLib" as ML
#Include "Libs/Nadeo/CMGame/Utils/Task.Script.txt" as Task
#Include "Libs/Nadeo/Trackmania/Modes/TimeAttack/StateManager.Script.txt" as StateMgr
#Include "Libs/Nadeo/Trackmania/MainMenu/Constants.Script.txt" as MenuConsts
// UI from Race
#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
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Settings
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Setting S_AltitudeUpdateFrequency 500 as "Altitude Update Frequency in ms"
#Setting S_XmlRpcUpdateFrequency 10000 as "XmlRpc update frequency in ms"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Constants
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Const C_ModeName "Time Attack"
//L16N [Time Attack] Description of the mode rules
#Const Description _("$zIn $<$t$6F9Time Attack$> mode, the goal is to set the $<$t$6F9best time$>.\n\nYou have as many tries as you want, and you can $<$t$6F9retry$> when you want by pressing the respawn button.\n\nWhen the time is up, the $<$t$6F9winner$> is the player with the $<$t$6F9best time$>.")
#Const C_HudModulePath "" //< Path to the hud module
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/Trackmania/Modes/TimeAttack.Script.txt" //< Url of the mania app
#Const C_UploadRecord True
#Const C_DisplayRecordGhost False
#Const C_DisplayRecordMedal False
#Const C_CelebrateRecordGhost False
#Const C_CelebrateRecordMedal False
#Const C_DisplayWorldTop False
#Const C_MlId_LiveAltitude "ClimbTheMap_Altidude"
#Const C_Callback_UpdatePBs "Trackmania.ClimbTheMap.UpdatePBs"
#Const C_Callback_RequestPB "Trackmania.ClimbTheMap.RequestPB"
#Const C_Method_SetPlayersPB "Trackmania.ClimbTheMap.SetPlayersPB"
#Const C_Method_SetWR "Trackmania.ClimbTheMap.SetWR"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Extends
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
***Match_LogVersions***
***
Log::RegisterScript(ScriptName, Version);
Log::RegisterScript(StateMgr::ScriptName, StateMgr::Version);
***
***Match_LoadLibraries***
***
StateMgr::Load();
XmlRpc::RegisterCallback(C_Callback_UpdatePBs, """
* Name: {{{C_Callback_UpdatePBs}}}
* Type: CallbackArray
* Description: List of the new PB
* Data:
- Version >=2.0.0:
```
[
"[
"Vvjdn4WBRE6irR6Oie7RpA": 2000,
...
]"
]
```
""");
XmlRpc::RegisterCallback(C_Callback_RequestPB, """
* Name: {{{C_Callback_RequestPB}}}
* Type: CallbackArray
* Description: Request PB to be sure that the Player is initialized by the ManiaScript
* Data:
- Version >=2.0.0:
```
[
"Vvjdn4WBRE6irR6Oie7RpA"
]
```
""");
XmlRpc::RegisterMethod(C_Method_SetPlayersPB, """
* Name: {{{C_Method_SetPlayersPB}}}
* Type: TriggerModeScriptEventArray
* Description: List of the new PB
* Data:
- Version >=3.5.0:
```
[
"[
"Vvjdn4WBRE6irR6Oie7RpA": 2000,
...
]"
]
```
""");
XmlRpc::RegisterMethod(C_Method_SetWR, """
* Name: {{{C_Method_SetWR}}}
* Type: TriggerModeScriptEventArray
* Description: WR
* Data:
- Version >=3.5.0:
```
[
"Beu_",
"2000"
]
```
""");
***
***Match_UnloadLibraries***
***
StateMgr::Unload();
XmlRpc::UnregisterCallback(C_Callback_UpdatePBs);
XmlRpc::UnregisterCallback(C_Callback_RequestPB);
XmlRpc::UnregisterMethod(C_Method_SetPlayersPB);
XmlRpc::UnregisterMethod(C_Method_SetWR);
***
***Match_Settings***
***
MB_Settings_UseDefaultTimer = False;
MB_Settings_UseDefaultHud = (C_HudModulePath == "");
MB_Settings_UseDefaultPodiumSequence = 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***
***
UIManager.UIAll.ScoreTableOnlyManialink = True;
UIModules_Checkpoint::SetRankMode(UIModules_Checkpoint::C_RankMode_BestRace);
ClientManiaAppUrl = C_ManiaAppUrl;
Race::SortScores(UIModules_ScoresTable::C_Mode_BestTime);
UIModules_TimeGap::SetTimeGapMode(UIModules_TimeGap::C_TimeGapMode_BestRace);
UIModules_PauseMenu_Online::SetHelp(Description);
UIModules_Sign16x9Small::SetScoreMode(UIModules_Sign16x9Small::C_ScoreMode_BestRaceTime);
UIManager.UIAll.OverlayHideCountdown = True;
UIManager.UIAll.OverlayHideSpectatorInfos = True;
UIManager.UIAll.OverlayHideChrono = True;
SetMl();
***
***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);
XmlRpc::SendCallback(C_Callback_RequestPB, [Event.Player.User.Login]);
}
}
}
foreach (Event in XmlRpc.PendingEvents) {
if (Event.Type == CXmlRpcEvent::EType::CallbackArray) {
if (Event.ParamArray1 == C_Method_SetPlayersPB) {
if (Event.ParamArray2.count < 1) continue;
declare Integer[Text] AltitudePerLogin;
AltitudePerLogin.fromjson(Event.ParamArray2[0]);
declare netwrite Int2 Net_ClimbTheMap_AltitudeOfWaypoints for Teams[0];
foreach (Login => RelativeAltitude in AltitudePerLogin) {
declare CSmPlayer Player <=> GetPlayer(Login);
if (Player == Null) continue;
declare Integer Altitude = RelativeAltitude + Net_ClimbTheMap_AltitudeOfWaypoints.X;
declare netwrite Integer Net_ClimbTheMap_AltitudeOfPB for Player;
if (Net_ClimbTheMap_AltitudeOfPB < Altitude) {
Net_ClimbTheMap_AltitudeOfPB = Altitude;
}
}
declare netwrite Integer Net_ClimbTheMap_AltitudePerName_Update for Teams[0];
Net_ClimbTheMap_AltitudePerName_Update += 1;
} else if (Event.ParamArray1 == C_Method_SetWR) {
if (Event.ParamArray2.count < 3) continue;
declare netwrite Int2 Net_ClimbTheMap_AltitudeOfWaypoints for Teams[0];
declare Integer Altitude = ML::Min(TL::ToInteger(Event.ParamArray2[1]) + Net_ClimbTheMap_AltitudeOfWaypoints.X, Net_ClimbTheMap_AltitudeOfWaypoints.Y);
declare Integer Time = TL::ToInteger(Event.ParamArray2[2]);
declare netwrite Integer Net_ClimbTheMap_TimeOfWR for Teams[0];
declare netwrite Integer Net_ClimbTheMap_AltitudeOfWR for Teams[0];
if ((Time > 0 && Net_ClimbTheMap_TimeOfWR <= 0 || Net_ClimbTheMap_TimeOfWR > Time) ||
(Time <= 0 && Net_ClimbTheMap_AltitudeOfWR < Altitude)) {
Net_ClimbTheMap_TimeOfWR = Time;
Net_ClimbTheMap_AltitudeOfWR = Altitude;
declare netwrite Text Net_ClimbTheMap_NamefWR for Teams[0];
Net_ClimbTheMap_NamefWR = Event.ParamArray2[0];
declare netwrite Integer Net_ClimbTheMap_AltitudePerName_Update for Teams[0];
Net_ClimbTheMap_AltitudePerName_Update += 1;
}
}
}
}
StateMgr::Yield();
***
***Match_StartServer***
***
// Initialize mode
Clans::SetClansNb(0);
GiveUpBehaviour_RespawnAfter = True;
CrudeExtrapolation_AllowDelay = True;
Race::SetRespawnBehaviour(Race::C_RespawnBehaviour_GiveUpBeforeFirstCheckpoint);
StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]);
WarmUp::SetAvailability(True);
Race::SetupRecord(
MenuConsts::C_ScopeType_Season,
MenuConsts::C_ScopeType_PersonalBest,
MenuConsts::C_GameMode_TimeAttack,
"",
C_UploadRecord,
C_DisplayRecordGhost,
C_DisplayRecordMedal,
C_CelebrateRecordGhost,
C_CelebrateRecordMedal,
C_DisplayWorldTop
);
CarRank::Reset();
***
***Match_InitMap***
***
declare Integer Map_NextUpdate;
declare Integer[Text] Map_XmlRpc_AltitudePerLogin_Queue;
declare Integer Map_XmlRpc_Queue_NextUpdate;
declare netwrite Integer Net_ClimbTheMap_TimeOfWR for Teams[0];
declare netwrite Integer Net_ClimbTheMap_AltitudeOfWR for Teams[0];
declare netwrite Text Net_ClimbTheMap_NamefWR for Teams[0];
declare netwrite Integer[Text] Net_ClimbTheMap_AltitudePerName for Teams[0];
declare netwrite Integer Net_ClimbTheMap_AltitudePerName_Update for Teams[0];
declare netwrite Integer Net_ClimbTheMap_UpdateFrequency for Teams[0] = 500;
declare netwrite Int2 Net_ClimbTheMap_AltitudeOfWaypoints for Teams[0];
***
***Match_StartMap***
***
// Check if the map is valid or not
declare CMapLandmark Start = Map::GetStart();
declare CMapLandmark[] Finishes = Map::GetFinishes();
if (Start == Null && Finishes.count == 0) {
RaceStateMgr::ForcePlayersStates([RaceStateMgr::C_State_Waiting]);
UIModules_BigMessage::SetMessage( _("This map is not valid"));
MB_Sleep(3000);
UIModules_BigMessage::SetMessage("");
MB_StopMap();
} else {
declare netwrite Integer Net_ClimbTheMap_AltitudeOfWaypoints_Update for Teams[0];
declare Real FinishAltitude;
foreach (Finish in Finishes) {
FinishAltitude = ML::Max(Finish.Position.Y, FinishAltitude);
}
Net_ClimbTheMap_AltitudeOfWaypoints = <ML::CeilingInteger(Start.Position.Y), ML::CeilingInteger(FinishAltitude)>;
Net_ClimbTheMap_AltitudeOfWaypoints_Update += 1;
Net_ClimbTheMap_UpdateFrequency = S_AltitudeUpdateFrequency;
Net_ClimbTheMap_TimeOfWR = 0;
Net_ClimbTheMap_AltitudeOfWR = 0;
Net_ClimbTheMap_NamefWR = "";
Net_ClimbTheMap_AltitudePerName = [];
// Initialize race
StartTime = Now + Race::C_SpawnDuration;
EndTime = -1;
// Spawn players for the race
foreach (Player in Players) {
Race::Start(Player, StartTime);
declare netwrite Integer Net_ClimbTheMap_AltitudeOfPB for Player;
Net_ClimbTheMap_AltitudeOfPB = 0;
}
Net_ClimbTheMap_AltitudePerName_Update += 1;
StateMgr::ForcePlayersStates([StateMgr::C_State_Playing]);
CarRank::Update(CarRank::C_SortCriteria_BestRace);
Race::EnableIntroDuringMatch(True);
}
***
***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) {
if (Event.Player != Null) {
if (Event.IsEndRace) {
// Change Score
Scores::UpdatePlayerBestRaceIfBetter(Event.Player);
Scores::UpdatePlayerBestLapIfBetter(Event.Player);
Scores::UpdatePlayerPrevRace(Event.Player);
declare Integer Time = Event.Player.RaceWaypointTimes[Event.Player.RaceWaypointTimes.count - 1];
if (Net_ClimbTheMap_TimeOfWR <= 0 || Net_ClimbTheMap_TimeOfWR > Time) {
Net_ClimbTheMap_TimeOfWR = Time;
declare netwrite Int2 Net_ClimbTheMap_AltitudeOfWaypoints for Teams[0];
Net_ClimbTheMap_AltitudeOfWR = Net_ClimbTheMap_AltitudeOfWaypoints.Y;
Net_ClimbTheMap_NamefWR = Event.Player.User.Name;
}
}
CarRank::ThrottleUpdate(CarRank::C_SortCriteria_BestRace);
}
}
}
if (Now > Map_NextUpdate) {
Map_NextUpdate = Now + S_AltitudeUpdateFrequency;
declare Integer[Text] AltitudePerName;
foreach (Player in Players) {
if (Player.User == Null) continue;
if (Player.SpawnStatus == CSmPlayer::ESpawnStatus::NotSpawned) continue;
declare Integer Altitude = ML::FloorInteger(Player.Position.Y);
if (Net_ClimbTheMap_TimeOfWR <= 0 && Net_ClimbTheMap_AltitudeOfWR < Altitude) {
Net_ClimbTheMap_AltitudeOfWR = ML::Min(Altitude, Net_ClimbTheMap_AltitudeOfWaypoints.Y);
Net_ClimbTheMap_NamefWR = Player.User.Name;
}
declare netwrite Integer Net_ClimbTheMap_AltitudeOfPB for Player;
if (Net_ClimbTheMap_AltitudeOfPB < Altitude) {
Net_ClimbTheMap_AltitudeOfPB = Altitude;
// Map_XmlRpc_AltitudePerLogin_Queue is relative with the Start waypoint
Map_XmlRpc_AltitudePerLogin_Queue[Player.User.Login] = Altitude - Net_ClimbTheMap_AltitudeOfWaypoints.X;
}
AltitudePerName[Player.User.Name] = Altitude;
}
Net_ClimbTheMap_AltitudePerName = AltitudePerName;
Net_ClimbTheMap_AltitudePerName_Update += 1;
}
if (Now > Map_XmlRpc_Queue_NextUpdate) {
Map_XmlRpc_Queue_NextUpdate = Now + S_XmlRpcUpdateFrequency;
if (Map_XmlRpc_AltitudePerLogin_Queue.count > 0) {
XmlRpc::SendCallback(C_Callback_UpdatePBs, [Map_XmlRpc_AltitudePerLogin_Queue.tojson()]);
Map_XmlRpc_AltitudePerLogin_Queue = [];
}
}
// Manage mode events
foreach (Event in PendingEvents) {
if (Event.HasBeenPassed || Event.HasBeenDiscarded) continue;
Events::Invalid(Event);
}
// Spawn players
if (PlayersNbDead > 0) { //< Check for unspawned players only if at least one player is unspawned
foreach (Player in Players) {
if (Player.SpawnStatus == CSmPlayer::ESpawnStatus::NotSpawned && Race::IsReadyToStart(Player)) {
Race::Start(Player);
}
}
}
if (Net_ClimbTheMap_UpdateFrequency != S_AltitudeUpdateFrequency) {
Net_ClimbTheMap_UpdateFrequency = S_AltitudeUpdateFrequency;
}
***
***Match_EndMap***
***
// Ensure that we stop the match (after a vote for the next map, ...)
MB_StopMatch();
// Flush queue
if (Map_XmlRpc_AltitudePerLogin_Queue.count > 0) {
XmlRpc::SendCallback(C_Callback_UpdatePBs, [Map_XmlRpc_AltitudePerLogin_Queue.tojson()]);
Map_XmlRpc_AltitudePerLogin_Queue = [];
}
StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]);
Race::EnableIntroDuringMatch(False);
CarRank::Update(CarRank::C_SortCriteria_BestRace);
Race::StopSkipOutroAll();
***
***Match_BeforePodiumSequence***
***
ModeUtils::PlaySound(CUIConfig::EUISound::EndRound, 0);
UIModules_BigMessage::SetMessage("Changing Map");
MB_Sleep(5000);
***
***Match_AfterPodiumSequence***
***
UIModules_BigMessage::SetMessage("");
***
Void SetMl() {
declare Text FrameInstances;
for (I, 0, 100) {
FrameInstances ^= """<frameinstance modelid="framemodel-marker" hidden="1"/>""";
}
declare Text MLText = """
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<manialink version="3" name="{{{C_MlId_LiveAltitude}}}">
<stylesheet>
<style class="background" bgcolor="000" opacity="0.8" textcolor="000"/>
</stylesheet>
<framemodel id="framemodel-dot">
<quad class="background" size="1 1"/>
<label id="label-dotaltitude" class="background" pos="2 0" valign="center" textsize="1" textfont="GameFontExtraBold"/>
</framemodel>
<framemodel id="framemodel-marker">
<quad class="background" size="2 3.5" valign="center" image="https://files.virtit.fr/TrackMania/UI/triangle.png"/>
<quad id="quad-background" class="background" pos="2 0" size="0 3.5" valign="center"/>
<label id="label-playername" pos="3 -0.2" valign="center2" textsize="1" textcolor="888" textfont="GameFontSemiBold"/>
</framemodel>
<frame id="frame-global" hidden="1" pos="-150 75">
<frame z-index="-10">
<quad class="background" size="1 121"/>
<frame id="frame-dots" pos="1 -120">
<frameinstance modelid="framemodel-dot" pos="0 0"/>
<frameinstance modelid="framemodel-dot" pos="0 15"/>
<frameinstance modelid="framemodel-dot" pos="0 30"/>
<frameinstance modelid="framemodel-dot" pos="0 45"/>
<frameinstance modelid="framemodel-dot" pos="0 60"/>
<frameinstance modelid="framemodel-dot" pos="0 75"/>
<frameinstance modelid="framemodel-dot" pos="0 90"/>
<frameinstance modelid="framemodel-dot" pos="0 105"/>
<frameinstance modelid="framemodel-dot" pos="0 120"/>
</frame>
</frame>
<quad id="quad-invisiblebutton" pos="-10 10" size="45 140" scriptevents="1"/>
<frame pos="2 -120">
<frameinstance id="frame-marker-wr" modelid="framemodel-marker" z-index="-1" hidden="1"/>
<frameinstance id="frame-marker-pb" modelid="framemodel-marker" z-index="-2" hidden="1"/>
<frameinstance id="frame-marker-owner" modelid="framemodel-marker" z-index="2" hidden="1"/>
<frame id="frame-markers-live" >
{{{FrameInstances}}}
</frame>
</frame>
</frame>
<script><!--
#Include "TextLib" as TL
#Include "MathLib" as ML
#Const C_Type_Live 0
#Const C_Type_Owner 1
#Const C_Type_PB 2
#Const C_Type_WR 3
Real GetPosition(Int2 _AltitudeOfWaypoints, Integer _Altitude) {
if (_AltitudeOfWaypoints.Y == _AltitudeOfWaypoints.X) return 0.;
return (120. * (_Altitude - _AltitudeOfWaypoints.X)) / (_AltitudeOfWaypoints.Y - _AltitudeOfWaypoints.X);
}
Void UpdateMarker(CMlFrame _Frame, Int2 _AltitudeOfWaypoints, Integer _AnimationDuration, Integer _Type, Text _Name, Integer _Altitude) {
_Frame.Visible = True;
declare Text PlayerName for _Frame;
// Change Label only if name changed
declare Boolean ChangeLabel = (PlayerName != _Name);
PlayerName = _Name;
declare CMlLabel Label_PlayerName <=> (_Frame.GetFirstChild("label-playername") as CMlLabel);
if (ChangeLabel && _Type != C_Type_PB) {
Label_PlayerName.Value = _Name;
declare CMlQuad Quad_Background <=> (_Frame.GetFirstChild("quad-background") as CMlQuad);
Quad_Background.Size.X = Label_PlayerName.ComputeWidth(_Name) + 2.5;
}
AnimMgr.Flush(_Frame);
if (_AnimationDuration <= 0 || ChangeLabel) {
_Frame.RelativePosition_V3.Y = GetPosition(_AltitudeOfWaypoints, _Altitude);
} else {
AnimMgr.Add(_Frame, "<a pos=\"0 " ^ GetPosition(_AltitudeOfWaypoints, _Altitude) ^ "\"/>", _AnimationDuration, CAnimManager::EAnimManagerEasing::Linear);
}
}
Void SetMarkerStyle(CMlFrame _Frame, Integer _Type) {
declare CMlLabel Label_PlayerName <=> (_Frame.GetFirstChild("label-playername") as CMlLabel);
switch (_Type) {
case C_Type_Live: {
Label_PlayerName.TextColor = <.5, .5, .5>;
Label_PlayerName.TextFont = "GameFontSemiBold";
_Frame.ZIndex = 0.;
}
case C_Type_Owner: {
Label_PlayerName.TextColor = <1., 1., 1.>;
Label_PlayerName.TextFont = "GameFontExtraBold";
}
case C_Type_PB: {
Label_PlayerName.TextColor = <0.251,0.741,0.239>;
Label_PlayerName.TextFont = "GameFontExtraBold";
Label_PlayerName.Value = "Personal Best";
declare CMlQuad Quad_Background <=> (_Frame.GetFirstChild("quad-background") as CMlQuad);
Quad_Background.Size.X = Label_PlayerName.ComputeWidth(Label_PlayerName.Value) + 2.5;
}
case C_Type_WR: {
Label_PlayerName.TextColor = <1.,0.733,0.043>;
Label_PlayerName.TextFont = "GameFontExtraBold";
}
}
}
main() {
declare CMlFrame Frame_Global <=> (Page.GetFirstChild("frame-global") as CMlFrame) ;
declare CMlFrame Frame_Dots <=> (Frame_Global.GetFirstChild("frame-dots") as CMlFrame);
declare CMlFrame Frame_MarkersLive <=> (Frame_Global.GetFirstChild("frame-markers-live") as CMlFrame);
declare CMlFrame[] LiveMarkers;
foreach (Control in Frame_MarkersLive.Controls) {
LiveMarkers.add(Control as CMlFrame);
}
declare CMlFrame Frame_Marker_WR <=> (Frame_Global.GetFirstChild("frame-marker-wr") as CMlFrame);
SetMarkerStyle(Frame_Marker_WR, C_Type_WR);
declare CMlFrame Frame_Marker_PB <=> (Frame_Global.GetFirstChild("frame-marker-pb") as CMlFrame);
SetMarkerStyle(Frame_Marker_PB, C_Type_PB);
declare CMlFrame Frame_Marker_Owner <=> (Frame_Global.GetFirstChild("frame-marker-owner") as CMlFrame);
SetMarkerStyle(Frame_Marker_Owner, C_Type_Owner);
wait(InputPlayer != Null);
Frame_Global.Visible = True;
declare netread Integer Net_ClimbTheMap_UpdateFrequency for Teams[0] = 500;
declare netread Integer Net_ClimbTheMap_AltitudeOfWR for Teams[0];
declare netread Text Net_ClimbTheMap_NamefWR for Teams[0];
declare netread Int2 Net_ClimbTheMap_AltitudeOfWaypoints for Teams[0];
declare netread Integer Net_ClimbTheMap_AltitudeOfWaypoints_Update for Teams[0];
declare Integer Last_AltitudeOfWaypoints = -1;
declare netread Integer[Text] Net_ClimbTheMap_AltitudePerName for Teams[0];
declare netread Integer Net_ClimbTheMap_AltitudePerName_Update for Teams[0];
declare Integer Last_AltitudePerName = -1;
declare Boolean Last_PageWasVisible;
while (True) {
yield;
if (PageIsVisible) {
if (Last_AltitudeOfWaypoints != Net_ClimbTheMap_AltitudeOfWaypoints_Update) {
Last_AltitudeOfWaypoints = Net_ClimbTheMap_AltitudeOfWaypoints_Update;
foreach (Key => Control in Frame_Dots.Controls) {
declare CMlFrame Frame <=> (Control as CMlFrame);
declare CMlLabel Label_DotAltidude <=> (Frame.GetFirstChild("label-dotaltitude") as CMlLabel);
if (Key == 0) {
Label_DotAltidude.Value = "Start";
} else if (Key == Frame_Dots.Controls.count - 1) {
Label_DotAltidude.Value = "Finish";
} else {
Label_DotAltidude.Value = TL::ToText((Net_ClimbTheMap_AltitudeOfWaypoints.Y - Net_ClimbTheMap_AltitudeOfWaypoints.X) / (Frame_Dots.Controls.count - 1) * Key);
}
}
}
if (Last_AltitudePerName != Net_ClimbTheMap_AltitudePerName_Update) {
Last_AltitudePerName = Net_ClimbTheMap_AltitudePerName_Update;
declare Integer AnimationDuration = 0;
if (Last_PageWasVisible) AnimationDuration = Net_ClimbTheMap_UpdateFrequency;
if (Net_ClimbTheMap_AltitudeOfWR > 0) {
UpdateMarker(Frame_Marker_WR, Net_ClimbTheMap_AltitudeOfWaypoints, AnimationDuration, C_Type_WR, Net_ClimbTheMap_NamefWR, Net_ClimbTheMap_AltitudeOfWR);
}
declare CSmPlayer Owner <=> InputPlayer;
if (GUIPlayer != Null && GUIPlayer.User != Null) {
Owner <=> GUIPlayer;
}
declare Text OwnerName = Owner.User.Name;
declare netread Integer Net_ClimbTheMap_AltitudeOfPB for Owner;
if (Net_ClimbTheMap_AltitudeOfPB > 0 && Net_ClimbTheMap_AltitudeOfPB != Net_ClimbTheMap_AltitudeOfWR) {
UpdateMarker(Frame_Marker_PB, Net_ClimbTheMap_AltitudeOfWaypoints, AnimationDuration, C_Type_PB, OwnerName, Net_ClimbTheMap_AltitudeOfPB);
} else {
Frame_Marker_PB.Visible = False;
}
declare Integer I;
declare Boolean OwnerIsPassed;
foreach (Name => Altitude in Net_ClimbTheMap_AltitudePerName) {
if (!LiveMarkers.existskey(I)) break;
if (OwnerName == Name) {
UpdateMarker(Frame_Marker_Owner, Net_ClimbTheMap_AltitudeOfWaypoints, AnimationDuration, C_Type_Owner, Name, Altitude);
OwnerIsPassed = True;
} else {
UpdateMarker(LiveMarkers[I], Net_ClimbTheMap_AltitudeOfWaypoints, AnimationDuration, C_Type_Live, Name, Altitude);
I += 1;
}
}
if (!OwnerIsPassed) {
Frame_Marker_Owner.Visible = False;
}
while (LiveMarkers.existskey(I) && LiveMarkers[I].Visible) {
LiveMarkers[I].Visible = False;
I += 1;
}
}
foreach (Event in PendingEvents) {
if (Event.Type == CMlScriptEvent::Type::MouseClick && Event.ControlId == "quad-invisiblebutton") {
TriggerPageAction("Trackmania.ClimbTheMap.ShowAltitudeRecords");
}
}
Last_PageWasVisible = True;
} else {
Last_PageWasVisible = False;
}
}
}
--></script>
</manialink>
""";
Layers::Create(C_MlId_LiveAltitude, MLText);
Layers::SetType(C_MlId_LiveAltitude, CUILayer::EUILayerType::Normal);
Layers::Attach(C_MlId_LiveAltitude);
}

View File

@ -1,5 +1,7 @@
#Extends "Modes/TrackMania/TM_TimeAttack_Online.Script.txt"
#Include "Libs/Nadeo/Trackmania/MainMenu/Constants.Script.txt" as MenuConsts
#Setting S_TrustClientSimu False
#Setting S_UseCrudeExtrapolation False

View File

@ -0,0 +1,97 @@
#Extends "Modes/TrackMania/TM_TimeAttack_Online.Script.txt"
#Setting S_RandomizedFakePlayersTimeMax 120000 as "Max time to connect or disconnect a player (0 = disabled)"
#Setting S_RandomizedFakePlayersTimeMin 1000 as "Min time to connect or disconnect a player (0 = disabled)"
#Setting S_RandomizedSpeedMax 100.
#Setting S_RandomizedSpeedMin 30.
#Const C_CheckSpawnedTime 1000
***Match_InitMap***
***
declare Integer Map_CheckSpawnedPlayers;
declare Integer Map_CheckConnectPlayer;
***
***Match_StartMap***
***
if (ServerAdmin != Null) {
MB_Sleep(500); // Wait a bit before if bot already exits
log("Spawning " ^ServerAdmin.ServerInfo.MaxPlayerCount - 5 - Players.count ^ " bots");
Users_SetNbFakeUsers(ML::Max(0, ServerAdmin.ServerInfo.MaxPlayerCount - 5 - Players.count) , 0);
}
if (RandomizedFakePlayersEnabled()) Map_CheckConnectPlayer = Now + ML::Rand(1000,120000);
***
***Match_PlayLoop***
***
foreach (Event in Race::GetPendingEvents()) {
if (Event.Type == Events::C_Type_StartLine) {
ApplyRandomSpeed(Event.Player);
}
}
// check if a player joined the server or if spectator became player
if (Map_CheckSpawnedPlayers < Now) {
Map_CheckSpawnedPlayers = Now + C_CheckSpawnedTime;
foreach (Player in Players) {
if (Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawned && Player.TrustClientSimu) {
ApplyRandomSpeed(Player);
}
}
}
if (RandomizedFakePlayersEnabled() && Map_CheckConnectPlayer < Now && ServerAdmin != Null) {
Map_CheckConnectPlayer = Now + ML::Rand(1000,120000);
if (Players.count >= ServerAdmin.ServerInfo.MaxPlayerCount) {
log("Removing randomly FakePlayer");
DisconnectRandomBot();
} else if (ML::Rand(0, 1) == 1) {
log("Removing randomly FakePlayer");
DisconnectRandomBot();
} else {
log("Adding Fakeplayer");
Users_CreateFake("", 0);
}
}
***
Void ApplyRandomSpeed(CSmPlayer _Player) {
if (_Player == Null) return;
if (_Player.SpawnStatus != CSmPlayer::ESpawnStatus::Spawned) return;
declare Integer Race_RaceState for _Player = Race::C_RaceState_Waiting;
if (Race_RaceState == 3 && _Player.IsFakePlayer) {
Race::EndIntro(_Player);
return;
}
if (Race_RaceState != Race::C_RaceState_Racing) return;
_Player.TrustClientSimu = False;
SetPlayerVehicle_ControlledByMode(_Player, True);
SetPlayerVehicle_TargetSpeedValue(_Player, ML::Rand(S_RandomizedSpeedMin, S_RandomizedSpeedMax));
}
Void DisconnectRandomBot() {
if (Players.count <= 0) return;
declare Integer Index = ML::Rand(0, Players.count - 1);
declare Integer Iteration = 0;
while (Players.count > 0 && Iteration < Players.count) {
if (!Players.existskey(Index)) {
Index = 0;
}
if (Players[Index].IsFakePlayer) {
Users_DestroyFake(Players[Index].User);
return;
}
Iteration += 1;
}
}
Boolean RandomizedFakePlayersEnabled() {
return (S_RandomizedFakePlayersTimeMin > 0 && S_RandomizedFakePlayersTimeMax > 0);
}

View File

@ -3,23 +3,35 @@
* This mode is used to debug your modes during development.
* To use it, you just have to change the line below to put the path of your mode:
*/
#Extends "Modes/TM_TeamsCup.Script.txt"
#Extends "Modes/TrackMania/TM_Rounds_Online.Script.txt"
// #RequireContext CSmMode
#Include "TimeLib" as DebugMode_TiL // @mslint-disable-line include-use-common-namespace
#Include "MathLib" as DebugMode_ML // @mslint-disable-line include-use-common-namespace
#Include "TextLib" as DebugMode_TL // @mslint-disable-line include-use-common-namespace
#Setting S_DebugLib_RestrictUIto "" as "Restrict UI Admin to comma separated logins (all if empty)"
#Const C_DebugMode_Version "2023-08-28"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Constants
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Const C_DebugMode_Version "2024-05-08"
#Const C_DebugMode_MainUI "DebugMode_MainUI"
#Include "TimeLib" as TiL
#Include "MathLib" as ML
#Include "TextLib" as TL
#Include "Libs/Nadeo/ModeLibs/Common/Utils.Script.txt" as ModeUtils
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Settings
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Setting S_ScriptEnvironment "development"
#Setting S_DebugLib_RestrictUIto "" as "Restrict UI Admin to comma separated logins (all if empty)"
***Match_LogVersions***
***
log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
log("~~~~~~~~~~~~~ Starting DebugMode v" ^ C_DebugMode_Version ^ " ~~~~~~~~~~~~~");
log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
log("~~ Launched at: "^ System.CurrentLocalDateText ^" ("^ System.CurrentTimezone ^")");
log("~~ Server version: "^ System.ExeVersion);
log("~~ TitlePack version: "^ DebugMode_TL::Split(" ", LoadedTitle.TitleVersion)[0]);
log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
***
***Match_AfterLoadHud***
***
@ -33,7 +45,7 @@ if (S_DebugLib_RestrictUIto == "") {
Layers::Attach(C_DebugMode_MainUI);
} else {
Layers::Detach(C_DebugMode_MainUI);
foreach (Login in TL::Split(",", S_DebugLib_RestrictUIto)) {
foreach (Login in DebugMode_TL::Split(",", S_DebugLib_RestrictUIto)) {
declare CSmPlayer Player = GetPlayer(Login);
if (Player != Null) Layers::Attach(C_DebugMode_MainUI, Player);
}
@ -51,13 +63,13 @@ foreach (Event in UIManager.PendingEvents) {
}
case "DebugMode.Bots.DelBots": {
declare Text Name = Event.CustomEventData[0];
declare Integer NumberOfBots = TL::ToInteger(Event.CustomEventData[1]);
declare Integer NumberOfBots = DebugMode_TL::ToInteger(Event.CustomEventData[1]);
if (NumberOfBots == -1) {
Users_DestroyAllFakes();
} else {
foreach (Player in AllPlayers) {
if (Player.IsFakePlayer) {
if (Name == "" || TL::ToLowerCase(Player.User.Name) == TL::ToLowerCase(Name)) {
if (Name == "" || DebugMode_TL::ToLowerCase(Player.User.Name) == DebugMode_TL::ToLowerCase(Name)) {
Net_DebugMode_Logs = Prefix ^ "Bots: Remove bot \"" ^ Player.User.Name ^ "\"\n" ^ Net_DebugMode_Logs;
Users_DestroyFake(Player.User);
NumberOfBots -= 1;
@ -71,9 +83,9 @@ foreach (Event in UIManager.PendingEvents) {
}
case "DebugMode.Bots.AddBots": {
declare Text Name = Event.CustomEventData[0];
declare Integer TeamId = TL::ToInteger(Event.CustomEventData[1]);
declare Integer TeamId = DebugMode_TL::ToInteger(Event.CustomEventData[1]);
if (TeamId == -1 && Event.CustomEventData[1] == "") TeamId = 0;
declare Integer NumberOfBots = TL::ToInteger(Event.CustomEventData[2]);
declare Integer NumberOfBots = DebugMode_TL::ToInteger(Event.CustomEventData[2]);
if (TeamId != -1 && Teams.existskey(TeamId)) {
for (I, 1, NumberOfBots) {
declare CUser User = Users_CreateFake(Name, TeamId);
@ -102,13 +114,13 @@ foreach (Event in UIManager.PendingEvents) {
}
}
declare Real[] Speed;
foreach (SpeedText in TL::Split(",", Event.CustomEventData[0])) {
Speed.add(ML::Clamp(TL::ToReal(SpeedText), -999., 999.));
foreach (SpeedText in DebugMode_TL::Split(",", Event.CustomEventData[0])) {
Speed.add(DebugMode_ML::Clamp(DebugMode_TL::ToReal(SpeedText), -999., 999.));
}
if (Speed.count > 0) {
declare Real[] Steer;
foreach (SteerText in TL::Split(",", Event.CustomEventData[1])) {
Steer.add(ML::Clamp(TL::ToReal(SteerText), -1., 1.));
foreach (SteerText in DebugMode_TL::Split(",", Event.CustomEventData[1])) {
Steer.add(DebugMode_ML::Clamp(DebugMode_TL::ToReal(SteerText), -1., 1.));
}
foreach (Target in Targets) {
@ -119,7 +131,7 @@ foreach (Event in UIManager.PendingEvents) {
declare Real SteerValue;
if (Steer.count == 2) {
SteerValue = ML::Rand(Steer[0] , Steer[1]);
SteerValue = DebugMode_ML::Rand(Steer[0] , Steer[1]);
} else if (Steer.count == 1) {
SteerValue = Steer[0];
} else {
@ -131,7 +143,7 @@ foreach (Event in UIManager.PendingEvents) {
if (Speed.count == 1) {
SpeedValue = Speed[0];
} else {
SpeedValue = ML::Rand(Speed[0], Speed[1]);
SpeedValue = DebugMode_ML::Rand(Speed[0], Speed[1]);
}
SetPlayerVehicle_TargetSpeedValue(Target, SpeedValue);
@ -207,13 +219,13 @@ foreach (Event in UIManager.PendingEvents) {
***DebugMode_FindPlayer***
***
if (TL::Length(Event.CustomEventData[0]) == 36) {
if (DebugMode_TL::Length(Event.CustomEventData[0]) == 36) {
Player <=> ModeUtils::GetPlayerFromAccountId(Event.CustomEventData[0]);
} else if (TL::Length(Event.CustomEventData[0]) == 22) {
} else if (DebugMode_TL::Length(Event.CustomEventData[0]) == 22) {
Player <=> GetPlayer(Event.CustomEventData[0]);
} else {
foreach (TmpPlayer in AllPlayers) {
if (TL::ToLowerCase(Event.CustomEventData[0]) == TL::ToLowerCase(TmpPlayer.User.Name)) {
if (DebugMode_TL::ToLowerCase(Event.CustomEventData[0]) == DebugMode_TL::ToLowerCase(TmpPlayer.User.Name)) {
Player <=> TmpPlayer;
break;
}
@ -242,7 +254,7 @@ foreach (Event in RacePendingEvents) {
+++DebugMode_LogFormat+++
Net_DebugMode_Logs = Prefix ^ "CSmMode PendingEvent: " ^ Event.Type ^"\n" ^ Net_DebugMode_Logs;
if (Event.Type == CSmModeEvent::EType::OnPlayerAdded) {
if (Event.Player != Null && TL::Split(",", S_DebugLib_RestrictUIto).exists(Event.Player.User.Login)) {
if (Event.Player != Null && DebugMode_TL::Split(",", S_DebugLib_RestrictUIto).exists(Event.Player.User.Login)) {
Layers::Attach(C_DebugMode_MainUI, Event.Player);
}
}
@ -275,7 +287,7 @@ Net_DebugMode_Logs = Prefix ^ "Race Pending Event: " ^ Type ^"\n" ^ Net_DebugMod
***DebugMode_LogFormat***
***
declare Integer CurrentTimeStamp = TL::ToInteger(TiL::GetCurrent());
declare Integer CurrentTimeStamp = DebugMode_TL::ToInteger(DebugMode_TiL::GetCurrent());
declare Integer Hours = (CurrentTimeStamp / 3600) % 24;
declare Integer Minutes = (CurrentTimeStamp / 60) % 60;
@ -291,8 +303,8 @@ else Prefix ^= Seconds;
Prefix ^= "] ";
declare netwrite Text Net_DebugMode_Logs for Teams[0];
if (TL::Length(Net_DebugMode_Logs) > 1000000) {
Net_DebugMode_Logs = TL::SubString(Net_DebugMode_Logs, 0, 1000000);
if (DebugMode_TL::Length(Net_DebugMode_Logs) > 1000000) {
Net_DebugMode_Logs = DebugMode_TL::SubString(Net_DebugMode_Logs, 0, 1000000);
}
declare netwrite Integer Net_DebugMode_Logs_Serial for Teams[0];
@ -536,6 +548,10 @@ Text GetManialink() {
#Include "MathLib" as ML
// #RequireContext CSmMlScriptIngame
#Const ScriptName {{{dump(C_DebugMode_MainUI)}}}
#Const Version {{{dump(C_DebugMode_Version)}}}
Real ComputeBackgroundSize(CMlLabel _Label, Text _Value, Real _MaxLineWidth) {
declare Real SpaceSize = _Label.ComputeWidth(" ");

624
TM_EnduroCup.Script.txt Normal file
View File

@ -0,0 +1,624 @@
/**
* EnduroCup mode
*/
#Extends "Modes/Nadeo/Trackmania/Base/TrackmaniaRoundsBase.Script.txt"
#Const CompatibleMapTypes "TrackMania\\TM_Race,TM_Race"
#Const Version "2024-12-23"
#Const ScriptName "Modes/TM2020-Gamemodes/TM_EnduroCup.Script.txt"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// 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_CheckpointsWithoutElimination -1 as "Number of checkpoint without elimination. Negative value = Nb of Laps"
#Setting S_TimeToReachCheckpoint 30000 as "Default time to reach checkpoint in ms"
#Setting S_TimeReductionEveryCP 500 as "Reduction of time in ms applied to each checkpoint"
#Setting S_PointsLimit 0 as _("Points limit")
#Setting S_RoundsPerMap 4 as _("Number of rounds per track") ///< Number of round to play on one map before going to the next one
#Setting S_MapsPerMatch 1 as _("Number of tracks per match") ///< Number of maps to play before finishing the match
#Setting S_UseTieBreak False as _("Use tie-break") ///< Continue to play the map until the tie is broken
#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")
// Default settings that sould be ignored
#Setting S_InfiniteLaps True
#Setting S_ForceLapsNb 0
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Constants
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Const C_ModeName "EnduroCup"
//L16N [Rounds] Description of the mode rules
#Const Description "" // TODO CHANGE
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/Trackmania/Modes/Rounds.Script.txt" //< Url of the mania app
#Const C_FakeUsersNb 0
// Server can allow up to 500ms of latency from the clients, so the double should be enough
#Const C_LatencyCompensation 1000
#Const C_CleanCheckDelay 1000
#Const C_ScriptName_Timer "EnduroCup_Timer"
#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
#Struct K_CheckpointState {
Integer RaceTime;
Integer TimeToReach;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// 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 = True;
***
***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;
// Unload default UI
UIModules::UnloadModules(["UIModule_Rounds_SmallScoresTable"]);
CreateTimerUI();
***
***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);
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
);
declare netwrite K_CheckpointState[Integer] Net_EnduroCup_Checkpoints for Teams[0] = [];
Net_EnduroCup_Checkpoints = [];
***
***Match_InitMap***
***
declare Integer Map_ValidRoundsNb;
declare Boolean Map_Skipped;
declare Integer Map_PointsLimit = S_PointsLimit;
declare Integer Map_RoundsPerMap = S_RoundsPerMap;
declare Integer Map_MapsPerMatch = S_MapsPerMatch;
UpdateScoresTableFooter(S_PointsLimit, S_RoundsPerMap, S_MapsPerMatch, Map_ValidRoundsNb);
***
***Match_StartMap***
***
// Set inifinite laps (ignoring server settings)
Race::SetLapsSettings(True, 0);
Map_Skipped = True;
CarRank::Reset();
// Warm up
UIModules_ScoresTable::SetFooterInfo(_("Warm up"));
MB_WarmUp(S_WarmUpNb, S_WarmUpDuration * 1000, S_WarmUpTimeout * 1000);
***
***Match_InitRound***
***
declare Integer Round_CheckpointsWithoutElimination;
declare netwrite K_CheckpointState[Integer] Net_EnduroCup_Checkpoints for Teams[0] = [];
declare Integer Last_CheckAlivePlayers = 0;
while (Players.count < 2 && MB_MapIsRunning()) {
MB_Yield();
}
***
***Match_StartRound***
***
if (S_CheckpointsWithoutElimination < 0) {
Round_CheckpointsWithoutElimination = (Map::GetCheckpointsCount() + 1) * ML::Abs(Round_CheckpointsWithoutElimination);
} else {
Round_CheckpointsWithoutElimination = S_CheckpointsWithoutElimination;
}
UpdateScoresTableFooter(S_PointsLimit, S_RoundsPerMap, S_MapsPerMatch, Map_ValidRoundsNb);
StateMgr::ForcePlayersStates([StateMgr::C_State_Playing]);
***
***Rounds_PlayerSpawned***
***
CarRank::ThrottleUpdate(CarRank::C_SortCriteria_CurrentRace);
***
***Rounds_AfterSpawningPlayers***
***
// Prevent late players to spawn
foreach (Score in Scores) {
Rounds_SetCanSpawn(Score, False);
}
***
***Match_PlayLoop***
***
// Manage race events
foreach (Event in Race::GetPendingEvents()) {
Race::ValidEvent(Event);
if (Event.Player == Null) continue;
// Waypoint
if (Event.Type == Events::C_Type_Waypoint) {
CarRank::ThrottleUpdate(CarRank::C_SortCriteria_CurrentRace);
if (Event.IsEndLap) {
Scores::UpdatePlayerBestLapIfBetter(Event.Player);
}
// Update the PrevRace at each checkpoint to be able to sort scores even if someone disconnect or something
Scores::UpdatePlayerPrevRace(Event.Player);
declare Integer NumberOfCP = Event.Player.RaceWaypointTimes.count;
if (Round_CheckpointsWithoutElimination < NumberOfCP) {
// Player is first
if (!Net_EnduroCup_Checkpoints.existskey(NumberOfCP) || Net_EnduroCup_Checkpoints[NumberOfCP].RaceTime > Event.RaceTime) {
Net_EnduroCup_Checkpoints[NumberOfCP] = K_CheckpointState {
RaceTime = Event.RaceTime,
TimeToReach = S_TimeToReachCheckpoint - S_TimeReductionEveryCP * (NumberOfCP - Round_CheckpointsWithoutElimination)
};
if (Last_CheckAlivePlayers == 0) Last_CheckAlivePlayers = Now + C_CleanCheckDelay;
} else if (Net_EnduroCup_Checkpoints[NumberOfCP].RaceTime + Net_EnduroCup_Checkpoints[NumberOfCP].TimeToReach < Event.RaceTime) {
EliminatePlayer(Event.Player);
}
}
} else if (Event.Type == Events::C_Type_GiveUp) {
EliminatePlayer(Event.Player);
}
}
// Manage mode events
foreach (Event in PendingEvents) {
if (Event.HasBeenPassed || Event.HasBeenDiscarded) continue;
Events::Invalid(Event);
}
// Check if players need to be unspawned
if (Last_CheckAlivePlayers > 0 && Last_CheckAlivePlayers < Now) {
Last_CheckAlivePlayers = 0;
foreach (Player in Players) {
if (Player.SpawnStatus != CSmPlayer::ESpawnStatus::Spawned) continue;
// Use get because it's more optimized
declare K_CheckpointState NextCheckpointState = Net_EnduroCup_Checkpoints.get(Player.RaceWaypointTimes.count + 1, K_CheckpointState{});
if (NextCheckpointState.RaceTime == 0) continue;
if (NextCheckpointState.RaceTime + NextCheckpointState.TimeToReach + C_LatencyCompensation < Player.CurrentRaceTime) {
EliminatePlayer(Player);
}
}
}
// Server info change
if (
Map_PointsLimit != S_PointsLimit ||
Map_RoundsPerMap != S_RoundsPerMap ||
Map_MapsPerMatch != S_MapsPerMatch
) {
Map_PointsLimit = S_PointsLimit;
Map_RoundsPerMap = S_RoundsPerMap;
Map_MapsPerMatch = S_MapsPerMatch;
UpdateScoresTableFooter(S_PointsLimit, S_RoundsPerMap, S_MapsPerMatch, Map_ValidRoundsNb);
}
***
***Rounds_CheckStopRound***
***
// End the round
// If All players finished
if (Players.count > 0 && PlayersNbAlive <= 1) {
MB_StopRound();
Round_Skipped = False;
}
// If forced end round or round skipped after pause
if (Round_ForceEndRound || Round_SkipPauseRound) {
MB_StopRound();
Round_Skipped = False;
}
***
***Match_EndRound***
***
Race::StopSkipOutroAll();
StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]);
CarRank::Update(CarRank::C_SortCriteria_CurrentRace);
Net_EnduroCup_Checkpoints = [];
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
ComputePoints();
Race::SortScores(Race::C_Sort_TotalPoints);
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
UIManager.UIAll.UISequence = CUIConfig::EUISequence::EndRound;
MB_Sleep(5000);
// Add them to the total scores
ComputeScores();
Race::SortScores(Race::C_Sort_TotalPoints);
MB_Sleep(3000);
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::Normal;
UIManager.UIAll.UISequence = CUIConfig::EUISequence::Playing;
if (MapIsOver(S_UseTieBreak, S_PointsLimit, Map_ValidRoundsNb, S_RoundsPerMap)) {
Map_Skipped = False;
MB_StopMap();
}
}
***
***Match_EndMap***
***
if (MatchIsOver(S_UseTieBreak, S_PointsLimit, MB_GetMapCount(), S_MapsPerMatch, S_RoundsPerMap, Map_Skipped)) MB_StopMatch();
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]));
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Compute round points of the prev race
Void ComputePoints() {
declare Ident[][Integer][Integer] Ranking = [];
foreach (Score in Scores) {
declare Integer NumberOfCP = Score.PrevRaceTimes.count;
declare Integer LastTime = 0;
if (NumberOfCP > 0) LastTime = Score.PrevRaceTimes[-1];
if (!Ranking.existskey(NumberOfCP)) Ranking[NumberOfCP] = [];
if (!Ranking[NumberOfCP].existskey(LastTime)) Ranking[NumberOfCP][LastTime] = [];
Ranking[NumberOfCP][LastTime].add(Score.Id);
}
declare Integer[] PointsRepartition = PointsRepartition::GetPointsRepartition();
declare Integer Index = 0;
foreach (Times in Ranking.sortkeyreverse()) {
foreach (ScoresId in Times.sortkey()) {
foreach (ScoreId in ScoresId) {
if (!Scores.existskey(ScoreId)) continue;
declare CSmScore Score <=> Scores[ScoreId];
declare Integer Points = 0;
if (PointsRepartition.count > 0) {
if (PointsRepartition.existskey(Index)) {
Points = PointsRepartition[Index];
} else {
Points = PointsRepartition[-1];
}
}
Scores::SetPlayerRoundPoints(Score, Points);
Index += 1;
}
}
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Compute the map scores
Void ComputeScores() {
Scores::EndRound();
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** Eliminate Player
*
* @param _Player Player to eliminate
*/
Void EliminatePlayer(CSmPlayer _Player) {
Race::StopSkipOutro(_Player);
if (_Player.User != Null) {
UIManager.UIAll.SendChat("$f00" ^ _Player.User.Name ^" elimitated");
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** Check if the points limit was reached
*
* @param _UseTieBreak Prevent ties or not
* @param _PointsLimit Number of points to get to win the match
*
* @return C_PointsLimit_Reached if the points limit is reached
* C_PointsLimit_Tie if there is a tie
* C_PointsLimit_NotReached if the points limit is not reached
*/
Integer PointsLimitReached(Boolean _UseTieBreak, Integer _PointsLimit) {
declare Integer MaxScore = -1;
declare Boolean Tie = False;
foreach (Score in Scores) {
declare Integer Points = Scores::GetPlayerMatchPoints(Score);
if (Points > MaxScore) {
MaxScore = Points;
Tie = False;
} else if (Points == MaxScore) {
Tie = True;
}
}
if (_UseTieBreak && Tie) return C_PointsLimit_Tie; //< There is a tie and it is not allowed
if (_PointsLimit > 0 && MaxScore >= _PointsLimit) return C_PointsLimit_Reached; //< There is a points limit and it is reached
return C_PointsLimit_NotReached; //< There is no points limit or the points limit is not reached
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** Check if we should go to the next map
*
* @param _UseTieBreak Prevent ties or not
* @param _PointsLimit Number of points to get to win the match
* @param _ValidRoundsNb Number of valid rounds played
* @param _RoundsPerMap Number of rounds to play to complete the map
*
* @return True if it is the case, false otherwise
*/
Boolean MapIsOver(Boolean _UseTieBreak, Integer _PointsLimit, Integer _ValidRoundsNb, Integer _RoundsPerMap) {
declare Integer PointsLimitReached = PointsLimitReached(_UseTieBreak, _PointsLimit);
Log::Log("""[Rounds] MapIsOver() > _UseTieBreak: {{{_UseTieBreak}}} | _PointsLimit: {{{_PointsLimit}}} | _ValidRoundsNb: {{{_ValidRoundsNb}}} | _RoundsPerMap: {{{_RoundsPerMap}}} | PointsLimitReached: {{{PointsLimitReached}}}""");
if (PointsLimitReached == C_PointsLimit_Reached) return True; //< There is a points limit and it is reached
if (_RoundsPerMap > 0 && _ValidRoundsNb >= _RoundsPerMap) return True; //< There is a rounds limit and it is reached
return False;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** Check if we should go to the next match
*
* @param _UseTieBreak Prevent ties or not
* @param _PointsLimit Number of points to get to win the match
* @param _MapsPerMatch Number of maps to play to complete a match
* @param _RoundsPerMap Number of rounds to play to complete the map
*
* @return True if it is the case, false otherwise
*/
Boolean MatchIsOver(Boolean _UseTieBreak, Integer _PointsLimit, Integer _MapCount, Integer _MapsPerMatch, Integer _RoundsPerMap, Boolean _MapSkipped) {
declare Integer PointsLimitReached = PointsLimitReached(_UseTieBreak, _PointsLimit);
Log::Log("""[Rounds] MatchIsOver() > _UseTieBreak: {{{_UseTieBreak}}} | _PointsLimit: {{{_PointsLimit}}} | _MapCount: {{{_MapCount}}} | _MapsPerMatch: {{{_MapsPerMatch}}} | _RoundsPerMap: {{{_RoundsPerMap}}} | PointsLimitReached: {{{PointsLimitReached}}} | _MapSkipped : {{{_MapSkipped}}}""");
// If there is a point limit and it is reached, stop the match
if (PointsLimitReached == C_PointsLimit_Reached) {
return True;
}
// If there is an explicit maps limit ...
else if (_MapsPerMatch >= 1) {
if (
(_MapCount >= _MapsPerMatch && PointsLimitReached != C_PointsLimit_Tie) || //< ... stop the match if the maps limit is reached and the match is not a tie
(_MapSkipped && _MapsPerMatch == 1 && _MapCount >= _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 (_RoundsPerMap >= 1) {
return False;
}
// If there is neither a points limit nor a rounds limit, always stop the match at the end of the first map, even if there is a tie
else {
return True;
}
// In all other cases continue to play
return False;
}
Void CreateTimerUI() {
declare Text Manialink = """
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<manialink version="3" name="{{{C_ScriptName_Timer}}}">
<frame id="frame-global" pos="0 30" hidden="1">
<label id="label-timer" halign="center" valign="center" textprefix="$s" textsize="5" textfont="RajdhaniMono" textcolor="ffffff" hidden="1"/>
</frame>
<script><!--
#Include "TextLib" as TL
#Const ScriptName {{{dump(C_ScriptName_Timer)}}}
#Const Version {{{dump(Version)}}}
{{{dumptype(K_CheckpointState)}}}
main() {
log("Init "^ ScriptName ^ " v"^ Version);
declare CMlFrame Frame_Global <=> (Page.GetFirstChild("frame-global") as CMlFrame);
declare CMlLabel Label_Timer <=> (Frame_Global.GetFirstChild("label-timer") as CMlLabel);
// Wait C++ initialize the player
wait (InputPlayer != Null);
declare netread K_CheckpointState[Integer] Net_EnduroCup_Checkpoints for Teams[0] = [];
while(True) {
yield;
if (!PageIsVisible) continue;
Frame_Global.Visible = (GUIPlayer != Null && GUIPlayer.SpawnStatus == CSmPlayer::ESpawnStatus::Spawned);
if (!Frame_Global.Visible) continue;
declare K_CheckpointState CheckpointState = Net_EnduroCup_Checkpoints.get(GUIPlayer.RaceWaypointTimes.count + 1, K_CheckpointState {});
Label_Timer.Visible = (CheckpointState.RaceTime > 0 && CheckpointState.RaceTime < GUIPlayer.CurrentRaceTime);
if (!Label_Timer.Visible) continue;
declare Integer TimeLeft = CheckpointState.RaceTime + CheckpointState.TimeToReach - GUIPlayer.CurrentRaceTime;
if (TimeLeft <= 0) {
Label_Timer.Value = "$f0000:00.000";
} else {
Label_Timer.Value = TL::TimeToText(TimeLeft, True, True);
}
}
}
--></script>
</manialink>
""";
Layers::Create(C_ScriptName_Timer, Manialink);
Layers::SetType(C_ScriptName_Timer, CUILayer::EUILayerType::Normal);
Layers::Attach(C_ScriptName_Timer);
}

View File

@ -2,10 +2,10 @@
/*
* This mode uses the TMGL World Cup mode and removes all references to it
*/
#Extends "Modes/TrackMania/TM_Final86TMGL_Online.Script.txt"
#Extends "Modes/TrackMania/Deprecated/TM_Final86TMGL_Online.Script.txt"
// #RequireContext CSmMode
#Include "ManiaApps/Nadeo/TMNext/TrackMania/UIModules/TMGLMarkers_Server.Script.txt" as UIModules_TMGLMarkers
#Include "Libs/Nadeo/Trackmania/Modes/UIModules/TMGLMarkers_Server.Script.txt" as UIModules_TMGLMarkers
#Setting S_PointsLimit 100
#Setting S_RoundsPerMap 3

View File

@ -1,10 +1,10 @@
/**
* Official Knockout mode with delayed countdown (Finish Timeout)
*/
#Extends "Libs/Nadeo/TMNext/TrackMania/Modes/TMNextRoundsBase.Script.txt"
#Extends "Modes/Nadeo/Trackmania/Base/TrackmaniaRoundsBase.Script.txt"
#Const CompatibleMapTypes "TrackMania\\TM_Race,TM_Race"
#Const Version "2023-06-21"
#Const Version "2023-10-16"
#Const ScriptName "Modes/TrackMania/TM_Knockout_Online.Script.txt"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
@ -12,21 +12,24 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Include "TextLib" as TL
#Include "MathLib" as ML
#Include "Libs/Nadeo/CommonLibs/Common/Semver.Script.txt" as Semver
#Include "Libs/Nadeo/TMNext/TrackMania/Modes/Knockout/StateManager.Script.txt" as StateMgr
#Include "Libs/Nadeo/TMNext/TrackMania/Menu/Constants.Script.txt" as MenuConsts
#Include "ManiaApps/Nadeo/TMNext/TrackMania/Knockout/UIModules/KnockoutInfo_Server.Script.txt" as UIModules_KnockoutInfo
#Include "ManiaApps/Nadeo/TMNext/TrackMania/TimeAttack/UIModules/BestRaceViewer_Server.Script.txt" as UIModules_BestRaceViewer
#Include "ManiaApps/Nadeo/TMNext/TrackMania/Knockout/UIModules/KnockedOutPlayers_Server.Script.txt" as UIModules_KnockedOutPlayers
#Include "ManiaApps/Nadeo/TMNext/TrackMania/Knockout/UIModules/KnockoutReward_Server.Script.txt" as UIModules_KnockoutReward
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/PauseMenuOnline_Server.Script.txt" as UIModules_PauseMenu_Online
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/Checkpoint_Server.Script.txt" as UIModules_Checkpoint
#Include "Libs/Nadeo/CMGame/Utils/Semver.Script.txt" as Semver
#Include "Libs/Nadeo/CMGame/Modes/Utils.Script.txt" as ModeUtils
#Include "Libs/Nadeo/Trackmania/Modes/Knockout/StateManager.Script.txt" as StateMgr
#Include "Libs/Nadeo/Trackmania/MainMenu/Constants.Script.txt" as MenuConsts
#Include "Libs/Nadeo/Trackmania/Modes/Knockout/UIModules/KnockoutInfo_Server.Script.txt" as UIModules_KnockoutInfo
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/ScoresTable_Server.Script.txt" as UIModules_ScoresTable
#Include "Libs/Nadeo/Trackmania/Modes/TimeAttack/UIModules/BestRaceViewer_Server.Script.txt" as UIModules_BestRaceViewer
#Include "Libs/Nadeo/Trackmania/Modes/Knockout/UIModules/KnockedOutPlayers_Server.Script.txt" as UIModules_KnockedOutPlayers
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/BigMessage_Server.Script.txt" as UIModules_BigMessage
#Include "Libs/Nadeo/Trackmania/Modes/Knockout/UIModules/KnockoutReward_Server.Script.txt" as UIModules_KnockoutReward
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/PauseMenuOnline_Server.Script.txt" as UIModules_PauseMenu_Online
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/Checkpoint_Server.Script.txt" as UIModules_Checkpoint
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Settings
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Setting S_FinishTimeout 5 as _("Finish timeout")
#Setting S_RoundsPerMap -1 as _("Number of rounds per track") ///< Number of round to play on one map before going to the next one
#Setting S_RoundsPerMap -1 as _("Number of rounds per map") ///< Number of round to play on one map before going to the next one
#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")
@ -66,7 +69,7 @@
#Const Description _("$zIn $<$t$6F9Knockout$> mode, the goal is to be the last player standing. \n\nYou play a series of races as in Round mode. $<$t$6F9At the end of each race, the last players are eliminated$>!\n\nThe winner is the player who eliminates all of their opponents.")
#Const C_HudModulePath "" //< Path to the hud module
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/TMNext/TrackMania/Knockout/Knockout.Script.txt" //< Url of the mania app
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/Trackmania/Modes/Knockout.Script.txt" //< Url of the mania app
#Const C_FakeUsersNb 0
#Const C_Callback_Elimination "Trackmania.Knockout.Elimination"
@ -509,6 +512,7 @@ if (Round_ForceEndRound || Round_SkipPauseRound) {
if (!Round_SkipPauseRound) {
ForcedEndRoundSequence();
}
MB_SetValidRound(False);
} else {
// Eliminate players that did not finish in time
declare Ident[] EliminatedPlayersScoresIds = []; // Score.Id

View File

@ -3,10 +3,10 @@
*/
// #RequireContext CSmMode
#Extends "Libs/Nadeo/TMNext/TrackMania/Modes/TMNextBase.Script.txt"
#Extends "Modes/Nadeo/Trackmania/Base/TrackmaniaBase.Script.txt"
#Const CompatibleMapTypes "TrackMania\\TM_Race,TM_Race"
#Const Version "2022-10-22"
#Const Version "2023-09-25"
#Const ScriptName "Modes/TM2020-Gamemodes/TM_LapsKnockout.Script.txt"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
@ -14,15 +14,15 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#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 "Libs/Nadeo/Trackmania/MainMenu/Constants.Script.txt" as MenuConsts
#Include "Libs/Nadeo/Trackmania/Modes/Laps/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/Trackmania/Modes/LapsCommon/Libs/Constants.Script.txt" as LibLaps_Constants
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/BigMessage_Server.Script.txt" as UIModules_BigMessage
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/BigMessage_Server.Script.txt" as UIModules_BigMessage
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Settings
@ -69,7 +69,7 @@
#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_ManiaAppUrl "file://Media/ManiaApps/Nadeo/Trackmania/Modes/Laps.Script.txt" //< Url of the mania app
#Const C_FakeUsersNb 0
#Const C_UploadRecord True
@ -258,8 +258,7 @@ if (Last_NbLapsWithoutKO == 1) {
***Match_PlayLoop***
***
// Manage race events
declare RacePendingEvents = Race::GetPendingEvents();
foreach (Event in RacePendingEvents) {
foreach (Event in Race::GetPendingEvents()) {
Race::ValidEvent(Event);
// Waypoint
@ -293,8 +292,8 @@ foreach (Event in RacePendingEvents) {
if (Event.IsEndRace) {
// waiting GetCustomPoints function in ScoresTable_Server
declare netwrite Text[][Text] Net_TMxSM_ScoresTable_CustomPoints for Teams[0];
declare Text[][Text] CustomTimes = Net_TMxSM_ScoresTable_CustomPoints;
declare netwrite Text[][Text] Net_TMGame_ScoresTable_CustomPoints for Teams[0];
declare Text[][Text] CustomTimes = Net_TMGame_ScoresTable_CustomPoints;
foreach (Player in Players) {
if (Player != Event.Player) {
CustomTimes[Player.User.WebServicesUserId] = [_("|Status|K.O."), "f00"];

179
TM_Lobby.Script.txt Normal file
View File

@ -0,0 +1,179 @@
/**
* Lobby Gamemode
*/
// #RequireContext CSmMode
#Extends "Modes/TrackMania/TM_TimeAttack_Online.Script.txt"
#Include "Libs/Nadeo/Trackmania/Modes/Stunt/Constants.Script.txt" as StuntConstants
#Const StuntConstants::C_FigureNames as C_FigureNames
#Const C_Manialink_Id "Lobby.Records"
#Setting S_TimeLimit 0
#Struct K_BestTime {
Text PlayerName;
Integer Time;
}
#Struct K_BestStunt {
Text PlayerName;
Text FigureName;
Integer FigurePoints;
}
***Match_StartServer***
***
UseStunts = True;
***
***Match_AfterLoadHud***
***
SetManialink();
***
***Match_InitMatch***
***
declare netwrite K_BestTime Net_Lobby_BestTime for Teams[0];
declare netwrite Integer Net_Lobby_BestTime_Serial for Teams[0];
declare netwrite K_BestStunt Net_Lobby_BestStunt for Teams[0];
declare netwrite Integer Net_Lobby_BestStunt_Serial for Teams[0];
***
***Match_StartMatch***
***
Net_Lobby_BestTime = K_BestTime{};
Net_Lobby_BestTime_Serial += 1;
Net_Lobby_BestStunt = K_BestStunt{};
Net_Lobby_BestStunt_Serial += 1;
***
***Match_PlayLoop***
***
foreach (Event in PendingEvents) {
if (Event.Player == Null || Event.Player.User == Null) continue;
switch (Event.Type) {
case CSmModeEvent::EType::OnPlayerTriggersWaypoint: {
if (Event.IsFinish && (Net_Lobby_BestTime.Time == 0 || Net_Lobby_BestTime.Time > Event.WaypointTime)) {
Net_Lobby_BestTime = K_BestTime {
PlayerName = Event.Player.User.Name,
Time = Event.WaypointTime
};
Net_Lobby_BestTime_Serial += 1;
}
}
case CSmModeEvent::EType::OnStuntFigure: {
if (Event.StuntFigure.Points < 20) continue;
declare Integer FigurePoints = Event.StuntFigure.Points * 10;
declare Text FigureName = C_FigureNames[Event.StuntFigure.Name]^" "^ Event.StuntFigure.Angle ^"°";
if (Net_Lobby_BestStunt.FigurePoints == 0 || Net_Lobby_BestStunt.FigurePoints < Event.StuntFigure.Points * 10) {
Net_Lobby_BestStunt = K_BestStunt {
PlayerName = Event.Player.User.Name,
FigureName = FigureName,
FigurePoints = FigurePoints
};
Net_Lobby_BestStunt_Serial += 1;
}
declare CUIConfig UI <=> UIManager.GetUI(Event.Player);
if (UI != Null) {
UI.SendChat("$CFFYou made $<$fff"^ FigurePoints ^" points$> with a $<$fff"^ FigureName^ "$>");
}
}
}
}
***
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Load Manialink
Void SetManialink() {
declare Text MLText = """
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<manialink version="3" name="{{{C_Manialink_Id}}}">
<stylesheet>
<style class="text" textcolor="ffffff" textfont="GameFontSemiBold" textprefix="$s" textsize="1"/>
</stylesheet>
<frame id="frame-global" hidden="1" pos="0 90">
<frame pos="-71 0">
<quad size="45 15" bgcolor="000" opacity="0.5"/>
<label pos="22.5 -2" size="43 5" halign="center" textfont="GameFontBlack" textcolor="ffffff" textsize="2" textprefix="$i" text="BEST RACE TIME"/>
<label id="label-time-time" pos="22.5 -6.5" size="43 5" halign="center" textfont="GameFontExtraBold" textcolor="ffffff" textsize="1.2" text=""/>
<label id="label-time-playername" pos="22.5 -10" size="43 5" halign="center" textfont="GameFontSemiBold" textcolor="ffffff" textsize="1.2" text=""/>
</frame>
<frame pos="29 0">
<quad size="45 15" bgcolor="000" opacity="0.5"/>
<label pos="22.5 -2" size="43 5" halign="center" textfont="GameFontBlack" textcolor="ffffff" textsize="2" textprefix="$i" text="BEST STUNT FIGURE"/>
<label id="label-stunt-figurename" pos="22.5 -6.5" size="43 5" halign="center" textfont="GameFontExtraBold" textcolor="ffffff" textsize="1.2" text=""/>
<label id="label-stunt-playername" pos="22.5 -10" size="43 5" halign="center" textfont="GameFontSemiBold" textcolor="ffffff" textsize="1.2" text=""/>
</frame>
</frame>
<script><!--
#Include "TextLib" as TL
#Const ScriptName {{{dump(C_Manialink_Id)}}}
#Const Version {{{dump(Version)}}}
{{{dumptype(K_BestTime)}}}
{{{dumptype(K_BestStunt)}}}
main() {
declare CMlFrame Frame_Global <=> (Page.GetFirstChild("frame-global") as CMlFrame);
declare CMlLabel Label_Time_Time <=> (Frame_Global.GetFirstChild("label-time-time") as CMlLabel);
declare CMlLabel Label_Time_PlayerName <=> (Frame_Global.GetFirstChild("label-time-playername") as CMlLabel);
declare CMlLabel Label_Stunt_FigureName <=> (Frame_Global.GetFirstChild("label-stunt-figurename") as CMlLabel);
declare CMlLabel Label_Stunt_PlayerName <=> (Frame_Global.GetFirstChild("label-stunt-playername") as CMlLabel);
wait (InputPlayer != Null);
Frame_Global.Visible = True;
declare netread K_BestTime Net_Lobby_BestTime for Teams[0];
declare netread Integer Net_Lobby_BestTime_Serial for Teams[0];
declare Integer Last_BestTime_Serial = -1;
declare netread K_BestStunt Net_Lobby_BestStunt for Teams[0];
declare netread Integer Net_Lobby_BestStunt_Serial for Teams[0];
declare Integer Last_BestStunt_Serial = -1;
while (True) {
yield;
if (Last_BestTime_Serial != Net_Lobby_BestTime_Serial) {
Last_BestTime_Serial = Net_Lobby_BestTime_Serial;
if (Net_Lobby_BestTime.PlayerName == "") {
Label_Time_Time.Value = "";
Label_Time_PlayerName.Value = "";
} else {
Label_Time_Time.Value = TL::TimeToText(Net_Lobby_BestTime.Time, True, True);
Label_Time_PlayerName.Value = "by "^ Net_Lobby_BestTime.PlayerName;
}
}
if (Last_BestStunt_Serial != Net_Lobby_BestStunt_Serial) {
Last_BestStunt_Serial = Net_Lobby_BestStunt_Serial;
if (Net_Lobby_BestStunt.PlayerName == "") {
Label_Stunt_FigureName.Value = "";
Label_Stunt_PlayerName.Value = "";
} else {
Label_Stunt_FigureName.Value = Net_Lobby_BestStunt.FigurePoints ^" points - "^ Net_Lobby_BestStunt.FigureName;
Label_Stunt_PlayerName.Value = "by "^ Net_Lobby_BestStunt.PlayerName;
}
}
}
}
--></script>
</manialink>
""";
Layers::Create(C_Manialink_Id, MLText);
Layers::SetType(C_Manialink_Id, CUILayer::EUILayerType::Normal);
Layers::Attach(C_Manialink_Id);
}

View File

@ -7,7 +7,7 @@
#Extends "Modes/TrackMania/TM_Rounds_Online.Script.txt"
#Include "Libs/Nadeo/TMNext/TrackMania/Menu/Constants.Script.txt" as MenuConsts
#Include "Libs/Nadeo/Trackmania/MainMenu/Constants.Script.txt" as MenuConsts
#Include "Modes/TM2020-Gamemodes/Libs/Malus.Script.txt" as Malus
@ -99,8 +99,8 @@ Void AddMalusUI() {
declare CMlFrame Frame_Global <=> (Page.GetFirstChild("frame-global") as CMlFrame);
declare CMlLabel Label_Message <=> (Frame_Global.GetFirstChild("label-message") as CMlLabel);
G_Sound_CountDown = Audio.CreateSound("file://Media/Manialinks/Nadeo/TMConsole/MapEditor/Sounds/MenuDialogHidden.wav", 1.0, False, False, False);
G_Sound_Malus = Audio.CreateSound("file://Media/Manialinks/Nadeo/TMConsole/MapEditor/Sounds/MenuDialogDisplayed.wav", 1.0, False, False, False);
G_Sound_CountDown = Audio.CreateSound("https://files.virtit.fr/TrackMania/UI/MenuDialogHidden-06471.wav", 1.0, False, False, False);
G_Sound_Malus = Audio.CreateSound("https://files.virtit.fr/TrackMania/UI/MenuDialogDisplayed-21252.wav", 1.0, False, False, False);
// Wait C++ initialize the player
wait (InputPlayer != Null);

File diff suppressed because it is too large Load Diff

View File

@ -5,10 +5,10 @@
*/
// #RequireContext CSmMode
#Extends "Libs/Nadeo/TMNext/TrackMania/Modes/TMNextRoundsBase.Script.txt"
#Extends "Modes/Nadeo/Trackmania/Base/TrackmaniaRoundsBase.Script.txt"
#Const CompatibleMapTypes "TrackMania\\TM_Race,TM_Race"
#Const Version "2023-08-08"
#Const Version "2023-10-16"
#Const ScriptName "Modes/TM2020-Gamemodes/TM_ReverseCup.Script.txt"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
@ -16,14 +16,14 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Include "TextLib" as TL
#Include "MathLib" as ML
#Include "Libs/Nadeo/CommonLibs/Common/Semver.Script.txt" as Semver
#Include "Libs/Nadeo/TMNext/TrackMania/Menu/Constants.Script.txt" as Menu_Const
#Include "Libs/Nadeo/TMNext/TrackMania/Modes/CupCommon/Constants.Script.txt" as CupCommon_Const
#Include "Libs/Nadeo/TMNext/TrackMania/Modes/Cup/StateManager.Script.txt" as StateMgr
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/ScoresTable_Server.Script.txt" as UIModules_ScoresTable
#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/TMxSM/Race/UIModules/BigMessage_Server.Script.txt" as UIModules_BigMessage
#Include "Libs/Nadeo/CMGame/Utils/Semver.Script.txt" as Semver
#Include "Libs/Nadeo/Trackmania/MainMenu/Constants.Script.txt" as Menu_Const
#Include "Libs/Nadeo/Trackmania/Modes/CupCommon/Constants.Script.txt" as CupCommon_Const
#Include "Libs/Nadeo/Trackmania/Modes/Cup/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/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
@ -55,7 +55,7 @@
#Const Description _("$zThe cup mode consists of $<$t$6F9a series of races on multiple maps$>.\n\nWhen you finish a race in a bad $<$t$6F9position$>, you loose $<$t$6F9points$> substracted from your total.\nServers might propose warmup races to get familiar with a map first.\n\nTo win, you must be the last player with points. Once you are a LastChance, if you finish a race last you will be eliminated.The cup mode ends once there is one player left.")
#Const C_HudModulePath "" //< Path to the hud module
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/TMNext/TrackMania/Cup/Cup.Script.txt" //< Url of the mania app
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/Trackmania/Modes/Cup.Script.txt" //< Url of the mania app
#Const C_FakeUsersNb 0
#Const C_UploadRecord True
@ -136,9 +136,7 @@ UIModules_Checkpoint::SetRankMode(UIModules_Checkpoint::C_RankMode_CurrentRace);
UIModules_PauseMenu_Online::SetHelp(Description);
// Hide SM Overlay
UIManager.UIAll.OverlayHideSpectatorControllers = True;
UIManager.UIAll.OverlayHideSpectatorInfos = True;
UIManager.UIAll.OverlayHideChrono = True;
UIManager.UIAll.OverlayHideCountdown = True;
// Unload default UI
@ -160,8 +158,8 @@ foreach (Event in PendingEvents) {
if (Server_MatchInfo.RegistrationClosed && !Server_MatchInfo.Participants.exists(Event.Player.User.Login)) {
Scores::SetPlayerMatchPoints(Event.Player.Score, C_Points_Spectator);
// Equivalent of getCustomPoints:
declare netwrite Text[][Text] Net_TMxSM_ScoresTable_CustomPoints for Teams[0] = [];
Net_TMxSM_ScoresTable_CustomPoints[Event.Player.User.WebServicesUserId] = [C_Text_Spectator, C_Color_Spectator];
declare netwrite Text[][Text] Net_TMGame_ScoresTable_CustomPoints for Teams[0] = [];
Net_TMGame_ScoresTable_CustomPoints[Event.Player.User.WebServicesUserId] = [C_Text_Spectator, C_Color_Spectator];
}
}
}
@ -442,6 +440,9 @@ StateMgr::ForcePlayersStates([CupCommon_Const::C_State_Waiting]);
CarRank::Update(CarRank::C_SortCriteria_CurrentRace);
UpdateLiveRaceUI();
// Compute round points before the callback
ComputeLatestRaceScores(Round_NbPlayersInThisRound);
if (Semver::Compare(XmlRpc::GetApiVersion(), ">=", "2.1.1")) {
Scores::XmlRpc_SendScores(Scores::C_Section_PreEndRound, "");
}
@ -455,11 +456,10 @@ if (Round_ForceEndRound || Round_SkipPauseRound || Round_Skipped) {
if (!Round_SkipPauseRound) {
ForcedEndRoundSequence();
}
MB_SetValidRound(False);
DisplayCustomPoints();
MB_Sleep(3000);
} else {
// Get the last round points
ComputeLatestRaceScores(Round_NbPlayersInThisRound);
+++Cup_EndRound_BeforeScoresUpdate+++
Race::SortScores(Race::C_Sort_TotalPoints);
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
@ -503,26 +503,27 @@ if (MB_MatchIsRunning()) {
***
ModeUtils::PlaySound(CUIConfig::EUISound::EndRound, 0);
declare Text[] WinnersNames;
foreach (Player in Players) {
if(!Spectators.exists(Player)) {
if (Scores::GetPlayerMatchPoints(Player.Score) >= C_Points_LastChance) {
if (Player.User.ClubTag == "") {
WinnersNames.add(Player.User.Name);
} else {
WinnersNames.add("[$<"^Player.User.ClubTag^"$>] " ^ Player.User.Name);
}
}
if (!MB_Private_SkipPodiumSequence) {
declare Text[] WinnersNames;
foreach (Player in Players) {
if(!Spectators.exists(Player)) {
if (Scores::GetPlayerMatchPoints(Player.Score) >= C_Points_LastChance) {
if (Player.User.ClubTag == "") {
WinnersNames.add(Player.User.Name);
} else {
WinnersNames.add("[$<"^Player.User.ClubTag^"$>] " ^ Player.User.Name);
}
}
}
}
if(WinnersNames.count >= 1) {
UIModules_BigMessage::SetMessage(TL::Compose(_("$<%1$> wins the match!"), TL::Join(", ", WinnersNames)));
UIManager.UIAll.SendChat(TL::Compose(_("$<%1$> wins the match!"), TL::Join(", ", WinnersNames)));
} else {
UIModules_BigMessage::SetMessage(_("|Match|Draw"));
}
}
if(WinnersNames.count >= 1) {
UIModules_BigMessage::SetMessage(TL::Compose(_("$<%1$> wins the match!"), TL::Join(", ", WinnersNames)));
UIManager.UIAll.SendChat(TL::Compose(_("$<%1$> wins the match!"), TL::Join(", ", WinnersNames)));
} else {
UIModules_BigMessage::SetMessage(_("|Match|Draw"));
}
***
***Match_PodiumSequence***
@ -923,7 +924,7 @@ Void SetManialink_LiveRace() {
</framemodel>
<frame id="frame-global" pos="-160 50">
<frame id="frame-toggle" pos="58 -2.5" >
<quad id="Toggle_SettingButton" pos="0 0" size="4 4" class="quad-base" z-index="3" opacity="0.9" scriptevents="1" halign="center" valign="center" image="file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds" colorize="fff"/>
<quad id="Toggle_SettingButton" pos="0 0" size="4 4" class="quad-base" z-index="3" opacity="0.9" scriptevents="1" halign="center" valign="center" image="file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds" colorize="fff"/>
</frame>
<frame id="frame-UI">
<quad id="quad-bg" pos="0 0" z-index="-1" size="60 14" bgcolor="000" opacity="0.5" /><!-- 16 + (ML::Max(NB_Players,16) * 5) -->
@ -1029,12 +1030,12 @@ Void SetManialink_LiveRace() {
declare Real GlobalEndPosX;
if (Frame_UI.Visible) {
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_RIGHT_OBLIQUE.dds");
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_RIGHT_OBLIQUE.dds");
Quad_Toggle.RelativePosition_V3.X = 5.;
GlobalEndPosX = -220.;
AnimMgr.Add(Frame_UI, "<frame hidden=\"1\" />", Now, 250, CAnimManager::EAnimManagerEasing::Linear);
} else {
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds");
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds");
Quad_Toggle.RelativePosition_V3.X = 0.;
GlobalEndPosX = -160.;
Frame_UI.Visible = True;

657
TM_RoundsNearest.Script.txt Normal file
View File

@ -0,0 +1,657 @@
/**
* 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 "2025-05-08"
#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
#Include "Libs/Nadeo/Trackmania/Modes/Rounds/UIModules/SmallScoresTable_Server.Script.txt" as UIModules_SmallScoresTable
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// 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);
Log::RegisterScript(UIModules_SmallScoresTable::ScriptName, UIModules_SmallScoresTable::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"]);
***
***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([]);
UIModules_SmallScoresTable::ResetCustomTimes();
UIModules_SmallScoresTable::ResetCustomResults();
***
***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();
}
}
UIModules_SmallScoresTable::ResetCustomTimes();
UIModules_SmallScoresTable::ResetCustomResults();
***
***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;
declare Integer[Text] CustomTimes;
declare Text[Text] CustomResult;
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 ^ ")"];
if (AbsoluteDelta == 0) {
CustomResult[Score.User.WebServicesUserId] = "PERFECT";
} else {
CustomResult[Score.User.WebServicesUserId] = TextDelta;
}
CustomTimes[Score.User.WebServicesUserId] = AbsoluteDelta;
}
}
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;
}
UIModules_SmallScoresTable::SetCustomTimes(CustomTimes);
UIModules_SmallScoresTable::SetCustomResults(CustomResult);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// 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;
}

View File

@ -0,0 +1,633 @@
#Extends "Modes/Nadeo/Trackmania/Base/TrackmaniaRoundsBase.Script.txt"
// #RequireContext CSmMode
#Const CompatibleMapTypes "TrackMania\\TM_Race,TM_Race"
#Const Version "2024-08-12"
#Const ScriptName "Modes/TM2020-Gamemodes/TM_RoundsThenTimeattack.Script.txt"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// MARK: 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
#Include "Libs/Nadeo/CMGame/Modes/UIModules_Server.Script.txt" as UIModules_Server
#Include "Libs/Nadeo/CMGame/Modes/UIModules_Common.Script.txt" as UIModules_Common
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// MARK: Settings
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Setting S_DisableFreeCam False
#Setting S_TimeLimit 300 as _("Time limit") ///< Time limit before going to the next map
#Setting S_PointsLimit 50 as _("Points limit")
#Setting S_FinishTimeout -1 as _("Finish timeout")
#Setting S_RoundsPerMap -1 as _("Number of rounds per track") ///< Number of round to play on one map before going to the next one
#Setting S_MapsPerMatch -1 as _("Number of tracks per match") ///< Number of maps to play before finishing the match
#Setting S_UseTieBreak True as _("Use tie-break") ///< Continue to play the map until the tie is broken
#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")
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// MARK: 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_FakeUsersNb 0
#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
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// MARK: 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 == "");
MB_Settings_UseDefaultRespawnBehaviour = False;
***
***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;
***
***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_InitMap***
***
declare Integer Map_ValidRoundsNb;
declare Boolean Map_Skipped;
UpdateScoresTableFooter(S_PointsLimit, S_RoundsPerMap, S_MapsPerMatch, Map_ValidRoundsNb);
***
***Match_StartMap***
***
// Add bot when necessary
Users_SetNbFakeUsers(C_FakeUsersNb, 0);
UIManager.UIAll.PlayerDisableFreeCam = S_DisableFreeCam;
Map_Skipped = True;
CarRank::Reset();
// Warm up
UIModules_ScoresTable::SetFooterInfo(_("Warm up"));
MB_WarmUp(S_WarmUpNb, S_WarmUpDuration * 1000, S_WarmUpTimeout * 1000);
***
***Match_InitRound***
***
declare Boolean Round_IsTimeAttack;
declare Integer Round_TimeLimit;
declare Integer[Ident] Round_Scores_Times;
***
***Match_StartRound***
***
UpdateScoresTableFooter(S_PointsLimit, S_RoundsPerMap, S_MapsPerMatch, Map_ValidRoundsNb);
StateMgr::ForcePlayersStates([StateMgr::C_State_Playing]);
Round_IsTimeAttack = (Map_ValidRoundsNb + 1 >= S_RoundsPerMap);
if (Round_IsTimeAttack) {
Race::SetRespawnBehaviour(Race::C_RespawnBehaviour_GiveUpBeforeFirstCheckpoint);
Round_TimeLimit = S_TimeLimit;
StartTime = Now + Race::C_SpawnDuration;
SetTimeLimit(StartTime, S_TimeLimit);
UIModules_Server::Private_CustomizableModule_SetProperties("Rounds_SmallScoresTable", UIModules_Common::K_ModuleProperties {
Position = <-158.5, 40.>,
Scale = 1.,
Visible = False
});
UIModules_ScoresTable::SetScoreMode(UIModules_ScoresTable::C_Mode_PrevTime);
} else {
Race::SetRespawnBehaviour(Race::C_RespawnBehaviour_NeverGiveUp);
UIModules_Server::Private_CustomizableModule_SetProperties("Rounds_SmallScoresTable", UIModules_Common::K_ModuleProperties {
Position = <-158.5, 40.>,
Scale = 1.,
Visible = True
});
UIModules_ScoresTable::SetScoreMode(UIModules_ScoresTable::C_Mode_Points);
}
UIModules_ScoresTable::SetCustomTimes([]);
***
***Rounds_PlayerSpawned***
***
CarRank::ThrottleUpdate(CarRank::C_SortCriteria_CurrentRace);
***
***Match_PlayLoop***
***
// Manage race events
foreach (Event in Race::GetPendingEvents()) {
Race::ValidEvent(Event);
// Waypoint
if (Event.Type == Events::C_Type_Waypoint) {
CarRank::ThrottleUpdate(CarRank::C_SortCriteria_CurrentRace);
if (Event.Player != Null && Event.Player.Score != Null) {
if (Event.IsEndRace) {
Scores::UpdatePlayerBestRaceIfBetter(Event.Player);
Scores::UpdatePlayerBestLapIfBetter(Event.Player);
Scores::UpdatePlayerPrevRace(Event.Player);
Race::SortScores(Race::C_Sort_TotalPoints);
if (Round_IsTimeAttack) {
if (!Round_Scores_Times.existskey(Event.Player.Score.Id)) {
Round_Scores_Times[Event.Player.Score.Id] = Event.RaceTime;
declare Integer[Text] CustomTimes = UIModules_ScoresTable::GetCustomTimes();
CustomTimes[Event.Player.User.WebServicesUserId] = Event.RaceTime;
UIModules_ScoresTable::SetCustomTimes(CustomTimes);
} else if (Round_Scores_Times[Event.Player.Score.Id] > Event.RaceTime) {
Round_Scores_Times[Event.Player.Score.Id] = Event.RaceTime;
declare Integer[Text] CustomTimes = UIModules_ScoresTable::GetCustomTimes();
CustomTimes[Event.Player.User.WebServicesUserId] = Event.RaceTime;
UIModules_ScoresTable::SetCustomTimes(CustomTimes);
}
} else {
ComputeLatestRaceScores();
}
// 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);
}
}
}
}
if (Round_IsTimeAttack) {
if (PlayersNbDead > 0) {
foreach (Player in Players) {
if (Player.SpawnStatus == CSmPlayer::ESpawnStatus::NotSpawned && Race::IsReadyToStart(Player)) {
Race::Start(Player);
}
}
}
// Update the map duration setting
if (Round_TimeLimit != S_TimeLimit) {
Round_TimeLimit = S_TimeLimit;
SetTimeLimit(StartTime, S_TimeLimit);
}
}
// 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);
}
***
***Rounds_CheckStopRound***
***
// End the round
// If All players finished
if (!Round_IsTimeAttack && Players.count > 0 && PlayersNbAlive <= 0) {
MB_StopRound();
Round_Skipped = False;
}
// If time limit is reached
if (EndTime > 0 && Now >= EndTime) {
MB_StopRound();
Round_Skipped = False;
}
// If forced end round or round skipped after pause
if (Round_ForceEndRound || Round_SkipPauseRound) {
MB_StopRound();
Round_Skipped = False;
}
***
***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;
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
UIManager.UIAll.UISequence = CUIConfig::EUISequence::EndRound;
// Get the last round points
if (Round_IsTimeAttack) {
MB_Sleep(3000);
ComputeBestRaceScores(Round_Scores_Times);
UIModules_ScoresTable::SetCustomTimes([]);
UIModules_ScoresTable::SetScoreMode(UIModules_ScoresTable::C_Mode_Points);
} else {
ComputeLatestRaceScores();
}
Race::SortScores(Race::C_Sort_TotalPoints);
MB_Sleep(3000);
// Add them to the total scores
ComputeScores();
Race::SortScores(Race::C_Sort_TotalPoints);
MB_Sleep(3000);
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::Normal;
UIManager.UIAll.UISequence = CUIConfig::EUISequence::Playing;
if (MapIsOver(S_UseTieBreak, S_PointsLimit, Map_ValidRoundsNb, S_RoundsPerMap)) {
Map_Skipped = False;
MB_StopMap();
}
}
***
***Match_EndMap***
***
if (MatchIsOver(S_UseTieBreak, S_PointsLimit, MB_GetMapCount(), S_MapsPerMatch, S_RoundsPerMap, Map_Skipped)) MB_StopMatch();
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);
}
}
***
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// MARK: Functions
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** Update the time limit
*
* @param _StartTime The starting time of the map
* @param _NewTimeLimit The time limit before going to the next map
*/
Void SetTimeLimit(Integer _StartTime, Integer _NewTimeLimit) {
if (_NewTimeLimit <= 0) {
EndTime = -1;
UIModules_ScoresTable::SetFooterInfo(TL::Compose("%1 -", _("Time Limit")));
} else {
EndTime = _StartTime + (_NewTimeLimit * 1000);
UIModules_ScoresTable::SetFooterInfo(TL::Compose("%1 "^TL::TimeToText(_NewTimeLimit*1000), _("Time Limit")));
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** 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]));
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** 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;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Compute the latest race scores
Void ComputeLatestRaceScores() {
Race::SortScores(Race::C_Sort_PrevRaceTime);
// Points distributed between all players
declare Integer I = 0;
declare Integer[] PointsRepartition = PointsRepartition::GetPointsRepartition();
foreach (Score in Scores) {
if (Scores::GetPlayerPrevRaceTime(Score) > 0) {
declare Integer Points = 0;
if (PointsRepartition.count > 0) {
if (PointsRepartition.existskey(I)) {
Points = PointsRepartition[I];
} else {
Points = PointsRepartition[PointsRepartition.count - 1];
}
}
Scores::SetPlayerRoundPoints(Score, Points);
I += 1;
} else {
Scores::SetPlayerRoundPoints(Score, 0);
}
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Compute the latest race scores
Void ComputeBestRaceScores(Integer[Ident] _Times) {
declare Integer[Ident] Times = _Times.sort();
// Points distributed between all players
declare Integer I = 0;
declare Integer[] PointsRepartition = PointsRepartition::GetPointsRepartition();
foreach (ScoreId => Time in Times) {
if (!Scores.existskey(ScoreId)) continue;
declare CSmScore Score <=> Scores[ScoreId];
declare Integer Points = 0;
if (PointsRepartition.count > 0) {
if (PointsRepartition.existskey(I)) {
Points = PointsRepartition[I];
} else {
Points = PointsRepartition[PointsRepartition.count - 1];
}
}
Scores::SetPlayerRoundPoints(Score, Points);
I += 1;
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Compute the map scores
Void ComputeScores() {
Scores::EndRound();
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** Check if the points limit was reached
*
* @param _UseTieBreak Prevent ties or not
* @param _PointsLimit Number of points to get to win the match
*
* @return C_PointsLimit_Reached if the points limit is reached
* C_PointsLimit_Tie if there is a tie
* C_PointsLimit_NotReached if the points limit is not reached
*/
Integer PointsLimitReached(Boolean _UseTieBreak, Integer _PointsLimit) {
declare Integer MaxScore = -1;
declare Boolean Tie = False;
foreach (Score in Scores) {
declare Integer Points = Scores::GetPlayerMatchPoints(Score);
if (Points > MaxScore) {
MaxScore = Points;
Tie = False;
} else if (Points == MaxScore) {
Tie = True;
}
}
if (_UseTieBreak && Tie) return C_PointsLimit_Tie; //< There is a tie and it is not allowed
if (_PointsLimit > 0 && MaxScore >= _PointsLimit) return C_PointsLimit_Reached; //< There is a points limit and it is reached
return C_PointsLimit_NotReached; //< There is no points limit or the points limit is not reached
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** Check if we should go to the next map
*
* @param _UseTieBreak Prevent ties or not
* @param _PointsLimit Number of points to get to win the match
* @param _ValidRoundsNb Number of valid rounds played
* @param _RoundsPerMap Number of rounds to play to complete the map
*
* @return True if it is the case, false otherwise
*/
Boolean MapIsOver(Boolean _UseTieBreak, Integer _PointsLimit, Integer _ValidRoundsNb, Integer _RoundsPerMap) {
declare Integer PointsLimitReached = PointsLimitReached(_UseTieBreak, _PointsLimit);
Log::Log("""[Rounds] MapIsOver() > _UseTieBreak: {{{_UseTieBreak}}} | _PointsLimit: {{{_PointsLimit}}} | _ValidRoundsNb: {{{_ValidRoundsNb}}} | _RoundsPerMap: {{{_RoundsPerMap}}} | PointsLimitReached: {{{PointsLimitReached}}}""");
if (PointsLimitReached == C_PointsLimit_Reached) return True; //< There is a points limit and it is reached
if (_RoundsPerMap > 0 && _ValidRoundsNb >= _RoundsPerMap) return True; //< There is a rounds limit and it is reached
return False;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** Check if we should go to the next match
*
* @param _UseTieBreak Prevent ties or not
* @param _PointsLimit Number of points to get to win the match
* @param _MapsPerMatch Number of maps to play to complete a match
* @param _RoundsPerMap Number of rounds to play to complete the map
*
* @return True if it is the case, false otherwise
*/
Boolean MatchIsOver(Boolean _UseTieBreak, Integer _PointsLimit, Integer _MapCount, Integer _MapsPerMatch, Integer _RoundsPerMap, Boolean _MapSkipped) {
declare Integer PointsLimitReached = PointsLimitReached(_UseTieBreak, _PointsLimit);
Log::Log("""[Rounds] MatchIsOver() > _UseTieBreak: {{{_UseTieBreak}}} | _PointsLimit: {{{_PointsLimit}}} | _MapCount: {{{_MapCount}}} | _MapsPerMatch: {{{_MapsPerMatch}}} | _RoundsPerMap: {{{_RoundsPerMap}}} | PointsLimitReached: {{{PointsLimitReached}}} | _MapSkipped : {{{_MapSkipped}}}""");
// If there is a point limit and it is reached, stop the match
if (PointsLimitReached == C_PointsLimit_Reached) {
return True;
}
// If there is an explicit maps limit ...
else if (_MapsPerMatch >= 1) {
if (
(_MapCount >= _MapsPerMatch && PointsLimitReached != C_PointsLimit_Tie) || //< ... stop the match if the maps limit is reached and the match is not a tie
(_MapSkipped && _MapsPerMatch == 1 && _MapCount >= _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 (_RoundsPerMap >= 1) {
return False;
}
// If there is neither a points limit nor a rounds limit, always stop the match at the end of the first map, even if there is a tie
else {
return True;
}
// In all other cases continue to play
return False;
}

View File

@ -1,10 +1,10 @@
/**
* Royal Rounds mode
*/
#Extends "Libs/Nadeo/TMNext/TrackMania/Modes/TMNextRoundsBase.Script.txt"
#Extends "Modes/Nadeo/Trackmania/Base/TrackmaniaRoundsBase.Script.txt"
#Const CompatibleMapTypes "TrackMania\\TM_Royal,TM_Royal"
#Const Version "2021-08-21"
#Const Version "2024-04-05"
#Const ScriptName "Modes/TrackMania/TM_RoyalRounds_Online.Script.txt"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
@ -12,17 +12,17 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Include "TextLib" as TL
#Include "MathLib" as ML
#Include "Libs/Nadeo/CommonLibs/Common/Semver.Script.txt" as Semver
#Include "Libs/Nadeo/CMGame/Utils/Semver.Script.txt" as Semver
#Include "Libs/Nadeo/ModeLibs/Common/Utils.Script.txt" as ModeUtils
#Include "Libs/Nadeo/TMNext/TrackMania/Menu/Constants.Script.txt" as MenuConsts
#Include "Libs/Nadeo/TMNext/TrackMania/Modes/Rounds/StateManager.Script.txt" as StateMgr
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/ScoresTable_Server.Script.txt" as UIModules_ScoresTable
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/PauseMenuOnline_Server.Script.txt" as UIModules_PauseMenu_Online
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/Checkpoint_Server.Script.txt" as UIModules_Checkpoint
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/BigMessage_Server.Script.txt" as UIModules_BigMessage
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/Chrono_Server.Script.txt" as UIModules_Chrono
#Include "ManiaApps/Nadeo/TMNext/TrackMania/Rounds/UIModules/SmallScoresTable_Server.Script.txt" as UIModules_SmallScoresTable
#Include "Libs/Nadeo/CMGame/Modes/Utils.Script.txt" as ModeUtils
#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/PauseMenuOnline_Server.Script.txt" as UIModules_PauseMenu_Online
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/Checkpoint_Server.Script.txt" as UIModules_Checkpoint
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/BigMessage_Server.Script.txt" as UIModules_BigMessage
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/Chrono_Server.Script.txt" as UIModules_Chrono
#Include "Libs/Nadeo/Trackmania/Modes/Rounds/UIModules/SmallScoresTable_Server.Script.txt" as UIModules_SmallScoresTable
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Settings
@ -51,7 +51,7 @@
#Const Description _("$zIn $<$t$6F9RoyalRounds$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/TMNext/TrackMania/Rounds/Rounds.Script.txt" //< Url of the mania app
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/Trackmania/Modes/Rounds.Script.txt" //< Url of the mania app
#Const C_FakeUsersNb 0
@ -119,9 +119,7 @@ UIModules_Checkpoint::SetVisibleFor(UIModules_Checkpoint::C_Target_None);
UIModules_TimeGap::SetTimeGapMode(UIModules_TimeGap::C_TimeGapMode_Hidden);
UIModules_PauseMenu_Online::SetHelp(Description);
// Hide SM Overlay
UIManager.UIAll.OverlayHideSpectatorControllers = True;
UIManager.UIAll.OverlayHideSpectatorInfos = True;
UIManager.UIAll.OverlayHideChrono = True;
UIManager.UIAll.OverlayHideCountdown = True;
SetML();
@ -325,7 +323,7 @@ UIModules_Chrono::SetTimeOffset(Player, SpecificOffset);
declare netwrite Integer Net_RoyalRounds_CheckpointUI_TotalNbSegments for Teams[0];
declare Integer[Text] CustomTimes for This = [];
declare Integer[Text][Integer] CurrentRanking for This = []; // CurrentRanking[Segment][AccountId][Time]
CurrentRanking[0] = Integer[Text]; // Init white section
CurrentRanking[0] = []; // Init white section
declare Boolean IsStartRound = True;
// Reset players for the race
@ -351,8 +349,7 @@ IsStartRound = False;
***Match_PlayLoop***
***
// Manage race events
declare RacePendingEvents = Race::GetPendingEvents();
foreach (Event in RacePendingEvents) {
foreach (Event in Race::GetPendingEvents()) {
if (Event.Type == Events::C_Type_SkipOutro && C_DisableSkipOutro) {
Race::InvalidEvent(Event);
} else {
@ -365,7 +362,7 @@ foreach (Event in RacePendingEvents) {
declare Integer CurrentSegment for Event.Player.Score = -1;
Log::Log("""[RacePendingEvents] Player {{{Event.Player.User.Name }}} end the segment {{{CurrentSegment}}} at {{{Event.Player.StartTime - StartTime + Event.RaceTime}}}""");
if (CurrentRanking.existskey(CurrentSegment-1)) CurrentRanking[CurrentSegment-1].removekey(Event.Player.User.WebServicesUserId) ;
if (!CurrentRanking.existskey(CurrentSegment)) CurrentRanking[CurrentSegment] = Integer[Text];
if (!CurrentRanking.existskey(CurrentSegment)) CurrentRanking[CurrentSegment] = [];
CurrentRanking[CurrentSegment][Event.Player.User.WebServicesUserId] = Event.Player.StartTime - StartTime + Event.RaceTime;
// Update Ranking of the current players
@ -386,8 +383,8 @@ foreach (Event in RacePendingEvents) {
declare netwrite Integer Net_RoyalRounds_CheckpointUI_CurrentNbSegments for Event.Player;
Net_RoyalRounds_CheckpointUI_CurrentNbSegments = CurrentSegment;
if (CurrentSegment < S_SegmentsPerRound) { // TODO Try to keep CP diff a the bottom of the screen
declare ModeRounds_CanSpawn for Event.Player.Score = Rounds_Settings_CanSpawnDefault;
if (CurrentSegment < S_SegmentsPerRound) { // TODO Try to keep CP diff a the bottom of the screen
declare Boolean ModeRounds_CanSpawn for Event.Player.Score = Rounds_Settings_CanSpawnDefault;
ModeRounds_CanSpawn = True;
CurrentSegment = CurrentSegment + 1;
Race::StopSkipScoresTable(Event.Player);
@ -415,7 +412,6 @@ foreach (Event in RacePendingEvents) {
CurrentSegment = CurrentSegment - 1;
}
if (UpdateRankingTimer == 0) UpdateRankingTimer = Now + 1000;
declare Boolean ModeRounds_CanSpawn for Event.Player.Score = Rounds_Settings_CanSpawnDefault;
}
}
}
@ -510,7 +506,8 @@ if (Round_ForceEndRound || Round_SkipPauseRound) {
***
Race::StopSkipOutroAll();
EndTime = -1;
StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]);
// Keep Playing State to keep SmallScoresTable visible
StateMgr::ForcePlayersStates([StateMgr::C_State_Playing]);
if (Semver::Compare(XmlRpc::GetApiVersion(), ">=", "2.1.1")) {
Scores::XmlRpc_SendScores(Scores::C_Section_PreEndRound, "");
@ -525,14 +522,17 @@ if (Round_ForceEndRound || Round_SkipPauseRound || Round_Skipped) {
if (!Round_SkipPauseRound) {
ForcedEndRoundSequence();
}
MB_SetValidRound(False);
} else {
Map_ValidRoundsNb += 1;
// Get the last round points
UpdateCustomRanking(Null);
Race::SortScores(Race::C_Sort_TotalPoints);
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
UIManager.UIAll.UISequence = CUIConfig::EUISequence::EndRound;
MB_Sleep(3000);
Race::SortScores(Race::C_Sort_TotalPoints);
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
MB_Sleep(3000);
// Add them to the total scores
ComputeScores();
Race::SortScores(Race::C_Sort_TotalPoints);
@ -545,6 +545,7 @@ if (Round_ForceEndRound || Round_SkipPauseRound || Round_Skipped) {
MB_StopMap();
}
}
StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]);
CustomTimes.clear();
CurrentRanking.clear();
UIModules_SmallScoresTable::ResetCustomTimes();
@ -639,20 +640,18 @@ Integer GetFinishTimeout(Integer _FinishTimeout) {
if (_Player != Null) {
declare Integer CurrentSegment for _Player.Score;
CustomTimes[_Player.User.WebServicesUserId] = CurrentRanking[CurrentSegment][_Player.User.WebServicesUserId];
} else {
if (!S_StrictPointDistribution) {
CustomTimes.clear();
CurrentRanking = CurrentRanking.sortkeyreverse();
declare Integer LastTimeFromThePreviousSegment = 0;
declare Integer LastTime = 0;
foreach (Segment => DummyVar in CurrentRanking) {
if (Segment == 0) break;
if (CustomTimes.count > 0) LastTimeFromThePreviousSegment = LastTime;
foreach (ID => Time in CurrentRanking[Segment]) {
CustomTimes[ID] = Time + LastTimeFromThePreviousSegment;
LastTime = Time + LastTimeFromThePreviousSegment;
if (LastTimeFromThePreviousSegment > 0) CustomResult[ID] = """({{{Segment - S_SegmentsPerRound}}}) {{{TL::TimeToText(Time, True, True)}}}""";
}
} else if (!S_StrictPointDistribution) {
CustomTimes.clear();
CurrentRanking = CurrentRanking.sortkeyreverse();
declare Integer LastTimeFromThePreviousSegment = 0;
declare Integer LastTime = 0;
foreach (Segment => DummyVar in CurrentRanking) {
if (Segment == 0) break;
if (CustomTimes.count > 0) LastTimeFromThePreviousSegment = LastTime;
foreach (ID => Time in CurrentRanking[Segment]) {
CustomTimes[ID] = Time + LastTimeFromThePreviousSegment;
LastTime = Time + LastTimeFromThePreviousSegment;
if (LastTimeFromThePreviousSegment > 0) CustomResult[ID] = """({{{Segment - S_SegmentsPerRound}}}) {{{TL::TimeToText(Time, True, True)}}}""";
}
}
}
@ -679,7 +678,7 @@ Integer GetFinishTimeout(Integer _FinishTimeout) {
Points = PointsRepartition[PointsRepartition.count - 1];
}
declare Integer CurrentSegment for Player.Score;
if (CurrentSegment == S_SegmentsPerRound) {
if (CurrentSegment == S_SegmentsPerRound && Scores::GetPlayerPrevRaceTime(Player.Score) > 0) {
Points += BonusForFinishers;
}
Scores::SetPlayerRoundPoints(Player.Score, Points);
@ -948,9 +947,9 @@ Boolean MapIsOver(Boolean _UseTieBreak, Integer _PointsLimit, Integer _ValidRoun
<quad pos="-0.75 -10" z-index="0" size="50 10" opacity="0.7" image="https://files.virtit.fr/TrackMania/UI/tilted_bg_half.dds" colorize="000" halign="center"/>
<label id="label-text" pos="-1 -15" z-index="1" size="45 10" text="You are on the $<$fffwhite$> segment" textfont="GameFontSemiBold" halign="center" valign="center2" textprefix="$i" textsize="1.5" textcolor="D8D8D8"/>
<quad id="quad-left-button" pos="-30 1.9" z-index="0" size="13 23" opacity="0.7" image="https://files.virtit.fr/TrackMania/UI/tilted_bg_left_button.dds" colorize="000" halign="center" scriptevents="1"/>
<quad id="quad-left-arrow" pos="-30 -5" z-index="1" size="10 10" image="file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds" halign="center" colorize="000"/>
<quad id="quad-left-arrow" pos="-30 -5" z-index="1" size="10 10" image="file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds" halign="center" colorize="000"/>
<quad id="quad-right-button" pos="30.2 1.6" z-index="0" size="13 23.5" opacity="0.7" image="https://files.virtit.fr/TrackMania/UI/tilted_bg_right_button.dds" colorize="000" halign="center" scriptevents="1"/>
<quad id="quad-right-arrow" pos="30 -5" z-index="1" size="10 10" image="file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_RIGHT_OBLIQUE.dds" halign="center" colorize="1b2"/>
<quad id="quad-right-arrow" pos="30 -5" z-index="1" size="10 10" image="file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_RIGHT_OBLIQUE.dds" halign="center" colorize="1b2"/>
</frame>
</manialink>
""";

View File

@ -1,10 +1,10 @@
/**
* Seeding Time Attack mode
*/
#Extends "Libs/Nadeo/TMNext/TrackMania/Modes/TMNextBase.Script.txt"
#Extends "Modes/Nadeo/Trackmania/Base/TrackmaniaBase.Script.txt"
#Const CompatibleMapTypes "TrackMania\\TM_Race,TM_Race"
#Const Version "2021-10-16"
#Const Version "2023-09-25"
#Const ScriptName "Modes/TM2020-Gamemodes/TM_SeedingTimeAttack_Online.Script.txt"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
@ -12,20 +12,20 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Include "TextLib" as TL
#Include "MathLib" as ML
#Include "Libs/Nadeo/CommonLibs/Common/Task.Script.txt" as Task
#Include "Libs/Nadeo/TMNext/TrackMania/Modes/TimeAttack/StateManager.Script.txt" as StateMgr
#Include "Libs/Nadeo/TMNext/TrackMania/Modes/TrophyRanking.Script.txt" as TrophyRanking
#Include "Libs/Nadeo/TMNext/TrackMania/Menu/Constants.Script.txt" as MenuConsts
#Include "Libs/Nadeo/ModeLibs/Common/Utils.Script.txt" as ModeUtils
#Include "Libs/Nadeo/CommonLibs/Common/Semver.Script.txt" as Semver
#Include "Libs/Nadeo/CMGame/Utils/Task.Script.txt" as Task
#Include "Libs/Nadeo/Trackmania/Modes/TimeAttack/StateManager.Script.txt" as StateMgr
#Include "Libs/Nadeo/Trackmania/Modes/TrophyRanking.Script.txt" as TrophyRanking
#Include "Libs/Nadeo/Trackmania/MainMenu/Constants.Script.txt" as MenuConsts
#Include "Libs/Nadeo/CMGame/Modes/Utils.Script.txt" as ModeUtils
#Include "Libs/Nadeo/CMGame/Utils/Semver.Script.txt" as Semver
// UI from Race
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/TimeGap_Server.Script.txt" as UIModules_TimeGap
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/BigMessage_Server.Script.txt" as UIModules_BigMessage
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/Checkpoint_Server.Script.txt" as UIModules_Checkpoint
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/ScoresTable_Server.Script.txt" as UIModules_ScoresTable
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/PauseMenuOnline_Server.Script.txt" as UIModules_PauseMenu_Online
#Include "ManiaApps/Nadeo/TMNext/TrackMania/TimeAttack/UIModules/EndMatchTrophy_Server.Script.txt" as UIModules_EndMatchTrophy
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/TimeGap_Server.Script.txt" as UIModules_TimeGap
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/BigMessage_Server.Script.txt" as UIModules_BigMessage
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/Checkpoint_Server.Script.txt" as UIModules_Checkpoint
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/ScoresTable_Server.Script.txt" as UIModules_ScoresTable
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/PauseMenuOnline_Server.Script.txt" as UIModules_PauseMenu_Online
#Include "Libs/Nadeo/Trackmania/Modes/TimeAttack/UIModules/EndMatchTrophy_Server.Script.txt" as UIModules_EndMatchTrophy
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
@ -48,7 +48,7 @@
#Const Description _("$zIn $<$t$6F9Time Attack$> mode, the goal is to set the $<$t$6F9best time$>.\n\nYou have as many tries as you want, and you can $<$t$6F9retry$> when you want by pressing the respawn key.\n\nWhen the time is up, the $<$t$6F9winner$> is the player with the $<$t$6F9best time$>.")
#Const C_HudModulePath "" //< Path to the hud module
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/TMNext/TrackMania/TimeAttack/TimeAttack.Script.txt" //< Url of the mania app
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/Trackmania/Modes/TimeAttack.Script.txt" //< Url of the mania app
#Const C_FakeUsersNb 0
#Const C_UploadRecord True
@ -170,7 +170,7 @@ UIModules_Record::SetSpecialVisibility(False);
***Match_InitMatch***
***
declare Task::LibCommonTask_K_Task Match_TrophyTask;
declare Task::K_Task Match_TrophyTask;
declare Integer Match_TrophyTaskEndTime;
declare Integer Match_MatchDuration;
declare Boolean Match_CanForceTrophyRankUpdate for This = False;
@ -670,12 +670,12 @@ Void SetML() {
declare Real GlobalEndPosX;
if (Frame_UI.Visible) {
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_RIGHT_OBLIQUE.dds");
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_RIGHT_OBLIQUE.dds");
Quad_Toggle.RelativePosition_V3.X = 5.;
GlobalEndPosX = -215.;
AnimMgr.Add(Frame_UI, "<frame hidden=\"1\" />", Now, 250, CAnimManager::EAnimManagerEasing::Linear);
} else {
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds");
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds");
Quad_Toggle.RelativePosition_V3.X = 0.;
GlobalEndPosX = -160.;
Frame_UI.Visible = True;
@ -826,7 +826,7 @@ Void SetML() {
</framemodel>
<frame id="frame-global" pos="-160 30">
<frame pos="53 -2.5" id="frame-toggle" z-index="1" >
<quad id="quad-toggle" pos="0 0" size="4 4" class="quad-base" z-index="3" opacity="0.9" scriptevents="1" halign="center" valign="center" image="file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds" colorize="fff"/>
<quad id="quad-toggle" pos="0 0" size="4 4" class="quad-base" z-index="3" opacity="0.9" scriptevents="1" halign="center" valign="center" image="file://Media/Manialinks/Nadeo/CMGame/Utils/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds" colorize="fff"/>
</frame>
<frame id="frame-ui" z-index="1">
<quad pos="0 0" z-index="0" size="55 69" bgcolor="000" opacity="0.5"/>

View File

@ -3,10 +3,10 @@
* Quick script ordered by Bergie for Telialigaen
*/
// #RequireContext CSmMode
#Extends "Libs/Nadeo/TMNext/TrackMania/Modes/TMNextRoundsBase.Script.txt"
#Extends "Modes/Nadeo/Trackmania/Base/TrackmaniaRoundsBase.Script.txt"
#Const CompatibleMapTypes "TrackMania\\TM_Race,TM_Race"
#Const Version "2022-01-22"
#Const Version "2023-10-16"
#Const ScriptName "Modes/TrackMania/TM_TimeAttackRounds_Online.Script.txt"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
@ -14,18 +14,18 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Include "TextLib" as TL
#Include "MathLib" as ML
#Include "Libs/Nadeo/CommonLibs/Common/Task.Script.txt" as Task
#Include "Libs/Nadeo/TMNext/TrackMania/Modes/TimeAttack/StateManager.Script.txt" as StateMgr
#Include "Libs/Nadeo/TMNext/TrackMania/Modes/TrophyRanking.Script.txt" as TrophyRanking
#Include "Libs/Nadeo/TMNext/TrackMania/Menu/Constants.Script.txt" as MenuConsts
#Include "Libs/Nadeo/ModeLibs/Common/Utils.Script.txt" as ModeUtils
#Include "Libs/Nadeo/CommonLibs/Common/Semver.Script.txt" as Semver
#Include "Libs/Nadeo/CMGame/Utils/Task.Script.txt" as Task
#Include "Libs/Nadeo/Trackmania/Modes/TimeAttack/StateManager.Script.txt" as StateMgr
#Include "Libs/Nadeo/Trackmania/Modes/TrophyRanking.Script.txt" as TrophyRanking
#Include "Libs/Nadeo/Trackmania/MainMenu/Constants.Script.txt" as MenuConsts
#Include "Libs/Nadeo/CMGame/Modes/Utils.Script.txt" as ModeUtils
#Include "Libs/Nadeo/CMGame/Utils/Semver.Script.txt" as Semver
// UI from Race
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/Checkpoint_Server.Script.txt" as UIModules_Checkpoint
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/ScoresTable_Server.Script.txt" as UIModules_ScoresTable
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/PauseMenuOnline_Server.Script.txt" as UIModules_PauseMenu_Online
#Include "ManiaApps/Nadeo/TMNext/TrackMania/TimeAttack/UIModules/EndMatchTrophy_Server.Script.txt" as UIModules_EndMatchTrophy
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/Checkpoint_Server.Script.txt" as UIModules_Checkpoint
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/ScoresTable_Server.Script.txt" as UIModules_ScoresTable
#Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/PauseMenuOnline_Server.Script.txt" as UIModules_PauseMenu_Online
#Include "Libs/Nadeo/Trackmania/Modes/TimeAttack/UIModules/EndMatchTrophy_Server.Script.txt" as UIModules_EndMatchTrophy
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Settings
@ -50,7 +50,7 @@
#Const Description _("$zIn $<$t$6F9Time Attack$> mode, the goal is to set the $<$t$6F9best time$>.\n\nYou have as many tries as you want, and you can $<$t$6F9retry$> when you want by pressing the respawn key.\n\nWhen the time is up, the $<$t$6F9winner$> is the player with the $<$t$6F9best time$>.")
#Const C_HudModulePath "" //< Path to the hud module
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/TMNext/TrackMania/TimeAttack/TimeAttack.Script.txt" //< Url of the mania app
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/Trackmania/Modes/TimeAttack.Script.txt" //< Url of the mania app
#Const C_FakeUsersNb 0
#Const C_UploadRecord True
@ -168,7 +168,7 @@ UIModules_Record::SetSpecialVisibility(False);
***Match_InitMatch***
***
declare Task::LibCommonTask_K_Task Match_TrophyTask;
declare Task::K_Task Match_TrophyTask;
declare Integer Match_TrophyTaskEndTime;
declare Integer Match_MatchDuration;
declare Boolean Match_CanForceTrophyRankUpdate for This = False;
@ -366,6 +366,7 @@ if (Round_ForceEndRound || Round_SkipPauseRound || Round_Skipped) {
if (!Round_SkipPauseRound) {
ForcedEndRoundSequence();
}
MB_SetValidRound(False);
} else {
Map_ValidRoundsNb += 1;
ComputeLatestRaceScores();

239
TM_WaitYourMate.Script.txt Normal file
View File

@ -0,0 +1,239 @@
// #RequireContext CSmMode
#Extends "Modes/TrackMania/TM_TimeAttack_Online.Script.txt"
#Const C_MLID_WaitingMessage "WaitYourMate_WaitingMessage"
#Setting S_ForceRespawnWhenAhead False as ""
#Setting S_TeamsConfig "" as "json of the Team attribution" // {"56f8dd9f-8581-444e-a2ad-1e8e89eed1a4":1,"752c4db6-26f9-44a3-9c80-08264d1665dc":1}
***Match_Yield***
***
foreach (Event in PendingEvents) {
if (Event.Type == CSmModeEvent::EType::OnPlayerAdded) {
if (S_TeamsConfig == "") continue;
if (Event.Player == Null || Event.Player.User == Null || Event.Player.Score == Null) continue;
declare Integer[Text] TeamsConfig;
TeamsConfig.fromjson(S_TeamsConfig);
declare Integer ClanId = TeamsConfig.get(Event.Player.User.WebServicesUserId, 0);
Event.Player.Score.LadderClan = ClanId;
if (Event.Player.SpawnStatus == CSmPlayer::ESpawnStatus::NotSpawned) SetPlayerClan(Event.Player, ClanId);
log("Attributing Player "^ Event.Player.User.Name ^"to the clan " ^ ClanId);
}
}
***
***Match_AfterLoadHud***
***
UIModules::UnloadModules(["UIModule_Race_TimeGap"]);
LoadManialinks();
***
***Match_StartMatch***
***
Clans::SetClansNb(30);
***
***Match_InitMap***
***
foreach (Player in AllPlayers) {
declare netwrite Boolean Net_WaitYourMate_IsWaiting for Player = False;
Net_WaitYourMate_IsWaiting = False;
Player.TrustClientSimu = S_TrustClientSimu;
}
declare Text[] Map_LoginsToRespawn;
declare Text Map_TeamsConfig;
***
***Match_StartMap***
***
Map_TeamsConfig = S_TeamsConfig;
if (S_TeamsConfig != "") {
declare Integer[Text] TeamsConfig ;
TeamsConfig.fromjson(S_TeamsConfig);
foreach (Player in AllPlayers) {
if (Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawned) continue;
declare Integer ClanId = TeamsConfig.get(Player.User.WebServicesUserId, 0);
Player.Score.LadderClan = ClanId;
SetPlayerClan(Player, ClanId);
log("Attributing Player "^ Player.User.Name ^"to the clan " ^ ClanId);
declare CUIConfig UI <=> UIManager.GetUI(Player);
if (UI == Null) continue;
UI.SendChat("$ff3You play for the team $fff"^ ClanId);
}
}
Race::SetRespawnBehaviour(Race::C_RespawnBehaviour_NeverGiveUp);
***
***Match_PlayLoop***
***
foreach (Event in Race::GetPendingEvents()) {
if (Event.Player == Null) continue;
switch (Event.Type) {
case Events::C_Type_Waypoint: {
if (Event.IsEndRace) continue;
UpdateClanStates(Event.Player.Score.LadderClan);
if (S_ForceRespawnWhenAhead) {
Map_LoginsToRespawn.add(Event.Player.User.Login);
}
}
case Events::C_Type_StartLine: {
if (S_TeamsConfig != "") {
declare Integer[Text] TeamsConfig ;
TeamsConfig.fromjson(S_TeamsConfig);
declare Integer ClanId = TeamsConfig.get(Event.Player.User.WebServicesUserId, 0);
Event.Player.Score.LadderClan = ClanId;
if (Event.Player.Score.LadderClan != Event.Player.CurrentClan) {
log("Player spawned on the wrong team, but should not cause any issue");
}
}
UpdateClanStates(Event.Player.Score.LadderClan);
}
case Events::C_Type_GiveUp: {
UpdateClanStates(Event.Player.Score.LadderClan);
}
case Events::C_Type_Respawn: {
UpdateClanStates(Event.Player.Score.LadderClan);
Map_LoginsToRespawn.remove(Event.Player.User.Login);
}
}
}
foreach (LoginToRespawn in Map_LoginsToRespawn) {
declare CSmPlayer Player <=> GetPlayer(LoginToRespawn) ;
if (Player == Null) {
Map_LoginsToRespawn.remove(LoginToRespawn);
} else {
RespawnPlayer(Player);
}
}
if (Map_TeamsConfig != S_TeamsConfig) {
Map_TeamsConfig = S_TeamsConfig;
if (S_TeamsConfig != "") {
declare Integer[Text] TeamsConfig ;
TeamsConfig.fromjson(S_TeamsConfig);
foreach (Player in AllPlayers) {
declare Integer ClanId = TeamsConfig.get(Player.User.WebServicesUserId, 0);
Player.Score.LadderClan = ClanId;
if (Player.SpawnStatus == CSmPlayer::ESpawnStatus::NotSpawned) {
log("Attributing Player "^ Player.User.Name ^" to the clan " ^ ClanId);
SetPlayerClan(Player, ClanId);
} else {
log("Can't apply Player "^ Player.User.Name ^" to the clan " ^ ClanId ^ " because they is playing");
}
UpdateClanStates(Player.Score.LadderClan);
declare CUIConfig UI <=> UIManager.GetUI(Player);
if (UI == Null) continue;
UI.SendChat("$ff3You play for the team $fff"^ ClanId);
}
}
}
***
Void ApplyModeToPlayer(CSmPlayer _Player, Boolean _Lock) {
if (_Player == Null) return;
if (_Player.SpawnStatus == CSmPlayer::ESpawnStatus::NotSpawned) return;
if (_Lock) {
_Player.TrustClientSimu = False;
} else {
_Player.TrustClientSimu = S_TrustClientSimu;
}
SetPlayerVehicle_ControlledByMode(_Player, _Lock);
declare netwrite Boolean Net_WaitYourMate_IsWaiting for _Player = False;
Net_WaitYourMate_IsWaiting = _Lock;
}
Void LockPlayer(CSmPlayer _Player) {
ApplyModeToPlayer(_Player, True);
}
Void UnlockPlayer(CSmPlayer _Player) {
ApplyModeToPlayer(_Player, False);
}
Void UpdateClanStates(Integer _ClanId) {
log("UpdateClanStates _ClanId: " ^ _ClanId);
declare CSmPlayer[][Integer] TeammatesPerCheckpoint;
foreach (Player in Players) {
if (Player.Score == Null) continue;
if (Player.Score.BestRaceTimes.count > 0) continue;
if (Player.Score.LadderClan != _ClanId) continue;
if (!TeammatesPerCheckpoint.existskey(Player.RaceWaypointTimes.count)) TeammatesPerCheckpoint[Player.RaceWaypointTimes.count] = [];
TeammatesPerCheckpoint[Player.RaceWaypointTimes.count].add(Player);
}
if (TeammatesPerCheckpoint.count == 1) {
foreach (TeamMates in TeammatesPerCheckpoint) {
foreach (Player in TeamMates) {
UnlockPlayer(Player);
}
}
} else {
TeammatesPerCheckpoint = TeammatesPerCheckpoint.sortkey();
declare Integer MinCheckpointNumber = -1;
foreach (Checkpoint => TeamMates in TeammatesPerCheckpoint) {
if (MinCheckpointNumber == -1) MinCheckpointNumber = Checkpoint;
foreach (Player in TeamMates) {
ApplyModeToPlayer(Player, (Checkpoint != MinCheckpointNumber));
}
}
}
}
Void LoadManialinks() {
declare Text MLText = """
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<manialink version="3" name="{{{C_MLID_WaitingMessage}}}">
<label id="label-waiting" pos="0 35" halign="center" valign="center" textfont="GameFontBlack" textsize="10" textprefix="$s" text="Waiting for your mate" hidden="1"/>
<script><!--
#Include "TimeLib" as TiL
#Include "MathLib" as ML
main() {
declare CMlLabel Label_Waiting <=> (Page.GetFirstChild("label-waiting") as CMlLabel);
wait (InputPlayer != Null);
while (True) {
yield;
if (!PageIsVisible) continue;
if (GUIPlayer == Null) {
Label_Waiting.Visible = False;
} else {
declare netread Boolean Net_WaitYourMate_IsWaiting for GUIPlayer = False;
Label_Waiting.Visible = Net_WaitYourMate_IsWaiting;
}
}
}
--></script>
</manialink>
""";
Layers::Create(C_MLID_WaitingMessage, MLText);
Layers::SetType(C_MLID_WaitingMessage, CUILayer::EUILayerType::Normal);
Layers::Attach(C_MLID_WaitingMessage);
}