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 @@ -208,6 +208,19 @@ { "min": wonderDuration }); } + if (victoryCondition.Name == "capture_the_wonder") + { + const captureTheWonderDuration = Math.round(initAttributes.settings.CaptureTheWonderDuration); + title = sprintf( + translatePluralWithContext( + "victory condition", + "Capture the Wonder (%(min)s minute)", + "Capture the Wonder (%(min)s minutes)", + captureTheWonderDuration + ), + { "min": captureTheWonderDuration }); + } + let isCaptureTheRelic = victoryCondition.Name == "capture_the_relic"; if (isCaptureTheRelic) { Index: binaries/data/mods/public/gui/gamesettings/attributes/CaptureTheWonder.js =================================================================== --- binaries/data/mods/public/gui/gamesettings/attributes/CaptureTheWonder.js +++ binaries/data/mods/public/gui/gamesettings/attributes/CaptureTheWonder.js @@ -0,0 +1,40 @@ +GameSettings.prototype.Attributes.CaptureTheWonder = class CaptureTheWonder extends GameSetting +{ + init() + { + this.available = false; + this.duration = 0; + this.settings.victoryConditions.watch(() => this.maybeUpdate(), ["active","hidden"]); + this.settings.map.watch(() => this.onMapChange(), ["map"]); + } + + toInitAttributes(attribs) + { + if (this.available) + attribs.settings.CaptureTheWonderDuration = this.duration; + } + + fromInitAttributes(attribs) + { + if (this.getLegacySetting(attribs, "CaptureTheWonderDuration") !== undefined) + this.setDuration(+this.getLegacySetting(attribs, "CaptureTheWonderDuration")); + } + + onMapChange() + { + this.setDuration(+this.getMapSetting("CaptureTheWonderDuration") || 0); + } + + setDuration(duration) + { + const victoryConditions = this.settings.victoryConditions + this.available = victoryConditions.active.has("capture_the_wonder") && + !victoryConditions.hidden.has("capture_the_wonder"); + this.duration = Math.round(duration); + } + + maybeUpdate() + { + this.setDuration(this.duration); + } +}; Index: binaries/data/mods/public/gui/gamesettings/attributes/VictoryConditions.js =================================================================== --- binaries/data/mods/public/gui/gamesettings/attributes/VictoryConditions.js +++ binaries/data/mods/public/gui/gamesettings/attributes/VictoryConditions.js @@ -6,6 +6,7 @@ // Set of victory condition names. this.active = new Set(); this.disabled = new Set(); + this.hidden = new Set(); this.conditions = {}; } @@ -17,6 +18,8 @@ for (let cond of conditions) this.conditions[cond.Name] = cond; + this._reconstructHidden(); + for (let cond in this.conditions) if (this.conditions[cond].Default) this._add(this.conditions[cond].Name); @@ -32,6 +35,7 @@ let legacy = this.getLegacySetting(attribs, "VictoryConditions"); if (legacy) { + this._reconstructHidden(); this.disabled = new Set(); this.active = new Set(); for (let cond of legacy) @@ -41,6 +45,8 @@ onMapChange() { + this._reconstructHidden(); + if (this.settings.map.type != "scenario") return; // If a map specifies victory conditions, replace them all. @@ -53,6 +59,16 @@ this._add(cond); } + _reconstructHidden() + { + const hidden = new Set(); + // Expects the settings to be booleans, hide the victory condition if it fails to match all + for (const cond in this.conditions) + if(this.conditions[cond].NeedsMapSettings?.some(mapSetting => this.getMapSetting(mapSetting) !== true)) + hidden.add(this.conditions[cond].Name); + this.hidden = hidden; + } + _reconstructDisabled(active) { let disabled = new Set(); Index: binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingsLayout.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingsLayout.js +++ binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingsLayout.js @@ -42,6 +42,7 @@ ...g_VictoryConditions.map(victoryCondition => victoryCondition.Name), "RelicCount", "RelicDuration", + "CaptureTheWonderDuration", "RegicideGarrison", "WonderDuration", "GameSpeed", Index: binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Sliders/CaptureTheWonderDuration.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Sliders/CaptureTheWonderDuration.js +++ binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Sliders/CaptureTheWonderDuration.js @@ -0,0 +1,53 @@ +GameSettingControls.CaptureTheWonderDuration = class CaptureTheWonderDuration extends GameSettingControlSlider +{ + constructor(...args) + { + super(...args); + + this.sprintfValue = {}; + this.available = false; + + g_GameSettings.captureTheWonder.watch(() => this.render(), ["duration", "available"]); + g_GameSettings.map.watch(() => this.render(), ["type"]); + this.render(); + } + + render() + { + this.setHidden(!g_GameSettings.captureTheWonder.available); + this.setEnabled(g_GameSettings.map.type != "scenario"); + + if (g_GameSettings.captureTheWonder.available) + { + let value = g_GameSettings.captureTheWonder.duration; + this.sprintfValue.min = value; + this.setSelectedValue( + g_GameSettings.captureTheWonder.duration, + value == 0 ? this.InstantVictory : sprintf(this.CaptionVictoryTime(value), this.sprintfValue)); + } + } + + onValueChange(value) + { + g_GameSettings.captureTheWonder.setDuration(value); + this.gameSettingsController.setNetworkInitAttributes(); + } +}; + +GameSettingControls.CaptureTheWonderDuration.prototype.TitleCaption = + translate("Capture the Wonder Duration"); + +GameSettingControls.CaptureTheWonderDuration.prototype.Tooltip = + translate("Minutes until the player has achieved Wonder Victory"); + +GameSettingControls.CaptureTheWonderDuration.prototype.CaptionVictoryTime = + min => translatePluralWithContext("victory duration", "%(min)s minute", "%(min)s minutes", min); + +GameSettingControls.CaptureTheWonderDuration.prototype.InstantVictory = + translateWithContext("victory duration", "Immediate Victory."); + +GameSettingControls.CaptureTheWonderDuration.prototype.MinValue = 0; + +GameSettingControls.CaptureTheWonderDuration.prototype.MaxValue = 60; + +GameSettingControls.CaptureTheWonderDuration.prototype.DefaultValue = 20; Index: binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/VictoryConditionCheckbox.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/VictoryConditionCheckbox.js +++ binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/VictoryConditionCheckbox.js @@ -11,7 +11,7 @@ this.setTitle(victoryCondition.Title); this.setTooltip(victoryCondition.Description); - g_GameSettings.victoryConditions.watch(() => this.render(), ["active"]); + g_GameSettings.victoryConditions.watch(() => this.render(), ["active", "hidden"]); g_GameSettings.map.watch(() => this.render(), ["type"]); this.render(); } @@ -21,6 +21,7 @@ this.setEnabled(g_GameSettings.map.type != "scenario" && !g_GameSettings.victoryConditions.disabled.has(this.victoryCondition)); this.setChecked(g_GameSettings.victoryConditions.active.has(this.victoryCondition)); + this.setHidden(g_GameSettings.victoryConditions.hidden.has(this.victoryCondition)); } onPress(checked) Index: binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/GameDescription.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/GameDescription.js +++ binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/GameDescription.js @@ -33,6 +33,7 @@ g_GameSettings.triggerDifficulty.watch(update, ["value"]); g_GameSettings.victoryConditions.watch(update, ["active"]); g_GameSettings.wonder.watch(update, ["duration"]); + g_GameSettings.captureTheWonder.watch(update, ["enabled", "duration"]); g_GameSettings.mapSize.watch(update, ["size"]); } Index: binaries/data/mods/public/maps/random/mainland.js =================================================================== --- binaries/data/mods/public/maps/random/mainland.js +++ binaries/data/mods/public/maps/random/mainland.js @@ -90,6 +90,35 @@ else createMountains(tCliff, avoidClasses(clPlayer, 20, clHill, 15), clHill, scaleByMapSize(3, 15)); +if(g_captureTheWonderMode.isActive) +{ + g_Map.log("captureTheWonder mode, place wonder"); + + const dispersion = new Vector2D(1, 0). + rotate(Math.random()*Math.PI*2). + mult(Math.random()*12); + + const position = dispersion.add(g_Map.getCenter()); + + // Place wonder + g_Map.placeEntityPassable( + g_captureTheWonderMode.templatePrefix + "|structures/rome/wonder", + 0, + position, + 0.0 + ); + + // Smooth the wonder base terrain + new createArea( + new ClumpPlacer(diskArea(8), 0.1, 0.1, Infinity, position), + [ + new SmoothElevationPainter(ELEVATION_MODIFY, 0, 6), + new TileClassPainter(clForest) + ], + null + ); +} + var [forestTrees, stragglerTrees] = getTreeCounts(...rBiomeTreeCount(1)); createDefaultForests( [tMainTerrain, tForestFloor1, tForestFloor2, pForest1, pForest2], Index: binaries/data/mods/public/maps/random/mainland.json =================================================================== --- binaries/data/mods/public/maps/random/mainland.json +++ binaries/data/mods/public/maps/random/mainland.json @@ -6,6 +6,7 @@ "Preview" : "mainland.png", "Keywords": ["multiplayer"], "SupportedBiomes": "generic/", - "CircularMap" : true + "CircularMap" : true, + "SupportsCaptureTheWonder": true } } Index: binaries/data/mods/public/maps/random/rmgen-common/captureTheWonderMode.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen-common/captureTheWonderMode.js +++ binaries/data/mods/public/maps/random/rmgen-common/captureTheWonderMode.js @@ -0,0 +1,9 @@ +var g_captureTheWonderMode = { + + "isActive": g_MapSettings.VictoryConditions.includes("capture_the_wonder"), + /** + * This mixin will mark the template and require the necessary template components + * needed for the entity to work as the "capture the wonder" target. + **/ + "templatePrefix" : "captureTheWonderProperties" +} Index: binaries/data/mods/public/maps/scripts/CaptureTheWonder.js =================================================================== --- binaries/data/mods/public/maps/scripts/CaptureTheWonder.js +++ binaries/data/mods/public/maps/scripts/CaptureTheWonder.js @@ -0,0 +1,182 @@ +Trigger.prototype.IsCaptureTheWonderEntity = function(entity) +{ + return !!Engine.QueryInterface(entity, IID_CaptureTheWonder) +} + +Trigger.prototype.CaptureTheWonderEntityRenamed = function(data) +{ + if (this.captureTheWonderMessages[data.entity] && this.IsCaptureTheWonderEntity(data.newentity)) + { + // When an entity is renamed, we first create a new entity, + // which in case it is a wonder will receive a timer. + // However on a rename we want to use the timer from the old entity, + // so we need to remove the timer of the new entity. + this.CaptureTheWonderDeleteTimer(data.newentity); + + this.captureTheWonderMessages[data.newentity] = this.captureTheWonderMessages[data.entity]; + delete this.captureTheWonderMessages[data.entity]; + } +}; + +Trigger.prototype.CaptureTheWonderOwnershipChanged = function(data) +{ + if (!this.IsCaptureTheWonderEntity(data.entity)) + return; + + this.CaptureTheWonderDeleteTimer(data.entity); + + if (data.to > 0) + this.CaptureTheWonderStartTimer(data.entity, data.to); +}; + +Trigger.prototype.CaptureTheWonderDiplomacyChanged = function(data) +{ + if (!Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager).GetAlliedVictory()) + return; + + for (let ent in this.captureTheWonderMessages) + { + if (this.captureTheWonderMessages[ent].playerID != data.player && this.captureTheWonderMessages[ent].playerID != data.otherPlayer) + continue; + + let owner = this.captureTheWonderMessages[ent].playerID; + let otherPlayer = owner == data.player ? data.otherPlayer : data.player; + let newAllies = new Set(QueryPlayerIDInterface(owner).GetPlayersByDiplomacy("IsExclusiveMutualAlly")); + if (newAllies.has(otherPlayer) && !this.captureTheWonderMessages[ent].allies.has(otherPlayer) || + !newAllies.has(otherPlayer) && this.captureTheWonderMessages[ent].allies.has(otherPlayer)) + { + this.CaptureTheWonderDeleteTimer(ent); + this.CaptureTheWonderStartTimer(ent, owner); + } + } +}; + +/** + * Create new messages, and start timer to register defeat. + */ +Trigger.prototype.CaptureTheWonderStartTimer = function(ent, player) +{ + let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager); + let allies = cmpEndGameManager.GetAlliedVictory() ? + QueryPlayerIDInterface(player).GetPlayersByDiplomacy("IsExclusiveMutualAlly") : []; + + let others = [-1]; + for (let playerID = 1; playerID < TriggerHelper.GetNumberOfPlayers(); ++playerID) + { + let cmpPlayer = QueryPlayerIDInterface(playerID); + if (cmpPlayer.GetState() == "won") + return; + if (allies.indexOf(playerID) == -1 && playerID != player) + others.push(playerID); + } + + let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + + let wonderDuration = cmpEndGameManager.GetGameSettings().captureTheWonderDuration; + this.captureTheWonderMessages[ent] = { + "playerID": player, + "allies": new Set(allies), + "timer": cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Trigger, "CaptureTheWonderSetWinner", wonderDuration, player), + "messages": [ + cmpGuiInterface.AddTimeNotification( + { + "message": allies.length ? + markForTranslation("%(_player_)s captured a Wonder and %(_player_)s and their allies will win in %(time)s.") : + markForTranslation("%(_player_)s captured a Wonder and will win in %(time)s."), + "players": others, + "parameters": { + "_player_": player + }, + "translateMessage": true, + "translateParameters": [] + }, + wonderDuration), + cmpGuiInterface.AddTimeNotification( + { + "message": markForTranslation("%(_player_)s captured a Wonder and you will win in %(time)s."), + "players": allies, + "parameters": { + "_player_": player + }, + "translateMessage": true, + "translateParameters": [] + }, + wonderDuration), + cmpGuiInterface.AddTimeNotification( + { + "message": allies.length ? + markForTranslation("You own a Wonder and you and your allies will win in %(time)s.") : + markForTranslation("You own a Wonder and will win in %(time)s."), + "players": [player], + "translateMessage": true + }, + wonderDuration) + ] + }; +}; + +Trigger.prototype.CaptureTheWonderDeleteTimer = function(ent) +{ + if (!this.captureTheWonderMessages[ent]) + return; + + let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + + for (let message of this.captureTheWonderMessages[ent].messages) + cmpGuiInterface.DeleteTimeNotification(message); + cmpTimer.CancelTimer(this.captureTheWonderMessages[ent].timer); + delete this.captureTheWonderMessages[ent]; +}; + +Trigger.prototype.CaptureTheWonderPlayerWon = function(data) +{ + for (let ent in this.captureTheWonderMessages) + this.CaptureTheWonderDeleteTimer(ent); +}; + +Trigger.prototype.CaptureTheWonderPlayerDefeated = function(data) +{ + for (let ent in this.captureTheWonderMessages) + if (this.captureTheWonderMessages[ent].allies.has(data.playerId)) + { + let owner = this.captureTheWonderMessages[ent].playerID; + this.CaptureTheWonderDeleteTimer(ent); + this.CaptureTheWonderStartTimer(ent, owner); + } +}; + +Trigger.prototype.CaptureTheWonderSetWinner = function(playerID) +{ + let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager); + cmpEndGameManager.MarkPlayerAndAlliesAsWon( + playerID, + n => markForPluralTranslation( + "%(lastPlayer)s has won (capture the wonder victory).", + "%(players)s and %(lastPlayer)s have won (capture the wonder victory).", + n), + n => markForPluralTranslation( + "%(lastPlayer)s has been defeated (capture the wonder victory).", + "%(players)s and %(lastPlayer)s have been defeated (capture the wonder victory).", + n)); +}; + +{ + let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); + cmpTrigger.RegisterTrigger("OnEntityRenamed", "CaptureTheWonderEntityRenamed", { "enabled": true }); + cmpTrigger.RegisterTrigger("OnOwnershipChanged", "CaptureTheWonderOwnershipChanged", { "enabled": true }); + cmpTrigger.RegisterTrigger("OnDiplomacyChanged", "CaptureTheWonderDiplomacyChanged", { "enabled": true }); + cmpTrigger.RegisterTrigger("OnPlayerWon", "CaptureTheWonderPlayerWon", { "enabled": true }); + cmpTrigger.RegisterTrigger("OnPlayerDefeated", "CaptureTheWonderPlayerDefeated", { "enabled": true }); + cmpTrigger.captureTheWonderMessages = {}; +} + +{ + const wonders = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager) + .GetGaiaAndNonGaiaEntities() + .filter(entity => Engine.QueryInterface(entity, IID_CaptureTheWonder) != undefined) + + if(wonders.length == 0) + warn("Game mode is 'Capture the wonder' but there are no wonders in the map") +} Index: binaries/data/mods/public/maps/scripts/WonderVictory.js =================================================================== --- binaries/data/mods/public/maps/scripts/WonderVictory.js +++ binaries/data/mods/public/maps/scripts/WonderVictory.js @@ -1,6 +1,12 @@ +Trigger.prototype.IsNormalWonder = function(entity) +{ + return Engine.QueryInterface(entity, IID_Wonder) && + !Engine.QueryInterface(entity, IID_CaptureTheWonder); +}; + Trigger.prototype.WonderVictoryEntityRenamed = function(data) { - if (this.wonderVictoryMessages[data.entity] && Engine.QueryInterface(data.newentity, IID_Wonder)) + if (this.wonderVictoryMessages[data.entity] && this.IsNormalWonder(data.newentity)) { // When an entity is renamed, we first create a new entity, // which in case it is a wonder will receive a timer. @@ -15,7 +21,7 @@ Trigger.prototype.WonderVictoryOwnershipChanged = function(data) { - if (!Engine.QueryInterface(data.entity, IID_Wonder)) + if (!this.IsNormalWonder(data.entity)) return; this.WonderVictoryDeleteTimer(data.entity); Index: binaries/data/mods/public/simulation/components/CaptureTheWonder.js =================================================================== --- binaries/data/mods/public/simulation/components/CaptureTheWonder.js +++ binaries/data/mods/public/simulation/components/CaptureTheWonder.js @@ -0,0 +1,11 @@ +function CaptureTheWonder() {} + +CaptureTheWonder.prototype.Schema = ""; + +CaptureTheWonder.prototype.Init = function() +{ +}; + +CaptureTheWonder.prototype.Serialize = null; + +Engine.RegisterComponentType(IID_CaptureTheWonder, "CaptureTheWonder", CaptureTheWonder); Index: binaries/data/mods/public/simulation/components/interfaces/CaptureTheWonder.js =================================================================== --- binaries/data/mods/public/simulation/components/interfaces/CaptureTheWonder.js +++ binaries/data/mods/public/simulation/components/interfaces/CaptureTheWonder.js @@ -0,0 +1 @@ +Engine.RegisterInterface("CaptureTheWonder"); Index: binaries/data/mods/public/simulation/data/settings/victory_conditions/capture_the_wonder.json =================================================================== --- binaries/data/mods/public/simulation/data/settings/victory_conditions/capture_the_wonder.json +++ binaries/data/mods/public/simulation/data/settings/victory_conditions/capture_the_wonder.json @@ -0,0 +1,15 @@ +{ + "TranslatedKeys": ["Title", "Description"], + "Data": + { + "Title": "Capture the Wonder", + "Description": "Capture the wonder and keep it for a certain time to win the game.", + "Scripts": + [ + "scripts/TriggerHelper.js", + "scripts/CaptureTheWonder.js" + ], + "GUIOrder": 10, + "NeedsMapSettings" : ["SupportsCaptureTheWonder"] + } +} 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 @@ -49,14 +49,16 @@ const cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager); const gameSettings = { "victoryConditions": clone(settings.VictoryConditions) }; - if (gameSettings.victoryConditions.indexOf("capture_the_relic") != -1) + if (gameSettings.victoryConditions.includes("capture_the_relic")) { gameSettings.relicCount = (settings.RelicCount ?? 1); gameSettings.relicDuration = (settings.RelicDuration ?? 1) * 60 * 1000; } - if (gameSettings.victoryConditions.indexOf("wonder") != -1) + if (gameSettings.victoryConditions.includes("wonder")) gameSettings.wonderDuration = (settings.WonderDuration ?? 1) * 60 * 1000; - if (gameSettings.victoryConditions.indexOf("regicide") != -1) + if (gameSettings.victoryConditions.includes("capture_the_wonder")) + gameSettings.captureTheWonderDuration = (settings.CaptureTheWonderDuration ?? 1) * 60 * 1000; + if (gameSettings.victoryConditions.includes("regicide")) gameSettings.regicideGarrison = settings.RegicideGarrison; cmpEndGameManager.SetGameSettings(gameSettings); Index: binaries/data/mods/public/simulation/templates/special/filter/captureTheWonderProperties.xml =================================================================== --- binaries/data/mods/public/simulation/templates/special/filter/captureTheWonderProperties.xml +++ binaries/data/mods/public/simulation/templates/special/filter/captureTheWonderProperties.xml @@ -0,0 +1,11 @@ + + + + + + + true + + + +