mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-19 23:03:56 -07:00
296 lines
8.2 KiB
JavaScript
296 lines
8.2 KiB
JavaScript
function Capturable() {}
|
|
|
|
Capturable.prototype.Schema =
|
|
"<element name='CapturePoints' a:help='Maximum capture points'>" +
|
|
"<ref name='positiveDecimal'/>" +
|
|
"</element>" +
|
|
"<element name='RegenRate' a:help='Number of capture are regenerated per second in favour of the owner'>" +
|
|
"<ref name='nonNegativeDecimal'/>" +
|
|
"</element>" +
|
|
"<element name='GarrisonRegenRate' a:help='Number of capture are regenerated per second and per garrisoned unit in favour of the owner'>" +
|
|
"<ref name='nonNegativeDecimal'/>" +
|
|
"</element>";
|
|
|
|
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() == -1)
|
|
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 == -1 || 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;
|
|
|
|
cmpOwnership.SetOwner(bestPlayer);
|
|
};
|
|
|
|
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() == -1)
|
|
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, {"ticking": true, "regenRate": regenRate, "territoryDecay": decay});
|
|
};
|
|
|
|
//// Message Listeners ////
|
|
|
|
Capturable.prototype.OnValueModification = function(msg)
|
|
{
|
|
if (msg.component != "Capturable")
|
|
return;
|
|
|
|
var oldMaxCp = this.GetMaxCapturePoints();
|
|
this.maxCp = Math.round(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.OnOwnershipChanged = function(msg)
|
|
{
|
|
if (msg.to == -1)
|
|
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
|
|
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
|
|
for (let i = 0; i < cmpPlayerManager.GetNumPlayers(); ++i)
|
|
if (i == msg.to)
|
|
this.cp[i] = this.maxCp;
|
|
else
|
|
this.cp[i] = 0;
|
|
}
|
|
this.CheckTimer();
|
|
};
|
|
|
|
Engine.RegisterComponentType(IID_Capturable, "Capturable", Capturable);
|