forked from mirrors/0ad
Limit the number of gatherer per resource, as described in #1387. Based on work by crezax. Fixes #1387
This was SVN commit r13277.
This commit is contained in:
@@ -161,7 +161,14 @@ var EntityTemplate = Class({
|
||||
return undefined;
|
||||
return +this._template.ResourceSupply.Amount;
|
||||
},
|
||||
|
||||
|
||||
maxGatherers: function()
|
||||
{
|
||||
if (this._template.ResourceSupply !== undefined)
|
||||
return this._template.ResourceSupply.MaxGatherers;
|
||||
return 0;
|
||||
},
|
||||
|
||||
resourceGatherRates: function() {
|
||||
if (!this._template.ResourceGatherer)
|
||||
return undefined;
|
||||
@@ -339,6 +346,20 @@ var Entity = Class({
|
||||
return this._entity.resourceSupplyAmount;
|
||||
},
|
||||
|
||||
resourceSupplyGatherers: function()
|
||||
{
|
||||
if (this._entity.resourceSupplyGatherers !== undefined)
|
||||
return this._entity.resourceSupplyGatherers;
|
||||
return [];
|
||||
},
|
||||
|
||||
isFull: function()
|
||||
{
|
||||
if (this._entity.resourceSupplyGatherers !== undefined)
|
||||
return (this.maxGatherers === this._entity.resourceSupplyGatherers.length);
|
||||
return undefined;
|
||||
},
|
||||
|
||||
resourceCarrying: function() {
|
||||
if(this._entity.resourceCarrying === undefined)
|
||||
return undefined;
|
||||
|
||||
@@ -168,6 +168,10 @@ Worker.prototype.startGathering = function(gameState){
|
||||
if (!supply.position()){
|
||||
return;
|
||||
}
|
||||
|
||||
if (supply.isFull === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
// measure the distance to the resource
|
||||
var dist = VectorDistance(supply.position(), ent.position());
|
||||
|
||||
@@ -141,6 +141,12 @@ AIProxy.prototype.OnResourceSupplyChanged = function(msg)
|
||||
this.changes.resourceSupplyAmount = msg.to;
|
||||
};
|
||||
|
||||
AIProxy.prototype.OnResourceSupplyGatherersChanged = function(msg)
|
||||
{
|
||||
this.NotifyChange();
|
||||
this.changes.resourceSupplyGatherers = msg.to;
|
||||
};
|
||||
|
||||
AIProxy.prototype.OnResourceCarryingChanged = function(msg)
|
||||
{
|
||||
this.NotifyChange();
|
||||
@@ -225,6 +231,7 @@ AIProxy.prototype.GetFullRepresentation = function()
|
||||
{
|
||||
// Updated by OnResourceSupplyChanged
|
||||
ret.resourceSupplyAmount = cmpResourceSupply.GetCurrentAmount();
|
||||
ret.resourceSupplyGatherers = cmpResourceSupply.GetGatherers();
|
||||
}
|
||||
|
||||
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
|
||||
|
||||
@@ -261,7 +261,9 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
||||
"max": cmpResourceSupply.GetMaxAmount(),
|
||||
"amount": cmpResourceSupply.GetCurrentAmount(),
|
||||
"type": cmpResourceSupply.GetType(),
|
||||
"killBeforeGather": cmpResourceSupply.GetKillBeforeGather()
|
||||
"killBeforeGather": cmpResourceSupply.GetKillBeforeGather(),
|
||||
"maxGatherers": cmpResourceSupply.GetMaxGatherers(),
|
||||
"gatherers": cmpResourceSupply.GetGatherers()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -29,12 +29,16 @@ ResourceSupply.prototype.Schema =
|
||||
"<value>treasure.metal</value>" +
|
||||
"<value>treasure.food</value>" +
|
||||
"</choice>" +
|
||||
"</element>" +
|
||||
"<element name='MaxGatherers' a:help='Amount of gatherers who can gather resources from this entity at the same time'>" +
|
||||
"<data type='nonNegativeInteger'/>" +
|
||||
"</element>";
|
||||
|
||||
ResourceSupply.prototype.Init = function()
|
||||
{
|
||||
// Current resource amount (non-negative)
|
||||
this.amount = this.GetMaxAmount();
|
||||
this.gatherers = []; // list of IDs
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.GetKillBeforeGather = function()
|
||||
@@ -52,6 +56,16 @@ ResourceSupply.prototype.GetCurrentAmount = function()
|
||||
return this.amount;
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.GetMaxGatherers = function()
|
||||
{
|
||||
return +this.template.MaxGatherers;
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.GetGatherers = function()
|
||||
{
|
||||
return this.gatherers;
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.TakeResources = function(rate)
|
||||
{
|
||||
// 'rate' should be a non-negative integer
|
||||
@@ -77,4 +91,37 @@ ResourceSupply.prototype.GetType = function()
|
||||
return { "generic": type, "specific": subtype };
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.IsAvailable = function(gathererID)
|
||||
{
|
||||
if (this.gatherers.length < this.GetMaxGatherers() || this.gatherers.indexOf(gathererID) !== -1)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.AddGatherer = function(gathererID)
|
||||
{
|
||||
if (!this.IsAvailable(gathererID))
|
||||
return false;
|
||||
|
||||
if (this.gatherers.indexOf(gathererID) === -1)
|
||||
{
|
||||
this.gatherers.push(gathererID);
|
||||
// broadcast message, mainly useful for the AIs.
|
||||
Engine.PostMessage(this.entity, MT_ResourceSupplyGatherersChanged, { "to": this.gatherers });
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// should this return false if the gatherer didn't gather from said resource?
|
||||
ResourceSupply.prototype.RemoveGatherer = function(gathererID)
|
||||
{
|
||||
if (this.gatherers.indexOf(gathererID) !== -1)
|
||||
{
|
||||
this.gatherers.splice(this.gatherers.indexOf(gathererID),1);
|
||||
// broadcast message, mainly useful for the AIs.
|
||||
Engine.PostMessage(this.entity, MT_ResourceSupplyGatherersChanged, { "to": this.gatherers });
|
||||
}
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_ResourceSupply, "ResourceSupply", ResourceSupply);
|
||||
|
||||
@@ -1539,6 +1539,48 @@ var UnitFsmSpec = {
|
||||
"APPROACHING": {
|
||||
"enter": function() {
|
||||
this.SelectAnimation("move");
|
||||
|
||||
this.gatheringTarget = this.order.data.target; // temporary, deleted in "leave".
|
||||
|
||||
// check that we can gather from the resource we're supposed to gather from.
|
||||
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
|
||||
if (!cmpSupply || !cmpSupply.AddGatherer(this.entity))
|
||||
{
|
||||
// Save the current order's data in case we need it later
|
||||
var oldType = this.order.data.type;
|
||||
var oldTarget = this.order.data.target;
|
||||
var oldTemplate = this.order.data.template;
|
||||
|
||||
// Try the next queued order if there is any
|
||||
if (this.FinishOrder())
|
||||
return true;
|
||||
|
||||
// Try to find another nearby target of the same specific type
|
||||
// Also don't switch to a different type of huntable animal
|
||||
var nearby = this.FindNearbyResource(function (ent, type, template) {
|
||||
return (
|
||||
ent != oldTarget
|
||||
&& ((type.generic == "treasure" && oldType.generic == "treasure")
|
||||
|| (type.specific == oldType.specific
|
||||
&& (type.specific != "meat" || oldTemplate == template)))
|
||||
);
|
||||
});
|
||||
if (nearby)
|
||||
{
|
||||
this.PerformGather(nearby, false, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
var nearby = this.FindNearestDropsite(oldType.generic);
|
||||
if (nearby)
|
||||
{
|
||||
this.PushOrderFront("ReturnResource", { "target": nearby, "force": false });
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
"MoveCompleted": function(msg) {
|
||||
@@ -1546,6 +1588,11 @@ var UnitFsmSpec = {
|
||||
{
|
||||
// We failed to reach the target
|
||||
|
||||
// remove us from the list of entities gathering from Resource.
|
||||
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
|
||||
if (cmpSupply)
|
||||
cmpSupply.RemoveGatherer(this.entity);
|
||||
|
||||
// Save the current order's data in case we need it later
|
||||
var oldType = this.order.data.type;
|
||||
var oldTarget = this.order.data.target;
|
||||
@@ -1580,6 +1627,13 @@ var UnitFsmSpec = {
|
||||
// We reached the target - start gathering from it now
|
||||
this.SetNextState("GATHERING");
|
||||
},
|
||||
|
||||
"leave": function() {
|
||||
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
|
||||
if (cmpSupply)
|
||||
cmpSupply.RemoveGatherer(this.entity);
|
||||
delete this.gatheringTarget;
|
||||
},
|
||||
},
|
||||
|
||||
// Walking to a good place to gather resources near, used by GatherNearPosition
|
||||
@@ -1616,8 +1670,18 @@ var UnitFsmSpec = {
|
||||
|
||||
"GATHERING": {
|
||||
"enter": function() {
|
||||
var target = this.order.data.target;
|
||||
this.gatheringTarget = this.order.data.target; // deleted in "leave".
|
||||
|
||||
if (this.gatheringTarget) {
|
||||
// Check that we can gather from the resource we're supposed to gather from.
|
||||
// Will only be added if we're not already in.
|
||||
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
|
||||
if (!cmpSupply || !cmpSupply.AddGatherer(this.entity))
|
||||
{
|
||||
this.StartTimer(0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// If this order was forced, the player probably gave it, but now we've reached the target
|
||||
// switch to an unforced order (can be interrupted by attacks)
|
||||
this.order.data.force = false;
|
||||
@@ -1626,12 +1690,12 @@ var UnitFsmSpec = {
|
||||
// Calculate timing based on gather rates
|
||||
// This allows the gather rate to control how often we gather, instead of how much.
|
||||
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
|
||||
var rate = cmpResourceGatherer.GetTargetGatherRate(target);
|
||||
var rate = cmpResourceGatherer.GetTargetGatherRate(this.gatheringTarget);
|
||||
|
||||
if (!rate)
|
||||
{
|
||||
// Try to find another target if the current one stopped existing
|
||||
if (!Engine.QueryInterface(target, IID_Identity))
|
||||
if (!Engine.QueryInterface(this.gatheringTarget, IID_Identity))
|
||||
{
|
||||
// Let the Timer logic handle this
|
||||
this.StartTimer(0);
|
||||
@@ -1654,7 +1718,7 @@ var UnitFsmSpec = {
|
||||
// (else it'll look like we're chopping empty air).
|
||||
// (If it's not alive, the Timer handler will deal with sending us
|
||||
// off to a different target.)
|
||||
if (this.CheckTargetRange(target, IID_ResourceGatherer))
|
||||
if (this.CheckTargetRange(this.gatheringTarget, IID_ResourceGatherer))
|
||||
{
|
||||
var typename = "gather_" + this.order.data.type.specific;
|
||||
this.SelectAnimation(typename, false, 1.0, typename);
|
||||
@@ -1664,24 +1728,31 @@ var UnitFsmSpec = {
|
||||
|
||||
"leave": function() {
|
||||
this.StopTimer();
|
||||
|
||||
|
||||
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
|
||||
if (cmpSupply)
|
||||
cmpSupply.RemoveGatherer(this.entity);
|
||||
delete this.gatheringTarget;
|
||||
|
||||
// Show the carried resource, if we've gathered anything.
|
||||
this.SetGathererAnimationOverride();
|
||||
},
|
||||
|
||||
"Timer": function(msg) {
|
||||
var target = this.order.data.target;
|
||||
var resourceTemplate = this.order.data.template;
|
||||
var resourceType = this.order.data.type;
|
||||
|
||||
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
|
||||
|
||||
// Check we can still reach and gather from the target
|
||||
if (this.CheckTargetRange(target, IID_ResourceGatherer) && this.CanGather(target))
|
||||
if (this.CheckTargetRange(this.gatheringTarget, IID_ResourceGatherer) && this.CanGather(this.gatheringTarget) && cmpSupply && cmpSupply.IsAvailable(this.entity))
|
||||
{
|
||||
// Gather the resources:
|
||||
|
||||
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
|
||||
|
||||
// Try to gather treasure
|
||||
if (cmpResourceGatherer.TryInstantGather(target))
|
||||
if (cmpResourceGatherer.TryInstantGather(this.gatheringTarget))
|
||||
return;
|
||||
|
||||
// If we've already got some resources but they're the wrong type,
|
||||
@@ -1690,7 +1761,7 @@ var UnitFsmSpec = {
|
||||
cmpResourceGatherer.DropResources();
|
||||
|
||||
// Collect from the target
|
||||
var status = cmpResourceGatherer.PerformGather(target);
|
||||
var status = cmpResourceGatherer.PerformGather(this.gatheringTarget);
|
||||
|
||||
// If we've collected as many resources as possible,
|
||||
// return to the nearest dropsite
|
||||
@@ -1714,10 +1785,10 @@ var UnitFsmSpec = {
|
||||
if (!status.exhausted)
|
||||
return;
|
||||
}
|
||||
else
|
||||
else if (cmpSupply && cmpSupply.IsAvailable(this.entity))
|
||||
{
|
||||
// Try to follow the target
|
||||
if (this.MoveToTargetRange(target, IID_ResourceGatherer))
|
||||
if (this.MoveToTargetRange(this.gatheringTarget, IID_ResourceGatherer))
|
||||
{
|
||||
this.SetNextState("APPROACHING");
|
||||
return;
|
||||
@@ -2997,7 +3068,7 @@ UnitAI.prototype.FindNearbyResource = function(filter)
|
||||
if (template.indexOf("resource|") != -1)
|
||||
template = template.slice(9);
|
||||
|
||||
if (amount > 0 && filter(ent, type, template))
|
||||
if (amount > 0 && cmpResourceSupply.IsAvailable(this.entity) && filter(ent, type, template))
|
||||
return ent;
|
||||
}
|
||||
|
||||
@@ -4179,7 +4250,9 @@ UnitAI.prototype.CanGather = function(target)
|
||||
|
||||
// No need to verify ownership as we should be able to gather from
|
||||
// a target regardless of ownership.
|
||||
|
||||
// No need to call "cmpResourceSupply.IsAvailable()" either because that
|
||||
// would cause units to walk to full entities instead of choosing another one
|
||||
// nearby to gather from, which is undesirable.
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,3 +3,7 @@ Engine.RegisterInterface("ResourceSupply");
|
||||
// Message of the form { "from": 100, "to", 90 },
|
||||
// sent whenever supply level changes.
|
||||
Engine.RegisterMessageType("ResourceSupplyChanged");
|
||||
|
||||
// Message of the form { "to", [array of gatherers ID] },
|
||||
// sent whenever the number of gatherer changes
|
||||
Engine.RegisterMessageType("ResourceSupplyGatherersChanged");
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<ResourceSupply>
|
||||
<Amount>40</Amount>
|
||||
<Type>food.meat</Type>
|
||||
<MaxGatherers>5</MaxGatherers>
|
||||
</ResourceSupply>
|
||||
<Sound>
|
||||
<SoundGroups>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<ResourceSupply>
|
||||
<Amount>120</Amount>
|
||||
<Type>food.meat</Type>
|
||||
<MaxGatherers>6</MaxGatherers>
|
||||
</ResourceSupply>
|
||||
<VisualActor>
|
||||
<Actor>fauna/goat.xml</Actor>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<KillBeforeGather>false</KillBeforeGather>
|
||||
<Amount>200</Amount>
|
||||
<Type>food.fruit</Type>
|
||||
<MaxGatherers>8</MaxGatherers>
|
||||
</ResourceSupply>
|
||||
<Selectable>
|
||||
<EditorOnly disable=""/>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<KillBeforeGather>false</KillBeforeGather>
|
||||
<Amount>200</Amount>
|
||||
<Type>wood.tree</Type>
|
||||
<MaxGatherers>6</MaxGatherers>
|
||||
</ResourceSupply>
|
||||
<Selectable>
|
||||
<EditorOnly disable=""/>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<KillBeforeGather>false</KillBeforeGather>
|
||||
<Amount>1000</Amount>
|
||||
<Type>metal.ore</Type>
|
||||
<MaxGatherers>8</MaxGatherers>
|
||||
</ResourceSupply>
|
||||
<Sound>
|
||||
<SoundGroups>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
</Position>
|
||||
<ResourceSupply>
|
||||
<Amount>5000</Amount>
|
||||
<MaxGatherers>16</MaxGatherers>
|
||||
</ResourceSupply>
|
||||
<Sound>
|
||||
<SoundGroups>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<KillBeforeGather>false</KillBeforeGather>
|
||||
<Amount>1000</Amount>
|
||||
<Type>stone.rock</Type>
|
||||
<MaxGatherers>8</MaxGatherers>
|
||||
</ResourceSupply>
|
||||
<Sound>
|
||||
<SoundGroups>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
</Position>
|
||||
<ResourceSupply>
|
||||
<Amount>5000</Amount>
|
||||
<MaxGatherers>16</MaxGatherers>
|
||||
</ResourceSupply>
|
||||
<Sound>
|
||||
<SoundGroups>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<KillBeforeGather>false</KillBeforeGather>
|
||||
<Amount>500</Amount>
|
||||
<Type>stone.ruins</Type>
|
||||
<MaxGatherers>1</MaxGatherers>
|
||||
</ResourceSupply>
|
||||
<Selectable>
|
||||
<EditorOnly disable=""/>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<KillBeforeGather>false</KillBeforeGather>
|
||||
<Amount>300</Amount>
|
||||
<Type>treasure.metal</Type>
|
||||
<MaxGatherers>1</MaxGatherers>
|
||||
</ResourceSupply>
|
||||
<Sound>
|
||||
<SoundGroups>
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
<KillBeforeGather>false</KillBeforeGather>
|
||||
<Amount>200</Amount>
|
||||
<Type>food.milk</Type>
|
||||
<MaxGatherers>6</MaxGatherers>
|
||||
</ResourceSupply>
|
||||
<Sound>
|
||||
<SoundGroups>
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
<KillBeforeGather>false</KillBeforeGather>
|
||||
<Amount>2000</Amount>
|
||||
<Type>food.grain</Type>
|
||||
<MaxGatherers>20</MaxGatherers>
|
||||
</ResourceSupply>
|
||||
<Sound>
|
||||
<SoundGroups>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<KillBeforeGather>false</KillBeforeGather>
|
||||
<Amount>1000</Amount>
|
||||
<Type>food.fish</Type>
|
||||
<MaxGatherers>4</MaxGatherers>
|
||||
</ResourceSupply>
|
||||
<Selectable>
|
||||
<Overlay>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<KillBeforeGather>true</KillBeforeGather>
|
||||
<Amount>100</Amount>
|
||||
<Type>food.meat</Type>
|
||||
<MaxGatherers>8</MaxGatherers>
|
||||
</ResourceSupply>
|
||||
<Vision>
|
||||
<Range>20</Range>
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
<KillBeforeGather>true</KillBeforeGather>
|
||||
<Amount>100</Amount>
|
||||
<Type>food.meat</Type>
|
||||
<MaxGatherers>8</MaxGatherers>
|
||||
</ResourceSupply>
|
||||
</Entity>
|
||||
|
||||
@@ -14,5 +14,6 @@
|
||||
<KillBeforeGather>true</KillBeforeGather>
|
||||
<Amount>500</Amount>
|
||||
<Type>food.meat</Type>
|
||||
<MaxGatherers>4</MaxGatherers>
|
||||
</ResourceSupply>
|
||||
</Entity>
|
||||
|
||||
Reference in New Issue
Block a user