Index: ps/trunk/binaries/data/mods/public/maps/scripts/CaptureTheRelic.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/scripts/CaptureTheRelic.js (revision 21440) +++ ps/trunk/binaries/data/mods/public/maps/scripts/CaptureTheRelic.js (revision 21441) @@ -1,180 +1,191 @@ Trigger.prototype.InitCaptureTheRelic = function() { let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); let catafalqueTemplates = shuffleArray(cmpTemplateManager.FindAllTemplates(false).filter( name => GetIdentityClasses(cmpTemplateManager.GetTemplate(name).Identity || {}).indexOf("Relic") != -1)); let potentialSpawnPoints = TriggerHelper.GetLandSpawnPoints(); if (!potentialSpawnPoints.length) { error("No gaia entities found on this map that could be used as spawn points!"); return; } let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager); let numSpawnedRelics = cmpEndGameManager.GetGameTypeSettings().relicCount; this.playerRelicsCount = new Array(TriggerHelper.GetNumberOfPlayers()).fill(0, 1); this.playerRelicsCount[0] = numSpawnedRelics; for (let i = 0; i < numSpawnedRelics; ++i) { this.relics[i] = TriggerHelper.SpawnUnits(pickRandom(potentialSpawnPoints), catafalqueTemplates[i], 1, 0)[0]; let cmpDamageReceiver = Engine.QueryInterface(this.relics[i], IID_DamageReceiver); cmpDamageReceiver.SetInvulnerability(true); let cmpPositionRelic = Engine.QueryInterface(this.relics[i], IID_Position); cmpPositionRelic.SetYRotation(randomAngle()); } }; Trigger.prototype.CheckCaptureTheRelicVictory = function(data) { let cmpIdentity = Engine.QueryInterface(data.entity, IID_Identity); if (!cmpIdentity || !cmpIdentity.HasClass("Relic") || data.from == INVALID_PLAYER) return; --this.playerRelicsCount[data.from]; if (data.to == -1) { warn("Relic entity " + data.entity + " has been destroyed"); this.relics.splice(this.relics.indexOf(data.entity), 1); } else ++this.playerRelicsCount[data.to]; + this.DeleteCaptureTheRelicVictoryMessages(); this.CheckCaptureTheRelicCountdown(); }; /** - * Check if an individual player or team has acquired all relics. - * Also check if the countdown needs to be stopped if a player/team no longer has all relics. - * Reset the countdown if any of the original allies tries to change their diplomacy with one of these allies. + * Check if a group of mutually allied players have acquired all relics. + * The winning players are the relic owners and all players mutually allied to all relic owners. + * Reset the countdown if the group of winning players changes or extends. */ -Trigger.prototype.CheckCaptureTheRelicCountdown = function(data) +Trigger.prototype.CheckCaptureTheRelicCountdown = function() { - let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager); - - for (let playerID = 1; playerID < TriggerHelper.GetNumberOfPlayers(); ++playerID) + if (this.playerRelicsCount[0]) { - let playerAndAllies = cmpEndGameManager.GetAlliedVictory() ? - QueryPlayerIDInterface(playerID).GetMutualAllies() : [playerID]; + this.DeleteCaptureTheRelicVictoryMessages(); + return; + } - let teamRelicsOwned = 0; + let activePlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNonGaiaPlayers().filter( + playerID => QueryPlayerIDInterface(playerID).GetState() == "active"); + let relicOwners = activePlayers.filter(playerID => this.playerRelicsCount[playerID]); + if (!relicOwners.length) + { + this.DeleteCaptureTheRelicVictoryMessages(); + return; + } - for (let ally of playerAndAllies) - teamRelicsOwned += this.playerRelicsCount[ally]; + let winningPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager).GetAlliedVictory() ? + activePlayers.filter(playerID => relicOwners.every(owner => QueryPlayerIDInterface(playerID).IsMutualAlly(owner))) : + [relicOwners[0]]; - if (teamRelicsOwned == this.relics.length) - { - if (!data || - !this.relicsVictoryCountdownPlayers.length || - this.relicsVictoryCountdownPlayers.indexOf(data.player) != -1 && - this.relicsVictoryCountdownPlayers.indexOf(data.otherPlayer) != -1) - { - this.relicsVictoryCountdownPlayers = playerAndAllies; - this.StartCaptureTheRelicCountdown(playerAndAllies); - } - return; - } + // All relicOwners should be mutually allied + if (relicOwners.some(owner => winningPlayers.indexOf(owner) == -1)) + { + this.DeleteCaptureTheRelicVictoryMessages(); + return; } - this.DeleteCaptureTheRelicVictoryMessages(); + // Reset the timer when playerAndAllies isn't the same as this.relicsVictoryCountdownPlayers + if (winningPlayers.length != this.relicsVictoryCountdownPlayers.length || + winningPlayers.some(player => this.relicsVictoryCountdownPlayers.indexOf(player) == -1)) + { + this.relicsVictoryCountdownPlayers = winningPlayers; + this.StartCaptureTheRelicCountdown(winningPlayers); + } }; Trigger.prototype.DeleteCaptureTheRelicVictoryMessages = function() { - let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - cmpTimer.CancelTimer(this.relicsVictoryTimer); + if (!this.relicsVictoryTimer) + return; + + Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).CancelTimer(this.relicsVictoryTimer); + this.relicsVictoryTimer = undefined; let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGuiInterface.DeleteTimeNotification(this.ownRelicsVictoryMessage); cmpGuiInterface.DeleteTimeNotification(this.othersRelicsVictoryMessage); this.relicsVictoryCountdownPlayers = []; }; -Trigger.prototype.StartCaptureTheRelicCountdown = function(playerAndAllies) +Trigger.prototype.StartCaptureTheRelicCountdown = function(winningPlayers) { let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); if (this.relicsVictoryTimer) { cmpTimer.CancelTimer(this.relicsVictoryTimer); cmpGuiInterface.DeleteTimeNotification(this.ownRelicsVictoryMessage); cmpGuiInterface.DeleteTimeNotification(this.othersRelicsVictoryMessage); } if (!this.relics.length) return; let others = [-1]; for (let playerID = 1; playerID < TriggerHelper.GetNumberOfPlayers(); ++playerID) { let cmpPlayer = QueryPlayerIDInterface(playerID); if (cmpPlayer.GetState() == "won") return; - if (playerAndAllies.indexOf(playerID) == -1) + if (winningPlayers.indexOf(playerID) == -1) others.push(playerID); } let cmpPlayer = QueryOwnerInterface(this.relics[0], IID_Player); let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager); let captureTheRelicDuration = cmpEndGameManager.GetGameTypeSettings().relicDuration; - let isTeam = playerAndAllies.length > 1; + let isTeam = winningPlayers.length > 1; this.ownRelicsVictoryMessage = cmpGuiInterface.AddTimeNotification({ "message": isTeam ? - markForTranslation("%(_player_)s's team has captured all relics and will win in %(time)s.") : + markForTranslation("%(_player_)s and their allies have captured all relics and will win in %(time)s.") : markForTranslation("%(_player_)s has captured all relics and will win in %(time)s."), "players": others, "parameters": { "_player_": cmpPlayer.GetPlayerID() }, "translateMessage": true, "translateParameters": [] }, captureTheRelicDuration); this.othersRelicsVictoryMessage = cmpGuiInterface.AddTimeNotification({ "message": isTeam ? - markForTranslation("Your team has captured all relics and will win in %(time)s.") : + markForTranslation("You and your allies have captured all relics and will win in %(time)s.") : markForTranslation("You have captured all relics and will win in %(time)s."), - "players": playerAndAllies, + "players": winningPlayers, "translateMessage": true }, captureTheRelicDuration); this.relicsVictoryTimer = cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Trigger, - "CaptureTheRelicVictorySetWinner", captureTheRelicDuration, playerAndAllies[0]); + "CaptureTheRelicVictorySetWinner", captureTheRelicDuration, winningPlayers); }; -Trigger.prototype.CaptureTheRelicVictorySetWinner = function(playerID) +Trigger.prototype.CaptureTheRelicVictorySetWinner = function(winningPlayers) { let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager); - cmpEndGameManager.MarkPlayerAsWon( - playerID, + cmpEndGameManager.MarkPlayersAsWon( + winningPlayers, n => markForPluralTranslation( "%(lastPlayer)s has won (Capture the Relic).", "%(players)s and %(lastPlayer)s have won (Capture the Relic).", n), n => markForPluralTranslation( "%(lastPlayer)s has been defeated (Capture the Relic).", "%(players)s and %(lastPlayer)s have been defeated (Capture the Relic).", n)); }; { let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); cmpTrigger.relics = []; cmpTrigger.playerRelicsCount = []; cmpTrigger.relicsVictoryTimer = undefined; cmpTrigger.ownRelicsVictoryMessage = undefined; cmpTrigger.othersRelicsVictoryMessage = undefined; cmpTrigger.relicsVictoryCountdownPlayers = []; cmpTrigger.DoAfterDelay(0, "InitCaptureTheRelic", {}); cmpTrigger.RegisterTrigger("OnDiplomacyChanged", "CheckCaptureTheRelicCountdown", { "enabled": true }); cmpTrigger.RegisterTrigger("OnOwnershipChanged", "CheckCaptureTheRelicVictory", { "enabled": true }); cmpTrigger.RegisterTrigger("OnPlayerWon", "DeleteCaptureTheRelicVictoryMessages", { "enabled": true }); + cmpTrigger.RegisterTrigger("OnPlayerDefeated", "CheckCaptureTheRelicCountdown", { "enabled": true }); } Index: ps/trunk/binaries/data/mods/public/maps/scripts/TriggerHelper.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/scripts/TriggerHelper.js (revision 21440) +++ ps/trunk/binaries/data/mods/public/maps/scripts/TriggerHelper.js (revision 21441) @@ -1,322 +1,322 @@ // Contains standardized functions suitable for using in trigger scripts. // Do not use them in any other simulation script. var TriggerHelper = {}; TriggerHelper.GetPlayerIDFromEntity = function(ent) { let cmpPlayer = Engine.QueryInterface(ent, IID_Player); if (cmpPlayer) return cmpPlayer.GetPlayerID(); return -1; }; TriggerHelper.GetOwner = function(ent) { let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); if (cmpOwnership) return cmpOwnership.GetOwner(); return -1; }; /** * Can be used to "force" a building/unit to spawn a group of entities. * * @param source Entity id of the point where they will be spawned from * @param template Name of the template * @param count Number of units to spawn * @param owner Player id of the owner of the new units. By default, the owner * of the source entity. */ TriggerHelper.SpawnUnits = function(source, template, count, owner) { let entities = []; let cmpFootprint = Engine.QueryInterface(source, IID_Footprint); let cmpPosition = Engine.QueryInterface(source, IID_Position); if (!cmpPosition || !cmpPosition.IsInWorld()) { error("tried to create entity from a source without position"); return entities; } if (owner == null) owner = TriggerHelper.GetOwner(source); for (let i = 0; i < count; ++i) { let ent = Engine.AddEntity(template); let cmpEntPosition = Engine.QueryInterface(ent, IID_Position); if (!cmpEntPosition) { Engine.DestroyEntity(ent); error("tried to create entity without position"); continue; } let cmpEntOwnership = Engine.QueryInterface(ent, IID_Ownership); if (cmpEntOwnership) cmpEntOwnership.SetOwner(owner); entities.push(ent); let pos; if (cmpFootprint) pos = cmpFootprint.PickSpawnPoint(ent); // TODO this can happen if the player build on the place // where our trigger point is // We should probably warn the trigger maker in some way, // but not interrupt the game for the player if (!pos || pos.y < 0) pos = cmpPosition.GetPosition(); cmpEntPosition.JumpTo(pos.x, pos.z); } return entities; }; /** * Can be used to spawn garrisoned units inside a building/ship. * * @param entity Entity id of the garrison holder in which units will be garrisoned * @param template Name of the template * @param count Number of units to spawn * @param owner Player id of the owner of the new units. By default, the owner * of the garrisonholder entity. */ TriggerHelper.SpawnGarrisonedUnits = function(entity, template, count, owner) { let entities = []; let cmpGarrisonHolder = Engine.QueryInterface(entity, IID_GarrisonHolder); if (!cmpGarrisonHolder) { error("tried to create garrisoned entities inside a non-garrisonholder"); return entities; } if (owner == null) owner = TriggerHelper.GetOwner(entity); for (let i = 0; i < count; ++i) { let ent = Engine.AddEntity(template); let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); if (cmpOwnership) cmpOwnership.SetOwner(owner); if (cmpGarrisonHolder.PerformGarrison(ent)) { if (Engine.QueryInterface(ent, IID_UnitAI)) Engine.QueryInterface(ent, IID_UnitAI).Autogarrison(entity); entities.push(ent); } else error("failed to garrison entity " + template + " inside " + entity); } return entities; }; /** * Spawn units from all trigger points with this reference * If player is defined, only spaw units from the trigger points * that belong to that player * @param ref Trigger point reference name to spawn units from * @param template Template name * @param count Number of spawned entities per Trigger point * @param owner Owner of the spawned units. Default: the owner of the origins * @return A list of new entities per origin like * {originId1: [entId1, entId2], originId2: [entId3, entId4], ...} */ TriggerHelper.SpawnUnitsFromTriggerPoints = function(ref, template, count, owner = null) { let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); let triggerPoints = cmpTrigger.GetTriggerPoints(ref); let entities = {}; for (let point of triggerPoints) entities[point] = TriggerHelper.SpawnUnits(point, template, count, owner); return entities; }; /** * Returns the resource type that can be gathered from an entity */ TriggerHelper.GetResourceType = function(entity) { let cmpResourceSupply = Engine.QueryInterface(entity, IID_ResourceSupply); if (!cmpResourceSupply) return undefined; return cmpResourceSupply.GetType(); }; /** * The given player will win the game. * If it's not a last man standing game, then allies will win too and others will be defeated. * * @param {number} playerID - The player who will win. * @param {function} victoryReason - Function that maps from number to plural string, for example * n => markForPluralTranslation( * "%(lastPlayer)s has won (game mode).", * "%(players)s and %(lastPlayer)s have won (game mode).", * n)); * It's a function since we don't know in advance how many players will have won. */ TriggerHelper.SetPlayerWon = function(playerID, victoryReason, defeatReason) { let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager); - cmpEndGameManager.MarkPlayerAsWon(playerID, victoryReason, defeatReason); + cmpEndGameManager.MarkPlayerAndAlliesAsWon(playerID, victoryReason, defeatReason); }; /** * Defeats a single player. * * @param {number} - ID of that player. * @param {string} - String to be shown in chat, for example * markForTranslation("%(player)s has been defeated (objective).") */ TriggerHelper.DefeatPlayer = function(playerID, defeatReason) { let cmpPlayer = QueryPlayerIDInterface(playerID); if (cmpPlayer) cmpPlayer.SetState("defeated", defeatReason); }; /** * Returns the number of current players */ TriggerHelper.GetNumberOfPlayers = function() { return Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers(); }; /** * A function to determine if an entity matches specific classes. * See globalscripts/Templates.js for details of MatchesClassList. * * @param entity - ID of the entity that we want to check for classes. * @param classlist - List of the classes we are checking if the entity matches. */ TriggerHelper.EntityMatchesClassList = function(entity, classlist) { let cmpIdentity = Engine.QueryInterface(entity, IID_Identity); return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), classlist); }; /** * Return valid gaia-owned spawn points on land in neutral territory. * If there are none, use those available in player-owned territory. */ TriggerHelper.GetLandSpawnPoints = function() { let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); let cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager); let cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager); let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); let neutralSpawnPoints = []; let nonNeutralSpawnPoints = []; for (let ent of cmpRangeManager.GetEntitiesByPlayer(0)) { let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); let cmpPosition = Engine.QueryInterface(ent, IID_Position); if (!cmpIdentity || !cmpPosition || !cmpPosition.IsInWorld()) continue; let templateName = cmpTemplateManager.GetCurrentTemplateName(ent); if (!templateName) continue; let template = cmpTemplateManager.GetTemplate(templateName); if (!template || template.UnitMotionFlying) continue; let pos = cmpPosition.GetPosition(); if (pos.y <= cmpWaterManager.GetWaterLevel(pos.x, pos.z)) continue; if (cmpTerritoryManager.GetOwner(pos.x, pos.z) == 0) neutralSpawnPoints.push(ent); else nonNeutralSpawnPoints.push(ent); } return neutralSpawnPoints.length ? neutralSpawnPoints : nonNeutralSpawnPoints; }; TriggerHelper.HasDealtWithTech = function(playerID, techName) { let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); let playerEnt = cmpPlayerManager.GetPlayerByID(playerID); let cmpTechnologyManager = Engine.QueryInterface(playerEnt, IID_TechnologyManager); return cmpTechnologyManager && (cmpTechnologyManager.IsTechnologyQueued(techName) || cmpTechnologyManager.IsTechnologyStarted(techName) || cmpTechnologyManager.IsTechnologyResearched(techName)); }; /** * Composes a random set of the given templates of the given total size. * Returns an object where the keys are template names and values are amounts. */ Trigger.prototype.RandomTemplateComposition = function(templates, count) { let ratios = new Array(templates.length).fill(1).map(i => randFloat(0, 1)); let ratioSum = ratios.reduce((current, sum) => current + sum, 0); let remainder = count; let templateCounts = {}; for (let i = 0; i < templates.length; ++i) { let currentCount = i == templates.length - 1 ? remainder : Math.round(ratios[i] / ratioSum * count); if (!currentCount) continue; templateCounts[templates[i]] = currentCount; remainder -= currentCount; } if (remainder != 0) error("Could not chose as many templates as intended: " + count + " vs " + uneval(templateCounts)); return templateCounts; }; /** * This will spawn random compositions of entities of the given templates at all garrisonholders of the given targetClass of the given player. * The garrisonholder will be filled to capacityPercent. * Returns an object where keys are entityIDs of the affected garrisonholders and the properties are template compositions, see RandomTemplateComposition. */ Trigger.prototype.SpawnAndGarrison = function(playerID, targetClass, templates, capacityPercent) { let results = {}; for (let entGarrisonHolder of Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).GetEntitiesByPlayer(playerID)) { let cmpIdentity = Engine.QueryInterface(entGarrisonHolder, IID_Identity); if (!cmpIdentity || !cmpIdentity.HasClass(targetClass)) continue; let cmpGarrisonHolder = Engine.QueryInterface(entGarrisonHolder, IID_GarrisonHolder); if (!cmpGarrisonHolder) continue; // TODO: account for already garrisoned entities results[entGarrisonHolder] = this.RandomTemplateComposition(templates, Math.floor(cmpGarrisonHolder.GetCapacity() * capacityPercent)); for (let template in results[entGarrisonHolder]) for (let entSpawned of TriggerHelper.SpawnUnits(entGarrisonHolder, template, results[entGarrisonHolder][template], playerID)) Engine.QueryInterface(entGarrisonHolder, IID_GarrisonHolder).Garrison(entSpawned); } return results; }; Engine.RegisterGlobal("TriggerHelper", TriggerHelper); Index: ps/trunk/binaries/data/mods/public/maps/scripts/WonderVictory.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/scripts/WonderVictory.js (revision 21440) +++ ps/trunk/binaries/data/mods/public/maps/scripts/WonderVictory.js (revision 21441) @@ -1,141 +1,153 @@ Trigger.prototype.WonderVictoryOwnershipChanged = function(data) { let cmpWonder = Engine.QueryInterface(data.entity, IID_Wonder); if (!cmpWonder) return; this.WonderVictoryDeleteTimer(data.entity); if (data.to > 0) this.WonderVictoryStartTimer(data.entity, data.to); }; Trigger.prototype.WonderVictoryDiplomacyChanged = function(data) { if (!Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager).GetAlliedVictory()) return; for (let ent in this.wonderVictoryMessages) { if (this.wonderVictoryMessages[ent].playerID != data.player && this.wonderVictoryMessages[ent].playerID != data.otherPlayer) continue; let owner = this.wonderVictoryMessages[ent].playerID; let otherPlayer = owner == data.player ? data.otherPlayer : data.player; let newAllies = new Set(QueryPlayerIDInterface(owner).GetPlayersByDiplomacy("IsExclusiveMutualAlly")); if (newAllies.has(otherPlayer) && !this.wonderVictoryMessages[ent].allies.has(otherPlayer) || !newAllies.has(otherPlayer) && this.wonderVictoryMessages[ent].allies.has(otherPlayer)) { this.WonderVictoryDeleteTimer(ent); this.WonderVictoryStartTimer(ent, owner); } } }; /** * Create new messages, and start timer to register defeat. */ Trigger.prototype.WonderVictoryStartTimer = 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.GetGameTypeSettings().wonderDuration; this.wonderVictoryMessages[ent] = { "playerID": player, "allies": new Set(allies), "timer": cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Trigger, "WonderVictorySetWinner", wonderDuration, player), "messages": [ cmpGuiInterface.AddTimeNotification( { "message": allies.length ? markForTranslation("%(_player_)s owns a Wonder and %(_player_)s and their allies will win in %(time)s.") : markForTranslation("%(_player_)s owns a Wonder and will win in %(time)s."), "players": others, "parameters": { "_player_": player }, "translateMessage": true, "translateParameters": [] }, wonderDuration), cmpGuiInterface.AddTimeNotification( { "message": markForTranslation("%(_player_)s owns 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.WonderVictoryDeleteTimer = function(ent) { if (!this.wonderVictoryMessages[ent]) return; let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); for (let message of this.wonderVictoryMessages[ent].messages) cmpGuiInterface.DeleteTimeNotification(message); cmpTimer.CancelTimer(this.wonderVictoryMessages[ent].timer); delete this.wonderVictoryMessages[ent]; }; Trigger.prototype.WonderVictoryPlayerWon = function(data) { for (let ent in this.wonderVictoryMessages) this.WonderVictoryDeleteTimer(ent); }; +Trigger.prototype.WonderVictoryPlayerDefeated = function(data) +{ + for (let ent in this.wonderVictoryMessages) + if (this.wonderVictoryMessages[ent].allies.has(data.playerId)) + { + let owner = this.wonderVictoryMessages[ent].playerID; + this.WonderVictoryDeleteTimer(ent); + this.WonderVictoryStartTimer(ent, owner); + } +}; + Trigger.prototype.WonderVictorySetWinner = function(playerID) { let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager); - cmpEndGameManager.MarkPlayerAsWon( + cmpEndGameManager.MarkPlayerAndAlliesAsWon( playerID, n => markForPluralTranslation( "%(lastPlayer)s has won (wonder victory).", "%(players)s and %(lastPlayer)s have won (wonder victory).", n), n => markForPluralTranslation( "%(lastPlayer)s has been defeated (wonder victory).", "%(players)s and %(lastPlayer)s have been defeated (wonder victory).", n)); }; { let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); cmpTrigger.RegisterTrigger("OnOwnershipChanged", "WonderVictoryOwnershipChanged", { "enabled": true }); cmpTrigger.RegisterTrigger("OnDiplomacyChanged", "WonderVictoryDiplomacyChanged", { "enabled": true }); cmpTrigger.RegisterTrigger("OnPlayerWon", "WonderVictoryPlayerWon", { "enabled": true }); + cmpTrigger.RegisterTrigger("OnPlayerDefeated", "WonderVictoryPlayerDefeated", { "enabled": true }); cmpTrigger.wonderVictoryMessages = {}; } Index: ps/trunk/binaries/data/mods/public/simulation/components/EndGameManager.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/EndGameManager.js (revision 21440) +++ ps/trunk/binaries/data/mods/public/simulation/components/EndGameManager.js (revision 21441) @@ -1,187 +1,202 @@ /** * System component to store the gametype, gametype settings and * check for allied victory / last-man-standing. */ function EndGameManager() {} EndGameManager.prototype.Schema = ""; EndGameManager.prototype.Init = function() { this.gameType = "conquest"; // Contains settings specific to the victory condition, // for example wonder victory duration. this.gameTypeSettings = {}; // Allied victory means allied players can win if victory conditions are met for each of them // False for a "last man standing" game this.alliedVictory = true; // Don't do any checks before the diplomacies were set for each player // or when marking a player as won. this.skipAlliedVictoryCheck = true; this.lastManStandingMessage = undefined; this.endlessGame = false; }; EndGameManager.prototype.GetGameType = function() { return this.gameType; }; EndGameManager.prototype.GetGameTypeSettings = function() { return this.gameTypeSettings; }; EndGameManager.prototype.SetGameType = function(newGameType, newSettings = {}) { this.gameType = newGameType; this.gameTypeSettings = newSettings; this.skipAlliedVictoryCheck = false; this.endlessGame = newGameType == "endless"; Engine.BroadcastMessage(MT_GameTypeChanged, {}); }; /** * Sets the given player (and the allies if allied victory is enabled) as a winner. * * @param {number} playerID - The player that should win. * @param {function} victoryReason - Function that maps from number to plural string, for example * n => markForPluralTranslation( * "%(lastPlayer)s has won (game mode).", * "%(players)s and %(lastPlayer)s have won (game mode).", * n)); */ -EndGameManager.prototype.MarkPlayerAsWon = function(playerID, victoryString, defeatString) +EndGameManager.prototype.MarkPlayerAndAlliesAsWon = function(playerID, victoryString, defeatString) { let state = QueryPlayerIDInterface(playerID).GetState(); if (state != "active") { warn("Can't mark player " + playerID + " as won, since the state is " + state); return; } - this.skipAlliedVictoryCheck = true; + let winningPlayers = [playerID]; + if (this.alliedVictory) + winningPlayers = QueryPlayerIDInterface(playerID).GetMutualAllies(playerID).filter( + player => QueryPlayerIDInterface(player).GetState() == "active"); - let winningPlayers = []; - let defeatedPlayers = []; + this.MarkPlayersAsWon(winningPlayers, victoryString, defeatString); +}; - let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers(); - for (let i = 1; i < numPlayers; ++i) +/** + * Sets the given players as won and others as defeated. + * + * @param {array} winningPlayers - The players that should win. + * @param {function} victoryReason - Function that maps from number to plural string, for example + * n => markForPluralTranslation( + * "%(lastPlayer)s has won (game mode).", + * "%(players)s and %(lastPlayer)s have won (game mode).", + * n)); + */ +EndGameManager.prototype.MarkPlayersAsWon = function(winningPlayers, victoryString, defeatString) +{ + this.skipAlliedVictoryCheck = true; + for (let playerID of winningPlayers) { - let cmpPlayer = QueryPlayerIDInterface(i); - if (cmpPlayer.GetState() != "active") - continue; - - if (i == playerID || this.alliedVictory && cmpPlayer.IsMutualAlly(playerID)) - { - cmpPlayer.SetState("won", undefined); - winningPlayers.push(i); - } - else + let cmpPlayer = QueryPlayerIDInterface(playerID); + let state = cmpPlayer.GetState(); + if (state != "active") { - cmpPlayer.SetState("defeated", undefined); - defeatedPlayers.push(i); + warn("Can't mark player " + playerID + " as won, since the state is " + state); + continue; } + cmpPlayer.SetState("won", undefined); } + let defeatedPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNonGaiaPlayers().filter( + playerID => winningPlayers.indexOf(playerID) == -1 && QueryPlayerIDInterface(playerID).GetState() == "active"); + + for (let playerID of defeatedPlayers) + QueryPlayerIDInterface(playerID).SetState("defeated", undefined); + let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGUIInterface.PushNotification({ "type": "won", "players": [winningPlayers[0]], "allies" : winningPlayers, "message": victoryString(winningPlayers.length) }); if (defeatedPlayers.length) cmpGUIInterface.PushNotification({ "type": "defeat", "players": [defeatedPlayers[0]], "allies" : defeatedPlayers, "message": defeatString(defeatedPlayers.length) }); this.skipAlliedVictoryCheck = false; }; EndGameManager.prototype.SetAlliedVictory = function(flag) { this.alliedVictory = flag; }; EndGameManager.prototype.GetAlliedVictory = function() { return this.alliedVictory; }; EndGameManager.prototype.AlliedVictoryCheck = function() { if (this.skipAlliedVictoryCheck || this.endlessGame) return; let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGuiInterface.DeleteTimeNotification(this.lastManStandingMessage); // Proceed if only allies are remaining let allies = []; let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers(); for (let playerID = 1; playerID < numPlayers; ++playerID) { let cmpPlayer = QueryPlayerIDInterface(playerID); if (cmpPlayer.GetState() != "active") continue; if (allies.length && !cmpPlayer.IsMutualAlly(allies[0])) return; allies.push(playerID); } if (this.alliedVictory || allies.length == 1) { for (let playerID of allies) { let cmpPlayer = QueryPlayerIDInterface(playerID); if (cmpPlayer) cmpPlayer.SetState("won", undefined); } cmpGuiInterface.PushNotification({ "type": "won", "players": [allies[0]], "allies" : allies, "message": markForPluralTranslation( "%(lastPlayer)s has won (last player alive).", "%(players)s and %(lastPlayer)s have won (last players alive).", allies.length) }); } else this.lastManStandingMessage = cmpGuiInterface.AddTimeNotification({ "message": markForTranslation("Last remaining player wins."), "translateMessage": true, }, 12 * 60 * 60 * 1000); // 12 hours }; EndGameManager.prototype.OnInitGame = function(msg) { this.AlliedVictoryCheck(); }; EndGameManager.prototype.OnGlobalDiplomacyChanged = function(msg) { this.AlliedVictoryCheck(); }; EndGameManager.prototype.OnGlobalPlayerDefeated = function(msg) { this.AlliedVictoryCheck(); }; Engine.RegisterSystemComponentType(IID_EndGameManager, "EndGameManager", EndGameManager); Index: ps/trunk/binaries/data/mods/public/simulation/components/PlayerManager.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/PlayerManager.js (revision 21440) +++ ps/trunk/binaries/data/mods/public/simulation/components/PlayerManager.js (revision 21441) @@ -1,122 +1,133 @@ function PlayerManager() {} PlayerManager.prototype.Schema = ""; PlayerManager.prototype.Init = function() { this.playerEntities = []; // list of player entity IDs }; PlayerManager.prototype.AddPlayer = function(ent) { var id = this.playerEntities.length; var cmpPlayer = Engine.QueryInterface(ent, IID_Player); cmpPlayer.SetPlayerID(id); this.playerEntities.push(ent); // initialize / update the diplomacy arrays var newDiplo = []; for (var i = 0; i < id; i++) { var cmpOtherPlayer = Engine.QueryInterface(this.GetPlayerByID(i), IID_Player); cmpOtherPlayer.diplomacy[id] = -1; newDiplo[i] = -1; } newDiplo[id] = 1; cmpPlayer.SetDiplomacy(newDiplo); return id; }; /** * To avoid possible problems with cached quantities (as in TechnologyManager), * we first remove all entities from this player, and add them back after the replacement. * Note: This should only be called during setup/init and not during the game */ PlayerManager.prototype.ReplacePlayer = function(id, ent) { var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); var entities = cmpRangeManager.GetEntitiesByPlayer(id); for (var e of entities) { var cmpOwnership = Engine.QueryInterface(e, IID_Ownership); if (cmpOwnership) cmpOwnership.SetOwner(INVALID_PLAYER); } var oldent = this.playerEntities[id]; var cmpPlayer = Engine.QueryInterface(oldent, IID_Player); var diplo = cmpPlayer.GetDiplomacy(); var color = cmpPlayer.GetColor(); var cmpPlayer = Engine.QueryInterface(ent, IID_Player); cmpPlayer.SetPlayerID(id); this.playerEntities[id] = ent; cmpPlayer.SetColor(color); cmpPlayer.SetDiplomacy(diplo); Engine.DestroyEntity(oldent); Engine.FlushDestroyedEntities(); for (var e of entities) { var cmpOwnership = Engine.QueryInterface(e, IID_Ownership); if (cmpOwnership) cmpOwnership.SetOwner(id); } }; /** * Returns the player entity ID for the given player ID. * The player ID must be valid (else there will be an error message). */ PlayerManager.prototype.GetPlayerByID = function(id) { if (id in this.playerEntities) return this.playerEntities[id]; // All players at or below ID 0 get gaia-level data. (Observers for example) if (id <= 0) return this.playerEntities[0]; var stack = new Error().stack.trimRight().replace(/^/mg, ' '); // indent each line warn("GetPlayerByID: no player defined for id '"+id+"'\n"+stack); return INVALID_ENTITY; }; PlayerManager.prototype.GetNumPlayers = function() { return this.playerEntities.length; }; /** * Returns IDs of all players including gaia. */ PlayerManager.prototype.GetAllPlayers = function() { let players = []; for (let i = 0; i < this.playerEntities.length; ++i) players.push(i); return players; }; +/** + * Returns IDs of all players excluding gaia. + */ +PlayerManager.prototype.GetNonGaiaPlayers = function() +{ + let players = []; + for (let i = 1; i < this.playerEntities.length; ++i) + players.push(i); + return players; +}; + PlayerManager.prototype.RemoveAllPlayers = function() { // Destroy existing player entities for (var id of this.playerEntities) Engine.DestroyEntity(id); this.playerEntities = []; }; PlayerManager.prototype.RemoveLastPlayer = function() { if (this.playerEntities.length == 0) return; var lastId = this.playerEntities.pop(); Engine.DestroyEntity(lastId); }; Engine.RegisterSystemComponentType(IID_PlayerManager, "PlayerManager", PlayerManager);