1
0
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:
wraitii
2013-03-13 20:10:46 +00:00
parent 844f71681a
commit 8ac9f1d8ea
23 changed files with 189 additions and 15 deletions
@@ -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>