Index: ps/trunk/binaries/data/mods/public/simulation/components/Auras.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Auras.js (revision 21711) +++ ps/trunk/binaries/data/mods/public/simulation/components/Auras.js (revision 21712) @@ -1,494 +1,494 @@ function Auras() {} Auras.prototype.Schema = "" + "tokens" + "" + ""; Auras.prototype.Init = function() { this.affectedPlayers = {}; for (let name of this.GetAuraNames()) this.affectedPlayers[name] = []; // In case of autogarrisoning, this component can be called before ownership is set. // So it needs to be completely initialised from the start. this.Clean(); }; // We can modify identifier if we want stackable auras in some case. Auras.prototype.GetModifierIdentifier = function(name) { if (AuraTemplates.Get(name).stackable) return name + this.entity; return name; }; Auras.prototype.GetDescriptions = function() { var ret = {}; for (let auraID of this.GetAuraNames()) { let aura = AuraTemplates.Get(auraID); ret[auraID] = { "name": aura.auraName, "description": aura.auraDescription || null, "radius": this.GetRange(auraID) || null }; } return ret; }; Auras.prototype.GetAuraNames = function() { return this.template._string.split(/\s+/); }; Auras.prototype.GetOverlayIcon = function(name) { return AuraTemplates.Get(name).overlayIcon || ""; }; Auras.prototype.GetAffectedEntities = function(name) { return this[name].targetUnits; }; Auras.prototype.GetRange = function(name) { if (this.IsRangeAura(name)) return +AuraTemplates.Get(name).radius; return undefined; }; Auras.prototype.GetClasses = function(name) { return AuraTemplates.Get(name).affects; }; Auras.prototype.GetModifications = function(name) { return AuraTemplates.Get(name).modifications; }; Auras.prototype.GetAffectedPlayers = function(name) { return this.affectedPlayers[name]; }; Auras.prototype.GetRangeOverlays = function() { let rangeOverlays = []; for (let name of this.GetAuraNames()) { if (!this.IsRangeAura(name) || !this[name].isApplied) continue; let rangeOverlay = AuraTemplates.Get(name).rangeOverlay; rangeOverlays.push( rangeOverlay ? { "radius": this.GetRange(name), "texture": rangeOverlay.lineTexture, "textureMask": rangeOverlay.lineTextureMask, "thickness": rangeOverlay.lineThickness } : // Specify default in order not to specify it in about 40 auras { "radius": this.GetRange(name), "texture": "outline_border.png", "textureMask": "outline_border_mask.png", "thickness": 0.2 }); } return rangeOverlays; }; Auras.prototype.CalculateAffectedPlayers = function(name) { var affectedPlayers = AuraTemplates.Get(name).affectedPlayers || ["Player"]; this.affectedPlayers[name] = []; var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player); if (!cmpPlayer) cmpPlayer = QueryOwnerInterface(this.entity); if (!cmpPlayer || cmpPlayer.GetState() == "defeated") return; var numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers(); for (var i = 0; i < numPlayers; ++i) { for (let p of affectedPlayers) { if (p == "Player" ? cmpPlayer.GetPlayerID() == i : cmpPlayer["Is" + p](i)) { this.affectedPlayers[name].push(i); break; } } } }; Auras.prototype.CanApply = function(name) { if (!AuraTemplates.Get(name).requiredTechnology) return true; let cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager); if (!cmpTechnologyManager) return false; return cmpTechnologyManager.IsTechnologyResearched(AuraTemplates.Get(name).requiredTechnology); }; Auras.prototype.HasFormationAura = function() { return this.GetAuraNames().some(n => this.IsFormationAura(n)); }; Auras.prototype.HasGarrisonAura = function() { return this.GetAuraNames().some(n => this.IsGarrisonAura(n)); }; Auras.prototype.HasGarrisonedUnitsAura = function() { return this.GetAuraNames().some(n => this.IsGarrisonedUnitsAura(n)); }; Auras.prototype.GetType = function(name) { return AuraTemplates.Get(name).type; }; Auras.prototype.IsFormationAura = function(name) { return this.GetType(name) == "formation"; }; Auras.prototype.IsGarrisonAura = function(name) { return this.GetType(name) == "garrison"; }; Auras.prototype.IsGarrisonedUnitsAura = function(name) { return this.GetType(name) == "garrisonedUnits"; }; Auras.prototype.IsRangeAura = function(name) { return this.GetType(name) == "range"; }; Auras.prototype.IsGlobalAura = function(name) { return this.GetType(name) == "global" || this.GetType(name) == "player"; }; Auras.prototype.IsPlayerAura = function(name) { return this.GetType(name) == "player"; }; /** * clean all bonuses. Remove the old ones and re-apply the new ones */ Auras.prototype.Clean = function() { var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); var auraNames = this.GetAuraNames(); let targetUnitsClone = {}; let needVisualizationUpdate = false; // remove all bonuses for (let name of auraNames) { targetUnitsClone[name] = []; if (!this[name]) continue; if (this.IsRangeAura(name)) needVisualizationUpdate = true; if (this[name].targetUnits) targetUnitsClone[name] = this[name].targetUnits.slice(); if (this.IsGlobalAura(name)) this.RemoveTemplateBonus(name); this.RemoveBonus(name, this[name].targetUnits); if (this[name].rangeQuery) cmpRangeManager.DestroyActiveQuery(this[name].rangeQuery); } for (let name of auraNames) { // only calculate the affected players on re-applying the bonuses // this makes sure the template bonuses are removed from the correct players this.CalculateAffectedPlayers(name); // initialise range query this[name] = {}; this[name].targetUnits = []; this[name].isApplied = this.CanApply(name); var affectedPlayers = this.GetAffectedPlayers(name); if (!affectedPlayers.length) continue; if (this.IsGlobalAura(name)) { for (let player of affectedPlayers) { this.ApplyTemplateBonus(name, affectedPlayers); if (this.IsPlayerAura(name)) { let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); let playerEnts = affectedPlayers.map(player => cmpPlayerManager.GetPlayerByID(player)); this.ApplyBonus(name, playerEnts); } else this.ApplyBonus(name, cmpRangeManager.GetEntitiesByPlayer(player)); } continue; } if (!this.IsRangeAura(name)) { this.ApplyBonus(name, targetUnitsClone[name]); continue; } needVisualizationUpdate = true; if (this[name].isApplied) { this[name].rangeQuery = cmpRangeManager.CreateActiveQuery( this.entity, 0, this.GetRange(name), affectedPlayers, IID_Identity, cmpRangeManager.GetEntityFlagMask("normal") ); cmpRangeManager.EnableActiveQuery(this[name].rangeQuery); } } if (needVisualizationUpdate) { let cmpRangeOverlayManager = Engine.QueryInterface(this.entity, IID_RangeOverlayManager); if (cmpRangeOverlayManager) { cmpRangeOverlayManager.UpdateRangeOverlays("Auras"); cmpRangeOverlayManager.RegenerateRangeOverlays(false); } } }; Auras.prototype.GiveMembersWithValidClass = function(auraName, entityList) { var match = this.GetClasses(auraName); return entityList.filter(ent => { let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), match); }); }; Auras.prototype.OnRangeUpdate = function(msg) { for (let name of this.GetAuraNames().filter(n => this[n] && msg.tag == this[n].rangeQuery)) { this.ApplyBonus(name, msg.added); this.RemoveBonus(name, msg.removed); } }; Auras.prototype.OnGarrisonedUnitsChanged = function(msg) { for (let name of this.GetAuraNames().filter(n => this.IsGarrisonedUnitsAura(n))) { this.ApplyBonus(name, msg.added); this.RemoveBonus(name, msg.removed); } }; Auras.prototype.RegisterGlobalOwnershipChanged = function(msg) { for (let name of this.GetAuraNames().filter(n => this.IsGlobalAura(n))) { let affectedPlayers = this.GetAffectedPlayers(name); let wasApplied = affectedPlayers.indexOf(msg.from) != -1; let willBeApplied = affectedPlayers.indexOf(msg.to) != -1; if (wasApplied && !willBeApplied) this.RemoveBonus(name, [msg.entity]); if (willBeApplied && !wasApplied) this.ApplyBonus(name, [msg.entity]); } }; Auras.prototype.ApplyFormationBonus = function(memberList) { for (let name of this.GetAuraNames().filter(n => this.IsFormationAura(n))) this.ApplyBonus(name, memberList); }; Auras.prototype.ApplyGarrisonBonus = function(structure) { for (let name of this.GetAuraNames().filter(n => this.IsGarrisonAura(n))) this.ApplyBonus(name, [structure]); }; Auras.prototype.ApplyTemplateBonus = function(name, players) { if (!this[name].isApplied) return; if (!this.IsGlobalAura(name)) return; var modifications = this.GetModifications(name); var cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager); var classes = this.GetClasses(name); cmpAuraManager.RegisterGlobalAuraSource(this.entity); for (let mod of modifications) for (let player of players) cmpAuraManager.ApplyTemplateBonus(mod.value, player, classes, mod, this.GetModifierIdentifier(name)); }; Auras.prototype.RemoveFormationBonus = function(memberList) { for (let name of this.GetAuraNames().filter(n => this.IsFormationAura(n))) this.RemoveBonus(name, memberList); }; Auras.prototype.RemoveGarrisonBonus = function(structure) { for (let name of this.GetAuraNames().filter(n => this.IsGarrisonAura(n))) this.RemoveBonus(name, [structure]); }; Auras.prototype.RemoveTemplateBonus = function(name) { if (!this[name].isApplied) return; if (!this.IsGlobalAura(name)) return; var cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager); cmpAuraManager.UnregisterGlobalAuraSource(this.entity); var modifications = this.GetModifications(name); var classes = this.GetClasses(name); var players = this.GetAffectedPlayers(name); for (let mod of modifications) for (let player of players) cmpAuraManager.RemoveTemplateBonus(mod.value, player, classes, this.GetModifierIdentifier(name)); }; Auras.prototype.ApplyBonus = function(name, ents) { var validEnts = this.GiveMembersWithValidClass(name, ents); if (!validEnts.length) return; this[name].targetUnits = this[name].targetUnits.concat(validEnts); if (!this[name].isApplied) return; var modifications = this.GetModifications(name); var cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager); for (let mod of modifications) cmpAuraManager.ApplyBonus(mod.value, validEnts, mod, this.GetModifierIdentifier(name)); // update status bars if this has an icon if (!this.GetOverlayIcon(name)) return; for (let ent of validEnts) { var cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars); if (cmpStatusBars) cmpStatusBars.AddAuraSource(this.entity, name); } }; Auras.prototype.RemoveBonus = function(name, ents) { var validEnts = this.GiveMembersWithValidClass(name, ents); if (!validEnts.length) return; this[name].targetUnits = this[name].targetUnits.filter(v => validEnts.indexOf(v) == -1); if (!this[name].isApplied) return; var modifications = this.GetModifications(name); var cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager); for (let mod of modifications) cmpAuraManager.RemoveBonus(mod.value, validEnts, this.GetModifierIdentifier(name)); // update status bars if this has an icon if (!this.GetOverlayIcon(name)) return; for (let ent of validEnts) { var cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars); if (cmpStatusBars) cmpStatusBars.RemoveAuraSource(this.entity, name); } }; Auras.prototype.OnOwnershipChanged = function(msg) { this.Clean(); }; Auras.prototype.OnDiplomacyChanged = function(msg) { var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player); if (cmpPlayer && (cmpPlayer.GetPlayerID() == msg.player || cmpPlayer.GetPlayerID() == msg.otherPlayer) || IsOwnedByPlayer(msg.player, this.entity) || IsOwnedByPlayer(msg.otherPlayer, this.entity)) this.Clean(); }; Auras.prototype.OnGlobalResearchFinished = function(msg) { var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player); if ((!cmpPlayer || cmpPlayer.GetPlayerID() != msg.player) && !IsOwnedByPlayer(msg.player, this.entity)) return; for (let name of this.GetAuraNames()) { let requiredTech = AuraTemplates.Get(name).requiredTechnology; if (requiredTech && requiredTech == msg.tech) { this.Clean(); return; } } }; /** * Only update playerauras, since units and structures are updated OnOwnershipChanged. */ -Auras.prototype.OnPlayerDefeated = function(msg) +Auras.prototype.OnGlobalPlayerDefeated = function(msg) { if (!Engine.QueryInterface(this.entity, IID_Player)) return; this.Clean(); }; Engine.RegisterComponentType(IID_Auras, "Auras", Auras); Index: ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js (revision 21711) +++ ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js (revision 21712) @@ -1,326 +1,326 @@ function Capturable() {} Capturable.prototype.Schema = "" + "" + "" + "" + "" + "" + "" + "" + ""; Capturable.prototype.Init = function() { // Cache this value this.maxCp = +this.template.CapturePoints; this.cp = []; }; //// Interface functions //// /** * Returns the current capture points array */ Capturable.prototype.GetCapturePoints = function() { return this.cp; }; Capturable.prototype.GetMaxCapturePoints = function() { return this.maxCp; }; Capturable.prototype.GetGarrisonRegenRate = function() { return ApplyValueModificationsToEntity("Capturable/GarrisonRegenRate", +this.template.GarrisonRegenRate, this.entity); }; /** * Set the new capture points, used for cloning entities * The caller should assure that the sum of capture points * matches the max. */ Capturable.prototype.SetCapturePoints = function(capturePointsArray) { this.cp = capturePointsArray; }; /** * Reduces the amount of capture points of an entity, * in favour of the player of the source * Returns the number of capture points actually taken */ Capturable.prototype.Reduce = function(amount, playerID) { if (amount <= 0) return 0; var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); if (!cmpOwnership || cmpOwnership.GetOwner() == INVALID_PLAYER) return 0; var cmpPlayerSource = QueryPlayerIDInterface(playerID); if (!cmpPlayerSource) return 0; // Before changing the value, activate Fogging if necessary to hide changes var cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging); if (cmpFogging) cmpFogging.Activate(); var numberOfEnemies = this.cp.filter((v, i) => v > 0 && cmpPlayerSource.IsEnemy(i)).length; if (numberOfEnemies == 0) return 0; // Distribute the capture points over all enemies. let distributedAmount = amount / numberOfEnemies; let removedAmount = 0; while (distributedAmount > 0.0001) { numberOfEnemies = 0; for (let i in this.cp) { if (!this.cp[i] || !cmpPlayerSource.IsEnemy(i)) continue; if (this.cp[i] > distributedAmount) { removedAmount += distributedAmount; this.cp[i] -= distributedAmount; ++numberOfEnemies; } else { removedAmount += this.cp[i]; this.cp[i] = 0; } } distributedAmount = numberOfEnemies ? (amount - removedAmount) / numberOfEnemies : 0; } // give all cp taken to the player var takenCp = this.maxCp - this.cp.reduce((a, b) => a + b); this.cp[playerID] += takenCp; this.CheckTimer(); this.RegisterCapturePointsChanged(); return takenCp; }; /** * Check if the source can (re)capture points from this building */ Capturable.prototype.CanCapture = function(playerID) { var cmpPlayerSource = QueryPlayerIDInterface(playerID); if (!cmpPlayerSource) warn(playerID + " has no player component defined on its id"); var cp = this.GetCapturePoints(); var sourceEnemyCp = 0; for (let i in this.GetCapturePoints()) if (cmpPlayerSource.IsEnemy(i)) sourceEnemyCp += cp[i]; return sourceEnemyCp > 0; }; //// Private functions //// /** * This has to be called whenever the capture points are changed. * It notifies other components of the change, and switches ownership when needed. */ Capturable.prototype.RegisterCapturePointsChanged = function() { var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); if (!cmpOwnership) return; Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp }); var owner = cmpOwnership.GetOwner(); if (owner == INVALID_PLAYER || this.cp[owner] > 0) return; // If all cp has been taken from the owner, convert it to the best player. var bestPlayer = 0; for (let i in this.cp) if (this.cp[i] >= this.cp[bestPlayer]) bestPlayer = +i; let cmpLostPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker); if (cmpLostPlayerStatisticsTracker) cmpLostPlayerStatisticsTracker.LostEntity(this.entity); cmpOwnership.SetOwner(bestPlayer); let cmpCapturedPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker); if (cmpCapturedPlayerStatisticsTracker) cmpCapturedPlayerStatisticsTracker.CapturedEntity(this.entity); }; Capturable.prototype.GetRegenRate = function() { var regenRate = +this.template.RegenRate; regenRate = ApplyValueModificationsToEntity("Capturable/RegenRate", regenRate, this.entity); var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); if (cmpGarrisonHolder) var garrisonRegenRate = this.GetGarrisonRegenRate() * cmpGarrisonHolder.GetEntities().length; else var garrisonRegenRate = 0; return regenRate + garrisonRegenRate; }; Capturable.prototype.TimerTick = function() { var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); if (!cmpOwnership || cmpOwnership.GetOwner() == INVALID_PLAYER) return; var owner = cmpOwnership.GetOwner(); var modifiedCp = 0; // Special handle for the territory decay. // Reduce cp from the owner in favour of all neighbours (also allies). var cmpTerritoryDecay = Engine.QueryInterface(this.entity, IID_TerritoryDecay); if (cmpTerritoryDecay && cmpTerritoryDecay.IsDecaying()) { var neighbours = cmpTerritoryDecay.GetConnectedNeighbours(); var totalNeighbours = neighbours.reduce((a, b) => a + b); var decay = Math.min(cmpTerritoryDecay.GetDecayRate(), this.cp[owner]); this.cp[owner] -= decay; if (totalNeighbours) for (let p in neighbours) this.cp[p] += decay * neighbours[p] / totalNeighbours; else // decay to gaia as default this.cp[0] += decay; modifiedCp += decay; this.RegisterCapturePointsChanged(); } var regenRate = this.GetRegenRate(); if (regenRate < 0) modifiedCp += this.Reduce(-regenRate, 0); else if (regenRate > 0) modifiedCp += this.Reduce(regenRate, owner); if (modifiedCp) return; // Nothing changed, stop the timer. var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); cmpTimer.CancelTimer(this.timer); this.timer = 0; Engine.PostMessage(this.entity, MT_CaptureRegenStateChanged, { "regenerating": false, "regenRate": 0, "territoryDecay": 0 }); }; /** * Start the regeneration timer when no timer exists. * When nothing can be modified (f.e. because it is fully regenerated), the * timer stops automatically after one execution. */ Capturable.prototype.CheckTimer = function() { if (this.timer) return; var regenRate = this.GetRegenRate(); var cmpDecay = Engine.QueryInterface(this.entity, IID_TerritoryDecay); var decay = cmpDecay && cmpDecay.IsDecaying() ? cmpDecay.GetDecayRate() : 0; if (regenRate == 0 && decay == 0) return; var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); this.timer = cmpTimer.SetInterval(this.entity, IID_Capturable, "TimerTick", 1000, 1000, null); Engine.PostMessage(this.entity, MT_CaptureRegenStateChanged, { "regenerating": true, "regenRate": regenRate, "territoryDecay": decay }); }; //// Message Listeners //// Capturable.prototype.OnValueModification = function(msg) { if (msg.component != "Capturable") return; var oldMaxCp = this.GetMaxCapturePoints(); this.maxCp = ApplyValueModificationsToEntity("Capturable/CapturePoints", +this.template.CapturePoints, this.entity); if (oldMaxCp == this.maxCp) return; var scale = this.maxCp / oldMaxCp; for (let i in this.cp) this.cp[i] *= scale; Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp }); this.CheckTimer(); }; Capturable.prototype.OnGarrisonedUnitsChanged = function(msg) { this.CheckTimer(); }; Capturable.prototype.OnTerritoryDecayChanged = function(msg) { if (msg.to) this.CheckTimer(); }; Capturable.prototype.OnDiplomacyChanged = function(msg) { this.CheckTimer(); }; Capturable.prototype.OnOwnershipChanged = function(msg) { if (msg.to == INVALID_PLAYER) return; // we're dead if (this.cp.length) { if (!this.cp[msg.from]) return; // nothing to change // Was already initialised, this happens on defeat or wololo // transfer the points of the old owner to the new one this.cp[msg.to] += this.cp[msg.from]; this.cp[msg.from] = 0; this.RegisterCapturePointsChanged(); } else { // Initialise the capture points when created. let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers(); for (let i = 0; i < numPlayers; ++i) if (i == msg.to) this.cp[i] = this.maxCp; else this.cp[i] = 0; } this.CheckTimer(); }; /** * When a player is defeated, reassign the cp of non-owned entities to gaia. * Those owned by the defeated player are dealt with onOwnershipChanged. */ -Capturable.prototype.OnPlayerDefeated = function(msg) +Capturable.prototype.OnGlobalPlayerDefeated = function(msg) { if (!this.cp[msg.playerId]) return; let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); if (cmpOwnership && cmpOwnership.GetOwner() == msg.playerId) return; this.cp[0] += this.cp[msg.playerId]; this.cp[msg.playerId] = 0; this.RegisterCapturePointsChanged(); this.CheckTimer(); }; Engine.RegisterComponentType(IID_Capturable, "Capturable", Capturable); Index: ps/trunk/binaries/data/mods/public/simulation/components/Player.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Player.js (revision 21711) +++ ps/trunk/binaries/data/mods/public/simulation/components/Player.js (revision 21712) @@ -1,983 +1,983 @@ function Player() {} Player.prototype.Schema = "" + "" + "" + Resources.BuildSchema("positiveDecimal") + "" + "" + Resources.BuildSchema("positiveDecimal") + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; /** * Don't serialize diplomacyColor or displayDiplomacyColor since they're modified by the GUI. */ Player.prototype.Serialize = function() { let state = {}; for (let key in this) if (this.hasOwnProperty(key)) state[key] = this[key]; state.diplomacyColor = undefined; state.displayDiplomacyColor = false; return state; }; /** * Which units will be shown with special icons at the top. */ var panelEntityClasses = "Hero Relic"; Player.prototype.Init = function() { this.playerID = undefined; this.name = undefined; // define defaults elsewhere (supporting other languages) this.civ = undefined; this.color = undefined; this.diplomacyColor = undefined; this.displayDiplomacyColor = false; this.popUsed = 0; // population of units owned or trained by this player this.popBonuses = 0; // sum of population bonuses of player's entities this.maxPop = 300; // maximum population this.trainingBlocked = false; // indicates whether any training queue is currently blocked this.resourceCount = {}; this.tradingGoods = []; // goods for next trade-route and its proba in % (the sum of probas must be 100) this.team = -1; // team number of the player, players on the same team will always have ally diplomatic status - also this is useful for team emblems, scoring, etc. this.teamsLocked = false; this.state = "active"; // game state - one of "active", "defeated", "won" this.diplomacy = []; // array of diplomatic stances for this player with respect to other players (including gaia and self) this.sharedDropsites = false; this.formations = []; this.startCam = undefined; this.controlAllUnits = false; this.isAI = false; this.timeMultiplier = 1; this.gatherRateMultiplier = 1; this.tradeRateMultiplier = 1; this.cheatsEnabled = false; this.cheatTimeMultiplier = 1; this.panelEntities = []; this.resourceNames = {}; this.disabledTemplates = {}; this.disabledTechnologies = {}; this.startingTechnologies = []; this.spyCostMultiplier = +this.template.SpyCostMultiplier; this.barterMultiplier = { "buy": clone(this.template.BarterMultiplier.Buy), "sell": clone(this.template.BarterMultiplier.Sell) }; // Initial resources and trading goods probability in steps of 5 let resCodes = Resources.GetCodes(); let quotient = Math.floor(20 / resCodes.length); let remainder = 20 % resCodes.length; for (let i in resCodes) { let res = resCodes[i]; this.resourceCount[res] = 300; this.resourceNames[res] = Resources.GetResource(res).name; this.tradingGoods.push({ "goods": res, "proba": 5 * (quotient + (+i < remainder ? 1 : 0)) }); } }; Player.prototype.SetPlayerID = function(id) { this.playerID = id; }; Player.prototype.GetPlayerID = function() { return this.playerID; }; Player.prototype.SetName = function(name) { this.name = name; }; Player.prototype.GetName = function() { return this.name; }; Player.prototype.SetCiv = function(civcode) { var oldCiv = this.civ; this.civ = civcode; // Normally, the civ is only set once // But in Atlas, the map designers can change civs at any time var playerID = this.GetPlayerID(); if (oldCiv && playerID && oldCiv != civcode) Engine.BroadcastMessage(MT_CivChanged, { "player": playerID, "from": oldCiv, "to": civcode }); }; Player.prototype.GetCiv = function() { return this.civ; }; Player.prototype.SetColor = function(r, g, b) { var colorInitialized = !!this.color; this.color = { "r": r / 255, "g": g / 255, "b": b / 255, "a": 1 }; // Used in Atlas if (colorInitialized) Engine.BroadcastMessage(MT_PlayerColorChanged, { "player": this.playerID }); }; Player.prototype.SetDiplomacyColor = function(color) { this.diplomacyColor = { "r": color.r / 255, "g": color.g / 255, "b": color.b / 255, "a": 1 }; }; Player.prototype.SetDisplayDiplomacyColor = function(displayDiplomacyColor) { this.displayDiplomacyColor = displayDiplomacyColor; }; Player.prototype.GetColor = function() { return this.color; }; Player.prototype.GetDisplayedColor = function() { return this.displayDiplomacyColor ? this.diplomacyColor : this.color; }; // Try reserving num population slots. Returns 0 on success or number of missing slots otherwise. Player.prototype.TryReservePopulationSlots = function(num) { if (num != 0 && num > (this.GetPopulationLimit() - this.GetPopulationCount())) return num - (this.GetPopulationLimit() - this.GetPopulationCount()); this.popUsed += num; return 0; }; Player.prototype.UnReservePopulationSlots = function(num) { this.popUsed -= num; }; Player.prototype.GetPopulationCount = function() { return this.popUsed; }; Player.prototype.AddPopulation = function(num) { this.popUsed += num; }; Player.prototype.SetPopulationBonuses = function(num) { this.popBonuses = num; }; Player.prototype.AddPopulationBonuses = function(num) { this.popBonuses += num; }; Player.prototype.GetPopulationLimit = function() { return Math.min(this.GetMaxPopulation(), this.popBonuses); }; Player.prototype.SetMaxPopulation = function(max) { this.maxPop = max; }; Player.prototype.GetMaxPopulation = function() { return Math.round(ApplyValueModificationsToEntity("Player/MaxPopulation", this.maxPop, this.entity)); }; Player.prototype.GetBarterMultiplier = function() { return this.barterMultiplier; }; Player.prototype.GetGatherRateMultiplier = function() { return this.gatherRateMultiplier / this.cheatTimeMultiplier; }; Player.prototype.GetTimeMultiplier = function() { return this.timeMultiplier * this.cheatTimeMultiplier; }; Player.prototype.GetTradeRateMultiplier = function() { return this.tradeRateMultiplier; }; Player.prototype.GetSpyCostMultiplier = function() { return this.spyCostMultiplier; }; /** * Setters currently used by the AI to set the difficulty level */ Player.prototype.SetGatherRateMultiplier = function(value) { this.gatherRateMultiplier = value; Engine.BroadcastMessage(MT_MultiplierChanged, { "player": this.playerID, "type": "gather" }); }; Player.prototype.SetTimeMultiplier = function(value) { this.timeMultiplier = value; Engine.BroadcastMessage(MT_MultiplierChanged, { "player": this.playerID, "type": "time" }); }; Player.prototype.SetTradeRateMultiplier = function(value) { this.tradeRateMultiplier = value; }; Player.prototype.GetPanelEntities = function() { return this.panelEntities; }; Player.prototype.IsTrainingBlocked = function() { return this.trainingBlocked; }; Player.prototype.BlockTraining = function() { this.trainingBlocked = true; }; Player.prototype.UnBlockTraining = function() { this.trainingBlocked = false; }; Player.prototype.SetResourceCounts = function(resources) { for (let res in resources) this.resourceCount[res] = resources[res]; }; Player.prototype.GetResourceCounts = function() { return this.resourceCount; }; /** * Add resource of specified type to player * @param type Generic type of resource (string) * @param amount Amount of resource, which should be added (integer) */ Player.prototype.AddResource = function(type, amount) { this.resourceCount[type] += +amount; }; /** * Add resources to player */ Player.prototype.AddResources = function(amounts) { for (var type in amounts) this.resourceCount[type] += +amounts[type]; }; Player.prototype.GetNeededResources = function(amounts) { // Check if we can afford it all var amountsNeeded = {}; for (var type in amounts) if (this.resourceCount[type] != undefined && amounts[type] > this.resourceCount[type]) amountsNeeded[type] = amounts[type] - Math.floor(this.resourceCount[type]); if (Object.keys(amountsNeeded).length == 0) return undefined; return amountsNeeded; }; Player.prototype.SubtractResourcesOrNotify = function(amounts) { var amountsNeeded = this.GetNeededResources(amounts); // If we don't have enough resources, send a notification to the player if (amountsNeeded) { var parameters = {}; var i = 0; for (var type in amountsNeeded) { ++i; parameters["resourceType"+i] = this.resourceNames[type]; parameters["resourceAmount"+i] = amountsNeeded[type]; } var msg = ""; // when marking strings for translations, you need to include the actual string, // not some way to derive the string if (i < 1) warn("Amounts needed but no amounts given?"); else if (i == 1) msg = markForTranslation("Insufficient resources - %(resourceAmount1)s %(resourceType1)s"); else if (i == 2) msg = markForTranslation("Insufficient resources - %(resourceAmount1)s %(resourceType1)s, %(resourceAmount2)s %(resourceType2)s"); else if (i == 3) msg = markForTranslation("Insufficient resources - %(resourceAmount1)s %(resourceType1)s, %(resourceAmount2)s %(resourceType2)s, %(resourceAmount3)s %(resourceType3)s"); else if (i == 4) msg = markForTranslation("Insufficient resources - %(resourceAmount1)s %(resourceType1)s, %(resourceAmount2)s %(resourceType2)s, %(resourceAmount3)s %(resourceType3)s, %(resourceAmount4)s %(resourceType4)s"); else warn("Localisation: Strings are not localised for more than 4 resources"); // Send as time-notification let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGUIInterface.PushNotification({ "players": [this.playerID], "message": msg, "parameters": parameters, "translateMessage": true, "translateParameters": { "resourceType1": "withinSentence", "resourceType2": "withinSentence", "resourceType3": "withinSentence", "resourceType4": "withinSentence", }, }); return false; } for (var type in amounts) this.resourceCount[type] -= amounts[type]; return true; }; Player.prototype.TrySubtractResources = function(amounts) { if (!this.SubtractResourcesOrNotify(amounts)) return false; var cmpStatisticsTracker = QueryPlayerIDInterface(this.playerID, IID_StatisticsTracker); if (cmpStatisticsTracker) for (var type in amounts) cmpStatisticsTracker.IncreaseResourceUsedCounter(type, amounts[type]); return true; }; Player.prototype.GetNextTradingGoods = function() { var value = randFloat(0, 100); var last = this.tradingGoods.length - 1; var sumProba = 0; for (var i = 0; i < last; ++i) { sumProba += this.tradingGoods[i].proba; if (value < sumProba) return this.tradingGoods[i].goods; } return this.tradingGoods[last].goods; }; Player.prototype.GetTradingGoods = function() { var tradingGoods = {}; for (let resource of this.tradingGoods) tradingGoods[resource.goods] = resource.proba; return tradingGoods; }; Player.prototype.SetTradingGoods = function(tradingGoods) { let resCodes = Resources.GetCodes(); let sumProba = 0; for (let resource in tradingGoods) { if (resCodes.indexOf(resource) == -1 || tradingGoods[resource] < 0) { error("Invalid trading goods: " + uneval(tradingGoods)); return; } sumProba += tradingGoods[resource]; } if (sumProba != 100) { error("Invalid trading goods: " + uneval(tradingGoods)); return; } this.tradingGoods = []; for (let resource in tradingGoods) this.tradingGoods.push({ "goods": resource, "proba": tradingGoods[resource] }); }; Player.prototype.GetState = function() { return this.state; }; /** * @param {string} newState - Either "defeated" or "won". * @param {string|undefined} message - A string to be shown in chat, for example * markForTranslation("%(player)s has been defeated (failed objective)."). * If it is undefined, the caller MUST send that GUI notification manually. */ Player.prototype.SetState = function(newState, message) { if (this.state != "active") return; if (newState != "won" && newState != "defeated") { warn("Can't change playerstate to " + this.state); return; } this.state = newState; let won = newState == "won"; let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); if (won) cmpRangeManager.SetLosRevealAll(this.playerID, true); else { // Reassign all player's entities to Gaia let entities = cmpRangeManager.GetEntitiesByPlayer(this.playerID); // The ownership change is done in two steps so that entities don't hit idle // (and thus possibly look for "enemies" to attack) before nearby allies get // converted to Gaia as well. for (let entity of entities) { let cmpOwnership = Engine.QueryInterface(entity, IID_Ownership); cmpOwnership.SetOwnerQuiet(0); } // With the real ownership change complete, send OwnershipChanged messages. for (let entity of entities) Engine.PostMessage(entity, MT_OwnershipChanged, { "entity": entity, "from": this.playerID, "to": 0 }); } - Engine.BroadcastMessage(won ? MT_PlayerWon : MT_PlayerDefeated, { "playerId": this.playerID }); + Engine.PostMessage(this.entity, won ? MT_PlayerWon : MT_PlayerDefeated, { "playerId": this.playerID }); if (message) { let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGUIInterface.PushNotification({ "type": won ? "won" : "defeat", "players": [this.playerID], "allies": [this.playerID], "message": message }); } }; Player.prototype.GetTeam = function() { return this.team; }; Player.prototype.SetTeam = function(team) { if (this.teamsLocked) return; this.team = team; // Set all team members as allies if (this.team != -1) { let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers(); for (let i = 0; i < numPlayers; ++i) { let cmpPlayer = QueryPlayerIDInterface(i); if (this.team != cmpPlayer.GetTeam()) continue; this.SetAlly(i); cmpPlayer.SetAlly(this.playerID); } } Engine.BroadcastMessage(MT_DiplomacyChanged, { "player": this.playerID, "otherPlayer": null }); }; Player.prototype.SetLockTeams = function(value) { this.teamsLocked = value; }; Player.prototype.GetLockTeams = function() { return this.teamsLocked; }; Player.prototype.GetDiplomacy = function() { return this.diplomacy.slice(); }; Player.prototype.SetDiplomacy = function(dipl) { this.diplomacy = dipl.slice(); Engine.BroadcastMessage(MT_DiplomacyChanged, { "player": this.playerID, "otherPlayer": null }); }; Player.prototype.SetDiplomacyIndex = function(idx, value) { let cmpPlayer = QueryPlayerIDInterface(idx); if (!cmpPlayer) return; if (this.state != "active" || cmpPlayer.state != "active") return; this.diplomacy[idx] = value; Engine.BroadcastMessage(MT_DiplomacyChanged, { "player": this.playerID, "otherPlayer": cmpPlayer.GetPlayerID() }); // Mutual worsening of relations if (cmpPlayer.diplomacy[this.playerID] > value) cmpPlayer.SetDiplomacyIndex(this.playerID, value); }; Player.prototype.UpdateSharedLos = function() { let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); let cmpTechnologyManager = Engine.QueryInterface(this.entity, IID_TechnologyManager); if (!cmpRangeManager || !cmpTechnologyManager) return; if (!cmpTechnologyManager.IsTechnologyResearched(this.template.SharedLosTech)) { cmpRangeManager.SetSharedLos(this.playerID, [this.playerID]); return; } cmpRangeManager.SetSharedLos(this.playerID, this.GetMutualAllies()); }; Player.prototype.GetFormations = function() { return this.formations; }; Player.prototype.SetFormations = function(formations) { this.formations = formations; }; Player.prototype.GetStartingCameraPos = function() { return this.startCam.position; }; Player.prototype.GetStartingCameraRot = function() { return this.startCam.rotation; }; Player.prototype.SetStartingCamera = function(pos, rot) { this.startCam = { "position": pos, "rotation": rot }; }; Player.prototype.HasStartingCamera = function() { return this.startCam !== undefined; }; Player.prototype.HasSharedLos = function() { let cmpTechnologyManager = Engine.QueryInterface(this.entity, IID_TechnologyManager); return cmpTechnologyManager && cmpTechnologyManager.IsTechnologyResearched(this.template.SharedLosTech); }; Player.prototype.HasSharedDropsites = function() { return this.sharedDropsites; }; Player.prototype.SetControlAllUnits = function(c) { this.controlAllUnits = c; }; Player.prototype.CanControlAllUnits = function() { return this.controlAllUnits; }; Player.prototype.SetAI = function(flag) { this.isAI = flag; }; Player.prototype.IsAI = function() { return this.isAI; }; Player.prototype.GetPlayersByDiplomacy = function(func) { var players = []; for (var i = 0; i < this.diplomacy.length; ++i) if (this[func](i)) players.push(i); return players; }; Player.prototype.SetAlly = function(id) { this.SetDiplomacyIndex(id, 1); }; /** * Check if given player is our ally */ Player.prototype.IsAlly = function(id) { return this.diplomacy[id] > 0; }; Player.prototype.GetAllies = function() { return this.GetPlayersByDiplomacy("IsAlly"); }; /** * Check if given player is our ally excluding ourself */ Player.prototype.IsExclusiveAlly = function(id) { return this.playerID != id && this.IsAlly(id); }; /** * Check if given player is our ally, and we are its ally */ Player.prototype.IsMutualAlly = function(id) { var cmpPlayer = QueryPlayerIDInterface(id); return this.IsAlly(id) && cmpPlayer && cmpPlayer.IsAlly(this.playerID); }; Player.prototype.GetMutualAllies = function() { return this.GetPlayersByDiplomacy("IsMutualAlly"); }; /** * Check if given player is our ally, and we are its ally, excluding ourself */ Player.prototype.IsExclusiveMutualAlly = function(id) { return this.playerID != id && this.IsMutualAlly(id); }; Player.prototype.SetEnemy = function(id) { this.SetDiplomacyIndex(id, -1); }; /** * Check if given player is our enemy */ Player.prototype.IsEnemy = function(id) { return this.diplomacy[id] < 0; }; Player.prototype.GetEnemies = function() { return this.GetPlayersByDiplomacy("IsEnemy"); }; Player.prototype.SetNeutral = function(id) { this.SetDiplomacyIndex(id, 0); }; /** * Check if given player is neutral */ Player.prototype.IsNeutral = function(id) { return this.diplomacy[id] == 0; }; /** * Do some map dependant initializations */ Player.prototype.OnGlobalInitGame = function(msg) { let cmpTechnologyManager = Engine.QueryInterface(this.entity, IID_TechnologyManager); if (cmpTechnologyManager) for (let tech of this.startingTechnologies) cmpTechnologyManager.ResearchTechnology(tech); // Replace the "{civ}" code with this civ ID let disabledTemplates = this.disabledTemplates; this.disabledTemplates = {}; for (let template in disabledTemplates) if (disabledTemplates[template]) this.disabledTemplates[template.replace(/\{civ\}/g, this.civ)] = true; }; /** * Keep track of population effects of all entities that * become owned or unowned by this player */ Player.prototype.OnGlobalOwnershipChanged = function(msg) { if (msg.from != this.playerID && msg.to != this.playerID) return; var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity); var cmpCost = Engine.QueryInterface(msg.entity, IID_Cost); if (msg.from == this.playerID) { if (cmpCost) this.popUsed -= cmpCost.GetPopCost(); if (cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), panelEntityClasses)) { let index = this.panelEntities.indexOf(msg.entity); if (index >= 0) this.panelEntities.splice(index, 1); } } if (msg.to == this.playerID) { if (cmpCost) this.popUsed += cmpCost.GetPopCost(); if (cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), panelEntityClasses)) this.panelEntities.push(msg.entity); } }; Player.prototype.OnResearchFinished = function(msg) { if (msg.tech == this.template.SharedLosTech) this.UpdateSharedLos(); else if (msg.tech == this.template.SharedDropsitesTech) this.sharedDropsites = true; }; Player.prototype.OnDiplomacyChanged = function() { this.UpdateSharedLos(); }; Player.prototype.OnValueModification = function(msg) { if (msg.component != "Player") return; if (msg.valueNames.indexOf("Player/SpyCostMultiplier") != -1) this.spyCostMultiplier = ApplyValueModificationsToEntity("Player/SpyCostMultiplier", +this.template.SpyCostMultiplier, this.entity); if (msg.valueNames.some(mod => mod.startsWith("Player/BarterMultiplier/"))) for (let res in this.template.BarterMultiplier.Buy) { this.barterMultiplier.buy[res] = ApplyValueModificationsToEntity("Player/BarterMultiplier/Buy/"+res, +this.template.BarterMultiplier.Buy[res], this.entity); this.barterMultiplier.sell[res] = ApplyValueModificationsToEntity("Player/BarterMultiplier/Sell/"+res, +this.template.BarterMultiplier.Sell[res], this.entity); } }; Player.prototype.SetCheatsEnabled = function(flag) { this.cheatsEnabled = flag; }; Player.prototype.GetCheatsEnabled = function() { return this.cheatsEnabled; }; Player.prototype.SetCheatTimeMultiplier = function(time) { this.cheatTimeMultiplier = time; Engine.BroadcastMessage(MT_MultiplierChanged, { "player": this.playerID, "type": "cheat" }); }; Player.prototype.GetCheatTimeMultiplier = function() { return this.cheatTimeMultiplier; }; Player.prototype.TributeResource = function(player, amounts) { var cmpPlayer = QueryPlayerIDInterface(player); if (!cmpPlayer) return; if (this.state != "active" || cmpPlayer.state != "active") return; for (let resCode in amounts) if (Resources.GetCodes().indexOf(resCode) == -1 || !Number.isInteger(amounts[resCode]) || amounts[resCode] < 0) { warn("Invalid tribute amounts: " + uneval(amounts)); return; } if (!this.SubtractResourcesOrNotify(amounts)) return; cmpPlayer.AddResources(amounts); var total = Object.keys(amounts).reduce((sum, type) => sum + amounts[type], 0); var cmpOurStatisticsTracker = QueryPlayerIDInterface(this.playerID, IID_StatisticsTracker); if (cmpOurStatisticsTracker) cmpOurStatisticsTracker.IncreaseTributesSentCounter(total); var cmpTheirStatisticsTracker = QueryPlayerIDInterface(player, IID_StatisticsTracker); if (cmpTheirStatisticsTracker) cmpTheirStatisticsTracker.IncreaseTributesReceivedCounter(total); var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); if (cmpGUIInterface) cmpGUIInterface.PushNotification({ "type": "tribute", "players": [player], "donator": this.playerID, "amounts": amounts }); Engine.BroadcastMessage(MT_TributeExchanged, { "to": player, "from": this.playerID, "amounts": amounts }); }; Player.prototype.AddDisabledTemplate = function(template) { this.disabledTemplates[template] = true; Engine.BroadcastMessage(MT_DisabledTemplatesChanged, {}); var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGuiInterface.PushNotification({ "type": "resetselectionpannel", "players": [this.GetPlayerID()] }); }; Player.prototype.RemoveDisabledTemplate = function(template) { this.disabledTemplates[template] = false; Engine.BroadcastMessage(MT_DisabledTemplatesChanged, {}); var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGuiInterface.PushNotification({ "type": "resetselectionpannel", "players": [this.GetPlayerID()] }); }; Player.prototype.SetDisabledTemplates = function(templates) { this.disabledTemplates = {}; for (let template of templates) this.disabledTemplates[template] = true; Engine.BroadcastMessage(MT_DisabledTemplatesChanged, {}); var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGuiInterface.PushNotification({ "type": "resetselectionpannel", "players": [this.GetPlayerID()] }); }; Player.prototype.GetDisabledTemplates = function() { return this.disabledTemplates; }; Player.prototype.AddDisabledTechnology = function(tech) { this.disabledTechnologies[tech] = true; Engine.BroadcastMessage(MT_DisabledTechnologiesChanged, {}); }; Player.prototype.RemoveDisabledTechnology = function(tech) { this.disabledTechnologies[tech] = false; Engine.BroadcastMessage(MT_DisabledTechnologiesChanged, {}); }; Player.prototype.SetDisabledTechnologies = function(techs) { this.disabledTechnologies = {}; for (let tech of techs) this.disabledTechnologies[tech] = true; Engine.BroadcastMessage(MT_DisabledTechnologiesChanged, {}); }; Player.prototype.GetDisabledTechnologies = function() { return this.disabledTechnologies; }; Player.prototype.AddStartingTechnology = function(tech) { if (this.startingTechnologies.indexOf(tech) == -1) this.startingTechnologies.push(tech); }; Player.prototype.SetStartingTechnologies = function(techs) { this.startingTechnologies = techs; }; Engine.RegisterComponentType(IID_Player, "Player", Player); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Auras.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Auras.js (revision 21711) +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Auras.js (revision 21712) @@ -1,150 +1,151 @@ Engine.LoadHelperScript("Player.js"); Engine.LoadHelperScript("ValueModification.js"); Engine.LoadComponentScript("interfaces/Auras.js"); Engine.LoadComponentScript("interfaces/AuraManager.js"); Engine.LoadComponentScript("interfaces/RangeOverlayManager.js"); Engine.LoadComponentScript("interfaces/TechnologyManager.js"); Engine.LoadComponentScript("Auras.js"); Engine.LoadComponentScript("AuraManager.js"); global.AuraTemplates = { "Get": name => { let template = { "type": name, "affectedPlayers": ["Ally"], "affects": ["CorrectClass"], "modifications": [{ "value": "Component/Value", "add": 10 }], "auraName": "name", "auraDescription": "description" }; if (name == "range") template.radius = auraRange; return template; } }; -let playerID = [0, 1, 2]; -let playerEnt = [10, 11, 12]; -let playerState = "active"; -let sourceEnt = 20; -let targetEnt = 30; -let auraRange = 40; -let template = { "Identity" : { "Classes" : { "_string" : "CorrectClass OtherClass" } } }; +var playerID = [0, 1, 2]; +var playerEnt = [10, 11, 12]; +var playerState = "active"; +var sourceEnt = 20; +var targetEnt = 30; +var auraRange = 40; +var template = { "Identity" : { "Classes" : { "_string" : "CorrectClass OtherClass" } } }; function testAuras(name, test_function) { ResetState(); AddMock(SYSTEM_ENTITY, IID_PlayerManager, { "GetPlayerByID": idx => playerEnt[idx], - "GetNumPlayers": () => 3 + "GetNumPlayers": () => 3, + "GetAllPlayers": () => playerID }); AddMock(SYSTEM_ENTITY, IID_RangeManager, { "CreateActiveQuery": (ent, minRange, maxRange, players, iid, flags) => 1, "EnableActiveQuery": id => {}, "ResetActiveQuery": id => {}, "DisableActiveQuery": id => {}, "DestroyActiveQuery": id => {}, "GetEntityFlagMask": identifier => {}, "GetEntitiesByPlayer": id => [30, 31, 32] }); AddMock(playerEnt[1], IID_Player, { "IsAlly": id => id == playerID[1] || id == playerID[2], "IsEnemy": id => id != playerID[1] || id != playerID[2], "GetPlayerID": () => playerID[1], "GetState": () => playerState }); AddMock(playerEnt[2], IID_Player, { "IsAlly": id => id == playerID[1] || id == playerID[2], "IsEnemy": id => id != playerID[1] || id != playerID[2], "GetPlayerID": () => playerID[2], "GetState": () => playerState }); AddMock(targetEnt, IID_Identity, { "GetClassesList": () => ["CorrectClass", "OtherClass"] }); AddMock(sourceEnt, IID_Position, { "GetPosition2D": () => new Vector2D() }); if (name != "player" || playerEnt.indexOf(targetEnt) == -1) { AddMock(targetEnt, IID_Position, { "GetPosition2D": () => new Vector2D() }); AddMock(targetEnt, IID_Ownership, { "GetOwner": () => playerID[1] }); } if (playerEnt.indexOf(sourceEnt) == -1) AddMock(sourceEnt, IID_Ownership, { "GetOwner": () => playerID[1] }); ConstructComponent(SYSTEM_ENTITY, "AuraManager", {}); let cmpAuras = ConstructComponent(sourceEnt, "Auras", { "_string": name }); test_function(name, cmpAuras); } testAuras("global", (name, cmpAuras) => { TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Component/Value", 5, targetEnt), 15); TS_ASSERT_EQUALS(ApplyValueModificationsToTemplate("Component/Value", 5, playerID[1], template), 15); }); targetEnt = playerEnt[playerID[2]]; testAuras("player", (name, cmpAuras) => { TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Component/Value", 5, targetEnt), 15); TS_ASSERT_EQUALS(ApplyValueModificationsToTemplate("Component/Value", 5, playerID[1], template), 15); }); targetEnt = 30; // Test the case when the aura source is a player entity. sourceEnt = 11; testAuras("global", (name, cmpAuras) => { TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Component/Value", 5, targetEnt), 15); TS_ASSERT_EQUALS(ApplyValueModificationsToTemplate("Component/Value", 5, playerID[1], template), 15); }); sourceEnt = 20; testAuras("range", (name, cmpAuras) => { cmpAuras.OnRangeUpdate({ "tag": 1, "added": [targetEnt], "removed": [] }); TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Component/Value", 5, targetEnt), 15); TS_ASSERT_EQUALS(ApplyValueModificationsToTemplate("Component/Value", 5, playerID[1], template), 5); cmpAuras.OnRangeUpdate({ "tag": 1, "added": [], "removed": [targetEnt] }); TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Component/Value", 5, targetEnt), 5); }); testAuras("garrisonedUnits", (name, cmpAuras) => { cmpAuras.OnGarrisonedUnitsChanged({ "added" : [targetEnt], "removed": [] }); TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Component/Value", 5, targetEnt), 15); cmpAuras.OnGarrisonedUnitsChanged({ "added" : [], "removed": [targetEnt] }); TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Component/Value", 5, targetEnt), 5); }); testAuras("garrison", (name, cmpAuras) => { TS_ASSERT_EQUALS(cmpAuras.HasGarrisonAura(), true); cmpAuras.ApplyGarrisonBonus(targetEnt); TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Component/Value", 5, targetEnt), 15); cmpAuras.RemoveGarrisonBonus(targetEnt); TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Component/Value", 1, targetEnt), 1); }); testAuras("formation", (name, cmpAuras) => { TS_ASSERT_EQUALS(cmpAuras.HasFormationAura(), true); cmpAuras.ApplyFormationBonus([targetEnt]); TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Component/Value", 5, targetEnt), 15); cmpAuras.RemoveFormationBonus([targetEnt]); TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Component/Value", 5, targetEnt), 5); }); playerState = "defeated"; testAuras("global", (name, cmpAuras) => { TS_ASSERT_EQUALS(ApplyValueModificationsToTemplate("Component/Value", 5, playerID[1], template), 5); }); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Capturable.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Capturable.js (revision 21711) +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Capturable.js (revision 21712) @@ -1,200 +1,200 @@ Engine.LoadHelperScript("Player.js"); Engine.LoadHelperScript("ValueModification.js"); Engine.LoadComponentScript("interfaces/AuraManager.js"); Engine.LoadComponentScript("interfaces/Auras.js"); Engine.LoadComponentScript("interfaces/Capturable.js"); Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); Engine.LoadComponentScript("interfaces/StatisticsTracker.js"); Engine.LoadComponentScript("interfaces/TechnologyManager.js"); Engine.LoadComponentScript("interfaces/TerritoryDecay.js"); Engine.LoadComponentScript("interfaces/Timer.js"); Engine.LoadComponentScript("Capturable.js"); -let testData = { +var testData = { "structure": 20, "playerID": 1, "regenRate": 2, "garrisonedEntities": [30, 31, 32, 33], "garrisonRegenRate": 5, "decay": false, "decayRate": 30, "maxCp": 3000, "neighbours": [20, 0, 20, 10] }; function testCapturable(testData, test_function) { ResetState(); AddMock(SYSTEM_ENTITY, IID_Timer, { "SetInterval": (ent, iid, funcname, time, repeattime, data) => {}, "CancelTimer": timer => {} }); AddMock(testData.structure, IID_Ownership, { "GetOwner": () => testData.playerID, "SetOwner": id => {} }); AddMock(testData.structure, IID_GarrisonHolder, { "GetEntities": () => testData.garrisonedEntities }); AddMock(testData.structure, IID_Fogging, { "Activate": () => {} }); AddMock(10, IID_Player, { "IsEnemy": id => id != 0 }); AddMock(11, IID_Player, { "IsEnemy": id => id != 1 && id != 2 }); AddMock(12, IID_Player, { "IsEnemy": id => id != 1 && id != 2 }); AddMock(13, IID_Player, { "IsEnemy": id => id != 3 }); AddMock(SYSTEM_ENTITY, IID_PlayerManager, { "GetNumPlayers": () => 4, "GetPlayerByID": id => 10 + id }); AddMock(testData.structure, IID_StatisticsTracker, { "LostEntity": () => {}, "CapturedBuilding": () => {} }); let cmpCapturable = ConstructComponent(testData.structure, "Capturable", { "CapturePoints": testData.maxCp, "RegenRate": testData.regenRate, "GarrisonRegenRate": testData.garrisonRegenRate }); AddMock(testData.structure, IID_TerritoryDecay, { "IsDecaying": () => testData.decay, "GetDecayRate": () => testData.decayRate, "GetConnectedNeighbours": () => testData.neighbours }); TS_ASSERT_EQUALS(cmpCapturable.GetRegenRate(), testData.regenRate + testData.garrisonRegenRate * testData.garrisonedEntities.length); test_function(cmpCapturable); Engine.PostMessage = (ent, iid, message) => {}; } // Tests initialisation of the capture points when the entity is created testCapturable(testData, cmpCapturable => { Engine.PostMessage = function(ent, iid, message) { TS_ASSERT_UNEVAL_EQUALS(message, { "regenerating": true, "regenRate": cmpCapturable.GetRegenRate(), "territoryDecay": 0 }); }; cmpCapturable.OnOwnershipChanged({ "from": INVALID_PLAYER, "to": testData.playerID }); TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.GetCapturePoints(), [0, 3000, 0, 0]); }); // Tests if the message is sent when capture points change testCapturable(testData, cmpCapturable => { cmpCapturable.SetCapturePoints([0, 2000, 0 , 1000]); TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.GetCapturePoints(), [0, 2000, 0, 1000]); Engine.PostMessage = function(ent, iid, message) { TS_ASSERT_UNEVAL_EQUALS(message, { "capturePoints": [0, 2000, 0, 1000] }); }; cmpCapturable.RegisterCapturePointsChanged(); }); // Tests reducing capture points (after a capture attack or a decay) testCapturable(testData, cmpCapturable => { cmpCapturable.SetCapturePoints([0, 2000, 0, 1000]); cmpCapturable.CheckTimer(); Engine.PostMessage = function(ent, iid, message) { if (iid == MT_CapturePointsChanged) TS_ASSERT_UNEVAL_EQUALS(message, { "capturePoints": [0, 2000 - 100, 0, 1000 + 100] }); if (iid == MT_CaptureRegenStateChanged) TS_ASSERT_UNEVAL_EQUALS(message, { "regenerating": true, "regenRate": cmpCapturable.GetRegenRate(), "territoryDecay": 0 }); }; TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.Reduce(100, 3), 100); TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.GetCapturePoints(), [0, 2000 - 100, 0, 1000 + 100]); }); // Tests reducing capture points (after a capture attack or a decay) testCapturable(testData, cmpCapturable => { cmpCapturable.SetCapturePoints([0, 2000, 0, 1000]); cmpCapturable.CheckTimer(); TS_ASSERT_EQUALS(cmpCapturable.Reduce(2500, 3), 2000); TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.GetCapturePoints(), [0, 0, 0, 3000]); }); function testRegen(testData, cpIn, cpOut, regenerating) { testCapturable(testData, cmpCapturable => { cmpCapturable.SetCapturePoints(cpIn); cmpCapturable.CheckTimer(); Engine.PostMessage = function(ent, iid, message) { if (iid == MT_CaptureRegenStateChanged) TS_ASSERT_UNEVAL_EQUALS(message.regenerating, regenerating); }; cmpCapturable.TimerTick(cpIn); TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.GetCapturePoints(), cpOut); }); } // With our testData, the total regen rate is 22. That should be taken from the ennemies testRegen(testData, [12, 2950, 2, 36], [1, 2972, 2, 25], true); testRegen(testData, [0, 2994, 2, 4], [0, 2998, 2, 0], true); testRegen(testData, [0, 2998, 2, 0], [0, 2998, 2, 0], false); // If the regeneration rate becomes negative, capture points are given in favour of gaia testData.regenRate = -32; // With our testData, the total regen rate is -12. That should be taken from all players to gaia testRegen(testData, [100, 2800, 50, 50], [112, 2796, 46, 46], true); testData.regenRate = 2; function testDecay(testData, cpIn, cpOut) { testCapturable(testData, cmpCapturable => { cmpCapturable.SetCapturePoints(cpIn); cmpCapturable.CheckTimer(); Engine.PostMessage = function(ent, iid, message) { if (iid == MT_CaptureRegenStateChanged) TS_ASSERT_UNEVAL_EQUALS(message.territoryDecay, testData.decayRate); }; cmpCapturable.TimerTick(); TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.GetCapturePoints(), cpOut); }); } testData.decay = true; // With our testData, the decay rate is 30, that should be given to all neighbours with weights [20/50, 0, 20/50, 10/50], then it regens. testDecay(testData, [2900, 35, 10, 55], [2901, 27, 22, 50]); testData.decay = false; // Tests Reduce function testReduce(testData, amount, player, taken) { testCapturable(testData, cmpCapturable => { cmpCapturable.SetCapturePoints([0, 2000, 0, 1000]); cmpCapturable.CheckTimer(); TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.Reduce(amount, player), taken); }); } testReduce(testData, 50, 3, 50); testReduce(testData, 50, 2, 50); testReduce(testData, 50, 1, 50); testReduce(testData, -50, 3, 0); testReduce(testData, 50, 0, 50); testReduce(testData, 0, 3, 0); testReduce(testData, 1500, 3, 1500); testReduce(testData, 2000, 3, 2000); testReduce(testData, 3000, 3, 2000); // Test defeated player testCapturable(testData, cmpCapturable => { cmpCapturable.SetCapturePoints([500, 1000, 0, 250]); - cmpCapturable.OnPlayerDefeated({ "playerId": 3 }); + cmpCapturable.OnGlobalPlayerDefeated({ "playerId": 3 }); TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.GetCapturePoints(), [750, 1000, 0, 0]); });