Index: ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js (revision 23632)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js (revision 23633)
@@ -1,344 +1,373 @@
function Capturable() {}
Capturable.prototype.Schema =
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
Capturable.prototype.Init = function()
{
- // Cache this value
+ // 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 ApplyValueModificationsToEntity("Capturable/GarrisonRegenRate", +this.template.GarrisonRegenRate, this.entity);
+ 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()
{
- var regenRate = +this.template.RegenRate;
- regenRate = ApplyValueModificationsToEntity("Capturable/RegenRate", regenRate, this.entity);
+ let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
+ if (!cmpGarrisonHolder)
+ return this.regenRate;
- var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
- if (cmpGarrisonHolder)
- var garrisonRegenRate = this.GetGarrisonRegenRate() * cmpGarrisonHolder.GetEntities().length;
- else
- var garrisonRegenRate = 0;
-
- return regenRate + garrisonRegenRate;
+ 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;
- var regenRate = this.GetRegenRate();
- var cmpDecay = Engine.QueryInterface(this.entity, IID_TerritoryDecay);
- var decay = cmpDecay && cmpDecay.IsDecaying() ? cmpDecay.GetDecayRate() : 0;
+ 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;
- var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ 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 oldGarrosionRegenRate = 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 (oldGarrosionRegenRate != this.garrisonRegenRate || oldRegenRate != this.regenRate)
+ this.CheckTimer();
+}
+
//// 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();
+ 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
- if (this.cp.length)
+ // Initialise the capture points when created.
+ 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.
+ 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;
}
- this.CheckTimer();
+
+ // 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)
return;
this.cp[0] += this.cp[msg.playerId];
this.cp[msg.playerId] = 0;
this.RegisterCapturePointsChanged();
this.CheckTimer();
};
Engine.RegisterComponentType(IID_Capturable, "Capturable", Capturable);