historic_bruno
bf55acadaa
Adds some placeholder rubble entities. They currently reuse foundation actors until proper rubble models are created. This was SVN commit r12717.
291 lines
8.8 KiB
JavaScript
291 lines
8.8 KiB
JavaScript
function Health() {}
|
|
|
|
Health.prototype.Schema =
|
|
"<a:help>Deals with hitpoints and death.</a:help>" +
|
|
"<a:example>" +
|
|
"<Max>100</Max>" +
|
|
"<RegenRate>1.0</RegenRate>" +
|
|
"<DeathType>corpse</DeathType>" +
|
|
"</a:example>" +
|
|
"<element name='Max' a:help='Maximum hitpoints'>" +
|
|
"<data type='positiveInteger'/>" +
|
|
"</element>" +
|
|
"<optional>" +
|
|
"<element name='Initial' a:help='Initial hitpoints. Default if unspecified is equal to Max'>" +
|
|
"<data type='positiveInteger'/>" +
|
|
"</element>" +
|
|
"</optional>" +
|
|
"<optional>" +
|
|
"<element name='SpawnEntityOnDeath' a:help='Entity template to spawn when this entity dies. Note: this is different than the corpse, which retains the original entity's appearance'>" +
|
|
"<text/>" +
|
|
"</element>" +
|
|
"</optional>" +
|
|
"<element name='RegenRate' a:help='Hitpoint regeneration rate per second. Not yet implemented'>" +
|
|
"<ref name='nonNegativeDecimal'/>" +
|
|
"</element>" +
|
|
"<element name='DeathType' a:help='Behaviour when the unit dies'>" +
|
|
"<choice>" +
|
|
"<value a:help='Disappear instantly'>vanish</value>" +
|
|
"<value a:help='Turn into a corpse'>corpse</value>" +
|
|
"<value a:help='Remain in the world with 0 health'>remain</value>" +
|
|
"</choice>" +
|
|
"</element>" +
|
|
"<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'>" +
|
|
"<data type='boolean'/>" +
|
|
"</element>";
|
|
|
|
Health.prototype.Init = function()
|
|
{
|
|
// Cache this value so it allows techs to maintain previous health level
|
|
this.maxHitpoints = +this.template.Max;
|
|
// Default to <Initial>, but use <Max> if it's undefined or zero
|
|
// (Allowing 0 initial HP would break our death detection code)
|
|
this.hitpoints = +(this.template.Initial || this.GetMaxHitpoints());
|
|
};
|
|
|
|
//// Interface functions ////
|
|
|
|
/**
|
|
* Returns the current hitpoint value.
|
|
* This is 0 if (and only if) the unit is dead.
|
|
*/
|
|
Health.prototype.GetHitpoints = function()
|
|
{
|
|
return this.hitpoints;
|
|
};
|
|
|
|
Health.prototype.GetMaxHitpoints = function()
|
|
{
|
|
return this.maxHitpoints;
|
|
};
|
|
|
|
Health.prototype.SetHitpoints = function(value)
|
|
{
|
|
// If we're already dead, don't allow resurrection
|
|
if (this.hitpoints == 0)
|
|
return;
|
|
|
|
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 });
|
|
};
|
|
|
|
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);
|
|
};
|
|
|
|
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.
|
|
// (The entity will exist a little while after calling DestroyEntity so this
|
|
// might get called multiple times)
|
|
if (this.hitpoints)
|
|
{
|
|
state.killed = true;
|
|
|
|
PlaySound("death", this.entity);
|
|
|
|
// If SpawnEntityOnDeath is set, spawn the entity
|
|
if(this.template.SpawnEntityOnDeath)
|
|
this.CreateDeathSpawnedEntity();
|
|
|
|
if (this.template.DeathType == "corpse")
|
|
{
|
|
this.CreateCorpse();
|
|
Engine.DestroyEntity(this.entity);
|
|
}
|
|
else if (this.template.DeathType == "vanish")
|
|
{
|
|
Engine.DestroyEntity(this.entity);
|
|
}
|
|
else if (this.template.DeathType == "remain")
|
|
{
|
|
var resource = this.CreateCorpse(true);
|
|
if (resource != INVALID_ENTITY)
|
|
Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: resource });
|
|
Engine.DestroyEntity(this.entity);
|
|
}
|
|
|
|
var old = this.hitpoints;
|
|
this.hitpoints = 0;
|
|
|
|
Engine.PostMessage(this.entity, MT_HealthChanged, { "from": old, "to": this.hitpoints });
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
var old = this.hitpoints;
|
|
this.hitpoints -= amount;
|
|
|
|
Engine.PostMessage(this.entity, MT_HealthChanged, { "from": old, "to": this.hitpoints });
|
|
}
|
|
return state;
|
|
};
|
|
|
|
Health.prototype.Increase = function(amount)
|
|
{
|
|
// If we're already dead, don't allow resurrection
|
|
if (this.hitpoints == 0)
|
|
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 ////
|
|
|
|
Health.prototype.CreateCorpse = function(leaveResources)
|
|
{
|
|
// If the unit died while not in the world, don't create any corpse for it
|
|
// since there's nowhere for the corpse to be placed
|
|
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
|
if (!cmpPosition.IsInWorld())
|
|
return INVALID_ENTITY;
|
|
|
|
// Either creates a static local version of the current entity, or a
|
|
// persistent corpse retaining the ResourceSupply element of the parent.
|
|
var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
|
|
var templateName = cmpTempMan.GetCurrentTemplateName(this.entity);
|
|
var corpse;
|
|
if (leaveResources)
|
|
corpse = Engine.AddEntity("resource|" + templateName);
|
|
else
|
|
corpse = Engine.AddLocalEntity("corpse|" + templateName);
|
|
|
|
// Copy various parameters so it looks just like us
|
|
|
|
var cmpCorpsePosition = Engine.QueryInterface(corpse, IID_Position);
|
|
var pos = cmpPosition.GetPosition();
|
|
cmpCorpsePosition.JumpTo(pos.x, pos.z);
|
|
var rot = cmpPosition.GetRotation();
|
|
cmpCorpsePosition.SetYRotation(rot.y);
|
|
cmpCorpsePosition.SetXZRotation(rot.x, rot.z);
|
|
|
|
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
|
var cmpCorpseOwnership = Engine.QueryInterface(corpse, IID_Ownership);
|
|
cmpCorpseOwnership.SetOwner(cmpOwnership.GetOwner());
|
|
|
|
// Make it fall over
|
|
var cmpCorpseVisual = Engine.QueryInterface(corpse, IID_Visual);
|
|
cmpCorpseVisual.SelectAnimation("death", true, 1.0, "");
|
|
|
|
return corpse;
|
|
};
|
|
|
|
Health.prototype.CreateDeathSpawnedEntity = function()
|
|
{
|
|
// If the unit died while not in the world, don't spawn a death entity for it
|
|
// since there's nowhere for it to be placed
|
|
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
|
if (!cmpPosition.IsInWorld())
|
|
return INVALID_ENTITY;
|
|
|
|
// Create SpawnEntityOnDeath entity
|
|
var spawnedEntity = Engine.AddLocalEntity(this.template.SpawnEntityOnDeath);
|
|
|
|
// Move to same position
|
|
var cmpSpawnedPosition = Engine.QueryInterface(spawnedEntity, IID_Position);
|
|
var pos = cmpPosition.GetPosition();
|
|
cmpSpawnedPosition.JumpTo(pos.x, pos.z);
|
|
var rot = cmpPosition.GetRotation();
|
|
cmpSpawnedPosition.SetYRotation(rot.y);
|
|
cmpSpawnedPosition.SetXZRotation(rot.x, rot.z);
|
|
|
|
return spawnedEntity;
|
|
};
|
|
|
|
Health.prototype.Repair = function(builderEnt, work)
|
|
{
|
|
var damage = this.GetMaxHitpoints() - this.GetHitpoints();
|
|
|
|
// Do nothing if we're already at full hitpoints
|
|
if (damage <= 0)
|
|
return;
|
|
|
|
// Calculate the amount of hitpoints that will be added
|
|
// TODO: what computation should we use?
|
|
// TODO: should we do some diminishing returns thing? (see Foundation.Build)
|
|
var amount = Math.min(damage, work);
|
|
|
|
// TODO: resource costs?
|
|
|
|
// Add hitpoints
|
|
this.Increase(amount);
|
|
|
|
// If we repaired all the damage, send a message to entities
|
|
// to stop repairing this building
|
|
if (amount >= damage)
|
|
{
|
|
Engine.PostMessage(this.entity, MT_ConstructionFinished,
|
|
{ "entity": this.entity, "newentity": this.entity });
|
|
}
|
|
};
|
|
|
|
Health.prototype.OnTechnologyModification = function(msg)
|
|
{
|
|
if (msg.component == "Health")
|
|
{
|
|
var cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
|
|
if (cmpTechnologyManager)
|
|
{
|
|
var oldMaxHitpoints = this.GetMaxHitpoints();
|
|
var newMaxHitpoints = Math.round(cmpTechnologyManager.ApplyModifications("Health/Max", +this.template.Max, this.entity));
|
|
if (oldMaxHitpoints != newMaxHitpoints)
|
|
{
|
|
var newHitpoints = Math.round(this.GetHitpoints() * newMaxHitpoints/oldMaxHitpoints);
|
|
this.maxHitpoints = newMaxHitpoints;
|
|
this.SetHitpoints(newHitpoints);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
Engine.RegisterComponentType(IID_Health, "Health", Health);
|