/** * Mode Royal */ #Extends "Modes/ShootMania/ModeBase.Script.txt" #Const CompatibleMapTypes "RoyalArena" #Const Version "2012-08-23" #Include "MathLib" as MathLib #Include "TextLib" as TextLib #Include "Libs/Nadeo/Top.Script.txt" as Top #Include "Libs/Nadeo/Layers.Script.txt" as Layers #Include "Libs/Nadeo/ShootMania/SM.Script.txt" as SM #Include "Libs/Nadeo/ShootMania/Rules.Script.txt" as Rules #Include "Libs/Nadeo/ShootMania/Score.Script.txt" as Score /* ------------------------------------- */ // Settings /* ------------------------------------- */ #Setting S_MapPointsLimit 150 as _("Points to win a map") #Setting S_OffZoneActivationTime 4 as _("OffZone activation duration") #Setting S_OffZoneTimeLimit 50 as _("OffZone shrink duration") #Setting S_SpawnInterval 5 as _("Time between each wave of spawns") #Setting S_OffZoneAutoStartTime 90 as _("Time before auto activation of the OffZone") #Const C_UITickPeriod 200 ///< Update UI every xx milliseconds #Const C_OffZoneMinRadius 8. ///< Minimum size of the OffZone at the end #Const C_DefaultOffZoneRadius 300. ///< Default radius of the OffZone #Const C_NbBots 0 ///< Number of bots for debug /* ------------------------------------- */ // Globales variables /* ------------------------------------- */ declare Integer[Ident] G_PlayerSpawnQueue; ///< A list of players to spawn declare Ident[][Integer] G_PriorityBlockSpawnQueue; ///< A list of spawn points to use declare Integer G_TotalPlayersSpawned; ///< Number of players spawned for the current round /* -------------------------------------- */ // Extend /* -------------------------------------- */ /* -------------------------------------- */ // Server start /* -------------------------------------- */ ***StartServer*** *** /* -------------------------------------- */ // Set mode options and tops MB_UseSectionRound = True; UseClans = False; Top::AddTop("Pole", 5); Top::AddTop("Hit", 5); Top::AddTop("Survival", 5); Top::AddTop("Round", 5); /* ------------------------------------- */ // Create Rules declare ModeName = "Royal"; declare ModeRules = TextLib::Compose(_("Free for all\n- Survive as long as possible to score a maximum of points.\n- Bonus points are awarded for the pole capture and for each player hit.\n- If the pole is captured then the playing area will start to shrink. If a player leaves this area he is eliminated.\n- The first player to reach %1 points wins."), TextLib::ToText(S_MapPointsLimit)); Rules::Create(ModeName, ModeRules); /* ------------------------------------- */ // Init UI declare LayerAttached = False; declare LayerDetached = False; declare LayerUpdated = False; declare LayerSpawnQueueId = Layers::Create("SpawnQueue"); declare LayerScoresTableId = Layers::Create("ScoresTable"); declare LayerInfosId = Layers::Create("Infos"); declare LayerTopsId = Layers::Create("Tops"); declare LayerScoresInSpawnId = Layers::Create("ScoresInSpawn"); Layers::GetFromId(LayerScoresTableId).Type = CUILayer::EUILayerType::ScoresTable; Layers::GetFromId(LayerScoresInSpawnId).Type = CUILayer::EUILayerType::ScreenIn3d; *** /* -------------------------------------- */ // Map start /* -------------------------------------- */ ***StartMap*** *** Score::MatchBegin(); Top::MatchBegin(); Top::RoundBegin(); ///< Want global tops for the match declare MapWinnerId = NullId; *** /* -------------------------------------- */ // Round init /* -------------------------------------- */ ***InitRound*** *** /* ------------------------------------- */ // Init variables UpdatePlayerSpawnQueue(); G_TotalPlayersSpawned = G_PlayerSpawnQueue.count; declare AutoActivationInProgress = False; declare OffZoneBonus = G_TotalPlayersSpawned; declare RoundEndRequested = False; declare RoundWinnerId = NullId; declare LastSpawnTime = -S_SpawnInterval * 1000; declare LastMessageTime = Now; declare LastUIUpdateTime = -C_UITickPeriod; declare Goal <=> SM::GetPole("Goal", 0); *** /* -------------------------------------- */ // Round start /* -------------------------------------- */ ***StartRound*** *** UIManager.UIAll.UISequence = CUIConfig::EUISequence::Playing; UIManager.ResetAll(); SM::SetupDefaultVisibility(); Score::RoundBegin(); SetNbFakePlayers(C_NbBots, 0); /* ------------------------------------- */ // Init goal Goal.Gauge.Clan = 0; Goal.Gauge.Value = 0; Goal.Gauge.Max = S_OffZoneActivationTime * 1000; Goal.Gauge.Speed = 0; /* ------------------------------------- */ // Init offzone OffZoneRadius = -1.; OffZoneRadiusSpeed = 0.; OffZoneCenterBlockId = Goal.Id; /* ------------------------------------- */ // Wait enough players WaitForPlayers(1); /* ------------------------------------- */ // Init the players and their scores foreach (Score in Scores) { declare Integer PoleBonus for Score; declare Integer SurvivalBonus for Score; declare Integer HitBonus for Score; PoleBonus = 0; SurvivalBonus = 0; HitBonus = 0; Score.RoundPoints = 0; } foreach (Player in Players) { declare Integer RoundHits for Player; RoundHits = 0; if (Player.Score != Null) Player.Score.RoundPoints = 1; } /* ------------------------------------- */ // Attach layers LayerDetached = Layers::DetachAll(NullId); LayerAttached = Layers::Attach("SpawnQueue", NullId); LayerAttached = Layers::Attach("ScoresTable", NullId); LayerAttached = Layers::Attach("Infos", NullId); LayerAttached = Layers::Attach("ScoresInSpawn", NullId); LayerUpdated = Layers::Update("ScoresInSpawn", UpdateLayerScoresInSpawn()); Rules::Attach(); LayerUpdated = Layers::Update("ScoresTable", UpdateLayerScoresTable()); StartTime = Now; EndTime = -1; UIManager.UIAll.CountdownEndTime = -1; *** /* -------------------------------------- */ // Play loop /* -------------------------------------- */ ***PlayLoop*** *** /* ------------------------------------- */ // Spawn players if (G_PlayerSpawnQueue.count > 0 && LastSpawnTime + (S_SpawnInterval * 1000) < Now) { UpdateBlockSpawnQueue(); SpawnPlayers(); LastSpawnTime = Now; UIManager.UIAll.BigMessageSound = CUIConfig::EUISound::StartRound; UIManager.UIAll.BigMessage = _("Spawning players..."); LastMessageTime = -1; UIManager.UIAll.CountdownEndTime = LastSpawnTime + (S_SpawnInterval * 1000); LayerUpdated = Layers::Update("SpawnQueue", UpdateLayerSpawnQueue()); } /* ------------------------------------- */ // OffZone can be activated else if (G_PlayerSpawnQueue.count <= 0 && LastSpawnTime + 100 > Now) { if (S_OffZoneAutoStartTime >= 0) UIManager.UIAll.CountdownEndTime = LastSpawnTime + (S_OffZoneAutoStartTime * 1000); else UIManager.UIAll.CountdownEndTime = -1; LayerDetached = Layers::Detach("SpawnQueue", NullId); UIManager.UIAll.BigMessageSound = CUIConfig::EUISound::PhaseChange; UIManager.UIAll.BigMessage = _("The OffZone can now be activated."); LastMessageTime = Now; UIManager.UIAll.Hud3dMarkers = """ """; } /* ------------------------------------- */ // Auto activate goal else if (S_OffZoneAutoStartTime >= 0 && G_PlayerSpawnQueue.count <= 0 && OffZoneRadiusSpeed == 0. && OffZoneRadius == -1 && LastSpawnTime + (S_OffZoneAutoStartTime * 1000) < Now) { Goal.Gauge.Speed = Goal.Gauge.Max; Goal.Gauge.Value = Goal.Gauge.Max + 1; UIManager.UIAll.CountdownEndTime = -1; AutoActivationInProgress = True; } /* ------------------------------------- */ // Manage event foreach (Event in PendingEvents) { /* ------------------------------------- */ // OnArmorEmpty if (Event.Type == CSmModeEvent::EType::OnArmorEmpty) { declare Integer SpawnTime for Event.Victim; SpawnTime = Now; UpdateSurvivalScore(Event.Victim.Id); LayerUpdated = Layers::Update("ScoresTable", UpdateLayerScoresTable()); PassOn(Event); } /* ------------------------------------- */ // OnCapture else if (Event.Type == CSmModeEvent::EType::OnCapture ) { if( OffZoneRadiusSpeed == 0. && OffZoneRadius == -1) { /* ------------------------------------- */ // Active OffZone OffZoneRadius = C_DefaultOffZoneRadius; OffZoneRadiusSpeed = C_DefaultOffZoneRadius / S_OffZoneTimeLimit; Goal.Gauge.Max = S_OffZoneTimeLimit * 1000; Goal.Gauge.Value = Goal.Gauge.Max; Goal.Gauge.Speed = -1; AutoActivationInProgress = False; /* ------------------------------------- */ // Give points if (Goal.Sector.PlayersIds.count > 0 && Goal.Gauge.Value >= Goal.Gauge.Max) { declare Bonus = OffZoneBonus / Goal.Sector.PlayersIds.count; foreach (PlayerId in Goal.Sector.PlayersIds) { if (Players.existskey(PlayerId) && Players[PlayerId].Score != Null) { declare PoleBonus for Players[PlayerId].Score = 0; PoleBonus = Bonus; Score::AddPoints(Players[PlayerId], PoleBonus); Top::IncrementPlayerPoints("Pole", Players[PlayerId], PoleBonus); UIManager.UIAll.SendNotice( TextLib::Compose(_("$<%1$> activated the OffZone"), Players[PlayerId].Name), CUIConfig::ENoticeLevel::MatchInfo, Null, CUIConfig::EAvatarVariant::Default, CUIConfig::EUISound::PhaseChange, 0 ); } } } else { UIManager.UIAll.SendNotice( _("OffZone activated!"), CUIConfig::ENoticeLevel::MatchInfo, Null, CUIConfig::EAvatarVariant::Default, CUIConfig::EUISound::PhaseChange, 0 ); } } LayerUpdated = Layers::Update("ScoresTable", UpdateLayerScoresTable()); PassOn(Event); } /* ------------------------------------- */ // OnPlayerRequestRespawn else if (Event.Type == CSmModeEvent::EType::OnPlayerRequestRespawn) { declare Integer SpawnTime for Event.Player; SpawnTime = Now; UpdateSurvivalScore(Event.Player.Id); LayerUpdated = Layers::Update("ScoresTable", UpdateLayerScoresTable()); PassOn(Event); } /* ------------------------------------- */ // OnHit else if (Event.Type == CSmModeEvent::EType::OnHit) { if (Event.Victim != Null && Event.Shooter != Null && Event.Victim != Event.Shooter && Event.Damage > 0) { declare RoundHits for Event.Shooter = 0; declare LostArmor = 0; declare CumulPoints = 0; if (Event.Victim.Armor < Event.Damage) LostArmor = Event.Victim.Armor; else LostArmor = Event.Damage; for (I, 1, LostArmor / 100) { RoundHits += 1; declare Points = (RoundHits + 1) / 2; Score::AddPoints(Event.Shooter, Points); Top::IncrementPlayerPoints("Hit", Event.Shooter, Points); CumulPoints += Points; } Event.ShooterPoints = CumulPoints; if (Event.Shooter.Score != Null) { declare HitBonus for Event.Shooter.Score = 0; HitBonus += CumulPoints; } LayerUpdated = Layers::Update("ScoresTable", UpdateLayerScoresTable()); PassOn(Event); } else { Discard(Event); } } /* ------------------------------------- */ // Others else { PassOn(Event); } } /* ------------------------------------- */ // Active goal if (G_PlayerSpawnQueue.count <= 0 && OffZoneRadiusSpeed == 0. && OffZoneRadius == -1 && !AutoActivationInProgress) { if (Goal.Sector.PlayersIds.count > 0) Goal.Gauge.Speed = 1; else Goal.Gauge.Speed = 0; } /* ------------------------------------- */ // Stop OffZone at MinSize if (OffZoneRadiusSpeed > 0 && OffZoneRadius <= C_OffZoneMinRadius) { OffZoneRadiusSpeed = 0.; OffZoneRadius = C_OffZoneMinRadius; } /* ------------------------------------- */ // UI if (LastMessageTime >= 0 && LastMessageTime + 3000 < Now) { UIManager.UIAll.BigMessage = ""; } if (LastUIUpdateTime + C_UITickPeriod < Now) { LastUIUpdateTime = Now; LayerUpdated = Layers::Update("Infos", UpdateLayerInfos()); } /* ------------------------------------- */ // Round end conditions if ((G_TotalPlayersSpawned > 1 && G_PlayerSpawnQueue.count <= 0 && PlayersNbAlive <= 1) || (G_TotalPlayersSpawned <= 1 && G_PlayerSpawnQueue.count <= 0 && PlayersNbAlive <= 0)) ///< Allow solo playing { foreach (Player in Players) { if (Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawned) { declare Integer SpawnTime for Player; SpawnTime = Now + 1; RoundWinnerId = Player.Id; UpdateSurvivalScore(Player.Id); } if (Players.count == 1) RoundWinnerId = Player.Id; } MB_StopRound = True; } *** /* ------------------------------------- */ // Round End /* ------------------------------------- */ ***EndRound*** *** UIManager.ResetAll(); OffZoneRadiusSpeed = 0.; Goal.Gauge.Speed = 0; foreach (Player in Players) { if (Player.Score != Null && Player.Score.RoundPoints > Top::GetPlayerPoints("Round", Player)) { Top::SetPlayerPoints("Round", Player, Player.Score.RoundPoints); } } LayerDetached = Layers::DetachAll(NullId); LayerAttached = Layers::Attach("ScoresTable", NullId); LayerAttached = Layers::Attach("Tops", NullId); declare ManialinkTop = Top::GetFrameTop("Survival", _("Top 5 Survival"), "-75 88", "") ^ Top::GetFrameTop("Hit", _("Top 5 Hit"), "-25 88", "") ^ Top::GetFrameTop("Pole", _("Top 5 Pole"), "25 88", "") ^ Top::GetFrameTop("Round", _("Best Round Score"), "75 88", ""); LayerUpdated = Layers::Update("ScoresTable", UpdateLayerScoresTable()); LayerUpdated = Layers::Update("Tops", ManialinkTop); if (Players.existskey(RoundWinnerId)) { UIManager.UIAll.BigMessageSound = CUIConfig::EUISound::EndRound; UIManager.UIAll.BigMessage = TextLib::Compose(_("$<%1$> wins the round!"), Players[RoundWinnerId].Name); } else { UIManager.UIAll.BigMessageSound = CUIConfig::EUISound::EndRound; UIManager.UIAll.BigMessage = _("|Match|Draw"); } sleep(1000); /* ------------------------------------- */ // Show Results OffZoneRadius = -1.; StartTime = -1; EndTime = -1; SM::UnspawnAllPlayers(); UIManager.UIAll.UISequence = CUIConfig::EUISequence::EndRound; UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible; sleep(5000); Score::RoundEnd(); sleep(3000); UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::Normal; UIManager.UIAll.BigMessage = ""; /* ------------------------------------- */ // Map end conditions declare MaxPoints = 0; foreach (Score in Scores) { if (Score.Points >= MaxPoints) { if (Score.Points > MaxPoints) { MapWinnerId = Score.User.Id; } else if (Score.Points == MaxPoints) { MapWinnerId = NullId; } MaxPoints = Score.Points; } } if (MaxPoints >= S_MapPointsLimit) MB_StopMap = True; *** /* ------------------------------------- */ // Map End /* ------------------------------------- */ ***EndMap*** *** Score::MatchEnd(True); Top::RoundEnd(); Top::MatchEnd(); LayerDetached = Layers::DetachAll(NullId); UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible; if (Users.existskey(MapWinnerId)) { UIManager.UIAll.BigMessageSound = CUIConfig::EUISound::EndRound; UIManager.UIAll.BigMessage = TextLib::Compose(_("$<%1$> wins the match!"), Users[MapWinnerId].Name); } else { UIManager.UIAll.BigMessageSound = CUIConfig::EUISound::EndRound; UIManager.UIAll.BigMessage = _("|Match|Draw"); } sleep(6000); UIManager.UIAll.UISequence = CUIConfig::EUISequence::Podium; LayerAttached = Layers::Attach("Tops", NullId); sleep(3000); UIManager.UIAll.BigMessage = ""; Mode::UnloadMap(); *** /* ------------------------------------- */ // Server End /* ------------------------------------- */ ***EndServer*** *** /* -------------------------------------- */ // Layers destruction LayerDetached = Layers::DetachAll(NullId); Layers::Clean(); *** /* ------------------------------------- */ // Functions /* ------------------------------------- */ /* ------------------------------------- */ /** Wait until there's enough players to play * * @param _MinPlayers minimum number of players to begin */ Void WaitForPlayers(Integer _MinPlayers) { UIManager.UIAll.BigMessageSound = CUIConfig::EUISound::Warning; UIManager.UIAll.BigMessage = _("Waiting for players..."); StartTime = -1; EndTime = -1; while (PlayersNbTotal < _MinPlayers && !MatchEndRequested) { yield; } StartTime = -1; UIManager.UIAll.BigMessage = ""; } /* ------------------------------------- */ /// Update the player spawn queue Void UpdatePlayerSpawnQueue() { G_PlayerSpawnQueue.clear(); foreach (Player in Players) { declare SpawnTime for Player = -1; G_PlayerSpawnQueue[Player.Id] = SpawnTime; } G_PlayerSpawnQueue = G_PlayerSpawnQueue.sort(); } /* ------------------------------------- */ /// Update the block spawn queue Void UpdateBlockSpawnQueue() { G_PriorityBlockSpawnQueue = [1=>Ident[], 2=>Ident[], 3=>Ident[]]; foreach (BlockSpawn in BlockSpawns) { if (BlockSpawn.Order <= 1) G_PriorityBlockSpawnQueue[1].add(BlockSpawn.Id); else if (BlockSpawn.Order == 2) G_PriorityBlockSpawnQueue[2].add(BlockSpawn.Id); else if (BlockSpawn.Order >= 3) G_PriorityBlockSpawnQueue[3].add(BlockSpawn.Id); } } /* ------------------------------------- */ /// Spawn the players Void SpawnPlayers() { declare Ident[] ToRemove; for (I, 1, 3) { if (G_PlayerSpawnQueue.count <= 0) break; while (G_PriorityBlockSpawnQueue[I].count > 0) { if (G_PlayerSpawnQueue.count <= 0) break; declare SpawnId = G_PriorityBlockSpawnQueue[I][MathLib::Rand(0, G_PriorityBlockSpawnQueue[I].count - 1)]; declare Tmp = G_PriorityBlockSpawnQueue[I].remove(SpawnId); foreach (PlayerId => SpawnTime in G_PlayerSpawnQueue) { if (Players.existskey(PlayerId)) { SM::SpawnPlayer(Players[PlayerId], 0, BlockSpawns[SpawnId]); ToRemove.add(PlayerId); break; } else { ToRemove.add(PlayerId); } } foreach (Id in ToRemove) { declare Tmp = G_PlayerSpawnQueue.removekey(Id); } } } } /* ------------------------------------- */ /** Update survival score * * @param _PlayerId The player who just have been eliminated */ Void UpdateSurvivalScore(Ident _PlayerId) { // When more than 3 players was spawned if (PlayersNbAlive > 1 && G_TotalPlayersSpawned > 2) { foreach (Player in Players) { if (Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawned && Player.Id != _PlayerId) { Score::AddPoints(Player, 1); if (PlayersNbAlive == 2) Score::AddPoints(Player, (G_TotalPlayersSpawned / 2) + (G_TotalPlayersSpawned % 2)); if (PlayersNbAlive == 3) Score::AddPoints(Player, G_TotalPlayersSpawned / 2); } } } // Special case when there was less than 3 players spawned else if (G_TotalPlayersSpawned <= 2 && PlayersNbAlive > 1) { foreach (Player in Players) { if (Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawned) { if (Player.Id == _PlayerId) { Score::AddPoints(Player, 1); } else { Score::AddPoints(Player, 3); } } } } if (!Players.existskey(_PlayerId)) return; if (Players[_PlayerId].Score != Null) { declare SurvivalBonus for Players[_PlayerId].Score = 0; SurvivalBonus = G_TotalPlayersSpawned - PlayersNbAlive + 1; if (PlayersNbAlive == 1) SurvivalBonus = SurvivalBonus + G_TotalPlayersSpawned; if (PlayersNbAlive == 2) SurvivalBonus = SurvivalBonus + (G_TotalPlayersSpawned / 2); Top::IncrementPlayerPoints("Survival", Players[_PlayerId], SurvivalBonus); } } /* ------------------------------------- */ /** Generate the player spawn queue manialink * * @return The manialink Text */ Text UpdateLayerSpawnQueue() { declare ML = ""; declare List = ""; declare I = 0; declare Max = 15; declare MoreString = TextLib::Compose(_("%1 more ..."), TextLib::ToText(G_PlayerSpawnQueue.count - Max)); foreach (PlayerId => Time in G_PlayerSpawnQueue) { if (!Players.existskey(PlayerId)) continue; declare PseudoString = TextLib::MLEncode(Players[PlayerId].Name); List ^= """