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);