TimeAttack mode in splitscreen
Features:
- Time Attack rounds
- Match modes
- PointsLimit mode - First player to reach a certain amount of points wins the match.
- Nr of rounds mode - Winner is decided after a certain number of rounds have been played.
- Map playlist mode - Winner is decided after all maps in playlist have been played. - Tiebreaker rounds
- Respawn once to go to latest checkpoint. Respawn 2 times within 1 sec to go to the start line.
- This is very useful since TM2 currently doesn't support configurable "Give up" buttons for split screen players.
(Use Respawn button specified in Profile -> Inputs -> Split Screen Inputs) - End TimeAttack round at will and go to next map in the playlist.
- Use the "Give Up" button specified in Profile -> Inputs -> General Inputs
If no player has finished a race prior to pressing "Give Up" then no points are counted and the next map is loaded. (In "Number of rounds mode" this doesn't count as a round.)
If a player has finished the race before pressing "Give up", the round ends prematurely and points are counted as usual. - Random map order setting
- TimeLimitSeconds
- Time limit for time attack round in seconds - AlwaysRespawnToStart
- True = Respawn to start
- False = Respawn to latest checkpoint. Respawn twice within 1 second to respawn to start - MatchMode
- 0 = Match mode off
- 1 = Points mode
- 2 = Nr of rounds mode
- 3 = Map playlist mode - PointsLimit
- Nr of Points needed to win match. (Only used in Points mode) - NrOfRoundsLimit
- Nr of rounds to play before match ends. (Only used in "Nr of rounds mode") - CanMatchEndInTie
- True = The match can end in a tie
- False = Tiebreaker rounds if scores are tied - RandomMapOrder
- True = Random Map Order
- False = Map Order of PlayList - ShowGoldTime
- True = Show the map's gold time in upper left corner
- False = Don't show gold time
1. Copy the code below. (Click on Code: "Select All", Press Ctrl + C)
2. Start a normal splitscreen race with the maps you want to play.
3. Press F12.
4. Paste the code into the "Script" frame with Ctrl + V.
5. Adjust the settings in the script according to your liking. (Lines starting with: #Settings)
6. Press "Save and Test".
(If you press F12 while you're playing the time attack mode the script editor might display a loaded script other than the time attack script. To make the editor show the Time Attack script press INCLUDES until you see a list of loaded scripts and then select Main. You can then adjust the settings and press "Save and Test".)
Next script I make will be "Stunt mode for split screen ".
Have fun!
(Here's the RSS feed if you want to keep track of updates to the "Time Attack split screen script": http://forum.maniaplanet.com/feed.php?f=279&t=12786)
------------------------------------------------------
Code: Select all
// *********************** ** ** * *
// * SplitScreen TimeAttack competition
// ******************* ** ** * *
#RequireContext CTmMode
#Include "TextLib" as TextLib
#Include "MathLib" as MathLib
#Include "Libs/Nadeo/Mode.Script.txt"
#Include "Libs/Nadeo/TrackMania/TM.Script.txt" as TM
// ******** TimeAttack Settings **********
#Setting TimeLimitSeconds 300 //Time limit for time attack round in seconds
#Setting AlwaysRespawnToStart False //True = Respawn to start; False = Respawn to latest checkpoint, but respawn twice within 1 sec to respawn to start.
// ******** Match Mode Settings *********
#Setting MatchMode 0 //0 = Match mode off; 1 = Points mode; 2 = Nr of rounds mode; 3 = Map playlist mode
#Setting PointsLimit 30 //Nr of Points needed to win match. (Only used when MatchMode is set to 1)
#Setting NrOfRoundsLimit 5 //Nr of rounds to play before match ends. (Only used when MatchMode is set to 2)
#Setting CanMatchEndInTie False //True = The match can end in a tie; False = Extra round(s) are played if winning score is tied at end of match
// ******** Various Settings ******
#Setting RandomMapOrder False //True = Random Map Order; False = Map Order of PlayList
#Setting ShowGoldTime False //True = Show the map's gold time in upper left corner; False = Don't show gold time
//***** Constants ******
#Const RoundPoints [10,6,4,3,2,1]
#Const MATCH_MODE_OFF 0 //In this mode there is no match, just plain old time attack.
#Const MATCH_MODE_POINTS 1 //In this mode the match ends when a player reaches the point limit.
#Const MATCH_MODE_NR_OF_ROUNDS 2 //In this mode the match ends after a certain number of rounds have been played.
#Const MATCH_MODE_PLAYLIST 3 //In this mode the match ends after all maps in playlist have been played.
#Const DOUBLE_CLICK_THRESHOLD 1000 //
declare Integer[Ident] RoundArrivalOrder;
declare Integer[] MapOrder;
declare Boolean IsSkipThisRound;
// === Scores ===
Boolean IsRaceBetter(CTmResult _Race1, CTmResult _Race2)
{
declare Comp = _Race1.Compare(_Race2, CTmResult::ETmRaceResultCriteria::Time);
return Comp > 0;
}
Void ClearScoreTimes() {
foreach (Score in Scores) {
Score.BestRace.Time = -1;
Score.BestLap.Time = -1;
Score.PrevRace.Time = -1;
Score.TempResult.Time = -1;
}
}
Boolean IsPointsTied() {
if (Scores.count > 1)
return Scores[0].Points == Scores[1].Points;
return False;
}
Boolean IsEndOfMatchReached(Integer MatchRoundNr) {
if (MatchMode == MATCH_MODE_POINTS && Scores.count > 0 && Scores[0].Points >= PointsLimit)
return True;
else if (MatchMode == MATCH_MODE_NR_OF_ROUNDS && MatchRoundNr >= NrOfRoundsLimit)
return True;
else if (MatchMode == MATCH_MODE_PLAYLIST && MatchRoundNr >= MapList.count)
return True;
return False;
}
Void UnSpawn(CTmPlayer Player) {
TM::EndRaceSequence_Remove(Player);
Player.RaceStartTime = 0;
}
Integer[] CreateMapOrder(Boolean shouldRandomize) {
declare Integer[] newMapOrder;
declare Integer maxMapNr = MapList.count-1;
for(i,0,maxMapNr) {
newMapOrder.add(i);
}
if (shouldRandomize) {
declare Integer tmp;
declare Integer rand;
for (i, 0, maxMapNr) {
rand = MathLib::Rand(0, maxMapNr);
tmp = newMapOrder[i];
newMapOrder[i] = newMapOrder[rand];
newMapOrder[rand] = tmp;
}
}
return newMapOrder;
}
Text GetManiaLinkPage(Text text, Integer length, Integer height) {
return """<frame posn="0 60">
<quad posn="0 3 -1" sizen="{{{length}}} {{{height}}}" halign="center" style="Bgs1InRace" substyle="BgTitle3" />
<label posn="0 0" halign="center" scale="2" text="{{{text}}}" />
</frame>""";
}
Text GetManiaLinkPage(Text text) {
return GetManiaLinkPage(text, 130, 14);
}
Text GetFrameMapTimes()
{
declare Integer[Text] Times;
if (ShowGoldTime) {
Times["Gold"] = Map.TMObjective_GoldTime;
}
foreach (Score in Scores) {
if (Score.BestRace.Time != -1)
Times[Score.User.Name] = Score.BestRace.Time;
}
Times = Times.sort();
declare Legend = _("$oBest times:");
declare Frame = """ <frame posn="-160 86">
<label posn="1 3" style="TextRaceStaticSmall" sizen="50 2" halign="left" text="{{{Legend}}}" />""";
declare Line = 0;
foreach (Name => Time in Times) {
Frame ^= """<label posn="2 {{{-3*Line}}}" style="TextRaceStaticSmall" sizen="25 3" halign="left" text="{{{Name}}}" />
<label posn="30 {{{-3*Line}}}" style="TextPlayerCardScore" sizen="25 3" halign="left" text="$fff{{{TextLib::TimeToText(Time, True)}}}" />""";
Line += 1;
}
Frame ^= """</frame>""" ;
return Frame;
}
declare CUILayer UILayerScores;
declare CUILayer UILayerInfo;
// ============ Intro Sequence:
Void PlayIntroSequence(Text InfoText){
sleep(0); // flush loading lag.
UILayerScores.ManialinkPage = GetFrameMapTimes();
UIManager.UIAll.UILayers.clear();
UIManager.UIAll.UILayers.add(UILayerScores);
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
UIManager.UIAll.UISequence = CUIConfig::EUISequence::Intro;
sleep(4000); // HACK; should be "wait(sequences over)"
if (InfoText == "")
UILayerInfo.ManialinkPage = "";
else {
UILayerInfo.ManialinkPage = GetManiaLinkPage(InfoText);
}
UIManager.UIAll.UILayers.add(UILayerInfo);
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::Normal;
UIManager.UIAll.UISequence = CUIConfig::EUISequence::Playing;
foreach(Player, Players) {
declare UI <=> UIManager.UI[Player];
UI.BigMessage = Player.User.Name;
UI.BigMessageAvatarLogin = Player.User.Login;
UI.BigMessageAvatarVariant = CUIConfig::EAvatarVariant::Default;
Player.RaceStartTime = Now + 100000; // spawn the cars.
}
sleep(3000);
//Remove UILayerInfo
UIManager.UIAll.UILayers.clear();
UIManager.UIAll.UILayers.add(UILayerScores);
UIManager.UIAll.BigMessage = "";
UIManager.UIAll.StatusMessage = "";
foreach(Player, Players) {
declare UI <=> UIManager.UI[Player];
UI.BigMessage = "";
UI.BigMessageAvatarLogin = "";
UI.StatusMessage = "";
}
}
// === Race rounds ==
Void DoTimeAttackRace()
{
UiRounds = False;
UIManager.UIAll.UISequence = CUIConfig::EUISequence::Playing;
TM::Players_SpawnAll(Now + 2000);
foreach(Score, Scores) {
Score.PrevRaceDeltaPoints = 0;
}
sleep(500);
Synchro_DoBarrier();
RoundArrivalOrder = Integer[Ident];
declare RoundArrivalOrderDirty = True;
declare LastRespawnTime = Integer[Ident];
declare CheckpointCount = Integer[Ident];
foreach (Player, Players) {
LastRespawnTime[Player.Id] = Now;
}
declare MatchBestTime for Map = -1;
UILayerScores.ManialinkPage = GetFrameMapTimes();
CutOffTimeLimit = Now + TimeLimitSeconds * 1000 +2500;
//=== Loop until all players finished racing
declare NbContestants = Players.count;
while( Now < CutOffTimeLimit ) {
TM::RunGame();
TM::EndRaceSequence_Update();
if (MatchEndRequested)
return;
// Process events queue
foreach(Event, PendingEvents) {
PassOn(Event);
declare Player <=> Event.Player;
//if AlwaysRespawn to Start or respawn before first checkpoint or second respawn within 1 sec -> make player respawn at beginning and reset time.
if (Event.Type == CTmModeEvent::EType::Respawn) {
if (AlwaysRespawnToStart || CheckpointCount[Player.Id] == 0 || Now - LastRespawnTime[Player.Id] <= DOUBLE_CLICK_THRESHOLD) {
UnSpawn(Player);
}
LastRespawnTime[Player.Id] = Now;
} else if (Event.Type == CTmModeEvent::EType::GiveUp) {
if (MatchBestTime == -1) {
IsSkipThisRound = True;
UILayerInfo.ManialinkPage = GetManiaLinkPage("Skipping this round!");
UIManager.UIAll.UILayers.add(UILayerInfo);
sleep(1500);
UIManager.UIAll.UILayers.clear();
}
TM::Players_UnspawnAll();
CutOffTimeLimit = Now -1;
break;
} else if (Event.Type == CTmModeEvent::EType::StartLine) {
CheckpointCount[Player.Id] = 0;
} else if (Event.Type == CTmModeEvent::EType::WayPoint) {
CheckpointCount[Player.Id] +=1;
if (Event.IsEndRace) {
assert(Player.CurRace.Time == Event.RaceTime);
Player.Score.PrevRace = Player.CurRace;
if (IsRaceBetter(Player.CurRace, Player.Score.BestRace)) {
Player.Score.BestRace = Player.CurRace;
declare TimeText = "$o$EA2" ^ TextLib::TimeToText(Event.RaceTime, True);
if (MatchBestTime == -1 || MatchBestTime > Event.RaceTime) {
MatchBestTime = Event.RaceTime;
TM::EndRaceSequence_Add(Player, TextLib::Compose(_("New match record! %1"), TimeText));
} else {
TM::EndRaceSequence_Add(Player, TextLib::Compose(_("Personal record! %1"), TimeText));
}
RoundArrivalOrder[Player.Id] = Event.RaceTime;
RoundArrivalOrderDirty = True;
} else {
TM::EndRaceSequence_Add(Player, "");
}
}
}
}
// Refresh score table.
if (RoundArrivalOrderDirty) {
RoundArrivalOrderDirty = False;
RoundArrivalOrder = RoundArrivalOrder.sort();
UILayerScores.ManialinkPage = GetFrameMapTimes(); // refresh scores...
}
// Respawn unspawned players
if (PlayersWaiting.count > 0) {
TM::Players_SpawnWaiting(0);
}
}
TM::Players_UnspawnAll();
Synchro_DoBarrier();
// endround sequence
sleep(1000);
}
Void PlayEndOfRoundSequence(Text InfoText, Integer MatchRoundNr) {
UIManager.UIAll.UISequence = CUIConfig::EUISequence::EndRound;
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
sleep(2500); //Showing race times
if (MatchMode != MATCH_MODE_OFF) { //Display match point sequence
UiRounds = True;
if (InfoText == "")
UILayerInfo.ManialinkPage = "";
else {
UILayerInfo.ManialinkPage = GetManiaLinkPage(InfoText);
}
UIManager.UIAll.UILayers.add(UILayerInfo);
declare Index = 0;
declare Table = """<scoretable type="time">""";
declare Ident PrevPlayerId = NullId;
declare ScoreInc = 0;
foreach (PlayerId => Time in RoundArrivalOrder) {
declare Player <=> Players[PlayerId]; // faut le PlayerInfo
//Only update score of previous player if the times aren't identical
declare Boolean IsSameTimeAsPrevPlayer = (PrevPlayerId != NullId && Time == RoundArrivalOrder[PrevPlayerId]);
if (!IsSameTimeAsPrevPlayer) {
if (Index < RoundPoints.count)
ScoreInc = RoundPoints[Index];
else
ScoreInc = 1;
if (Index == Players.count -1) // last players gets no points.
ScoreInc = 0;
}
Index += 1;
Table ^= """<score login="{{{Player.Login}}}" value="{{{Time}}}" inc="{{{ScoreInc}}}"/>""";
Player.Score.PrevRaceDeltaPoints = ScoreInc;
PrevPlayerId = PlayerId;
}
Table ^= """</scoretable>""";
UIManager.UIAll.SmallScoreTable = Table;
sleep(2000); //Showing DeltaPoints
foreach(Score, Scores) {
Score.Points += Score.PrevRaceDeltaPoints;
Score.PrevRaceDeltaPoints = 0;
}
Scores_Sort(::ETmScoreSortOrder::TotalPoints);
if (IsPointsTied() && IsEndOfMatchReached(MatchRoundNr))
UILayerInfo.ManialinkPage = GetManiaLinkPage("Points are tied! Next round will be a tiebreaker!", 210, 14);
sleep(2500); //Showing TotalPoints
UILayerInfo.ManialinkPage = "";
}
UIManager.UIAll.SmallScoreTable = "";
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::Normal;
sleep(500);
Synchro_DoBarrier();
}
//==== EndOfMatchSequence ====
Void EndOfMatchSequence() {
TM::Players_UnspawnAll();
UiRounds = True;
if (Scores.count > 0 && Scores[0].Points > 0 && !ServerShutdownRequested) {
UILayerScores.ManialinkPage = GetFrameMapTimes();
UIManager.UIAll.UISequence = CUIConfig::EUISequence::Podium;
sleep(2500);
declare Text WinnerNames = Scores[0].User.Name;
declare TextBoxWidth = 130;
for (i, 1, Scores.count -1) {
if ( Scores[i].Points == Scores[0].Points) {
WinnerNames ^= TextLib::MLEncode(" & ") ^Scores[i].User.Name;
TextBoxWidth = 320;
}
else
break;
}
declare VictoryText = TextLib::Compose(_("$<%1$> wins the match!"), WinnerNames);
UILayerScores.ManialinkPage = GetFrameMapTimes();
UILayerInfo.ManialinkPage = GetManiaLinkPage(VictoryText, TextBoxWidth, 14);
UIManager.UIAll.UILayers.clear();
UIManager.UIAll.UILayers.add(UILayerScores);
UIManager.UIAll.UILayers.add(UILayerInfo);
sleep(3000);
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
sleep(5000);
}
Synchro_DoBarrier();
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::Normal;
UIManager.UIAll.UILayers.clear();
Scores_Clear();
MatchEndRequested = False; // taken into account.
}
// === Main ===
main()
{
//Init
UIManager.ResetAll();
log("restart...");
IndependantLaps = True;
//Don't use ::ETMRespawnBehaviour::GiveUpBeforeFirstCheckPoint or ETMRespawnBehaviour::AlwaysGiveUp
//because the "give up" event is needed to implement skip current map feature.
RespawnBehaviour = ::ETMRespawnBehaviour::Normal;
UILayerScores <=> UIManager.UILayerCreate();
UILayerInfo <=> UIManager.UILayerCreate();
MapOrder = CreateMapOrder(RandomMapOrder);
declare MapOrderIndex = 0;
//Since the script needs to be pasted instead of loaded there is already a loaded map.
//Need to unload it to enforce map order.
if (MapLoaded)
UnloadMap();
// loop until server interrupted
while( !ServerShutdownRequested ) {
declare MatchRoundNr = 1;
declare IsTieBreakRound = False;
declare IntroText = "";
declare EndOfRoundText = "";
//make sure new match start with first map in maplist in playlist mode. (Last match could've ended in tiebreak.)
if (MatchMode == MATCH_MODE_PLAYLIST) {
MapOrderIndex = 0;
}
// ============ Play the Time Attack races until the end of match condition is reached:
while( !MatchEndRequested ) {
IsSkipThisRound = False;
//Display scoreTable before match in Rounds mode if a Match Mode is set
if (MatchMode!= MATCH_MODE_OFF)
UiRounds = True;
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
UIManager.UIAll.UISequence = CUIConfig::EUISequence::Intro;
NextMapIndex = MapOrder[MapOrderIndex];
LoadMap();
ClearScoreTimes(); //Don't call "Scores_Clear" because we want to preserve the match points
TM::Players_UnspawnAll();
//Set texts according to settings and state
if (IsTieBreakRound) {
IntroText = "Tiebreaker round!";
EndOfRoundText = "Player with most points wins!";
} else if (MatchMode == MATCH_MODE_POINTS) {
if (MatchRoundNr == 1)
IntroText = "New match!";
else
IntroText = "";
EndOfRoundText = """{{{PointsLimit}}} points to win!""";
} else if (MatchMode == MATCH_MODE_NR_OF_ROUNDS) {
IntroText = """Round {{{MatchRoundNr}}} of {{{NrOfRoundsLimit}}}""";
EndOfRoundText = """Round {{{MatchRoundNr}}} of {{{NrOfRoundsLimit}}}""";
} else if (MatchMode == MATCH_MODE_PLAYLIST) {
IntroText = """Round {{{MatchRoundNr}}} of {{{MapList.count}}}""";
EndOfRoundText = """Round {{{MatchRoundNr}}} of {{{MapList.count}}}""";
}
PlayIntroSequence(IntroText);
DoTimeAttackRace();
if (!IsSkipThisRound)
PlayEndOfRoundSequence(EndOfRoundText, MatchRoundNr);
if (IsEndOfMatchReached(MatchRoundNr)) {
if (!CanMatchEndInTie && IsPointsTied())
IsTieBreakRound = True;
else {
IsTieBreakRound = False;
break; // score limit reached.
}
}
if (!IsSkipThisRound || MatchMode == MATCH_MODE_PLAYLIST)
MatchRoundNr += 1;
MapOrderIndex += 1;
if (MapOrderIndex >= MapList.count) {
MapOrderIndex = 0;
}
UnloadMap();
}
// ============ End of Match Sequence:
if (MatchMode != MATCH_MODE_OFF)
EndOfMatchSequence();
UnloadMap();
}
// ====== Cleanup
log("Cleanup! (Is this bit of code ever called???)");
UIManager.UILayerDestroy(UILayerScores);
UIManager.UILayerDestroy(UILayerInfo);
}