Differential D1718 Diff 16148 ps/trunk/binaries/data/mods/public/simulation/components/ResourceSupply.js
Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/binaries/data/mods/public/simulation/components/ResourceSupply.js
function ResourceSupply() {} | function ResourceSupply() {} | ||||
ResourceSupply.prototype.Schema = | ResourceSupply.prototype.Schema = | ||||
"<a:help>Provides a supply of one particular type of resource.</a:help>" + | "<a:help>Provides a supply of one particular type of resource.</a:help>" + | ||||
"<a:example>" + | "<a:example>" + | ||||
"<Amount>1000</Amount>" + | "<Max>1000</Max>" + | ||||
"<Initial>1000</Initial>" + | |||||
"<Type>food.meat</Type>" + | "<Type>food.meat</Type>" + | ||||
"<KillBeforeGather>false</KillBeforeGather>" + | "<KillBeforeGather>false</KillBeforeGather>" + | ||||
"<MaxGatherers>25</MaxGatherers>" + | "<MaxGatherers>25</MaxGatherers>" + | ||||
"<DiminishingReturns>0.8</DiminishingReturns>" + | "<DiminishingReturns>0.8</DiminishingReturns>" + | ||||
"<Change>" + | |||||
"<AnyName>" + | |||||
"<Value>2</Value>" + | |||||
"<Interval>1000</Interval>" + | |||||
"</AnyName>" + | |||||
"<Growth>" + | |||||
"<State>alive</State>" + | |||||
"<Value>2</Value>" + | |||||
"<Interval>1000</Interval>" + | |||||
"<UpperLimit>500</UpperLimit>" + | |||||
"</Growth>" + | |||||
"<Rotting>" + | |||||
"<State>dead notGathered</State>" + | |||||
"<Value>-2</Value>" + | |||||
"<Interval>1000</Interval>" + | |||||
"</Rotting>" + | |||||
"<Decay>" + | |||||
"<State>dead</State>" + | |||||
"<Value>-1</Value>" + | |||||
"<Interval>1000</Interval>" + | |||||
"<LowerLimit>500</LowerLimit>" + | |||||
"</Decay>" + | |||||
"</Change>" + | |||||
"</a:example>" + | "</a:example>" + | ||||
"<element name='KillBeforeGather' a:help='Whether this entity must be killed (health reduced to 0) before its resources can be gathered'>" + | "<element name='KillBeforeGather' a:help='Whether this entity must be killed (health reduced to 0) before its resources can be gathered'>" + | ||||
"<data type='boolean'/>" + | "<data type='boolean'/>" + | ||||
"</element>" + | "</element>" + | ||||
"<element name='Amount' a:help='Amount of resources available from this entity'>" + | "<element name='Max' a:help='Max amount of resources available from this entity.'>" + | ||||
"<choice><data type='nonNegativeInteger'/><value>Infinity</value></choice>" + | "<choice><data type='nonNegativeInteger'/><value>Infinity</value></choice>" + | ||||
"</element>" + | "</element>" + | ||||
"<optional>" + | |||||
"<element name='Initial' a:help='Initial amount of resources available from this entity, if this is not specified, Max is used.'>" + | |||||
"<choice><data type='nonNegativeInteger'/><value>Infinity</value></choice>" + | |||||
"</element>" + | |||||
"</optional>" + | |||||
"<element name='Type' a:help='Type and Subtype of resource available from this entity'>" + | "<element name='Type' a:help='Type and Subtype of resource available from this entity'>" + | ||||
Resources.BuildChoicesSchema(true, true) + | Resources.BuildChoicesSchema(true, true) + | ||||
"</element>" + | "</element>" + | ||||
"<element name='MaxGatherers' a:help='Amount of gatherers who can gather resources from this entity at the same time'>" + | "<element name='MaxGatherers' a:help='Amount of gatherers who can gather resources from this entity at the same time'>" + | ||||
"<data type='nonNegativeInteger'/>" + | "<data type='nonNegativeInteger'/>" + | ||||
"</element>" + | "</element>" + | ||||
"<optional>" + | "<optional>" + | ||||
"<element name='DiminishingReturns' a:help='The relative rate of any new gatherer compared to the previous one (geometric sequence). Leave the element out for no diminishing returns.'>" + | "<element name='DiminishingReturns' a:help='The relative rate of any new gatherer compared to the previous one (geometric sequence). Leave the element out for no diminishing returns.'>" + | ||||
"<ref name='positiveDecimal'/>" + | "<ref name='positiveDecimal'/>" + | ||||
"</element>" + | "</element>" + | ||||
"</optional>" + | |||||
"<optional>" + | |||||
"<element name='Change' a:help='Optional element containing all the modifications that affects a resource supply'>" + | |||||
"<oneOrMore>" + | |||||
"<element a:help='Element defining whether and how a resource supply regenerates or decays'>" + | |||||
"<anyName/>" + | |||||
"<interleave>" + | |||||
"<element name='Value' a:help='The amount of resource added per interval.'>" + | |||||
"<data type='integer'/>" + | |||||
"</element>" + | |||||
"<element name='Interval' a:help='The interval in milliseconds.'>" + | |||||
"<data type='positiveInteger'/>" + | |||||
"</element>" + | |||||
"<optional>" + | |||||
"<element name='UpperLimit' a:help='The upper limit of the value after which the Change has no effect.'>" + | |||||
"<data type='nonNegativeInteger'/>" + | |||||
"</element>" + | |||||
"</optional>" + | |||||
"<optional>" + | |||||
"<element name='LowerLimit' a:help='The bottom limit of the value after which the Change has no effect.'>" + | |||||
"<data type='nonNegativeInteger'/>" + | |||||
"</element>" + | |||||
"</optional>" + | |||||
"<optional>" + | |||||
"<element name='State' a:help='What state the entity must be in for the effect to occur.'>" + | |||||
"<list>" + | |||||
"<oneOrMore>" + | |||||
"<choice>" + | |||||
"<value>alive</value>" + | |||||
"<value>dead</value>" + | |||||
"<value>gathered</value>" + | |||||
"<value>notGathered</value>" + | |||||
"</choice>" + | |||||
"</oneOrMore>" + | |||||
"</list>" + | |||||
"</element>" + | |||||
"</optional>" + | |||||
"</interleave>" + | |||||
"</element>" + | |||||
"</oneOrMore>" + | |||||
"</element>" + | |||||
"</optional>"; | "</optional>"; | ||||
ResourceSupply.prototype.Init = function() | ResourceSupply.prototype.Init = function() | ||||
{ | { | ||||
// Current resource amount (non-negative) | this.amount = +(this.template.Initial || this.template.Max); | ||||
this.amount = this.GetMaxAmount(); | |||||
// Includes the ones that are tasked but not here yet, i.e. approaching. | |||||
this.gatherers = []; | this.gatherers = []; | ||||
this.activeGatherers = []; | |||||
let [type, subtype] = this.template.Type.split('.'); | let [type, subtype] = this.template.Type.split('.'); | ||||
this.cachedType = { "generic": type, "specific": subtype }; | this.cachedType = { "generic": type, "specific": subtype }; | ||||
if (this.template.Change) | |||||
{ | |||||
this.timers = {}; | |||||
this.cachedChanges = {}; | |||||
} | |||||
}; | }; | ||||
ResourceSupply.prototype.IsInfinite = function() | ResourceSupply.prototype.IsInfinite = function() | ||||
{ | { | ||||
return !isFinite(+this.template.Amount); | return !isFinite(+this.template.Max); | ||||
}; | }; | ||||
ResourceSupply.prototype.GetKillBeforeGather = function() | ResourceSupply.prototype.GetKillBeforeGather = function() | ||||
{ | { | ||||
return this.template.KillBeforeGather == "true"; | return this.template.KillBeforeGather == "true"; | ||||
}; | }; | ||||
ResourceSupply.prototype.GetMaxAmount = function() | ResourceSupply.prototype.GetMaxAmount = function() | ||||
{ | { | ||||
return +this.template.Amount; | return this.maxAmount; | ||||
}; | }; | ||||
ResourceSupply.prototype.GetCurrentAmount = function() | ResourceSupply.prototype.GetCurrentAmount = function() | ||||
{ | { | ||||
return this.amount; | return this.amount; | ||||
}; | }; | ||||
ResourceSupply.prototype.GetMaxGatherers = function() | ResourceSupply.prototype.GetMaxGatherers = function() | ||||
{ | { | ||||
return +this.template.MaxGatherers; | return +this.template.MaxGatherers; | ||||
}; | }; | ||||
ResourceSupply.prototype.GetNumGatherers = function() | ResourceSupply.prototype.GetNumGatherers = function() | ||||
{ | { | ||||
return this.gatherers.length; | 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. | * @return {{ "generic": string, "specific": string }} An object containing the subtype and the generic type. All resources must have both. | ||||
*/ | */ | ||||
ResourceSupply.prototype.GetType = function() | ResourceSupply.prototype.GetType = function() | ||||
{ | { | ||||
return this.cachedType; | return this.cachedType; | ||||
}; | }; | ||||
/** | /** | ||||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Lines | |||||
}; | }; | ||||
/** | /** | ||||
* @param {number} amount The amount of resources that should be taken from the resource supply. The amount must be positive. | * @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. | * @return {{ "amount": number, "exhausted": boolean }} The current resource amount in the entity and whether it's exhausted or not. | ||||
*/ | */ | ||||
ResourceSupply.prototype.TakeResources = function(amount) | 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 | // Before changing the amount, activate Fogging if necessary to hide changes | ||||
let cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging); | let cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging); | ||||
if (cmpFogging) | if (cmpFogging) | ||||
cmpFogging.Activate(); | cmpFogging.Activate(); | ||||
if (this.IsInfinite()) | let oldAmount = this.amount; | ||||
return { "amount": amount, "exhausted": false }; | this.amount = Math.min(Math.max(oldAmount + change, 0), this.maxAmount); | ||||
let oldAmount = this.GetCurrentAmount(); | |||||
this.amount = Math.max(0, oldAmount - amount); | |||||
let isExhausted = this.GetCurrentAmount() == 0; | // Remove entities that have been exhausted. | ||||
// Remove entities that have been exhausted | if (this.amount == 0) | ||||
if (isExhausted) | |||||
Engine.DestroyEntity(this.entity); | Engine.DestroyEntity(this.entity); | ||||
Engine.PostMessage(this.entity, MT_ResourceSupplyChanged, { "from": oldAmount, "to": this.GetCurrentAmount() }); | let actualChange = this.amount - oldAmount; | ||||
if (actualChange != 0) | |||||
{ | |||||
Engine.PostMessage(this.entity, MT_ResourceSupplyChanged, { | |||||
"from": oldAmount, | |||||
"to": this.amount | |||||
}); | |||||
this.CheckTimers(); | |||||
} | |||||
return actualChange; | |||||
}; | |||||
return { "amount": oldAmount - this.GetCurrentAmount(), "exhausted": isExhausted }; | /** | ||||
* @param {number} newValue - The value to set the current amount to. | |||||
*/ | |||||
ResourceSupply.prototype.SetAmount = function(newValue) | |||||
{ | |||||
this.Change(newValue - this.amount); | |||||
}; | }; | ||||
/** | /** | ||||
* @param {number} gathererID - The gatherer to add. | * @param {number} gathererID - The gatherer to add. | ||||
* @return {boolean} - Whether the gatherer was successfully added to the entity's gatherers list | * @return {boolean} - Whether the gatherer was successfully added to the entity's gatherers list | ||||
* or the entity was already gathering us. | * or the entity was already gathering us. | ||||
*/ | */ | ||||
ResourceSupply.prototype.AddGatherer = function(gathererID) | ResourceSupply.prototype.AddGatherer = function(gathererID) | ||||
{ | { | ||||
if (!this.IsAvailable()) | if (!this.IsAvailable()) | ||||
return false; | return false; | ||||
if (this.IsGatheringUs(gathererID)) | if (this.IsGatheringUs(gathererID)) | ||||
return true; | return true; | ||||
this.gatherers.push(gathererID); | this.gatherers.push(gathererID); | ||||
Engine.PostMessage(this.entity, MT_ResourceSupplyNumGatherersChanged, { "to": this.GetNumGatherers() }); | Engine.PostMessage(this.entity, MT_ResourceSupplyNumGatherersChanged, { "to": this.GetNumGatherers() }); | ||||
return true; | 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. | * @param {number} gathererID - The gatherer's entity id. | ||||
* @todo: Should this return false if the gatherer didn't gather from said resource? | * @todo: Should this return false if the gatherer didn't gather from said resource? | ||||
*/ | */ | ||||
ResourceSupply.prototype.RemoveGatherer = function(gathererID) | ResourceSupply.prototype.RemoveGatherer = function(gathererID) | ||||
{ | { | ||||
let index = this.gatherers.indexOf(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) | if (index == -1) | ||||
return; | return; | ||||
this.activeGatherers.splice(index, 1); | |||||
this.CheckTimers(); | |||||
}; | |||||
this.gatherers.splice(index, 1); | /** | ||||
Engine.PostMessage(this.entity, MT_ResourceSupplyNumGatherersChanged, { "to": this.GetNumGatherers() }); | * 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()); | |||||
}; | }; | ||||
Engine.RegisterComponentType(IID_ResourceSupply, "ResourceSupply", ResourceSupply); | Engine.RegisterComponentType(IID_ResourceSupply, "ResourceSupply", ResourceSupply); |
Wildfire Games · Phabricator