Index: ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js (revision 21684)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js (revision 21685)
@@ -1,309 +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
+ // 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
+ * 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
+ // 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)
+ // 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
+ // 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
+ // 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)
+{
+ 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/tests/test_Capturable.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Capturable.js (revision 21684)
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Capturable.js (revision 21685)
@@ -1,193 +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 = {
"structure": 20,
"playerID": 1,
"regenRate": 2,
- "garrisonedEntities": [30,31,32,33],
+ "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" : () => {},
+ "LostEntity": () => {},
"CapturedBuilding": () => {}
});
let cmpCapturable = ConstructComponent(testData.structure, "Capturable", {
- "CapturePoints" : testData.maxCp,
- "RegenRate" : testData.regenRate,
- "GarrisonRegenRate" : testData.garrisonRegenRate
+ "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 });
+ 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]);
+ 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] });
+ 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.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.SetCapturePoints([0, 2000, 0, 1000]);
cmpCapturable.CheckTimer();
- TS_ASSERT_EQUALS(cmpCapturable.Reduce(2500, 3),2000);
+ 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, [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.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 });
+ TS_ASSERT_UNEVAL_EQUALS(cmpCapturable.GetCapturePoints(), [750, 1000, 0, 0]);
+});