# Rewrite unit AI code.

Use HFSM for unit AI.
Support queuing orders.
Automatically attack back when attacked.
Automatically gather from farms after building them.

This was SVN commit r7775.
This commit is contained in:
Ykkrosh 2010-07-21 16:09:58 +00:00
parent 8a1aa101c1
commit 4e5c5e2d8f
12 changed files with 608 additions and 408 deletions

View File

@ -213,6 +213,9 @@ Attack.prototype.CauseDamage = function(data)
if (!cmpDamageReceiver) if (!cmpDamageReceiver)
return; return;
cmpDamageReceiver.TakeDamage(strengths.hack, strengths.pierce, strengths.crush); cmpDamageReceiver.TakeDamage(strengths.hack, strengths.pierce, strengths.crush);
Engine.PostMessage(data.target, MT_Attacked,
{ "attacker": this.entity, "target": data.target });
}; };
Engine.RegisterComponentType(IID_Attack, "Attack", Attack); Engine.RegisterComponentType(IID_Attack, "Attack", Attack);

View File

@ -43,6 +43,7 @@ Builder.prototype.GetRange = function()
/** /**
* Build/repair the target entity. This should only be called after a successful range check. * Build/repair the target entity. This should only be called after a successful range check.
* It should be called at a rate of once per second. * It should be called at a rate of once per second.
* Returns obj with obj.finished==true if this is a repair and it's fully repaired.
*/ */
Builder.prototype.PerformBuilding = function(target) Builder.prototype.PerformBuilding = function(target)
{ {
@ -52,8 +53,8 @@ Builder.prototype.PerformBuilding = function(target)
var cmpFoundation = Engine.QueryInterface(target, IID_Foundation); var cmpFoundation = Engine.QueryInterface(target, IID_Foundation);
if (cmpFoundation) if (cmpFoundation)
{ {
var finished = cmpFoundation.Build(this.entity, rate); cmpFoundation.Build(this.entity, rate);
return { "finished": finished }; return { "finished": false };
} }
else else
{ {

View File

@ -58,8 +58,10 @@ Foundation.prototype.OnDestroy = function()
Foundation.prototype.Build = function(builderEnt, work) Foundation.prototype.Build = function(builderEnt, work)
{ {
// Do nothing if we've already finished building // Do nothing if we've already finished building
// (The entity will be destroyed soon after completion so
// this won't happen much)
if (this.buildProgress == 1.0) if (this.buildProgress == 1.0)
return true; return;
// Calculate the amount of progress that will be added (where 1.0 = completion) // Calculate the amount of progress that will be added (where 1.0 = completion)
var cmpCost = Engine.QueryInterface(this.entity, IID_Cost); var cmpCost = Engine.QueryInterface(this.entity, IID_Cost);
@ -110,13 +112,10 @@ Foundation.prototype.Build = function(builderEnt, work)
var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health); var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health);
cmpBuildingHealth.SetHitpoints(cmpHealth.GetHitpoints()); cmpBuildingHealth.SetHitpoints(cmpHealth.GetHitpoints());
Engine.DestroyEntity(this.entity); Engine.PostMessage(this.entity, MT_ConstructionFinished,
{ "entity": this.entity, "newentity": building });
return true; Engine.DestroyEntity(this.entity);
}
else
{
return false;
} }
}; };

View File

@ -42,7 +42,7 @@ ResourceGatherer.prototype.GetGatherRates = function()
ResourceGatherer.prototype.GetRange = function() ResourceGatherer.prototype.GetRange = function()
{ {
return { "max": 4, "min": 0 }; return { "max": 2, "min": 0 };
// maybe this should depend on the unit or target or something? // maybe this should depend on the unit or target or something?
} }
@ -68,6 +68,10 @@ ResourceGatherer.prototype.PerformGather = function(target)
var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(cmpOwnership.GetOwner()), IID_Player); var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(cmpOwnership.GetOwner()), IID_Player);
cmpPlayer.AddResource(type.generic, status.amount); cmpPlayer.AddResource(type.generic, status.amount);
// Tell the target we're gathering from it
Engine.PostMessage(target, MT_ResourceGather,
{ "entity": target, "gatherer": this.entity });
return status; return status;
}; };

View File

@ -32,14 +32,19 @@ Timer.prototype.OnUpdate = function(msg)
for each (var id in run) for each (var id in run)
{ {
var t = this.timers[id]; var t = this.timers[id];
if (!t)
continue; // an earlier timer might have cancelled this one, so skip it
var cmp = Engine.QueryInterface(t[0], t[1]); var cmp = Engine.QueryInterface(t[0], t[1]);
if (!cmp)
continue; // the entity was probably destroyed
try { try {
var lateness = this.time - t[3]; var lateness = this.time - t[3];
cmp[t[2]](t[4], lateness); cmp[t[2]](t[4], lateness);
} catch (e) { } catch (e) {
var stack = e.stack.trimRight().replace(/^/mg, ' '); // indent the stack trace var stack = e.stack.trimRight().replace(/^/mg, ' '); // indent the stack trace
print("Error in timer on entity "+t[0]+", IID "+t[1]+", function "+t[2]+": "+e+"\n"+stack+"\n"); error("Error in timer on entity "+t[0]+", IID "+t[1]+", function "+t[2]+": "+e+"\n"+stack+"\n");
// TODO: should report in an error log
} }
delete this.timers[id]; delete this.timers[id];
} }

File diff suppressed because it is too large Load Diff

View File

@ -1 +1,6 @@
Engine.RegisterInterface("Attack"); Engine.RegisterInterface("Attack");
// Message sent from Attack to the target entity, each
// time the target is damaged.
// Data: { attacker: 123, target: 234 }
Engine.RegisterMessageType("Attacked");

View File

@ -1 +1,7 @@
Engine.RegisterInterface("Foundation"); Engine.RegisterInterface("Foundation");
// Message sent from Foundation to its own entity when construction
// has been completed.
// Units can watch for this and change task once it's complete.
// Data: { entity: 123, newentity: 234 }
Engine.RegisterMessageType("ConstructionFinished");

View File

@ -1 +1,5 @@
Engine.RegisterInterface("ResourceGatherer"); Engine.RegisterInterface("ResourceGatherer");
// Message sent from ResourceGatherers to a ResourceSupply entity
// each time they gather resources from it
Engine.RegisterMessageType("ResourceGather");

View File

@ -1,5 +1 @@
Engine.RegisterInterface("ResourceSupply"); Engine.RegisterInterface("ResourceSupply");
// Message sent from gatherers to ResourceSupply entities
// when beginning to gather
Engine.RegisterMessageType("ResourceGather");

View File

@ -16,7 +16,7 @@ function ProcessCommand(player, cmd)
{ {
var ai = Engine.QueryInterface(ent, IID_UnitAI); var ai = Engine.QueryInterface(ent, IID_UnitAI);
if (ai) if (ai)
ai.Walk(cmd.x, cmd.z); ai.Walk(cmd.x, cmd.z, cmd.queued);
} }
break; break;
@ -25,7 +25,7 @@ function ProcessCommand(player, cmd)
{ {
var ai = Engine.QueryInterface(ent, IID_UnitAI); var ai = Engine.QueryInterface(ent, IID_UnitAI);
if (ai) if (ai)
ai.Attack(cmd.target); ai.Attack(cmd.target, cmd.queued);
} }
break; break;
@ -35,7 +35,7 @@ function ProcessCommand(player, cmd)
{ {
var ai = Engine.QueryInterface(ent, IID_UnitAI); var ai = Engine.QueryInterface(ent, IID_UnitAI);
if (ai) if (ai)
ai.Repair(cmd.target); ai.Repair(cmd.target, cmd.queued);
} }
break; break;
@ -44,7 +44,7 @@ function ProcessCommand(player, cmd)
{ {
var ai = Engine.QueryInterface(ent, IID_UnitAI); var ai = Engine.QueryInterface(ent, IID_UnitAI);
if (ai) if (ai)
ai.Gather(cmd.target); ai.Gather(cmd.target, cmd.queued);
} }
break; break;
@ -118,7 +118,8 @@ function ProcessCommand(player, cmd)
ProcessCommand(player, { ProcessCommand(player, {
"type": "repair", "type": "repair",
"entities": cmd.entities, "entities": cmd.entities,
"target": ent "target": ent,
"queued": cmd.queued
}); });
break; break;

View File

@ -152,7 +152,7 @@ FSM.prototype.SetNextState = function(obj, state)
FSM.prototype.ProcessMessage = function(obj, msg) FSM.prototype.ProcessMessage = function(obj, msg)
{ {
// print("ProcessMessage(obj, "+uneval(msg)+")\n"); // warn("ProcessMessage(obj, "+uneval(msg)+")");
var func = this.states[obj.fsmStateName][msg.type]; var func = this.states[obj.fsmStateName][msg.type];
if (!func) if (!func)