Index: ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js (revision 23708) +++ ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js (revision 23709) @@ -1,373 +1,374 @@ function Capturable() {} Capturable.prototype.Schema = "" + "" + "" + "" + "" + "" + "" + "" + ""; Capturable.prototype.Init = function() { // Values affected by modifications in onOwnershipChanged this.maxCp = +this.template.CapturePoints; this.garrisonRegenRate = +this.template.GarrisonRegenRate; this.regenRate = +this.template.RegenRate; 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 this.garrisonRegenRate; }; /** * 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; }; Capturable.prototype.Capture = function(effectData, attacker, attackerOwner, bonusMultiplier) { let cmpHealth = Engine.QueryInterface(this.entity, IID_Health); if (attackerOwner == INVALID_PLAYER || !this.CanCapture(attackerOwner) || !cmpHealth || cmpHealth.GetHitpoints() == 0) return {}; bonusMultiplier *= cmpHealth.GetMaxHitpoints() / (0.1 * cmpHealth.GetMaxHitpoints() + 0.9 * cmpHealth.GetHitpoints()); let total = Attacking.GetTotalAttackEffects({ "Capture": effectData }, "Capture") * bonusMultiplier; let change = this.Reduce(total, attackerOwner); // TODO: implement loot return { "captureChange": change }; }; /** * 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() { let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); if (!cmpGarrisonHolder) return this.regenRate; let garrisonRegenRate = this.GetGarrisonRegenRate() * cmpGarrisonHolder.GetEntities().length; return this.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; let regenRate = this.GetRegenRate(); let cmpDecay = Engine.QueryInterface(this.entity, IID_TerritoryDecay); let decay = cmpDecay && cmpDecay.IsDecaying() ? cmpDecay.GetDecayRate() : 0; if (regenRate == 0 && decay == 0) return; let 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 }); }; /** * Update all chached values that could be affected by modifications. */ Capturable.prototype.UpdateCachedValues = function() { this.garrisonRegenRate = ApplyValueModificationsToEntity("Capturable/GarrisonRegenRate", +this.template.GarrisonRegenRate, this.entity); this.regenRate = ApplyValueModificationsToEntity("Capturable/RegenRate", +this.template.RegenRate, this.entity); this.maxCp = ApplyValueModificationsToEntity("Capturable/CapturePoints", +this.template.CapturePoints, this.entity); }; /** * Update all chached values that could be affected by modifications. * Check timer and send changed messages when required. * @param {boolean} dontSendCpChanged - when true, caller will take care of sending that message */ Capturable.prototype.UpdateCachedValuesAndNotify = function(dontSendCpChanged = false) { let oldMaxCp = this.maxCp; let oldGarrisonRegenRate = this.garrisonRegenRate; let oldRegenRate = this.regenRate; this.UpdateCachedValues(); if (oldMaxCp != this.maxCp) { let scale = this.maxCp / oldMaxCp; for (let i in this.cp) this.cp[i] *= scale; if (!dontSendCpChanged) Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp }); } if (oldGarrisonRegenRate != this.garrisonRegenRate || oldRegenRate != this.regenRate) this.CheckTimer(); }; //// Message Listeners //// Capturable.prototype.OnValueModification = function(msg) { if (msg.component == "Capturable") this.UpdateCachedValuesAndNotify(); }; 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 // Initialise the capture points when created. if (!this.cp.length) { this.UpdateCachedValues(); 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(); return; } // Was already initialised, this happens on defeat or wololo // transfer the points of the old owner to the new one if (this.cp[msg.from]) { this.cp[msg.to] += this.cp[msg.from]; this.cp[msg.from] = 0; this.UpdateCachedValuesAndNotify(true); this.RegisterCapturePointsChanged(); return; } this.UpdateCachedValuesAndNotify(); }; /** * 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.OnGlobalPlayerDefeated = function(msg) { if (!this.cp[msg.playerId]) return; let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); - if (cmpOwnership && cmpOwnership.GetOwner() == msg.playerId) + if (cmpOwnership && (cmpOwnership.GetOwner() == INVALID_PLAYER || + 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);