Implement healing. Fixes #999.

This was SVN commit r11536.
This commit is contained in:
leper 2012-04-17 20:22:13 +00:00
parent 80277e3da9
commit c56f96040e
37 changed files with 697 additions and 41 deletions

View File

@ -8,6 +8,7 @@
<animation file="female/f_walk_01.dae" name="Walk" speed="30"/>
<animation file="female/f_walk_01.dae" name="Run" speed="45"/>
<animation file="female/f_death_01.dae" name="Death" speed="100"/>
<animation file="female/f_salute_01.dae" name="Heal" speed="30"/>
</animations>
<props>
<prop actor="props/units/heads/head_kart_priestess.xml" attachpoint="head"/>

View File

@ -8,6 +8,7 @@
<animation file="biped/inf_staff_walk_a.dae" name="Walk" speed="20"/>
<animation file="infantry/general/death/inf_01.psa" name="Death" speed="400"/>
<animation file="biped/inf_staff_walk_a.dae" name="Run" speed="40"/>
<animation file="female/f_salute_01.dae" name="Heal" speed="30"/>
</animations>
<mesh>skeletal/m_dress_cuffs.dae</mesh>
<props>

View File

@ -7,6 +7,7 @@
<animation file="biped/inf_staff_idle_a.dae" name="Idle" speed="200"/>
<animation file="biped/inf_staff_walk_a.dae" name="Walk" speed="20"/>
<animation file="infantry/general/death/inf_01.psa" name="Death" speed="400"/>
<animation file="female/f_salute_01.dae" name="Heal" speed="30"/>
</animations>
<mesh>skeletal/m_dress_a.pmd</mesh>
<props>

View File

@ -8,6 +8,7 @@
<animation file="female/f_walk_01.dae" name="Walk" speed="30"/>
<animation file="female/f_walk_01.dae" name="Run" speed="45"/>
<animation file="female/f_death_01.dae" name="Death" speed="120"/>
<animation file="female/f_salute_01.dae" name="Heal" speed="30"/>
</animations>
<props>
<prop actor="props/units/heads/head_iber_healer.xml" attachpoint="head"/>

View File

@ -7,6 +7,7 @@
<animation file="biped/inf_staff_idle_a.dae" name="Idle" speed="200"/>
<animation file="biped/inf_staff_walk_a.dae" name="Walk" speed="20"/>
<animation file="infantry/general/death/inf_01.psa" name="Death" speed="400"/>
<animation file="female/f_salute_01.dae" name="Heal" speed="30"/>
</animations>
<mesh>skeletal/m_dress_cuffs.dae</mesh>
<props>

View File

@ -8,6 +8,7 @@
<animation file="biped/inf_staff_walk_a.dae" name="Walk" speed="20"/>
<animation file="biped/inf_staff_walk_a.dae" name="Run" speed="20"/>
<animation file="infantry/general/death/inf_01.psa" name="Death" speed="400"/>
<animation file="female/f_salute_01.dae" name="Heal" speed="30"/>
</animations>
<mesh>skeletal/m_dress_a.pmd</mesh>
<props>

View File

@ -0,0 +1 @@
1 1

View File

@ -299,6 +299,29 @@ function getActionInfo(action, target)
return {"possible": true, "tooltip": tooltip};
}
break;
case "heal":
// The check if the target is unhealable is done by targetState.needsHeal
if (entState.Healer && hasClass(targetState, "Unit") && targetState.needsHeal && (playerOwned || allyOwned))
{
var unhealableClasses = entState.Healer.unhealableClasses;
for each (var unitClass in targetState.identity.classes)
{
if (unhealableClasses.indexOf(unitClass) != -1)
{
return {"possible": false};
}
}
var healableClasses = entState.Healer.healableClasses;
for each (var unitClass in targetState.identity.classes)
{
if (healableClasses.indexOf(unitClass) != -1)
{
return {"possible": true};
}
}
}
break;
case "gather":
if (targetState.resourceSupply)
{
@ -419,6 +442,8 @@ function determineAction(x, y, fromMinimap)
return {"type": "build", "cursor": "action-repair", "target": target};
else if ((actionInfo = getActionInfo("set-rallypoint", target)).possible)
return {"type": "set-rallypoint", "cursor": actionInfo.cursor, "data": actionInfo.data, "position": actionInfo.position};
else if (getActionInfo("heal", target).possible)
return {"type": "heal", "cursor": "action-heal", "target": target};
else if (getActionInfo("attack", target).possible)
return {"type": "attack", "cursor": "action-attack", "target": target};
else if (getActionInfo("unset-rallypoint", target).possible)
@ -1039,6 +1064,12 @@ function doAction(action, ev)
Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
return true;
case "heal":
Engine.PostNetworkCommand({"type": "heal", "entities": selection, "target": action.target, "queued": queued});
// TODO: Play a sound?
// Engine.GuiInterfaceCall("PlaySound", { "name": "order_heal", "entity": selection[0] });
return true;
case "build": // (same command as repair)
case "repair":
Engine.PostNetworkCommand({"type": "repair", "entities": selection, "target": action.target, "autocontinue": true, "queued": queued});

View File

@ -75,7 +75,7 @@ function displaySingle(entState, template)
experienceSize.rtop = 100 - 100 * Math.max(0, Math.min(1, 1.0 * entState.promotion.curr / entState.promotion.req));
experienceBar.size = experienceSize;
var experience = "[font=\"serif-bold-13\"]Experience [/font]" + entState.promotion.curr;
var experience = "[font=\"serif-bold-13\"]Experience [/font]" + Math.floor(entState.promotion.curr);
if (entState.promotion.curr < entState.promotion.req)
experience += "/" + entState.promotion.req;
getGUIObjectByName("experience").tooltip = experience;

View File

@ -320,7 +320,8 @@ GarrisonHolder.prototype.HealTimeout = function(data)
var cmpHealth = Engine.QueryInterface(entity, IID_Health);
if (cmpHealth)
{
if (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints())
// We do not want to heal unhealable units
if (!cmpHealth.IsUnhealable())
cmpHealth.Increase(this.healRate);
}
}

View File

@ -155,6 +155,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
ret.hitpoints = cmpHealth.GetHitpoints();
ret.maxHitpoints = cmpHealth.GetMaxHitpoints();
ret.needsRepair = cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints());
ret.needsHeal = !cmpHealth.IsUnhealable();
}
var cmpAttack = Engine.QueryInterface(ent, IID_Attack);
@ -273,6 +274,15 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
ret.barterMarket = { "prices": cmpBarter.GetPrices() };
}
var cmpHeal = Engine.QueryInterface(ent, IID_Heal);
if (cmpHeal)
{
ret.Healer = {
"unhealableClasses": cmpHeal.GetUnhealableClasses(),
"healableClasses": cmpHeal.GetHealableClasses(),
};
}
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false);

View File

@ -0,0 +1,91 @@
function Heal() {}
Heal.prototype.Schema =
"<a:help>Controls the healing abilities of the unit.</a:help>" +
"<a:example>" +
"<Range>20</Range>" +
"<HP>5</HP>" +
"<Rate>2000</Rate>" +
"<UnhealableClasses datatype=\"tokens\">Cavalry</UnhealableClasses>" +
"<HealableClasses datatype=\"tokens\">Support Infantry</HealableClasses>" +
"</a:example>" +
"<element name='Range' a:help='Range (in metres) where healing is possible'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='HP' a:help='Hitpoints healed per Rate'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='Rate' a:help='A heal is performed every Rate ms'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='UnhealableClasses' a:help='If the target has any of these classes it can not be healed (even if it has a class from HealableClasses)'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>" +
"<element name='HealableClasses' a:help='The target must have one of these classes to be healable'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>";
Heal.prototype.Init = function()
{
};
Heal.prototype.Serialize = null; // we have no dynamic state to save
Heal.prototype.GetTimers = function()
{
var prepare = 1000;
var repeat = +this.template.Rate;
return { "prepare": prepare, "repeat": repeat };
};
Heal.prototype.GetRange = function()
{
var max = +this.template.Range;
var min = 0;
return { "max": max, "min": min };
};
Heal.prototype.GetUnhealableClasses = function()
{
var classes = this.template.UnhealableClasses._string;
// If we have no unhealable classes defined classes is undefined
return classes ? classes.split(/\s+/) : [];
};
Heal.prototype.GetHealableClasses = function()
{
var classes = this.template.HealableClasses._string;
return classes.split(/\s+/);
};
/**
* Heal the target entity. This should only be called after a successful range
* check, and should only be called after GetTimers().repeat msec has passed
* since the last call to PerformHeal.
*/
Heal.prototype.PerformHeal = function(target)
{
var cmpHealth = Engine.QueryInterface(target, IID_Health);
if (!cmpHealth)
return;
var targetState = cmpHealth.Increase(Math.max(0,this.template.HP));
// Add XP
var cmpLoot = Engine.QueryInterface(target, IID_Loot);
var cmpPromotion = Engine.QueryInterface(this.entity, IID_Promotion);
if (targetState!==undefined && cmpLoot && cmpPromotion)
{
// HP healed * XP per HP
cmpPromotion.IncreaseXp((targetState.new-targetState.old)*(cmpLoot.GetXp()/cmpHealth.GetMaxHitpoints()));
}
//TODO we need a sound file
// PlaySound("heal_impact", this.entity);
};
Engine.RegisterComponentType(IID_Heal, "Heal", Heal);

View File

@ -25,7 +25,7 @@ Health.prototype.Schema =
"<value a:help='Remain in the world with 0 health'>remain</value>" +
"</choice>" +
"</element>" +
"<element name='Healable' a:help='Indicates that the entity can be healed by healer units'>" +
"<element name='Unhealable' a:help='Indicates that the entity can not be healed by healer units'>" +
"<data type='boolean'/>" +
"</element>" +
"<element name='Repairable' a:help='Indicates that the entity can be repaired by builder units'>" +
@ -64,6 +64,15 @@ Health.prototype.SetHitpoints = function(value)
var old = this.hitpoints;
this.hitpoints = Math.max(1, Math.min(this.GetMaxHitpoints(), value));
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpRangeManager)
{
if (this.hitpoints < this.GetMaxHitpoints())
cmpRangeManager.SetEntityFlag(this.entity, "injured", true);
else
cmpRangeManager.SetEntityFlag(this.entity, "injured", false);
}
Engine.PostMessage(this.entity, MT_HealthChanged, { "from": old, "to": this.hitpoints });
};
@ -72,6 +81,13 @@ Health.prototype.IsRepairable = function()
return (this.template.Repairable == "true");
};
Health.prototype.IsUnhealable = function()
{
return (this.template.Unhealable == "true"
|| this.GetHitpoints() <= 0
|| this.GetHitpoints() >= this.GetMaxHitpoints());
};
Health.prototype.Kill = function()
{
this.Reduce(this.hitpoints);
@ -80,6 +96,12 @@ Health.prototype.Kill = function()
Health.prototype.Reduce = function(amount)
{
var state = { "killed": false };
if (amount >= 0 && this.hitpoints == this.GetMaxHitpoints())
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpRangeManager)
cmpRangeManager.SetEntityFlag(this.entity, "injured", true);
}
if (amount >= this.hitpoints)
{
// If this is the first time we reached 0, then die.
@ -131,12 +153,21 @@ Health.prototype.Increase = function(amount)
{
// If we're already dead, don't allow resurrection
if (this.hitpoints == 0)
return;
return undefined;
var old = this.hitpoints;
this.hitpoints = Math.min(this.hitpoints + amount, this.GetMaxHitpoints());
if (this.hitpoints == this.GetMaxHitpoints())
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpRangeManager)
cmpRangeManager.SetEntityFlag(this.entity, "injured", false);
}
Engine.PostMessage(this.entity, MT_HealthChanged, { "from": old, "to": this.hitpoints });
// We return the old and the actual hp
return { "old": old, "new": this.hitpoints};
};
//// Private functions ////

View File

@ -21,7 +21,7 @@ Loot.prototype.Serialize = null; // we have no dynamic state to save
Loot.prototype.GetXp = function()
{
return this.template.xp;
return +(this.template.xp || 0);
};
Loot.prototype.GetResources = function()

View File

@ -132,6 +132,10 @@ var UnitFsmSpec = {
// ignore newly-seen units by default
},
"LosHealRangeUpdate": function(msg) {
// ignore newly-seen injured units by default
},
"Attacked": function(msg) {
// ignore attacker
},
@ -289,6 +293,43 @@ var UnitFsmSpec = {
this.FinishOrder();
},
"Order.Heal": function(msg) {
// Check the target is alive
if (!this.TargetIsAlive(this.order.data.target))
{
this.FinishOrder();
return;
}
// Check if the target is in range
if (this.CheckTargetRange(this.order.data.target, IID_Heal))
{
this.StopMoving();
this.SetNextState("INDIVIDUAL.HEAL.HEALING");
return;
}
// If we can't reach the target, but are standing ground,
// then abandon this heal order
if (this.GetStance().respondStandGround && !this.order.data.force)
{
this.FinishOrder();
return;
}
// Try to move within heal range
if (this.MoveToTargetRange(this.order.data.target, IID_Heal))
{
// We've started walking to the given point
this.SetNextState("INDIVIDUAL.HEAL.APPROACHING");
return;
}
// We can't reach the target, and can't move towards it,
// so abandon this heal order
this.FinishOrder();
},
"Order.Gather": function(msg) {
// If the target is still alive, we need to kill it first
@ -414,6 +455,13 @@ var UnitFsmSpec = {
cmpFormation.Disband();
},
"Order.Heal": function(msg) {
// TODO: see notes in Order.Attack
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
cmpFormation.CallMemberFunction("Heal", [msg.data.target, false]);
cmpFormation.Disband();
},
"Order.Repair": function(msg) {
// TODO: see notes in Order.Attack
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
@ -556,6 +604,13 @@ var UnitFsmSpec = {
// remain idle
this.StartTimer(1000);
// If a unit can heal and attack we first want to heal wounded units,
// so check if we are a healer and find whether there's anybody nearby to heal.
// (If anyone approaches later it'll be handled via LosHealRangeUpdate.)
// If anyone in sight gets hurt that will be handled via LosHealRangeUpdate.
if (this.IsHealer() && this.FindNewHealTargets())
return true; // (abort the FSM transition since we may have already switched state)
// If we entered the idle state we must have nothing better to do,
// so immediately check whether there's anybody nearby to attack.
// (If anyone approaches later, it'll be handled via LosRangeUpdate.)
@ -569,6 +624,8 @@ var UnitFsmSpec = {
"leave": function() {
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
rangeMan.DisableActiveQuery(this.losRangeQuery);
if (this.losHealRangeQuery)
rangeMan.DisableActiveQuery(this.losHealRangeQuery);
this.StopTimer();
@ -587,6 +644,10 @@ var UnitFsmSpec = {
}
},
"LosHealRangeUpdate": function(msg) {
this.RespondToHealableEntities(msg.data.added);
},
"Timer": function(msg) {
if (!this.isIdle)
{
@ -1009,6 +1070,124 @@ var UnitFsmSpec = {
},
},
"HEAL": {
"EntityRenamed": function(msg) {
if (this.order.data.target == msg.entity)
this.order.data.target = msg.newentity;
},
"Attacked": function(msg) {
// If we stand ground we will rather die than flee
if (!this.GetStance().respondStandGround)
this.Flee(msg.data.attacker, false);
},
"APPROACHING": {
"enter": function () {
this.SelectAnimation("move");
this.StartTimer(1000, 1000);
},
"leave": function() {
this.StopTimer();
},
"Timer": function(msg) {
if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force))
{
this.StopMoving();
this.FinishOrder();
// Return to our original position
if (this.GetStance().respondHoldGround)
this.WalkToHeldPosition();
}
},
"MoveCompleted": function() {
this.SetNextState("HEALING");
},
},
"HEALING": {
"enter": function() {
var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal);
this.healTimers = cmpHeal.GetTimers();
this.SelectAnimation("heal", false, 1.0, "heal");
this.SetAnimationSync(this.healTimers.prepare, this.healTimers.repeat);
this.StartTimer(this.healTimers.prepare, this.healTimers.repeat);
// TODO if .prepare is short, players can cheat by cycling heal/stop/heal
// to beat the .repeat time; should enforce a minimum time
// see comment in ATTACKING.enter
this.FaceTowardsTarget(this.order.data.target);
},
"leave": function() {
this.StopTimer();
},
"Timer": function(msg) {
var target = this.order.data.target;
// Check the target is still alive and healable
if (this.TargetIsAlive(target) && this.CanHeal(target))
{
// Check if we can still reach the target
if (this.CheckTargetRange(target, IID_Heal))
{
this.FaceTowardsTarget(target);
var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal);
cmpHeal.PerformHeal(target);
return;
}
// Can't reach it - try to chase after it
if (this.ShouldChaseTargetedEntity(target, this.order.data.force))
{
if (this.MoveToTargetRange(target, IID_Heal))
{
this.SetNextState("HEAL.CHASING");
return;
}
}
}
// Can't reach it, healed to max hp or doesn't exist any more - give up
if (this.FinishOrder())
return;
// Heal another one
if (this.FindNewHealTargets())
return;
// Return to our original position
if (this.GetStance().respondHoldGround)
this.WalkToHeldPosition();
},
},
"CHASING": {
"enter": function () {
this.SelectAnimation("move");
this.StartTimer(1000, 1000);
},
"leave": function () {
this.StopTimer();
},
"Timer": function(msg) {
if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force))
{
this.StopMoving();
this.FinishOrder();
// Return to our original position
if (this.GetStance().respondHoldGround)
this.WalkToHeldPosition();
}
},
"MoveCompleted": function () {
this.SetNextState("HEALING");
},
},
},
// Returning to dropsite
"RETURNRESOURCE": {
"APPROACHING": {
@ -1493,6 +1672,11 @@ UnitAI.prototype.IsDomestic = function()
return cmpIdentity.HasClass("Domestic");
};
UnitAI.prototype.IsHealer = function()
{
return Engine.QueryInterface(this.entity, IID_Heal);
};
UnitAI.prototype.IsIdle = function()
{
return this.isIdle;
@ -1521,6 +1705,8 @@ UnitAI.prototype.StateChanged = function()
UnitAI.prototype.OnOwnershipChanged = function(msg)
{
this.SetupRangeQuery();
if (this.IsHealer())
this.SetupHealRangeQuery();
};
UnitAI.prototype.OnDestroy = function()
@ -1532,6 +1718,8 @@ UnitAI.prototype.OnDestroy = function()
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (this.losRangeQuery)
rangeMan.DestroyActiveQuery(this.losRangeQuery);
if (this.losHealRangeQuery)
rangeMan.DestroyActiveQuery(this.losHealRangeQuery);
};
// Set up a range query for all enemy units within LOS range
@ -1564,12 +1752,47 @@ UnitAI.prototype.SetupRangeQuery = function()
}
}
var range = this.GetQueryRange();
var range = this.GetQueryRange(IID_Attack);
this.losRangeQuery = rangeMan.CreateActiveQuery(this.entity, range.min, range.max, players, IID_DamageReceiver);
this.losRangeQuery = rangeMan.CreateActiveQuery(this.entity, range.min, range.max, players, IID_DamageReceiver, rangeMan.GetEntityFlagMask("normal"));
rangeMan.EnableActiveQuery(this.losRangeQuery);
};
// Set up a range query for all own or ally units within LOS range
// which can be healed.
// This should be called whenever our ownership changes.
UnitAI.prototype.SetupHealRangeQuery = function()
{
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var owner = cmpOwnership.GetOwner();
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var playerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (this.losHealRangeQuery)
rangeMan.DestroyActiveQuery(this.losHealRangeQuery);
var players = [owner];
if (owner != -1)
{
// If unit not just killed, get ally players via diplomacy
var cmpPlayer = Engine.QueryInterface(playerMan.GetPlayerByID(owner), IID_Player);
var numPlayers = playerMan.GetNumPlayers();
for (var i = 1; i < numPlayers; ++i)
{
// Exclude gaia and enemies
if (cmpPlayer.IsAlly(i))
players.push(i);
}
}
var range = this.GetQueryRange(IID_Heal);
this.losHealRangeQuery = rangeMan.CreateActiveQuery(this.entity, range.min, range.max, players, IID_Health, rangeMan.GetEntityFlagMask("injured"));
rangeMan.EnableActiveQuery(this.losHealRangeQuery);
};
//// FSM linkage functions ////
UnitAI.prototype.SetNextState = function(state)
@ -1814,6 +2037,8 @@ UnitAI.prototype.OnRangeUpdate = function(msg)
{
if (msg.tag == this.losRangeQuery)
UnitFsm.ProcessMessage(this, {"type": "LosRangeUpdate", "data": msg});
else if (msg.tag == this.losHealRangeQuery)
UnitFsm.ProcessMessage(this, {"type": "LosHealRangeUpdate", "data": msg});
};
//// Helper functions to be called by the FSM ////
@ -2226,6 +2451,27 @@ UnitAI.prototype.RespondToTargetedEntities = function(ents)
return false;
};
/**
* Try to respond to healable entities.
* Returns true if it responded.
*/
UnitAI.prototype.RespondToHealableEntities = function(ents)
{
if (!ents.length)
return false;
for each (var ent in ents)
{
if (this.CanHeal(ent))
{
this.PushOrderFront("Heal", { "target": ent, "force": false });
return true;
}
}
return false;
};
/**
* Returns true if we should stop following the target entity.
*/
@ -2341,6 +2587,7 @@ UnitAI.prototype.ComputeWalkingDistance = function()
case "Flee":
case "LeaveFoundation":
case "Attack":
case "Heal":
case "Gather":
case "ReturnResource":
case "Repair":
@ -2403,6 +2650,11 @@ UnitAI.prototype.Attack = function(target, queued)
{
if (!this.CanAttack(target))
{
// We don't want to let healers walk to the target unit so they can be easily killed.
// Instead we just let them get into healing range.
if (this.IsHealer())
this.MoveToTargetRange(target, IID_Heal);
else
this.WalkToTarget(target, queued);
return;
}
@ -2464,6 +2716,17 @@ UnitAI.prototype.GatherNearPosition = function(x, z, type, queued)
this.AddOrder("GatherNearPosition", { "type": type, "x": x, "z": z }, queued);
}
UnitAI.prototype.Heal = function(target, queued)
{
if (!this.CanHeal(target))
{
this.WalkToTarget(target, queued);
return;
}
this.AddOrder("Heal", { "target": target, "force": true }, queued);
};
UnitAI.prototype.ReturnResource = function(target, queued)
{
if (!this.CanReturnResource(target, true))
@ -2575,7 +2838,7 @@ UnitAI.prototype.SetStance = function(stance)
this.stance = stance;
else
error("UnitAI: Setting to invalid stance '"+stance+"'");
}
};
UnitAI.prototype.SwitchToStance = function(stance)
{
@ -2593,7 +2856,11 @@ UnitAI.prototype.SwitchToStance = function(stance)
// Reset the range query, since the range depends on stance
this.SetupRangeQuery();
}
// Just if we are a healer
// TODO maybe move those two to a SetupRangeQuerys()
if (this.IsHealer())
this.SetupHealRangeQuery();
};
/**
* Resets losRangeQuery, and if there are some targets in range that we can
@ -2614,15 +2881,39 @@ UnitAI.prototype.FindNewTargets = function()
return this.RespondToTargetedEntities(ents);
};
UnitAI.prototype.GetQueryRange = function()
/**
* Resets losHealRangeQuery, and if there are some targets in range that we can heal
* then we start healing and this returns true; otherwise, returns false.
*/
UnitAI.prototype.FindNewHealTargets = function()
{
if (!this.losHealRangeQuery)
return false;
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var ents = rangeMan.ResetActiveQuery(this.losHealRangeQuery);
for each (var ent in ents)
{
if (this.CanHeal(ent))
{
this.PushOrderFront("Heal", { "target": ent, "force": false });
return true;
}
}
// We haven't found any target to heal
return false;
};
UnitAI.prototype.GetQueryRange = function(iid)
{
var ret = { "min": 0, "max": 0 };
if (this.GetStance().respondStandGround)
{
var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);
var cmpRanged = Engine.QueryInterface(this.entity, iid);
if (!cmpRanged)
return ret;
var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());
var range = iid !== IID_Attack ? cmpRanged.GetRange() : cmpRanged.GetRange(cmpRanged.GetBestAttack());
ret.min = range.min;
ret.max = range.max;
}
@ -2636,16 +2927,27 @@ UnitAI.prototype.GetQueryRange = function()
}
else if (this.GetStance().respondHoldGround)
{
var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);
var cmpRanged = Engine.QueryInterface(this.entity, iid);
if (!cmpRanged)
return ret;
var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());
var range = iid !== IID_Attack ? cmpRanged.GetRange() : cmpRanged.GetRange(cmpRanged.GetBestAttack());
var cmpVision = Engine.QueryInterface(this.entity, IID_Vision);
if (!cmpVision)
return ret;
var halfvision = cmpVision.GetRange() / 2;
ret.max = range.max + halfvision;
}
// We probably have stance 'passive' and we wouldn't have a range,
// but as it is the default for healers we need to set it to something sane.
else if (iid === IID_Heal)
{
var cmpRanged = Engine.QueryInterface(this.entity, iid);
if (!cmpRanged)
return ret;
var range = cmpRanged.GetRange();
ret.min = range.min;
ret.max = range.max;
}
return ret;
};
@ -2733,6 +3035,59 @@ UnitAI.prototype.CanGather = function(target)
return true;
};
UnitAI.prototype.CanHeal = function(target)
{
// Formation controllers should always respond to commands
// (then the individual units can make up their own minds)
if (this.IsFormationController())
return true;
// Verify that we're able to respond to Heal commands
var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal);
if (!cmpHeal)
return false;
// Verify that the target is alive
if (!this.TargetIsAlive(target))
return false;
// Verify that the target is owned by the same player as the entity or of an ally
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership || !(IsOwnedByPlayer(cmpOwnership.GetOwner(), target) || IsOwnedByAllyOfPlayer(cmpOwnership.GetOwner(), target)))
return false;
// Verify that the target is not unhealable (or at max health)
var cmpHealth = Engine.QueryInterface(target, IID_Health);
if (!cmpHealth || cmpHealth.IsUnhealable())
return false;
// Verify that the target has no unhealable class
var cmpIdentity = Engine.QueryInterface(target, IID_Identity);
if (!cmpIdentity)
return false;
for each (var unhealableClass in cmpHeal.GetUnhealableClasses())
{
if (cmpIdentity.HasClass(unhealableClass) != -1)
{
return false;
}
}
// Verify that the target is a healable class
var healable = false;
for each (var healableClass in cmpHeal.GetHealableClasses())
{
if (cmpIdentity.HasClass(healableClass) != -1)
{
healable = true;
}
}
if (!healable)
return false;
return true;
};
UnitAI.prototype.CanReturnResource = function(target, checkCarriedResource)
{
// Formation controllers should always respond to commands

View File

@ -0,0 +1 @@
Engine.RegisterInterface("Heal");

View File

@ -5,6 +5,7 @@ Engine.LoadComponentScript("interfaces/BuildLimits.js");
Engine.LoadComponentScript("interfaces/DamageReceiver.js");
Engine.LoadComponentScript("interfaces/Foundation.js");
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/Heal.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/Promotion.js");
Engine.LoadComponentScript("interfaces/RallyPoint.js");
@ -271,6 +272,7 @@ AddMock(10, IID_Health, {
GetHitpoints: function() { return 50; },
GetMaxHitpoints: function() { return 60; },
IsRepairable: function() { return false; },
IsUnhealable: function() { return false; },
});
AddMock(10, IID_Identity, {
@ -304,6 +306,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetEntityState(-1, 10), {
hitpoints: 50,
maxHitpoints: 60,
needsRepair: false,
needsHeal: true,
buildEntities: ["test1", "test2"],
barterMarket: {
prices: { "buy": {"food":150}, "sell": {"food":25} },

View File

@ -4,6 +4,7 @@ Engine.LoadHelperScript("Player.js");
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/DamageReceiver.js");
Engine.LoadComponentScript("interfaces/Formation.js");
Engine.LoadComponentScript("interfaces/Heal.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/ResourceSupply.js");
Engine.LoadComponentScript("interfaces/Timer.js");
@ -33,12 +34,13 @@ function TestFormationExiting(mode)
});
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
CreateActiveQuery: function(ent, minRange, maxRange, players, iid) {
CreateActiveQuery: function(ent, minRange, maxRange, players, iid, flags) {
return 1;
},
EnableActiveQuery: function(id) { },
ResetActiveQuery: function(id) { if (mode == 0) return []; else return [enemy]; },
DisableActiveQuery: function(id) { },
GetEntityFlagMask: function(identifier) { },
});
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {

View File

@ -66,6 +66,20 @@ function ProcessCommand(player, cmd)
});
break;
case "heal":
if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByAllyOfPlayer(player, cmd.target)))
{
// This check is for debugging only!
warn("Invalid command: heal target is not owned by player "+player+" or their ally: "+uneval(cmd));
}
// See UnitAI.CanHeal for target checks
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
GetFormationUnitAIs(entities).forEach(function(cmpUnitAI) {
cmpUnitAI.Heal(cmd.target, cmd.queued);
});
break;
case "repair":
// This covers both repairing damaged buildings, and constructing unfinished foundations
if (g_DebugCommands && !IsOwnedByAllyOfPlayer(player, cmd.target))

View File

@ -33,7 +33,7 @@
<Health>
<DeathType>corpse</DeathType>
<RegenRate>0</RegenRate>
<Healable>false</Healable>
<Unhealable>true</Unhealable>
<Repairable>true</Repairable>
</Health>
<Identity>

View File

@ -30,7 +30,7 @@
<DeathType>corpse</DeathType>
<Max>100</Max>
<RegenRate>0</RegenRate>
<Healable>true</Healable>
<Unhealable>false</Unhealable>
<Repairable>false</Repairable>
</Health>
<Identity>

View File

@ -4,7 +4,7 @@
<Max>100</Max>
<DeathType>remain</DeathType>
<RegenRate>1</RegenRate>
<Healable>false</Healable>
<Unhealable>true</Unhealable>
<Repairable>false</Repairable>
</Health>
<Identity>

View File

@ -7,7 +7,7 @@
<SinkAccel>2.0</SinkAccel>
</Decay>
<Health>
<Healable>false</Healable>
<Unhealable>true</Unhealable>
<Repairable>true</Repairable>
</Health>
<Identity>

View File

@ -5,12 +5,13 @@
<Pierce>2.0</Pierce>
<Crush>2.0</Crush>
</Armour>
<Auras>
<Heal>
<Radius>20</Radius>
<Speed>2000</Speed>
<Range>30</Range>
<HP>5</HP>
<Rate>2000</Rate>
<UnhealableClasses datatype="tokens"/>
<HealableClasses datatype="tokens">Organic</HealableClasses>
</Heal>
</Auras>
<Cost>
<Resources>
<metal>120</metal>
@ -23,8 +24,11 @@
<Identity>
<Classes datatype="tokens">Healer</Classes>
<GenericName>Healer</GenericName>
<Tooltip>Heal units within his Aura. (Not implemented yet)</Tooltip>
<Tooltip>Heals units.</Tooltip>
</Identity>
<Promotion>
<RequiredXp>100</RequiredXp>
</Promotion>
<Sound>
<SoundGroups>
<select>voice/hellenes/civ/civ_male_select.xml</select>

View File

@ -36,7 +36,7 @@
</Resources>
</Cost>
<Health>
<Healable>false</Healable>
<Unhealable>true</Unhealable>
</Health>
<Identity>
<Classes datatype="tokens">Slave</Classes>

View File

@ -6,7 +6,11 @@
<History>Tanit (also spelled TINITH, TINNIT, or TINT), chief goddess of Carthage, equivalent of Astarte. Although she seems to have had some connection with the heavens, she was also a mother goddess, and fertility symbols often accompany representations of her. She was probably the consort of Baal Hammon (or Amon), the chief god of Carthage, and was often given the attribute "face of Baal." Although Tanit did not appear at Carthage before the 5th century BC, she soon eclipsed the more established cult of Baal Hammon and, in the Carthaginian area at least, was frequently listed before him on the monuments. In the worship of Tanit and Baal Hammon, children, probably firstborn, were sacrificed. Ample evidence of the practice has been found west of Carthage in the precinct of Tanit, where a tofet (a sanctuary for the sacrifice of children) was discovered. Tanit was also worshipped on Malta, Sardinia, and in Spain. There is no other reason for giving the Carthaginians a priestess instead of a priest in 0 A.D., although Tanit was the most popular of their two main gods with the people. </History>
<Tooltip>Heal units within her aura. (Not implemented yet)</Tooltip>
<Icon>units/cart_support_healer.png</Icon>
<Rank>Basic</Rank>
</Identity>
<Promotion>
<Entity>units/cart_support_healer_a</Entity>
</Promotion>
<VisualActor>
<Actor>units/carthaginians/healer.xml</Actor>
</VisualActor>

View File

@ -5,7 +5,11 @@
<SpecificName>Druides </SpecificName>
<History>A druid may be one of many different professions; priest, historian, lawyer, judges, teachers, philosophers, poets, composers, musicians, astronomers, prophets, councillors, high craftsmen like a blacksmith, the classes of the 'men of art', and sometimes kings, chieftains, or other politicians. Druids were very hierarchal, with classes and ranks based on the length of their education and what fields they practiced. They learned their trades through mnemonics by way of poetry and songs, as writing was rarely used by Celts outside of prayers on votive objects, or lists of names for migratory records.</History>
<Icon>units/celt_support_healer.png</Icon>
<Rank>Basic</Rank>
</Identity>
<Promotion>
<Entity>units/celt_support_healer_a</Entity>
</Promotion>
<VisualActor>
<Actor>units/celts/healer.xml</Actor>
</VisualActor>

View File

@ -5,7 +5,11 @@
<SpecificName>Hiereús</SpecificName>
<History>The art of medicine was widely practised in Classical Greece. Hippocrates was the first physician to separate religion and superstition from actual medicine, and many others followed his lead.</History>
<Icon>units/hele_support_healer.png</Icon>
<Rank>Basic</Rank>
</Identity>
<Promotion>
<Entity>units/hele_support_healer_a</Entity>
</Promotion>
<VisualActor>
<Actor>units/hellenes/healer.xml</Actor>
</VisualActor>

View File

@ -5,7 +5,11 @@
<SpecificName>Sacerdotisa de Ataekina</SpecificName>
<History> To the best of our knowledge, only one 'temple'-like structure has been found on the Iberian Peninsula dating from the times and the Iberians worshiped their pantheon of gods at small home altars; however, a very special sculptured head and torso was found in a farmer's field around the turn of the 20th century of a personage who was obviously someone of great substance. As the two principal gods, of the many worshiped, were male Endovellikos and female Ataekina, we thought it would be nice to adopt The Lady of Elche as our priestess-healer representing Ataekina. We know from archelogy and the Romans that Ataekina was associated with spring, the changing of seasons, and nature in general. Ataekina also seems to have been associated with the cycle of birth-death-rebirth.</History>
<Icon>units/iber_support_healer.png</Icon>
<Rank>Basic</Rank>
</Identity>
<Promotion>
<Entity>units/iber_support_healer_a</Entity>
</Promotion>
<VisualActor>
<Actor>units/iberians/healer.xml</Actor>
</VisualActor>

View File

@ -6,7 +6,11 @@
<SpecificName>Maguš Mada</SpecificName>
<History>Under both the Medes and later the Persian the tribe of the Magi or the Magians were the masters of religious and oral tradition, comparable to the Levites of the Bible. They were connected to Zoroastrianism, but likely tended to other Iranian cults as well. Aside from religious duties the Magians also functioned as the Great King's bureaucrats and kept his administration running.</History>
<Icon>units/pers_support_healer.png</Icon>
<Rank>Basic</Rank>
</Identity>
<Promotion>
<Entity>units/pers_support_healer_a</Entity>
</Promotion>
<VisualActor>
<Actor>units/persians/healer.xml</Actor>
</VisualActor>

View File

@ -6,7 +6,11 @@
<SpecificName>Pontifex Minoris</SpecificName>
<History>During the Republic, the position of priest was elevated and required a lot of responsibilities, which is why priests were by no means chosen randomly. The position of Pontifex Maximus, the high priest of the Roman religion, was occupied by such prominent figures as Julius Caesar, Marcus Aemilius Lepidus and Augustus.</History>
<Icon>units/rome_support_healer.png</Icon>
<Rank>Basic</Rank>
</Identity>
<Promotion>
<Entity>units/rome_support_healer_a</Entity>
</Promotion>
<VisualActor>
<Actor>units/romans/healer.xml</Actor>
</VisualActor>

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -90,6 +90,16 @@ template<> bool ScriptInterface::FromJSVal<u16>(JSContext* cx, jsval v, u16& out
return true;
}
template<> bool ScriptInterface::FromJSVal<u8>(JSContext* cx, jsval v, u8& out)
{
uint16 ret;
WARN_IF_NOT(JSVAL_IS_NUMBER(v), v);
if (!JS_ValueToUint16(cx, v, &ret))
return false;
out = (u8)ret;
return true;
}
// NOTE: we can't define a jsval specialisation, because that conflicts with integer types
template<> bool ScriptInterface::FromJSVal<CScriptVal>(JSContext* UNUSED(cx), jsval v, CScriptVal& out)
{
@ -196,6 +206,11 @@ template<> jsval ScriptInterface::ToJSVal<u16>(JSContext* UNUSED(cx), const u16&
return INT_TO_JSVAL(val);
}
template<> jsval ScriptInterface::ToJSVal<u8>(JSContext* UNUSED(cx), const u8& val)
{
return INT_TO_JSVAL(val);
}
template<> jsval ScriptInterface::ToJSVal<u32>(JSContext* cx, const u32& val)
{
if (val <= JSVAL_INT_MAX)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -38,7 +38,7 @@ class AutoGCRooter;
// Set the maximum number of function arguments that can be handled
// (This should be as small as possible (for compiler efficiency),
// but as large as necessary for all wrapped functions)
#define SCRIPT_INTERFACE_MAX_ARGS 6
#define SCRIPT_INTERFACE_MAX_ARGS 7
// TODO: what's a good default?
#define DEFAULT_RUNTIME_SIZE 16 * 1024 * 1024

View File

@ -50,6 +50,7 @@ struct Query
u32 ownersMask;
i32 interface;
std::vector<entity_id_t> lastMatch;
u8 flagsMask;
};
/**
@ -69,12 +70,13 @@ static u32 CalcOwnerMask(i32 owner)
*/
struct EntityData
{
EntityData() : retainInFog(0), owner(-1), inWorld(0) { }
EntityData() : retainInFog(0), owner(-1), inWorld(0), flags(1) { }
entity_pos_t x, z;
entity_pos_t visionRange;
u8 retainInFog; // boolean
i8 owner;
u8 inWorld; // boolean
u8 flags; // See GetEntityFlagMask
};
cassert(sizeof(EntityData) == 16);
@ -95,6 +97,7 @@ struct SerializeQuery
serialize.NumberU32_Unbounded("owners mask", value.ownersMask);
serialize.NumberI32_Unbounded("interface", value.interface);
SerializeVector<SerializeU32_Unbounded>()(serialize, "last match", value.lastMatch);
serialize.NumberU8("flagsMask", value.flagsMask, 0, -1);
}
};
@ -112,6 +115,7 @@ struct SerializeEntityData
serialize.NumberU8("retain in fog", value.retainInFog, 0, 1);
serialize.NumberI8_Unbounded("owner", value.owner);
serialize.NumberU8("in world", value.inWorld, 0, 1);
serialize.NumberU8("flags", value.flags, 0, -1);
}
};
@ -514,10 +518,10 @@ public:
virtual tag_t CreateActiveQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange,
std::vector<int> owners, int requiredInterface)
std::vector<int> owners, int requiredInterface, u8 flags)
{
tag_t id = m_QueryNext++;
m_Queries[id] = ConstructQuery(source, minRange, maxRange, owners, requiredInterface);
m_Queries[id] = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flags);
return id;
}
@ -565,7 +569,7 @@ public:
{
PROFILE("ExecuteQuery");
Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface);
Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"));
std::vector<entity_id_t> r;
@ -709,6 +713,10 @@ public:
if (!entity.inWorld)
return false;
// Ignore entities that don't match the current flags
if (!(entity.flags & q.flagsMask))
return false;
// Ignore self
if (id == q.source)
return false;
@ -773,7 +781,7 @@ public:
Query ConstructQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange,
const std::vector<int>& owners, int requiredInterface)
const std::vector<int>& owners, int requiredInterface, u8 flagsMask)
{
// Min range must be non-negative
if (minRange < entity_pos_t::Zero())
@ -794,6 +802,7 @@ public:
q.ownersMask |= CalcOwnerMask(owners[i]);
q.interface = requiredInterface;
q.flagsMask = flagsMask;
return q;
}
@ -860,6 +869,42 @@ public:
collector.Submit(&m_DebugOverlayLines[i]);
}
virtual u8 GetEntityFlagMask(std::string identifier)
{
CStr id = CStr(identifier);
if (id == "normal")
return 1;
if (id == "injured")
return 2;
LOGWARNING(L"CCmpRangeManager: Invalid flag identifier %s", id.c_str());
return 0;
}
virtual void SetEntityFlag(entity_id_t ent, std::string identifier, bool value)
{
std::map<entity_id_t, EntityData>::iterator it = m_EntityData.find(ent);
// We don't have this entity
if (it == m_EntityData.end())
return;
u8 flag = GetEntityFlagMask(identifier);
// We don't have a flag set
if (flag == 0)
{
LOGWARNING(L"CCmpRangeManager: Invalid flag identifier %s for entity %u", identifier.c_str(), ent);
return;
}
if (value)
it->second.flags |= flag;
else
it->second.flags &= !flag;
}
// ****************************************************************
// LOS implementation:

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -35,11 +35,13 @@ std::string ICmpRangeManager::GetLosVisibility_wrapper(entity_id_t ent, int play
BEGIN_INTERFACE_WRAPPER(RangeManager)
DEFINE_INTERFACE_METHOD_5("ExecuteQuery", std::vector<entity_id_t>, ICmpRangeManager, ExecuteQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int)
DEFINE_INTERFACE_METHOD_5("CreateActiveQuery", ICmpRangeManager::tag_t, ICmpRangeManager, CreateActiveQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int)
DEFINE_INTERFACE_METHOD_6("CreateActiveQuery", ICmpRangeManager::tag_t, ICmpRangeManager, CreateActiveQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int, u8)
DEFINE_INTERFACE_METHOD_1("DestroyActiveQuery", void, ICmpRangeManager, DestroyActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_1("EnableActiveQuery", void, ICmpRangeManager, EnableActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_1("DisableActiveQuery", void, ICmpRangeManager, DisableActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_1("ResetActiveQuery", std::vector<entity_id_t>, ICmpRangeManager, ResetActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_3("SetEntityFlag", void, ICmpRangeManager, SetEntityFlag, entity_id_t, std::string, bool)
DEFINE_INTERFACE_METHOD_1("GetEntityFlagMask", u8, ICmpRangeManager, GetEntityFlagMask, std::string)
DEFINE_INTERFACE_METHOD_1("GetEntitiesByPlayer", std::vector<entity_id_t>, ICmpRangeManager, GetEntitiesByPlayer, player_id_t)
DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpRangeManager, SetDebugOverlay, bool)
DEFINE_INTERFACE_METHOD_2("SetLosRevealAll", void, ICmpRangeManager, SetLosRevealAll, player_id_t, bool)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -96,10 +96,11 @@ public:
* @param maxRange non-negative maximum distance in metres (inclusive); or -1.0 to ignore distance.
* @param owners list of player IDs that matching entities may have; -1 matches entities with no owner.
* @param requiredInterface if non-zero, an interface ID that matching entities must implement.
* @param flags if a entity in range has one of the flags set it will show up.
* @return unique non-zero identifier of query.
*/
virtual tag_t CreateActiveQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange, std::vector<int> owners, int requiredInterface) = 0;
entity_pos_t minRange, entity_pos_t maxRange, std::vector<int> owners, int requiredInterface, u8 flags) = 0;
/**
* Destroy a query and clean up resources. This must be called when an entity no longer needs its
@ -142,6 +143,19 @@ public:
*/
virtual void SetDebugOverlay(bool enabled) = 0;
/**
* Returns the mask for the specified identifier.
*/
virtual u8 GetEntityFlagMask(std::string identifier) = 0;
/**
* Set the flag specified by the identifier to the supplied value for the entity
* @param ent the entity whose flags will be modified.
* @param identifier the flag to be modified.
* @param value to which the flag will be set.
*/
virtual void SetEntityFlag(entity_id_t ent, std::string identifier, bool value) = 0;
// LOS interface:
enum ELosState

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -79,4 +79,10 @@
5, \
JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT },
#define DEFINE_INTERFACE_METHOD_6(scriptname, rettype, classname, methodname, arg1, arg2, arg3, arg4, arg5, arg6) \
{ scriptname, \
ScriptInterface::callMethod<rettype, arg1, arg2, arg3, arg4, arg5, arg6, &class_##classname, classname, &classname::methodname>, \
6, \
JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT },
#endif // INCLUDED_INTERFACE_SCRIPTED