Index: binaries/data/mods/public/gui/common/gamedescription.js =================================================================== --- binaries/data/mods/public/gui/common/gamedescription.js +++ binaries/data/mods/public/gui/common/gamedescription.js @@ -199,6 +199,17 @@ ), { "min": g_GameAttributes.settings.WonderDuration } ); + else if (g_VictoryConditions.Name[victoryIdx] == "capture_the_flag") + title = sprintf( + translatePluralWithContext( + "victory condition", + "Capture The Flag (%(min)s minute)", + "Capture The Flag (%(min)s minutes)", + g_GameAttributes.settings.CaptureTheFlagDuration + ), + { "min": g_GameAttributes.settings.CaptureTheFlagDuration } + ); + titles.push({ "label": title, "value": g_VictoryConditions.Description[victoryIdx] Index: binaries/data/mods/public/gui/common/settings.js =================================================================== --- binaries/data/mods/public/gui/common/settings.js +++ binaries/data/mods/public/gui/common/settings.js @@ -32,7 +32,8 @@ "AIDescriptions": loadAIDescriptions(), "AIDifficulties": loadAIDifficulties(), "Ceasefire": loadCeasefire(), - "WonderDurations": loadWonderDuration(), + "WonderDurations": loadVictoryDuration("wonder"), + "CaptureTheFlagDurations": loadVictoryDuration("capture_the_flag"), "GameSpeeds": loadSettingValuesFile("game_speeds.json"), "MapTypes": loadMapTypes(), "MapSizes": loadSettingValuesFile("map_sizes.json"), @@ -132,11 +133,11 @@ } /** - * Loads available wonder-victory times + * Loads available victory times for victory conditions like wonder and capture the flag. */ -function loadWonderDuration() +function loadVictoryDuration(gameType) { - var jsonFile = "wonder_times.json"; + var jsonFile = "victory_times.json"; var json = Engine.ReadJSONFile(g_SettingsDirectory + jsonFile); if (!json || json.Default === undefined || !json.Times || !Array.isArray(json.Times)) @@ -148,7 +149,8 @@ return json.Times.map(duration => ({ "Duration": duration, "Default": duration == json.Default, - "Title": sprintf(translatePluralWithContext("wonder victory", "%(min)s minute", "%(min)s minutes", duration), { "min": duration }) + "Title": sprintf(translatePluralWithContext(gameType == "wonder" ? "wonder victory" : "capture the flag victory", + "%(min)s minute", "%(min)s minutes", duration), { "min": duration }) })); } Index: binaries/data/mods/public/gui/gamesetup/gamesetup.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/gamesetup.js +++ binaries/data/mods/public/gui/gamesetup/gamesetup.js @@ -9,6 +9,7 @@ const g_StartingResources = prepareForDropdown(g_Settings && g_Settings.StartingResources); const g_VictoryConditions = prepareForDropdown(g_Settings && g_Settings.VictoryConditions); const g_WonderDurations = prepareForDropdown(g_Settings && g_Settings.WonderDurations); +const g_CaptureTheFlagDurations = prepareForDropdown(g_Settings && g_Settings.CaptureTheFlagDurations); /** * All selectable playercolors except gaia. @@ -307,6 +308,7 @@ initStartingResources(); initCeasefire(); initWonderDurations(); + initCaptureTheFlagDurations(); initVictoryConditions(); initMapSizes(); initRadioButtons(); @@ -516,6 +518,21 @@ wonderConditions.selected = g_WonderDurations.Default; } +function initCaptureTheFlagDurations() +{ + let captureTheFlagConditions = Engine.GetGUIObjectByName("captureTheFlagDuration"); + captureTheFlagConditions.list = g_CaptureTheFlagDurations.Title; + captureTheFlagConditions.list_data = g_CaptureTheFlagDurations.Duration; + captureTheFlagConditions.onSelectionChange = function() + { + if (this.selected != -1) + g_GameAttributes.settings.CaptureTheFlagDuration = g_CaptureTheFlagDurations.Duration[this.selected]; + + updateGameAttributes(); + }; + captureTheFlagConditions.selected = g_CaptureTheFlagDurations.Default; +} + function initMapSizes() { let mapSize = Engine.GetGUIObjectByName("mapSize"); @@ -1262,6 +1279,7 @@ { delete g_GameAttributes.settings.WonderDuration; delete g_GameAttributes.settings.LastManStanding; + delete g_GameAttributes.settings.CaptureTheFlagDuration; } if (mapSettings.PlayerData) @@ -1403,6 +1421,8 @@ let mapSizeIdx = mapSettings.Size !== undefined ? g_MapSizes.Tiles.indexOf(mapSettings.Size) : g_MapSizes.Default; let victoryIdx = mapSettings.GameType !== undefined ? g_VictoryConditions.Name.indexOf(mapSettings.GameType) : g_VictoryConditions.Default; let wonderDurationIdx = mapSettings.WonderDuration !== undefined ? g_WonderDurations.Duration.indexOf(mapSettings.WonderDuration) : g_WonderDurations.Default; + let captureTheFlagDurationIdx = mapSettings.CaptureTheFlagDuration !== undefined ? + g_CaptureTheFlagDurations.Duration.indexOf(mapSettings.CaptureTheFlagDuration) : g_CaptureTheFlagDurations.Default; let popIdx = mapSettings.PopulationCap !== undefined ? g_PopulationCapacities.Population.indexOf(mapSettings.PopulationCap) : g_PopulationCapacities.Default; let startingResIdx = mapSettings.StartingResources !== undefined ? g_StartingResources.Resources.indexOf(mapSettings.StartingResources) : g_StartingResources.Default; let ceasefireIdx = mapSettings.Ceasefire !== undefined ? g_Ceasefire.Duration.indexOf(mapSettings.Ceasefire) : g_Ceasefire.Default; @@ -1417,6 +1437,7 @@ Engine.GetGUIObjectByName("numPlayers").selected = numPlayers - 1; Engine.GetGUIObjectByName("victoryCondition").selected = victoryIdx; Engine.GetGUIObjectByName("wonderDuration").selected = wonderDurationIdx; + Engine.GetGUIObjectByName("captureTheFlagDuration").selected = captureTheFlagDurationIdx; Engine.GetGUIObjectByName("populationCap").selected = popIdx; Engine.GetGUIObjectByName("gameSpeed").selected = gameSpeedIdx; Engine.GetGUIObjectByName("ceasefire").selected = ceasefireIdx; @@ -1435,6 +1456,7 @@ Engine.GetGUIObjectByName("numPlayersText").caption = numPlayers; Engine.GetGUIObjectByName("victoryConditionText").caption = g_VictoryConditions.Title[victoryIdx]; Engine.GetGUIObjectByName("wonderDurationText").caption = g_WonderDurations.Title[wonderDurationIdx]; + Engine.GetGUIObjectByName("captureTheFlagDurationText").caption = g_CaptureTheFlagDurations.Title[captureTheFlagDurationIdx]; Engine.GetGUIObjectByName("populationCapText").caption = g_PopulationCapacities.Title[popIdx]; Engine.GetGUIObjectByName("startingResourcesText").caption = g_StartingResources.Title[startingResIdx]; Engine.GetGUIObjectByName("ceasefireText").caption = g_Ceasefire.Title[ceasefireIdx]; @@ -1452,6 +1474,10 @@ g_GameAttributes.settings.GameType && g_GameAttributes.settings.GameType != "wonder"; + Engine.GetGUIObjectByName("optionCaptureTheFlagDuration").hidden = + g_GameAttributes.settings.GameType && + g_GameAttributes.settings.GameType != "capture_the_flag"; + Engine.GetGUIObjectByName("cheatWarningText").hidden = !g_IsNetworked || !mapSettings.CheatsEnabled; Engine.GetGUIObjectByName("lastManStanding").enabled = !mapSettings.LockTeams; @@ -1467,7 +1493,7 @@ let notScenario = g_GameAttributes.mapType != "scenario" && g_IsController ; - for (let ctrl of ["victoryCondition", "wonderDuration", "populationCap", + for (let ctrl of ["victoryCondition", "wonderDuration", "captureTheFlagDuration", "populationCap", "startingResources", "ceasefire", "revealMap", "exploreMap", "disableTreasures", "lockTeams", "lastManStanding"]) hideControl(ctrl, ctrl + "Text", notScenario); Index: binaries/data/mods/public/gui/gamesetup/gamesetup.xml =================================================================== --- binaries/data/mods/public/gui/gamesetup/gamesetup.xml +++ binaries/data/mods/public/gui/gamesetup/gamesetup.xml @@ -347,6 +347,16 @@ + + + Capture The Flag Duration: + + + + + Population Cap: Index: binaries/data/mods/public/maps/scripts/CaptureTheFlag.js =================================================================== --- binaries/data/mods/public/maps/scripts/CaptureTheFlag.js +++ binaries/data/mods/public/maps/scripts/CaptureTheFlag.js @@ -0,0 +1,156 @@ +Trigger.prototype.CheckCaptureTheFlagVictory = function(data) +{ + let cmpIdentity = Engine.QueryInterface(data.entity, IID_Identity); + if (!cmpIdentity || !cmpIdentity.HasClass("Flag") || data.from == -1) + return; + + if (data.from != 0) + --this.playerFlagsCount[data.from]; + + ++this.playerFlagsCount[data.to]; + + this.CheckCountdown(); +}; + +/** + * Check if an individual player or a team has acquired all the flags + * Also check if the countdown needs to be stopped if a player/team no longer has all the flags + */ +Trigger.prototype.CheckCountdown = function() +{ + for (let playerID = 1; playerID < TriggerHelper.GetNumberOfPlayers(); ++playerID) + { + let playerAndAllies = QueryPlayerIDInterface(playerID).GetMutualAllies(); + let teamflagsOwned = 0; + + for (let ally of playerAndAllies) + teamflagsOwned += this.playerFlagsCount[ally]; + + if (teamflagsOwned == this.flags.length - 1) + { + let data = { "entity": this.flags[playerID], "to": playerAndAllies }; + this.StartCountdown(data, playerAndAllies.length > 1); + return; + } + } + + this.DeleteCaptureTheFlagVictoryMessages(); +}; + +Trigger.prototype.DeleteCaptureTheFlagVictoryMessages = function() +{ + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); + + for (let ent in this.flagsVictoryMessages) + { + cmpGuiInterface.DeleteTimeNotification(this.flagsVictoryMessages[ent].ownMessage); + cmpGuiInterface.DeleteTimeNotification(this.flagsVictoryMessages[ent].otherMessage); + cmpTimer.CancelTimer(this.flagsVictoryTimers[ent]); + } +}; + +Trigger.prototype.StartCountdown = function(data, isTeam) +{ + let timer = this.flagsVictoryTimers[data.entity]; + let messages = this.flagsVictoryMessages[data.entity] || {}; + + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); + + if (timer) + { + cmpTimer.CancelTimer(timer); + cmpGuiInterface.DeleteTimeNotification(messages.ownMessage); + cmpGuiInterface.DeleteTimeNotification(messages.otherMessage); + } + + let players = [-1]; + for (let playerID = 1; playerID < TriggerHelper.GetNumberOfPlayers(); ++playerID) + { + let cmpPlayer = QueryPlayerIDInterface(playerID); + if (cmpPlayer.GetState() == "won") + return; + if (data.to.indexOf(playerID) == -1) + players.push(playerID); + } + + let cmpPlayer = QueryOwnerInterface(data.entity, IID_Player); + let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager); + let captureTheFlagDuration = cmpEndGameManager.GetGameTypeSettings().captureTheFlagDuration || 0; + + messages.otherMessage = cmpGuiInterface.AddTimeNotification({ + "message": isTeam ? markForTranslation("%(player)s's team has captured all of the flags. They will have won in %(time)s") : + markForTranslation("%(player)s has captured all of the flags. They will have won in %(time)s"), + "players": players, + "parameters": { + "player": cmpPlayer.GetName() + }, + "translateMessage": true, + "translateParameters": [], + }, captureTheFlagDuration); + + messages.ownMessage = cmpGuiInterface.AddTimeNotification({ + "message": isTeam ? markForTranslation("Your team has captured all of the flags. You will have won in %(time)s") : + markForTranslation("You have captured all of the flags. You will have won in %(time)s"), + "players": data.to, + "translateMessage": true, + }, captureTheFlagDuration); + + timer = cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_EndGameManager, + "MarkPlayerAsWon", captureTheFlagDuration, data.to[0]); + + this.flagsVictoryTimers[data.entity] = timer; + this.flagsVictoryMessages[data.entity] = messages; +}; + +Trigger.prototype.InitflagsGame = function() +{ + // Attempt to spawn one flag per player randomly in neutral territory + let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + let cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager); + let cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager); + + let validSpawnPoint = entity => { + + let cmpIdentity = Engine.QueryInterface(entity, IID_Identity); + let cmpPosition = Engine.QueryInterface(entity, IID_Position); + + if (!cmpIdentity || !cmpPosition || !cmpPosition.IsInWorld()) + return false; + + let pos = cmpPosition.GetPosition(); + if (pos.y <= cmpWaterManager.GetWaterLevel(pos.x, pos.z) || + cmpTerritoryManager.GetOwner(pos.x, pos.z) != 0) + return false; + + return true; + }; + + let gaiaEntities = cmpRangeManager.GetEntitiesByPlayer(0).filter(entity => validSpawnPoint(entity)); + + if (!gaiaEntities.length) + { + error("No valid flag spawn points on map"); + return; + } + + for (let i = 1; i < TriggerHelper.GetNumberOfPlayers(); ++i) + { + this.flags[i] = TriggerHelper.SpawnUnits(pickRandom(gaiaEntities), "other/special_flag", 1, 0)[0]; + this.playerFlagsCount[i] = 0; + + let cmpDamageReceiver = Engine.QueryInterface(this.flags[i], IID_DamageReceiver); + cmpDamageReceiver.SetInvulnerability(true); + } +}; + +let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); +cmpTrigger.flags = []; +cmpTrigger.playerFlagsCount = []; +cmpTrigger.flagsVictoryTimers = {}; +cmpTrigger.flagsVictoryMessages = {}; + +cmpTrigger.DoAfterDelay(0, "InitflagsGame", {}); +cmpTrigger.RegisterTrigger("OnOwnershipChanged", "CheckCaptureTheFlagVictory", { "enabled": true }); +cmpTrigger.RegisterTrigger("OnDiplomacyChanged", "CheckCountdown", { "enabled": true }); Index: binaries/data/mods/public/simulation/components/Identity.js =================================================================== --- binaries/data/mods/public/simulation/components/Identity.js +++ binaries/data/mods/public/simulation/components/Identity.js @@ -54,7 +54,7 @@ "" + "" + "" + - "" + + "" + "" + "tokens" + "" + Index: binaries/data/mods/public/simulation/components/Trigger.js =================================================================== --- binaries/data/mods/public/simulation/components/Trigger.js +++ binaries/data/mods/public/simulation/components/Trigger.js @@ -11,6 +11,7 @@ "CinemaPathEnded", "CinemaQueueEnded", "ConstructionStarted", + "DiplomacyChanged", "InitGame", "Interval", "OwnershipChanged", @@ -271,6 +272,11 @@ this.CallEvent("PlayerWon", msg); }; +Trigger.prototype.OnGlobalDiplomacyChanged = function(msg) +{ + this.CallEvent("DiplomacyChanged", msg); +}; + /** * Execute a function after a certain delay. * Index: binaries/data/mods/public/simulation/data/settings/victory_conditions/capture_the_flag.json =================================================================== --- binaries/data/mods/public/simulation/data/settings/victory_conditions/capture_the_flag.json +++ binaries/data/mods/public/simulation/data/settings/victory_conditions/capture_the_flag.json @@ -0,0 +1,15 @@ +{ + "TranslatedKeys": ["Title", "Description"], + "Data": + { + "Title": "Capture The Flag", + "Description": "Collect all of the flags spread across the map and keep them for a certain time to win the game.", + "Scripts": + [ + "scripts/TriggerHelper.js", + "scripts/ConquestCommon.js", + "scripts/Conquest.js", + "scripts/CaptureTheFlag.js" + ] + } +} Index: binaries/data/mods/public/simulation/data/settings/victory_times.json =================================================================== --- binaries/data/mods/public/simulation/data/settings/victory_times.json +++ binaries/data/mods/public/simulation/data/settings/victory_times.json @@ -0,0 +1,4 @@ +{ + "Times": [0, 1, 3, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 75, 90, 105, 120], + "Default": 20 +} Index: binaries/data/mods/public/simulation/data/settings/wonder_times.json =================================================================== --- binaries/data/mods/public/simulation/data/settings/wonder_times.json +++ binaries/data/mods/public/simulation/data/settings/wonder_times.json @@ -1,4 +0,0 @@ -{ - "Times": [0, 1, 3, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 75, 90, 105, 120], - "Default": 20 -} Index: binaries/data/mods/public/simulation/helpers/Setup.js =================================================================== --- binaries/data/mods/public/simulation/helpers/Setup.js +++ binaries/data/mods/public/simulation/helpers/Setup.js @@ -47,6 +47,8 @@ let gameTypeSettings = {}; if (settings.WonderDuration) gameTypeSettings.wonderDuration = settings.WonderDuration * 60 * 1000; + if (settings.CaptureTheFlagDuration) + gameTypeSettings.captureTheFlagDuration = settings.CaptureTheFlagDuration * 60 * 1000; if (settings.GameType) cmpEndGameManager.SetGameType(settings.GameType, gameTypeSettings); Index: binaries/data/mods/public/simulation/templates/other/special_flag.xml =================================================================== --- binaries/data/mods/public/simulation/templates/other/special_flag.xml +++ binaries/data/mods/public/simulation/templates/other/special_flag.xml @@ -0,0 +1,43 @@ + + + + 100 + 1 + 10 + + + 0 + + + + 2.0 + + + true + + + gaia + Flag + Flag + technologies/metal_pot.png + Special unit for Capture The Flag games. + + + + + + star/256x256.png + star/256x256_mask.png + + + + + 6 + + 12.0 + + + + units/athenians/siege_spear_packed.xml + + Index: source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp =================================================================== --- source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp +++ source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp @@ -149,6 +149,7 @@ gameTypes.Add(_T("wonder")); gameTypes.Add(_T("endless")); gameTypes.Add(_T("regicide")); + gameTypes.Add(_T("capture_the_flag")); wxFlexGridSizer* gridSizer = new wxFlexGridSizer(2, 5, 5); gridSizer->AddGrowableCol(1);