As it turned out the code was a lot of help to others who modified it to bring stunt mode online.
Well done kapsubm!
(The first ever stunt server online was made by TGYoshi and was not related to this project.)
Anyhow, I'm a splitscreen player so this little project is focused on splitscreen play.
I'll keep posting updates I make to the script here in case someone is interested.
Changelog
v0.8
- Better looking stunt UI made with manialink frames.
- Added "dirty" flags to optimize ui redraw.
V0.7
- Fixed respawn penalty and added setting to turn it off. (Thanx for the report kapsubm.)
- Speeded up score countdown. (Feedback from kapsubm)
- Really fixed score sorting at end of match this time.
- Some code cleanup
- Fixed end of match scoring and matchmodes.
- You can now watch en entire intro or just skip it
- A tiny bit of code cleanup
- Add setting to make respawn penalty cause score to score at latest checkpoint
- Implement all standard game modes into this script (rounds, time attack, stunts, platform)
- If possible implement a ui at the beginning of race allowing users to change settings in a convenient way.
- Clean up the code
How to start SplitScreen stunt mode:
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 stunt mode the script editor might display a loaded script other than the "Stunt Mode script". To make the editor show the "Stunt Mode 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".)
Code: Select all
// *********************** ** ** * *
// * SplitScreen Stunt 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
// ******** StuntUi Settings *********
#Setting Splitscreen True //True if splitscreen; False if not splitscreen
#Setting SplitDirectionHorizontal True //True if splitscreen direction is horizontal; False if splitscreen direction is vertical (Only matters for 2 players)
// ******** 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
//********* GameMode Settings ********
#Setting GameMode 2 // 0 = Rounds (Not working) 1 = TimeAttack (Not working see separate script); 2 = StuntMode; 3 = Platform(Not working)
// ******** TimeAttack/Stunt Settings **********
#Setting TimeLimitSeconds 180 //Time limit for time attack/stunt 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.
#Setting RespawnPenalty -1 //0 = No penalty; -1 = default penalty(50 points); -2 = score reset to same as last Checkpoint (Not implemented yet)
// ******** 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
#Setting ForceStuntFigureText False //True = Show StuntFigureText regardless of game type; False = Show StuntFigureText depending on game type.
//***** Constants ******
#Const RoundPoints [10,6,4,3,2,1]
#Const GAME_MODE_ROUNDS 0
#Const GAME_MODE_TIME_ATTACK 1
#Const GAME_MODE_STUNTS 2
#Const GAME_MODE_PLATFORM 3
#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 RESPAWN_PENALTY_NONE 0
#Const RESPAWN_PENALTY_DEFAULT -1
#Const RESPAWN_PENALTY_LAST_CHECKPOINT -2
#Const DOUBLE_CLICK_THRESHOLD 1000 //
#Const STUNT_TEXT_TIMEOUT 2000
#Const STUNT_MODE_COUNT_DOWN 60000 // Not configurable/working in ManiaPlanet 1.2e beta so setting it as a constant
//StuntModeStates
#Const STUNTS_ACTIVE 0
#Const STUNT_TIME_UP 1
#Const FINISHED_RACE 2
//#Const STUNTS_ACTIVE 0 //Makes UI display CurRace.StuntsScore
//#Const COUNT_DOWN_SCORE -1 //Makes UI display CurRace.StuntsScore - countdown penalty
//#Const SKIP_REDRAW -2 //Used to optimize drawing of ui somewhat
//#Const FINISHED_RACE -3 //Makes UI display CurRace.StuntsScore at end of race
//StuntModeStates > 0 = time when to redraw regular score. (Last time a stunt was performed + STUNT_TEXT_TIMEOUT
declare Integer[Ident] RoundArrivalOrder;
declare Integer[] MapOrder;
declare Boolean IsSkipThisRound;
declare Text[CTmModeEvent::EStuntFigure] StuntFigureDictionary;
declare CTmResult::ETmRaceResultCriteria ResultCriteria;
declare Boolean ShowStuntFigureText;
Void InitializeGlobalVars() {
switch(GameMode) {
case GAME_MODE_ROUNDS: {
ShowStuntFigureText = ForceStuntFigureText;
ResultCriteria = CTmResult::ETmRaceResultCriteria::Time;
}
case GAME_MODE_TIME_ATTACK: {
ShowStuntFigureText = ForceStuntFigureText;
ResultCriteria = CTmResult::ETmRaceResultCriteria::Time;
}
case GAME_MODE_STUNTS: {
ShowStuntFigureText = True;
//ShowStuntUI = True;
ResultCriteria = CTmResult::ETmRaceResultCriteria::Stunts;
}
case GAME_MODE_PLATFORM: {
ShowStuntFigureText = ForceStuntFigureText;
ResultCriteria = CTmResult::ETmRaceResultCriteria::NbRespawns;
}
}
}
// === Scores ===
Boolean Event_IsPenalty(CTmModeEvent _Event) {
return _Event.StuntFigure == CTmModeEvent::EStuntFigure::RespawnPenalty
|| _Event.StuntFigure == CTmModeEvent::EStuntFigure::TimePenalty;
}
Integer GetResultValue(CTmResult _Result, CTmResult::ETmRaceResultCriteria _ResultCriteria) {
declare Integer ResultValue;
switch (_ResultCriteria) {
case CTmResult::ETmRaceResultCriteria::Time:
ResultValue = _Result.Time;
case CTmResult::ETmRaceResultCriteria::Stunts:
ResultValue = _Result.StuntsScore;
case CTmResult::ETmRaceResultCriteria::NbRespawns:
ResultValue = _Result.NbRespawns;
default:
assert(False); //Shouldn't be here! FIx it!
}
return ResultValue;
}
Integer GetResultValue(CTmModeEvent _Event, CTmResult::ETmRaceResultCriteria _ResultCriteria) {
declare Integer ResultValue;
declare Integer CheckpointCount for _Event.Player;
switch (_ResultCriteria) {
case CTmResult::ETmRaceResultCriteria::Time:
ResultValue = _Event.RaceTime;
case CTmResult::ETmRaceResultCriteria::Stunts:
if (Event_IsPenalty(_Event))
switch (RespawnPenalty) {
case RESPAWN_PENALTY_DEFAULT: {
if (CheckpointCount == 0) {
ResultValue = 0;
} else {
declare Integer StuntsScore for _Event.Player;
if (_Event.Points > StuntsScore) {
ResultValue = -StuntsScore;
} else {
ResultValue = -_Event.Points;
}
}
}
case RESPAWN_PENALTY_NONE:
ResultValue = 0;
case RESPAWN_PENALTY_LAST_CHECKPOINT:
assert(False); //Not implemented yet
default: {
if (CheckpointCount == 0) {
ResultValue = 0;
log("Checkpoint count was 0 -> No penalty");
} else {
declare Integer StuntsScore for _Event.Player;
if (MathLib::Abs(RespawnPenalty) > StuntsScore) {
ResultValue = -StuntsScore;
log("Penalty larger than score. Penalty = score.");
} else {
ResultValue = -MathLib::Abs(RespawnPenalty);
log("There is enough points draw the full penalty");
}
}
}
}
else {
ResultValue = _Event.Points;
}
case CTmResult::ETmRaceResultCriteria::NbRespawns:
ResultValue = _Event.NbRespawns;
default:
assert(False); //Shouldn't be here! FIx it!
}
//log("Event StuntScore" ^ResultValue);
return ResultValue;
}
Integer ResultValue_Compare(Integer _ResultValue1, Integer _ResultValue2, CTmResult::ETmRaceResultCriteria _ResultCriteria) {
declare Integer ResultValueDiff;
switch(_ResultCriteria) {
case CTmResult::ETmRaceResultCriteria::Time:
ResultValueDiff = _ResultValue2 - _ResultValue1;
case CTmResult::ETmRaceResultCriteria::Stunts:
ResultValueDiff = _ResultValue1 - _ResultValue2;
case CTmResult::ETmRaceResultCriteria::NbRespawns:
ResultValueDiff = _ResultValue2 - _ResultValue1;
default:
assert(False); //Shouldn't reach this point.
}
return ResultValueDiff;
}
Boolean IsResultBetter(Integer _ResultValue1, Integer _ResultValue2, CTmResult::ETmRaceResultCriteria _ResultCriteria) {
return ResultValue_Compare(_ResultValue1, _ResultValue2, _ResultCriteria) > 0;
}
Boolean IsResultBetter(CTmResult _Result1, CTmResult _Result2, CTmResult::ETmRaceResultCriteria _ResultCriteria)
{
declare Boolean IsBetter;
if ( _Result1 == Null ) {
IsBetter = False;
}
else if ( _Result2 == Null) {
IsBetter = True;
}
else {
declare Comp = _Result1.Compare(_Result2, _ResultCriteria);
IsBetter = Comp > 0;
}
return IsBetter;
}
Integer[Text] SortResultValueArray(Integer[Text] _ResultValueArray, CTmResult::ETmRaceResultCriteria _ResultCriteria) {
declare Integer[Text] SortedArray;
switch(_ResultCriteria) {
case CTmResult::ETmRaceResultCriteria::Time:
SortedArray = _ResultValueArray.sort();
case CTmResult::ETmRaceResultCriteria::Stunts: {
foreach (Name => ResultValue in _ResultValueArray) {
SortedArray[Name] = -ResultValue;
}
SortedArray = SortedArray.sort();
foreach (Name => ResultValue in SortedArray){
SortedArray[Name] = MathLib::Abs(ResultValue);
}
}
case CTmResult::ETmRaceResultCriteria::NbRespawns:
SortedArray = _ResultValueArray.sort();
default:
assert(False); //Shouldn't reach this point.
}
return SortedArray;
}
Integer[Ident] SortResultValueArray(Integer[Ident] _RoundArrivalArray, CTmResult::ETmRaceResultCriteria _ResultCriteria) {
declare Integer[Ident] SortedArray;
switch(_ResultCriteria) {
case CTmResult::ETmRaceResultCriteria::Time:
SortedArray = _RoundArrivalArray.sort();
case CTmResult::ETmRaceResultCriteria::Stunts: {
foreach (Id => ResultValue in _RoundArrivalArray) {
SortedArray[Id] = -ResultValue;
}
SortedArray = SortedArray.sort();
foreach (Id => ResultValue in SortedArray){
SortedArray[Id] = MathLib::Abs(ResultValue);
}
}
case CTmResult::ETmRaceResultCriteria::NbRespawns:
SortedArray = _RoundArrivalArray.sort();
default:
assert(False); //Shouldn't reach this point.
}
return SortedArray;
}
//Wasn't allowed to override Scores_Sort :(
Void SortScores(CTmResult::ETmRaceResultCriteria _ResultCriteria) {
switch(_ResultCriteria) {
case CTmResult::ETmRaceResultCriteria::Time:
Scores_Sort(::ETmScoreSortOrder::BestRace_Time);
case CTmResult::ETmRaceResultCriteria::Stunts:
Scores_Sort(::ETmScoreSortOrder::BestRace_Stunts);
case CTmResult::ETmRaceResultCriteria::NbRespawns:
Scores_Sort(::ETmScoreSortOrder::BestRace_NbRespawns);
default:
assert(False); //Shouldn't reach this point.
}
}
Void ClearScoreTimes() {
foreach (Score in Scores) {
Score.BestRace.Time = -1;
Score.BestLap.Time = -1;
Score.PrevRace.Time = -1;
Score.TempResult.Time = -1;
Score.BestRace.StuntsScore = 0;
Score.BestLap.StuntsScore = 0;
Score.PrevRace.StuntsScore = 0;
Score.TempResult.StuntsScore = 0;
Score.BestRace.NbRespawns = -1;
Score.BestLap.NbRespawns = -1;
Score.PrevRace.NbRespawns = -1;
Score.TempResult.NbRespawns = -1;
}
}
Void ClearStuntUI(CTmPlayer _Player) {
declare Text StuntFigureText for _Player;
declare Integer StuntPoints for _Player;
declare Integer StuntsScore for _Player;
declare Integer StuntsScoreAtTimeUp for _Player;
declare Boolean IsStuntUIDirty for _Player;
declare Integer StuntFigureTextTimer for _Player;
StuntFigureText = "";
StuntPoints = 0;
StuntsScore = 0;
StuntsScoreAtTimeUp = 0;
StuntFigureTextTimer = 0;
IsStuntUIDirty = True;
}
Boolean IsPointsTied() {
if (Scores.count > 1)
return Scores[0].Points == Scores[1].Points;
return False;
}
Integer CalcStuntScoreAtCountDown(CTmPlayer _Player, Integer _Time) {
declare Integer StuntsScoreAtTimeUp for _Player;
declare StuntScore = StuntsScoreAtTimeUp;
if (_Player.RaceStartTime != 0 && _Time > STUNT_MODE_COUNT_DOWN) {
StuntScore = StuntScore - (_Time - STUNT_MODE_COUNT_DOWN) / 200;
if (StuntScore < 0)
StuntScore = 0;
}
return StuntScore;
}
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 GetRaceResultText(Integer _Result, CTmResult::ETmRaceResultCriteria _ResultCriteria) {
//log("GetRaceResultText called");
declare Text RaceResultText;
switch (_ResultCriteria) {
case CTmResult::ETmRaceResultCriteria::Time: {
RaceResultText ^= TextLib::TimeToText(_Result, True);
}
case CTmResult::ETmRaceResultCriteria::Stunts: {
RaceResultText ^= _Result ^" Points";
}
default:
assert(False); //Should not be here
}
return RaceResultText;
}
Text GetRaceResultText(CTmResult _Result, CTmResult::ETmRaceResultCriteria _ResultCriteria) {
declare Text RaceResultText;
RaceResultText = GetRaceResultText(GetResultValue(_Result, _ResultCriteria), _ResultCriteria);
return RaceResultText;
}
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);
}
Void InitStuntFigureDictionary() {
StuntFigureDictionary.clear();
StuntFigureDictionary[CTmModeEvent::EStuntFigure::None] = "None???";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::StraightJump] = "Jump";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::Flip] = "Flip";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::BackFlip] = "Back Flip";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::Spin] = "Spin";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::Aerial] = "Aerial";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::AlleyOop] = "Alley Ooop";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::Roll] = "Roll";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::Corkscrew] = "Corkscrew";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::SpinOff] = "Spin Off";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::Rodeo] = "Rodeo";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::FlipFlap] = "Flip Flap";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::Twister] = "Twiseter";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::FreeStyle] = "Free Style";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::SpinningMix] = "Spinning Mix";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::FlippingChaos] = "Flipping Chaos";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::RollingMadness] = "Rolling Madness";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckNone] = "Wreck";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckStraightJump] = "Jump Wreck";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckFlip] = "Flip Wreck";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckBackFlip] = "Back Flip Wreck";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckSpin] = "Spin Wreck";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckAerial] = "Aerial Wreck";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckAlleyOop] = "Alley Ooop Wreck";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckRoll] = "Roll Wreck";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckCorkscrew] = "Corkscrew Wreck";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckSpinOff] = "Spin Off Wreck";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckRodeo] = "Rodeo Wreck";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckFlipFlap] = "Flip Flap Wreck";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckTwister] = "Twister Wreck";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckFreeStyle] = "Free Style Wreck";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckSpinningMix] = "Spinning Mix Wreck";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckFlippingChaos] = "Flipping Chaos Wreck";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::WreckRollingMadness] = "Rolling Madness Wreck";
///
StuntFigureDictionary[CTmModeEvent::EStuntFigure::TimePenalty] = "Time Penalty";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::Reset] = "Reset";
StuntFigureDictionary[CTmModeEvent::EStuntFigure::RespawnPenalty] = "Respawn Penalty";
///
StuntFigureDictionary[CTmModeEvent::EStuntFigure::Grind] = "Grind";
}
Void LogEvent(CTmModeEvent _Event) {
log("Type: "^_Event.Type ^ " RaceTime:"^ _Event.RaceTime ^" StuntFigure: "^_Event.StuntFigure^ " Angle: "^_Event.Angle^" Points: "^_Event.Points^" Combo: "^_Event.Combo^" IsStraight: "^_Event.IsStraight^" IsReverse: "^_Event.IsReverse^" IsMasterJump: "^_Event.IsMasterJump^" Factor: "^_Event.Factor^" StuntsScore: "^_Event.StuntsScore^" Speed: "^ _Event.Speed^ " Distance: "^_Event.Distance^ " Damages: "^_Event.Damages);
}
Text GetStuntText(CTmModeEvent _Event) {
declare Integer CheckpointCount for _Event.Player;
declare Boolean IsPenalty = Event_IsPenalty(_Event);
declare Text StuntText = "";
// log("StuntFigure: "^_Event.StuntFigure^ " Angle: "^_Event.Angle^" Points: "^_Event.Points^" Combo: "^_Event.Combo^" IsStraight: "^_Event.IsStraight^" IsReverse: "^_Event.IsReverse^" IsMasterJump: "^_Event.IsMasterJump^" Factor: "^_Event.Factor^" StuntsScore: "^_Event.StuntsScore^" Speed: "^ _Event.Speed^ " Distance: "^_Event.Distance^ " Damages: "^_Event.Damages);
/*
(Chained)(Master) (Straight) (Basic) xxx (Angle)(!!!)
+ 34
50 Points
*/
if (_Event.Combo > 0) {
if (_Event.Combo > 1)
StuntText ^= """{{{_Event.Combo}}}X""";
StuntText ^= " Chained";
}
if (_Event.IsMasterJump)
StuntText ^= " Master";
if (_Event.IsStraight)
StuntText ^= " Straight";
if (_Event.Angle == 0 && !IsPenalty) {
StuntText ^= " Basic";
}
if (!IsPenalty || (IsPenalty && (RespawnPenalty != RESPAWN_PENALTY_NONE) && CheckpointCount > 0)) {
StuntText ^=""" {{{StuntFigureDictionary[_Event.StuntFigure]}}}""";
}
if (_Event.Angle != 0) {
StuntText ^= """ {{{_Event.Angle}}}""";
for (i, 1, _Event.Angle/180)
StuntText ^= "!";
}
return StuntText;
}
Void InitStuntUIFields() {
//Create stuntui for each player
foreach (Player, Players) {
declare Integer centerX for Player = 0;
declare Integer centerY for Player = 0;
}
declare Integer InvScaleX for UIManager.UIAll;
declare Integer InvScaleY for UIManager.UIAll;
InvScaleX = 1;
InvScaleY = 1;
//log ("Players.count"^Players.count);
if (Splitscreen) {
//set Scale
if (Players.count == 2) {
if (SplitDirectionHorizontal) {
InvScaleX = 1;
InvScaleY = 2;
} else {
InvScaleX = 2;
InvScaleY = 1;
}
} else if (Players.count == 3 || Players.count == 4) {
InvScaleX = 2;
InvScaleY = 2;
}
// set center for each player (pos 80 -45) = lower right quadrant
for (playerIndex, 0, Players.count -1) {
declare Integer centerX for Players[playerIndex];
declare Integer centerY for Players[playerIndex];
if (Players.count == 2) {
if (SplitDirectionHorizontal) {
centerX = 0;
if (playerIndex == 0) //Player1
centerY = 45;
else //Player2
centerY = -45;
} else { //Vertical split
centerY = 0;
if (playerIndex == 0) //Player1
centerX = -80;
else //Player2
centerX = 80;
}
} else { //3 or 4 players
switch (playerIndex) {
case 0: {
centerX = -80;
centerY = 45;
}
case 1: {
centerX = -80;
centerY = -45;
}
case 2: {
centerX = 80;
centerY = 45;
}
case 3: {
centerX = 80;
centerY = -45;
}
}
}
}
}
}
Void UpdateStuntScoreUI(CTmPlayer _Player) {
declare Text StuntFigureText for _Player;
declare Integer StuntPoints for _Player;
declare Integer StuntsScore for _Player;
declare CUILayer StuntUILayer for _Player;
declare Integer centerX for _Player;
declare Integer centerY for _Player;
declare Integer InvScaleX for UIManager.UIAll;
declare Integer InvScaleY for UIManager.UIAll;
declare Text StuntPointsText = "";
if (StuntPoints > 0) {
StuntPointsText = "+" ^StuntPoints;
} else if (StuntPoints < 0) {
StuntPointsText ^= StuntPoints;
}
// log("centerX"^centerX);
// log("centerY"^centerY);
// log("InvScaleX"^InvScaleX);
// log("InvScaleY"^InvScaleY);
StuntUILayer.ManialinkPage = """<frame posn="{{{centerX}}} {{{centerY}}}" >
<quad posn="{{{160/InvScaleX}}} {{{39/InvScaleY}}} -1" valign="center" halign="right" sizen="23 10" style="Bgs1InRace" substyle="BgSlider" />
<quad posn="{{{158/InvScaleX}}} {{{39/InvScaleY}}} 0" valign="center" halign="right" sizen="6 6" style="BgRaceScore2" substyle="Points" />
<label posn="{{{80/InvScaleX}}} {{{70/InvScaleY}}}" style="TextRaceMessage" valign="center" halign="right" scale="1.5" text="$ff0{{{StuntFigureText}}}" />
<label posn="{{{80/InvScaleX}}} {{{70/InvScaleY-8}}}" style="TextRaceMessage" valign="center" halign="right" scale="1.5" text="$ff0{{{StuntPointsText}}}" />
<label posn="{{{160/InvScaleX-9}}} {{{39/InvScaleY}}}" style="TextValueMedium" valign="center" halign="right" scale="1" text="{{{StuntsScore}}}" />
</frame>""";
//UpdateStuntScoreUI(_Player, StuntFigureText, StuntPoints, StuntsScore);
}
Void logUI(CUIConfig _UI, Text _text) {
log(_text ^" UIStatus: " ^_UI.UIStatus);
log("Number of layers: "^_UI.UILayers.count);
//log("IsHidden: "^_UI.OverlayHideNotices ^ _UI.OverlayHideMapInfo^_UI.OverlayHideOpponentsInfo^_UI.OverlayHideChat^_UI.OverlayHideCheckPointList^_UI.OverlayHideRoundScores^_UI.OverlayHideAll^_UI.OverlayHideScoresOnAltMenu^_UI.NoticesFilter_HidePlayerInfo^_UI.NoticesFilter_HidePlayerWarning^_UI.NoticesFilter_HidePlayerInfoIfNotMe^_UI.NoticesFilter_HidePlayerWarningIfNotMe^_UI.NoticesFilter_HideMapInfo^_UI.NoticesFilter_HideMapWarning^_UI.NoticesFilter_HideMatchInfo^_UI.NoticesFilter_HideMatchWarning);
log("ScoreTableVisibility: "^_UI.ScoreTableVisibility^" SmallScoreTableVisibility: "^_UI.SmallScoreTableVisibility^" AlliesLabelsVisibility: "^_UI.AlliesLabelsVisibility^" EnemiesLabelsVisibility: "^_UI.EnemiesLabelsVisibility^" EnemiesLabelsShowGauges: "^_UI.EnemiesLabelsShowGauges);
log("ManiaLinkPage :"^ _UI.ManialinkPage);
}
//Best times shown in upper left corner
Text GetFrameMapResults(CTmResult::ETmRaceResultCriteria _ResultCriteria)
{
declare Integer[Text] ResultValues;
if (ShowGoldTime && _ResultCriteria == CTmResult::ETmRaceResultCriteria::Time) {
ResultValues["Gold"] = Map.TMObjective_GoldTime;
}
foreach (Score in Scores) {
switch (_ResultCriteria) {
case CTmResult::ETmRaceResultCriteria::Time: {
if (Score.BestRace.Time != -1)
ResultValues[Score.User.Name] = Score.BestRace.Time;
}
case CTmResult::ETmRaceResultCriteria::Stunts: {
if (Score.BestRace.StuntsScore != -1)
ResultValues[Score.User.Name] = Score.BestRace.StuntsScore;
}
default:
assert(False); //Should not reach this point
}
}
ResultValues = SortResultValueArray(ResultValues, _ResultCriteria);
declare Text Legend;
switch (_ResultCriteria) {
case CTmResult::ETmRaceResultCriteria::Time:
Legend = _("$oBest times:");
case CTmResult::ETmRaceResultCriteria::Stunts:
Legend = _("$oBest scores:");
default:
assert(False); //Shouldn't reach this point
}
declare Frame = """ <frame posn="-160 86">
<quad posn="0 4 -1" style="Bgs1" substyle="BgList" sizen="46 {{{3*(ResultValues.count +1) + 2}}}" />
<label posn="1 3" style="TextRaceStaticSmall" sizen="50 2" halign="left" text="{{{Legend}}}" />""";
declare Line = 0;
foreach (Name => Result in ResultValues) {
Frame ^= """<label posn="2 {{{-3*Line}}}" style="TextRaceStaticSmall" sizen="25 3" halign="left" text="$ff0{{{Name}}}" />
<label posn="30 {{{-3*Line}}}" style="TextPlayerCardScore" sizen="25 3" halign="left" text="$ff0{{{GetRaceResultText(Result, _ResultCriteria)}}}" />""";
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 = GetFrameMapResults(ResultCriteria);
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)"
wait(UIManager.UIAll.UISequenceIsCompleted);
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
UILayerInfo.ManialinkPage = "";
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;
foreach (Player, Players) {
declare CUILayer StuntUILayer for Player;
if (Splitscreen) {
UIManager.UIAll.UILayers.add(StuntUILayer);
} else {
UIManager.GetUI(Player).UILayers.add(StuntUILayer);
}
}
if (GameMode == GAME_MODE_STUNTS) { //Should be stuntmodeui= true
UiStuntsMode = True;
UiRaceChrono = CTmMode::ETmRaceChronoBehaviour::CountDown;
UiDisplayStuntsNames = True; //Nadeo hasn't got it working yet. Extracted own stunt name own StuntUI Layer.
ResultCriteria = CTmResult::ETmRaceResultCriteria::Stunts;
} else { //StuntMode Off
ResultCriteria = CTmResult::ETmRaceResultCriteria::Time;
}
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;
foreach (Player, Players) {
declare Integer LastRespawnTime for Player;
LastRespawnTime = Now;
declare Integer CheckpointCount for Player;
CheckpointCount = 0;
}
declare Integer MatchBestResult for Map = -1;
foreach(Player, Players) {
declare Integer StuntModeState for Player;
ClearStuntUI(Player);
StuntModeState = STUNTS_ACTIVE;
Player.CurRace.StuntsScore = 0;
}
UILayerScores.ManialinkPage = GetFrameMapResults(ResultCriteria);
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) {
//LogEvent(Event);
PassOn(Event);
// LogEvent(Event);
//log("StuntFigure: "^Event.StuntFigure^ " Angle: "^Event.Angle^" Points: "^Event.Points^" Combo: "^Event.Combo^" IsStraight: "^Event.IsStraight^" IsReverse: "^Event.IsReverse^" IsMasterJump: "^Event.IsMasterJump^" Factor: "^Event.Factor^" StuntsScore: "^Event.StuntsScore^" Speed: "^ Event.Speed^ " Distance: "^Event.Distance^ " Damages: "^Event.Damages);
declare Player <=> Event.Player;
declare Text StuntFigureText for Player;
declare Integer StuntFigureTextTimer for Player;
declare Integer StuntPoints for Player;
declare Integer StuntsScore for Player;
declare Integer StuntsScoreAtTimeUp for Player;
declare Boolean IsStuntUIDirty for Player;
declare Integer StuntModeState for Player;
declare Integer CheckpointCount for Player;
declare Integer LastRespawnTime for Player;
if (Event.Type == CTmModeEvent::EType::Stunt) {
//Player.CurRace.StuntsScore += GetResultValue(Event, ResultCriteria);
StuntFigureText = GetStuntText(Event);
StuntPoints = GetResultValue(Event, ResultCriteria);
StuntsScore += StuntPoints;
StuntsScoreAtTimeUp = StuntsScore;
IsStuntUIDirty = True;
//StuntsScore[Player.Id] += Event.Points;
//UpdateStuntScoreUI(Event.Player, GetStuntText(Event), GetResultValue(Event, ResultCriteria));
StuntFigureTextTimer = Now + STUNT_TEXT_TIMEOUT;
}
//if AlwaysRespawn to Start or respawn before first checkpoint or second respawn within 1 sec -> make player respawn at beginning and reset time.
else if (Event.Type == CTmModeEvent::EType::Respawn) {
if (AlwaysRespawnToStart || CheckpointCount == 0 || Now - LastRespawnTime <= DOUBLE_CLICK_THRESHOLD) {
UnSpawn(Player);
}
LastRespawnTime = Now;
} else if (Event.Type == CTmModeEvent::EType::GiveUp) {
if (MatchBestResult == -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 = 0;
StuntModeState = STUNTS_ACTIVE; //Displays stuntui when respawned
ClearStuntUI(Player);
} else if (Event.Type == CTmModeEvent::EType::WayPoint) {
CheckpointCount +=1;
if (Event.IsEndRace) {
//log("StuntsScore: "^Player.CurRace.StuntsScore);
assert(Player.CurRace.Time == Event.RaceTime);
StuntsScore = CalcStuntScoreAtCountDown(Player, Event.RaceTime);
Player.CurRace.StuntsScore = StuntsScore;
StuntFigureText = "";
StuntPoints = 0;
StuntFigureTextTimer = 0;
IsStuntUIDirty = True;
StuntModeState = FINISHED_RACE; //Stops the countdown in StuntUI and displays the final score.
Player.Score.PrevRace = Player.CurRace;
//log("End of race! StuntsScore: "^Player.Score.PrevRace.StuntsScore);
declare Text ResultText = "$o$EA2" ^ GetRaceResultText(Player.CurRace, ResultCriteria);
if (IsResultBetter(Player.CurRace, Player.Score.BestRace, ResultCriteria)) {
//log("some kind of record");
Player.Score.BestRace = Player.CurRace;
SortScores(ResultCriteria);
//log("Best Race Set");
if (IsResultBetter(GetResultValue(Player.CurRace, ResultCriteria), MatchBestResult, ResultCriteria)) {
//log("Match record!");
MatchBestResult = GetResultValue(Player.CurRace, ResultCriteria);
//log("Calling EndRaceSequence");
TM::EndRaceSequence_Add(Player, TextLib::Compose(_("New match record!\n%1"), ResultText));
} else {
//log("Personal Record! Calling EndRaceSequence");
TM::EndRaceSequence_Add(Player, TextLib::Compose(_("Personal record! %1"), ResultText));
}
RoundArrivalOrder[Player.Id] = GetResultValue(Event, ResultCriteria);
RoundArrivalOrderDirty = True;
} else {
// log("No Record! Calling EndRaceSequence");
// log("CurRace" ^Player.CurRace.StuntsScore ^ " MatchBestResult:"^MatchBestResult.StuntsScore);
if (GameMode != GAME_MODE_STUNTS)
ResultText = "";
TM::EndRaceSequence_Add(Player, ResultText);
}
}
}
}
//Not fully optimized, could have a proper Dirty flag...
foreach(Player, Players) {
declare Integer StuntModeState for Player;
declare Boolean IsStuntUIDirty for Player;
declare Integer StuntFigureTextTimer for Player;
declare Text StuntFigureText for Player;
declare Integer StuntPoints for Player;
declare Integer StuntsScore for Player;
//assert (Player.Id!= prevPlayerId);
if (StuntFigureTextTimer > 0 && Now > StuntFigureTextTimer) {
StuntFigureTextTimer = 0;
StuntFigureText = "";
StuntPoints = 0;
IsStuntUIDirty = True;
}// else if (StuntModeState == FINISHED_RACE) {
//UpdateStuntScoreUI(Player, "", 0);
//}
//Todo Optimize with stuntmode state so interface is updated on transition and when score changes.
//Detect Stunt time up
declare CurRaceTime = Now - Player.RaceStartTime;
if (StuntModeState == STUNTS_ACTIVE && Player.RaceStartTime > 0 && CurRaceTime > STUNT_MODE_COUNT_DOWN) {
//&& StuntModeState != FINISHED_RACE) {
StuntModeState = STUNT_TIME_UP;
StuntFigureText = "$f00Go to the Finish!";
StuntFigureTextTimer = 0;
StuntPoints = 0;
StuntsScore = CalcStuntScoreAtCountDown(Player, CurRaceTime);
IsStuntUIDirty = True;
//UpdateStuntScoreUI(Player, "Go to the Finish!", 0, CalcStuntScoreAtCountDown(Player, CurRaceTime));
}
if (StuntModeState == STUNT_TIME_UP) {
declare Integer PrevStuntsScore = StuntsScore;
StuntsScore = CalcStuntScoreAtCountDown(Player, CurRaceTime);
if (PrevStuntsScore != StuntsScore)
IsStuntUIDirty = True;
}
//prevPlayerId = Player.Id;
if (IsStuntUIDirty) {
UpdateStuntScoreUI(Player);
IsStuntUIDirty = False;
}
}
// Refresh score table.
if (RoundArrivalOrderDirty) {
RoundArrivalOrderDirty = False;
RoundArrivalOrder = SortResultValueArray(RoundArrivalOrder, ResultCriteria);
UILayerScores.ManialinkPage = GetFrameMapResults(ResultCriteria); // refresh scores...
}
// Respawn unspawned players
if (PlayersWaiting.count > 0) {
TM::Players_SpawnWaiting(0);
}
}
foreach (Player, Players) {
declare Boolean IsStuntUIDirty for Player;
ClearStuntUI(Player);
UpdateStuntScoreUI(Player);
IsStuntUIDirty = False;
}
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;
UiStuntsMode = False; // In order to display rounds points properly
if (_InfoText == "")
UILayerInfo.ManialinkPage = "";
else {
UILayerInfo.ManialinkPage = GetManiaLinkPage(_InfoText);
}
UIManager.UIAll.UILayers.add(UILayerInfo);
declare Index = 0;
declare Text 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 scorInc if current player != sameResult as previous player.
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 = GetFrameMapResults(ResultCriteria);
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 = GetFrameMapResults(ResultCriteria);
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...");
InitializeGlobalVars();
InitStuntUIFields();
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();
foreach(Player, Players) {
declare CUILayer StuntUILayer for Player;
StuntUILayer <=> UIManager.UILayerCreate();
}
InitStuntFigureDictionary();
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);
}