Index: ps/trunk/binaries/data/mods/public/simulation/components/ResourceSupply.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/ResourceSupply.js (revision 25911)
+++ ps/trunk/binaries/data/mods/public/simulation/components/ResourceSupply.js (revision 25912)
@@ -1,504 +1,507 @@
function ResourceSupply() {}
ResourceSupply.prototype.Schema =
"Provides a supply of one particular type of resource." +
"" +
"1000" +
"1000" +
"food.meat" +
"false" +
"25" +
"0.8" +
"" +
"" +
"2" +
"1000" +
"" +
"" +
"alive" +
"2" +
"1000" +
"500" +
"" +
"" +
"dead notGathered" +
"-2" +
"1000" +
"" +
"" +
"dead" +
"-1" +
"1000" +
"500" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"Infinity" +
"" +
"" +
"" +
"Infinity" +
"" +
"" +
"" +
Resources.BuildChoicesSchema(true) +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"alive" +
"dead" +
"gathered" +
"notGathered" +
"" +
"" +
"
" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
ResourceSupply.prototype.Init = function()
{
this.amount = +(this.template.Initial || this.template.Max);
// Includes the ones that are tasked but not here yet, i.e. approaching.
this.gatherers = [];
this.activeGatherers = [];
let [type, subtype] = this.template.Type.split('.');
this.cachedType = { "generic": type, "specific": subtype };
if (this.template.Change)
{
this.timers = {};
this.cachedChanges = {};
}
};
ResourceSupply.prototype.IsInfinite = function()
{
return !isFinite(+this.template.Max);
};
ResourceSupply.prototype.GetKillBeforeGather = function()
{
return this.template.KillBeforeGather == "true";
};
ResourceSupply.prototype.GetMaxAmount = function()
{
return this.maxAmount;
};
ResourceSupply.prototype.GetCurrentAmount = function()
{
return this.amount;
};
ResourceSupply.prototype.GetMaxGatherers = function()
{
return +this.template.MaxGatherers;
};
ResourceSupply.prototype.GetNumGatherers = function()
{
return this.gatherers.length;
};
/**
* @return {number} - The number of currently active gatherers.
*/
ResourceSupply.prototype.GetNumActiveGatherers = function()
{
return this.activeGatherers.length;
};
/**
* @return {{ "generic": string, "specific": string }} An object containing the subtype and the generic type. All resources must have both.
*/
ResourceSupply.prototype.GetType = function()
{
return this.cachedType;
};
/**
* @param {number} gathererID - The gatherer's entity id.
* @return {boolean} - Whether the ResourceSupply can have this additional gatherer or it is already gathering.
*/
ResourceSupply.prototype.IsAvailableTo = function(gathererID)
{
return this.IsAvailable() || this.IsGatheringUs(gathererID);
};
/**
* @return {boolean} - Whether this entity can have an additional gatherer.
*/
ResourceSupply.prototype.IsAvailable = function()
{
return this.amount && this.gatherers.length < this.GetMaxGatherers();
};
/**
* @param {number} entity - The entityID to check for.
* @return {boolean} - Whether the given entity is already gathering at us.
*/
ResourceSupply.prototype.IsGatheringUs = function(entity)
{
return this.gatherers.indexOf(entity) !== -1;
};
/**
* Each additional gatherer decreases the rate following a geometric sequence, with diminishingReturns as ratio.
* @return {number} The diminishing return if any, null otherwise.
*/
ResourceSupply.prototype.GetDiminishingReturns = function()
{
if (!this.template.DiminishingReturns)
return null;
let diminishingReturns = ApplyValueModificationsToEntity("ResourceSupply/DiminishingReturns", +this.template.DiminishingReturns, this.entity);
if (!diminishingReturns)
return null;
let numGatherers = this.GetNumGatherers();
if (numGatherers > 1)
return diminishingReturns == 1 ? 1 : (1 - Math.pow(diminishingReturns, numGatherers)) / (1 - diminishingReturns) / numGatherers;
return null;
};
/**
* @param {number} amount The amount of resources that should be taken from the resource supply. The amount must be positive.
* @return {{ "amount": number, "exhausted": boolean }} The current resource amount in the entity and whether it's exhausted or not.
*/
ResourceSupply.prototype.TakeResources = function(amount)
{
if (this.IsInfinite())
return { "amount": amount, "exhausted": false };
return {
"amount": Math.abs(this.Change(-amount)),
"exhausted": this.amount == 0
};
};
/**
* @param {number} change - The amount to change the resources with (can be negative).
* @return {number} - The actual change in resourceSupply.
*/
ResourceSupply.prototype.Change = function(change)
{
// Before changing the amount, activate Fogging if necessary to hide changes
let cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging);
if (cmpFogging)
cmpFogging.Activate();
let oldAmount = this.amount;
this.amount = Math.min(Math.max(oldAmount + change, 0), this.maxAmount);
// Remove entities that have been exhausted.
if (this.amount == 0)
Engine.DestroyEntity(this.entity);
let actualChange = this.amount - oldAmount;
if (actualChange != 0)
{
Engine.PostMessage(this.entity, MT_ResourceSupplyChanged, {
"from": oldAmount,
"to": this.amount
});
this.CheckTimers();
}
return actualChange;
};
/**
* @param {number} newValue - The value to set the current amount to.
*/
ResourceSupply.prototype.SetAmount = function(newValue)
{
+ // We currently don't support changing to or fro Infinity.
+ if (this.IsInfinite() || newValue === Infinity)
+ return;
this.Change(newValue - this.amount);
};
/**
* @param {number} gathererID - The gatherer to add.
* @return {boolean} - Whether the gatherer was successfully added to the entity's gatherers list
* or the entity was already gathering us.
*/
ResourceSupply.prototype.AddGatherer = function(gathererID)
{
if (!this.IsAvailable())
return false;
if (this.IsGatheringUs(gathererID))
return true;
this.gatherers.push(gathererID);
Engine.PostMessage(this.entity, MT_ResourceSupplyNumGatherersChanged, { "to": this.GetNumGatherers() });
return true;
};
/**
* @param {number} player - The playerID owning the gatherer.
* @param {number} entity - The entityID gathering.
*
* @return {boolean} - Whether the gatherer was successfully added to the active-gatherers list
* or the entity was already in that list.
*/
ResourceSupply.prototype.AddActiveGatherer = function(entity)
{
if (!this.AddGatherer(entity))
return false;
if (this.activeGatherers.indexOf(entity) == -1)
{
this.activeGatherers.push(entity);
this.CheckTimers();
}
return true;
};
/**
* @param {number} gathererID - The gatherer's entity id.
* @todo: Should this return false if the gatherer didn't gather from said resource?
*/
ResourceSupply.prototype.RemoveGatherer = function(gathererID)
{
let index = this.gatherers.indexOf(gathererID);
if (index != -1)
{
this.gatherers.splice(index, 1);
Engine.PostMessage(this.entity, MT_ResourceSupplyNumGatherersChanged, { "to": this.GetNumGatherers() });
}
index = this.activeGatherers.indexOf(gathererID);
if (index == -1)
return;
this.activeGatherers.splice(index, 1);
this.CheckTimers();
};
/**
* Checks whether a timer ought to be added or removed.
*/
ResourceSupply.prototype.CheckTimers = function()
{
if (!this.template.Change || this.IsInfinite())
return;
for (let changeKey in this.template.Change)
{
if (!this.CheckState(changeKey))
{
this.StopTimer(changeKey);
continue;
}
let template = this.template.Change[changeKey];
if (this.amount < +(template.LowerLimit || -1) ||
this.amount > +(template.UpperLimit || this.GetMaxAmount()))
{
this.StopTimer(changeKey);
continue;
}
if (this.cachedChanges[changeKey] == 0)
{
this.StopTimer(changeKey);
continue;
}
if (!this.timers[changeKey])
this.StartTimer(changeKey);
}
};
/**
* This verifies whether the current state of the supply matches the ones needed
* for the specific timer to run.
*
* @param {string} changeKey - The name of the Change to verify the state for.
* @return {boolean} - Whether the timer may run.
*/
ResourceSupply.prototype.CheckState = function(changeKey)
{
let template = this.template.Change[changeKey];
if (!template.State)
return true;
let states = template.State;
let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
if (states.indexOf("alive") != -1 && !cmpHealth && states.indexOf("dead") == -1 ||
states.indexOf("dead") != -1 && cmpHealth && states.indexOf("alive") == -1)
return false;
let activeGatherers = this.GetNumActiveGatherers();
if (states.indexOf("gathered") != -1 && activeGatherers == 0 && states.indexOf("notGathered") == -1 ||
states.indexOf("notGathered") != -1 && activeGatherers > 0 && states.indexOf("gathered") == -1)
return false;
return true;
};
/**
* @param {string} changeKey - The name of the Change to apply to the entity.
*/
ResourceSupply.prototype.StartTimer = function(changeKey)
{
if (this.timers[changeKey])
return;
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
let interval = ApplyValueModificationsToEntity("ResourceSupply/Change/" + changeKey + "/Interval", +(this.template.Change[changeKey].Interval || 1000), this.entity);
this.timers[changeKey] = cmpTimer.SetInterval(this.entity, IID_ResourceSupply, "TimerTick", interval, interval, changeKey);
};
/**
* @param {string} changeKey - The name of the change to stop the timer for.
*/
ResourceSupply.prototype.StopTimer = function(changeKey)
{
if (!this.timers[changeKey])
return;
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timers[changeKey]);
delete this.timers[changeKey];
};
/**
* @param {string} changeKey - The name of the change to apply to the entity.
*/
ResourceSupply.prototype.TimerTick = function(changeKey)
{
let template = this.template.Change[changeKey];
if (!template || !this.Change(this.cachedChanges[changeKey]))
this.StopTimer(changeKey);
};
/**
* Since the supposed changes can be affected by modifications, and applying those
* are slow, do not calculate them every timer tick.
*/
ResourceSupply.prototype.RecalculateValues = function()
{
this.maxAmount = ApplyValueModificationsToEntity("ResourceSupply/Max", +this.template.Max, this.entity);
if (!this.template.Change || this.IsInfinite())
return;
for (let changeKey in this.template.Change)
this.cachedChanges[changeKey] = ApplyValueModificationsToEntity("ResourceSupply/Change/" + changeKey + "/Value", +this.template.Change[changeKey].Value, this.entity);
this.CheckTimers();
};
/**
* @param {{ "component": string, "valueNames": string[] }} msg - Message containing a list of values that were changed.
*/
ResourceSupply.prototype.OnValueModification = function(msg)
{
if (msg.component != "ResourceSupply")
return;
this.RecalculateValues();
};
/**
* @param {{ "from": number, "to": number }} msg - Message containing the old new owner.
*/
ResourceSupply.prototype.OnOwnershipChanged = function(msg)
{
if (msg.to == INVALID_PLAYER)
{
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
for (let changeKey in this.timers)
cmpTimer.CancelTimer(this.timers[changeKey]);
}
else
this.RecalculateValues();
};
/**
* @param {{ "entity": number, "newentity": number }} msg - Message to what the entity has been renamed.
*/
ResourceSupply.prototype.OnEntityRenamed = function(msg)
{
let cmpResourceSupplyNew = Engine.QueryInterface(msg.newentity, IID_ResourceSupply);
if (cmpResourceSupplyNew)
cmpResourceSupplyNew.SetAmount(this.GetCurrentAmount());
};
function ResourceSupplyMirage() {}
ResourceSupplyMirage.prototype.Init = function(cmpResourceSupply)
{
this.maxAmount = cmpResourceSupply.GetMaxAmount();
this.amount = cmpResourceSupply.GetCurrentAmount();
this.type = cmpResourceSupply.GetType();
this.isInfinite = cmpResourceSupply.IsInfinite();
this.killBeforeGather = cmpResourceSupply.GetKillBeforeGather();
this.maxGatherers = cmpResourceSupply.GetMaxGatherers();
this.numGatherers = cmpResourceSupply.GetNumGatherers();
};
ResourceSupplyMirage.prototype.GetMaxAmount = function() { return this.maxAmount; };
ResourceSupplyMirage.prototype.GetCurrentAmount = function() { return this.amount; };
ResourceSupplyMirage.prototype.GetType = function() { return this.type; };
ResourceSupplyMirage.prototype.IsInfinite = function() { return this.isInfinite; };
ResourceSupplyMirage.prototype.GetKillBeforeGather = function() { return this.killBeforeGather; };
ResourceSupplyMirage.prototype.GetMaxGatherers = function() { return this.maxGatherers; };
ResourceSupplyMirage.prototype.GetNumGatherers = function() { return this.numGatherers; };
// Apply diminishing returns with more gatherers, for e.g. infinite farms. For most resources this has no effect
// (GetDiminishingReturns will return null). We can assume that for resources that are miraged this is the case.
ResourceSupplyMirage.prototype.GetDiminishingReturns = function() { return null; };
Engine.RegisterGlobal("ResourceSupplyMirage", ResourceSupplyMirage);
ResourceSupply.prototype.Mirage = function()
{
let mirage = new ResourceSupplyMirage();
mirage.Init(this);
return mirage;
};
Engine.RegisterComponentType(IID_ResourceSupply, "ResourceSupply", ResourceSupply);
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_ResourceSupply.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_ResourceSupply.js (revision 25911)
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_ResourceSupply.js (revision 25912)
@@ -1,744 +1,761 @@
Resources = {
"BuildChoicesSchema": () => {
let schema = "";
for (let res of ["food", "metal"])
{
for (let subtype in ["meat", "grain"])
schema += "" + res + "." + subtype + "";
schema += " treasure." + res + "";
}
return "" + schema + "";
}
};
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/ModifiersManager.js");
Engine.LoadComponentScript("interfaces/ResourceSupply.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("ResourceSupply.js");
Engine.LoadComponentScript("Timer.js");
let entity = 60;
AddMock(entity, IID_Fogging, {
"Activate": () => {}
});
let template = {
"Max": "1001",
"Initial": "1000",
"Type": "food.meat",
"KillBeforeGather": "false",
"MaxGatherers": "2"
};
let cmpResourceSupply = ConstructComponent(entity, "ResourceSupply", template);
cmpResourceSupply.OnOwnershipChanged({ "to": 1 });
TS_ASSERT(!cmpResourceSupply.IsInfinite());
TS_ASSERT(!cmpResourceSupply.GetKillBeforeGather());
TS_ASSERT_EQUALS(cmpResourceSupply.GetMaxAmount(), 1001);
TS_ASSERT_EQUALS(cmpResourceSupply.GetMaxGatherers(), 2);
TS_ASSERT_EQUALS(cmpResourceSupply.GetDiminishingReturns(), null);
TS_ASSERT_EQUALS(cmpResourceSupply.GetNumGatherers(), 0);
TS_ASSERT(cmpResourceSupply.IsAvailableTo(70));
TS_ASSERT(cmpResourceSupply.AddGatherer(70));
TS_ASSERT_EQUALS(cmpResourceSupply.GetNumGatherers(), 1);
TS_ASSERT(cmpResourceSupply.AddGatherer(71));
TS_ASSERT_EQUALS(cmpResourceSupply.GetNumGatherers(), 2);
TS_ASSERT(!cmpResourceSupply.AddGatherer(72));
TS_ASSERT_EQUALS(cmpResourceSupply.GetNumGatherers(), 2);
TS_ASSERT(cmpResourceSupply.IsAvailableTo(70));
TS_ASSERT(!cmpResourceSupply.IsAvailableTo(73));
TS_ASSERT(!cmpResourceSupply.AddGatherer(73));
TS_ASSERT_EQUALS(cmpResourceSupply.GetNumGatherers(), 2);
cmpResourceSupply.RemoveGatherer(70);
TS_ASSERT_EQUALS(cmpResourceSupply.GetNumGatherers(), 1);
TS_ASSERT(cmpResourceSupply.AddActiveGatherer(70));
TS_ASSERT_EQUALS(cmpResourceSupply.GetNumGatherers(), 2);
cmpResourceSupply.RemoveGatherer(70);
TS_ASSERT_EQUALS(cmpResourceSupply.GetNumGatherers(), 1);
TS_ASSERT(cmpResourceSupply.AddActiveGatherer(70));
TS_ASSERT_EQUALS(cmpResourceSupply.GetNumGatherers(), 2);
cmpResourceSupply.RemoveGatherer(70);
TS_ASSERT_EQUALS(cmpResourceSupply.GetNumGatherers(), 1);
TS_ASSERT_UNEVAL_EQUALS(cmpResourceSupply.GetCurrentAmount(), 1000);
TS_ASSERT_UNEVAL_EQUALS(cmpResourceSupply.TakeResources(300), { "amount": 300, "exhausted": false });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 700);
TS_ASSERT(cmpResourceSupply.IsAvailableTo(70));
TS_ASSERT_UNEVAL_EQUALS(cmpResourceSupply.TakeResources(800), { "amount": 700, "exhausted": true });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 0);
// The resource is not available when exhausted
TS_ASSERT(!cmpResourceSupply.IsAvailableTo(70));
cmpResourceSupply.RemoveGatherer(71);
TS_ASSERT_EQUALS(cmpResourceSupply.GetNumGatherers(), 0);
+// #6317
+const infiniteTemplate = {
+ "Max": "Infinity",
+ "Type": "food.grain",
+ "KillBeforeGather": "false",
+ "MaxGatherers": "1"
+};
+
+const cmpInfiniteResourceSupply = ConstructComponent(entity, "ResourceSupply", infiniteTemplate);
+cmpResourceSupply.OnOwnershipChanged({ "to": 1 });
+
+TS_ASSERT(cmpInfiniteResourceSupply.IsInfinite());
+TS_ASSERT_UNEVAL_EQUALS(cmpInfiniteResourceSupply.TakeResources(300), { "amount": 300, "exhausted": false });
+
+cmpInfiniteResourceSupply.OnEntityRenamed({ "newentity": entity });
+TS_ASSERT(cmpInfiniteResourceSupply.IsAvailable());
+
// Test Changes.
let cmpTimer;
function reset(newTemplate)
{
cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer");
cmpResourceSupply = ConstructComponent(entity, "ResourceSupply", newTemplate);
cmpResourceSupply.OnOwnershipChanged({ "to": 1 });
}
// Decay.
template = {
"Max": "1000",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Rotting": {
"Value": "-1",
"Interval": "1000"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 1000);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 999);
cmpTimer.OnUpdate({ "turnLength": 5 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 994);
// Decay with minimum.
template = {
"Max": "1000",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Rotting": {
"Value": "-1",
"Interval": "1000",
"LowerLimit": "997"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 1000);
cmpTimer.OnUpdate({ "turnLength": 3 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 997);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 996);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 996);
// Decay with maximum.
template = {
"Max": "1000",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Rotting": {
"Value": "-1",
"Interval": "1000",
"UpperLimit": "995"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 1000);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 1000);
// Decay with minimum and maximum.
template = {
"Max": "1000",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Rotting": {
"Value": "-1",
"Interval": "1000",
"UpperLimit": "995",
"LowerLimit": "990"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 1000);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 1000);
cmpResourceSupply.TakeResources(6);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 994);
cmpTimer.OnUpdate({ "turnLength": 10 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 989);
// Growth.
template = {
"Initial": "995",
"Max": "1000",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Growth": {
"Value": "1",
"Interval": "1000"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 995);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 996);
cmpTimer.OnUpdate({ "turnLength": 5 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 1000);
// Growth with minimum.
template = {
"Initial": "995",
"Max": "1000",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Growth": {
"Value": "1",
"Interval": "1000",
"LowerLimit": "997"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 995);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 995);
// Growth with maximum.
template = {
"Initial": "994",
"Max": "1000",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Growth": {
"Value": "1",
"Interval": "1000",
"UpperLimit": 995
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 994);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 995);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 996);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 996);
// Growth with minimum and maximum.
template = {
"Initial": "990",
"Max": "1000",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Growth": {
"Value": "1",
"Interval": "1000",
"UpperLimit": "995",
"LowerLimit": "990"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 990);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 991);
cmpTimer.OnUpdate({ "turnLength": 8 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 996);
// Growth when resources are taken again.
template = {
"Initial": "995",
"Max": "1000",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Growth": {
"Value": "1",
"Interval": "1000"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 995);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 996);
cmpResourceSupply.TakeResources(6);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 990);
cmpTimer.OnUpdate({ "turnLength": 5 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 995);
// Decay when dead.
template = {
"Max": "10",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Rotting": {
"Value": "-1",
"Interval": "1000",
"State": "dead"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 10);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 9);
// No growth when dead.
template = {
"Max": "10",
"Initial": "5",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Growth": {
"Value": "1",
"Interval": "1000",
"State": "alive"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 5);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 5);
// Decay when dead or alive.
template = {
"Max": "10",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Rotting": {
"Value": "-1",
"Interval": "1000",
"State": "dead alive"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 10);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 9);
AddMock(entity, IID_Health, {}); // Bring the entity to life.
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 8);
// No decay when alive.
template = {
"Max": "10",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Rotting": {
"Value": "-1",
"Interval": "1000",
"State": "dead"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 10);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 10);
// Growth when alive.
template = {
"Max": "10",
"Initial": "5",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Growth": {
"Value": "1",
"Interval": "1000",
"State": "alive"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 5);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 6);
// Growth when dead or alive.
template = {
"Max": "10",
"Initial": "5",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Growth": {
"Value": "1",
"Interval": "1000",
"State": "dead alive"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 5);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 6);
DeleteMock(entity, IID_Health); // "Kill" the entity.
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 7);
// Decay *and* growth.
template = {
"Max": "10",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Rotting": {
"Value": "-1",
"Interval": "1000"
},
"Growth": {
"Value": "1",
"Interval": "1000"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 10);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 10);
// Decay *and* growth with different health states.
template = {
"Max": "10",
"Initial": "5",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Rotting": {
"Value": "-1",
"Interval": "1000",
"State": "dead"
},
"Growth": {
"Value": "1",
"Interval": "1000",
"State": "alive"
}
},
"MaxGatherers": "2"
};
AddMock(entity, IID_Health, { }); // Bring the entity to life.
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 5);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 6);
DeleteMock(entity, IID_Health); // "Kill" the entity.
// We overshoot one due to lateness.
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 7);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 6);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 5);
// Two effects with different limits.
template = {
"Max": "20",
"Initial": "5",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"SuperGrowth": {
"Value": "2",
"Interval": "1000",
"UpperLimit": "8"
},
"Growth": {
"Value": "1",
"Interval": "1000",
"UpperLimit": "12"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 5);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 8);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 11);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 12);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 13);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 13);
// Two effects with different limits.
// This in an interesting case, where the order of the changes matters.
template = {
"Max": "20",
"Initial": "5",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Growth": {
"Value": "1",
"Interval": "1000",
"UpperLimit": "12"
},
"SuperGrowth": {
"Value": "2",
"Interval": "1000",
"UpperLimit": "8"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 5);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 8);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 9);
cmpTimer.OnUpdate({ "turnLength": 5 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 13);
// Infinity with growth.
template = {
"Max": "Infinity",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Growth": {
"Value": "1",
"Interval": "1000"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), Infinity);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), Infinity);
// Infinity with decay.
template = {
"Max": "Infinity",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Decay": {
"Value": "-1",
"Interval": "1000"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), Infinity);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), Infinity);
// Decay when not gathered.
template = {
"Max": "10",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Decay": {
"Value": "-1",
"Interval": "1000",
"State": "notGathered"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 10);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 9);
TS_ASSERT(cmpResourceSupply.AddActiveGatherer(70));
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 9);
cmpResourceSupply.RemoveGatherer(70);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 8);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 7);
// Grow when gathered.
template = {
"Max": "10",
"Initial": "5",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Growth": {
"Value": "1",
"Interval": "1000",
"State": "gathered"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 5);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 5);
TS_ASSERT(cmpResourceSupply.AddActiveGatherer(70));
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 6);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 7);
cmpResourceSupply.RemoveGatherer(70, 1);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 7);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 7);
// Grow when gathered or not.
template = {
"Max": "10",
"Initial": "5",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Growth": {
"Value": "1",
"Interval": "1000",
"State": "notGathered gathered"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 5);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 6);
TS_ASSERT(cmpResourceSupply.AddActiveGatherer(70));
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 7);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 8);
cmpResourceSupply.RemoveGatherer(70);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 9);
// Grow when gathered and alive.
template = {
"Max": "10",
"Initial": "5",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Growth": {
"Value": "1",
"Interval": "1000",
"State": "alive gathered"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 5);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 5);
TS_ASSERT(cmpResourceSupply.AddActiveGatherer(70));
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 5);
AddMock(entity, IID_Health, { }); // Bring the entity to life.
cmpResourceSupply.CheckTimers(); // No other way to tell we've come to life.
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 6);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 7);
cmpResourceSupply.RemoveGatherer(70);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 7);
DeleteMock(entity, IID_Health); // "Kill" the entity.
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 7);
// Decay when dead and not gathered.
template = {
"Max": "10",
"Initial": "5",
"Type": "food.meat",
"KillBeforeGather": "false",
"Change": {
"Decay": {
"Value": "-1",
"Interval": "1000",
"State": "dead notGathered"
}
},
"MaxGatherers": "2"
};
reset(template);
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 5);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 4);
TS_ASSERT(cmpResourceSupply.AddActiveGatherer(70));
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 4);
AddMock(entity, IID_Health, {}); // Bring the entity to life.
cmpResourceSupply.CheckTimers(); // No other way to tell we've come to life.
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 4);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 4);
cmpResourceSupply.RemoveGatherer(70);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 4);
DeleteMock(entity, IID_Health); // "Kill" the entity.
cmpResourceSupply.CheckTimers(); // No other way to tell we've died.
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 3);
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(cmpResourceSupply.GetCurrentAmount(), 2);