2010-02-05 23:00:39 +01:00
function Health ( ) { }
2010-04-09 21:02:39 +02:00
Health . prototype . Schema =
2010-04-23 18:09:03 +02:00
"<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'>" +
2010-04-09 21:02:39 +02:00
"<data type='positiveInteger'/>" +
"</element>" +
"<optional>" +
2010-04-23 18:09:03 +02:00
"<element name='Initial' a:help='Initial hitpoints. Default if unspecified is equal to Max'>" +
2010-04-09 21:02:39 +02:00
"<data type='positiveInteger'/>" +
"</element>" +
"</optional>" +
2012-09-29 00:02:13 +02:00
"<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>" +
2010-08-21 23:53:51 +02:00
"<element name='RegenRate' a:help='Hitpoint regeneration rate per second. Not yet implemented'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
2010-10-06 23:37:55 +02:00
"<element name='DeathType' a:help='Behaviour when the unit dies'>" +
2010-04-23 18:09:03 +02:00
"<choice>" +
"<value a:help='Disappear instantly'>vanish</value>" +
"<value a:help='Turn into a corpse'>corpse</value>" +
2010-10-06 23:37:55 +02:00
"<value a:help='Remain in the world with 0 health'>remain</value>" +
2010-04-23 18:09:03 +02:00
"</choice>" +
2010-05-15 23:07:52 +02:00
"</element>" +
2012-04-17 22:22:13 +02:00
"<element name='Unhealable' a:help='Indicates that the entity can not be healed by healer units'>" +
2010-08-21 23:53:51 +02:00
"<data type='boolean'/>" +
"</element>" +
"<element name='Repairable' a:help='Indicates that the entity can be repaired by builder units'>" +
"<data type='boolean'/>" +
"</element>" ;
2010-04-09 21:02:39 +02:00
2010-02-05 23:00:39 +01:00
Health . prototype . Init = function ( )
{
2012-09-23 21:24:43 +02:00
// Cache this value so it allows techs to maintain previous health level
this . maxHitpoints = + this . template . Max ;
2010-03-12 22:41:40 +01:00
// 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 ( ) ) ;
2010-02-05 23:00:39 +01:00
} ;
2010-03-07 21:14:30 +01:00
//// Interface functions ////
2010-10-06 23:37:55 +02:00
/ * *
* Returns the current hitpoint value .
* This is 0 if ( and only if ) the unit is dead .
* /
2010-02-05 23:00:39 +01:00
Health . prototype . GetHitpoints = function ( )
{
return this . hitpoints ;
} ;
2010-02-28 22:45:09 +01:00
Health . prototype . GetMaxHitpoints = function ( )
{
2012-09-23 21:24:43 +02:00
return this . maxHitpoints ;
2010-02-28 22:45:09 +01:00
} ;
2010-03-12 22:41:40 +01:00
Health . prototype . SetHitpoints = function ( value )
{
// If we're already dead, don't allow resurrection
if ( this . hitpoints == 0 )
return ;
2010-10-02 14:41:29 +02:00
var old = this . hitpoints ;
2010-03-12 22:41:40 +01:00
this . hitpoints = Math . max ( 1 , Math . min ( this . GetMaxHitpoints ( ) , value ) ) ;
2012-04-17 22:22:13 +02:00
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 ) ;
}
2010-10-02 14:41:29 +02:00
Engine . PostMessage ( this . entity , MT _HealthChanged , { "from" : old , "to" : this . hitpoints } ) ;
2010-08-01 19:38:01 +02:00
} ;
2010-08-21 23:53:51 +02:00
Health . prototype . IsRepairable = function ( )
{
return ( this . template . Repairable == "true" ) ;
} ;
2012-04-17 22:22:13 +02:00
Health . prototype . IsUnhealable = function ( )
{
return ( this . template . Unhealable == "true"
|| this . GetHitpoints ( ) <= 0
|| this . GetHitpoints ( ) >= this . GetMaxHitpoints ( ) ) ;
} ;
2010-08-01 19:38:01 +02:00
Health . prototype . Kill = function ( )
{
this . Reduce ( this . hitpoints ) ;
} ;
2010-03-12 22:41:40 +01:00
2010-02-05 23:00:39 +01:00
Health . prototype . Reduce = function ( amount )
{
2010-11-12 23:24:49 +01:00
var state = { "killed" : false } ;
2012-04-17 22:22:13 +02:00
if ( amount >= 0 && this . hitpoints == this . GetMaxHitpoints ( ) )
{
var cmpRangeManager = Engine . QueryInterface ( SYSTEM _ENTITY , IID _RangeManager ) ;
if ( cmpRangeManager )
cmpRangeManager . SetEntityFlag ( this . entity , "injured" , true ) ;
}
2010-02-05 23:00:39 +01:00
if ( amount >= this . hitpoints )
{
2010-03-07 21:14:30 +01:00
// 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 )
{
2010-11-12 23:24:49 +01:00
state . killed = true ;
2012-09-29 00:02:13 +02:00
2010-04-04 23:24:39 +02:00
PlaySound ( "death" , this . entity ) ;
2012-09-29 00:02:13 +02:00
// If SpawnEntityOnDeath is set, spawn the entity
if ( this . template . SpawnEntityOnDeath )
this . CreateDeathSpawnedEntity ( ) ;
2010-03-12 22:41:40 +01:00
if ( this . template . DeathType == "corpse" )
2010-10-06 23:37:55 +02:00
{
2010-03-12 22:41:40 +01:00
this . CreateCorpse ( ) ;
2010-10-06 23:37:55 +02:00
Engine . DestroyEntity ( this . entity ) ;
}
else if ( this . template . DeathType == "vanish" )
{
Engine . DestroyEntity ( this . entity ) ;
}
else if ( this . template . DeathType == "remain" )
{
2012-08-20 03:38:39 +02:00
var resource = this . CreateCorpse ( true ) ;
if ( resource != INVALID _ENTITY )
Engine . BroadcastMessage ( MT _EntityRenamed , { entity : this . entity , newentity : resource } ) ;
Engine . DestroyEntity ( this . entity ) ;
2010-10-06 23:37:55 +02:00
}
2010-10-02 14:41:29 +02:00
var old = this . hitpoints ;
this . hitpoints = 0 ;
2012-09-29 00:02:13 +02:00
2010-10-02 14:41:29 +02:00
Engine . PostMessage ( this . entity , MT _HealthChanged , { "from" : old , "to" : this . hitpoints } ) ;
2010-03-07 21:14:30 +01:00
}
2010-02-05 23:00:39 +01:00
}
else
{
2010-10-02 14:41:29 +02:00
var old = this . hitpoints ;
2010-02-05 23:00:39 +01:00
this . hitpoints -= amount ;
2010-10-02 14:41:29 +02:00
Engine . PostMessage ( this . entity , MT _HealthChanged , { "from" : old , "to" : this . hitpoints } ) ;
2010-02-05 23:00:39 +01:00
}
2013-01-20 23:47:59 +01:00
state . change = this . hitpoints - old ;
2010-11-12 23:24:49 +01:00
return state ;
2010-08-01 19:38:01 +02:00
} ;
2010-02-05 23:00:39 +01:00
2010-03-12 22:41:40 +01:00
Health . prototype . Increase = function ( amount )
{
// If we're already dead, don't allow resurrection
if ( this . hitpoints == 0 )
2012-04-17 22:22:13 +02:00
return undefined ;
2010-03-12 22:41:40 +01:00
2010-10-02 14:41:29 +02:00
var old = this . hitpoints ;
2010-03-12 22:41:40 +01:00
this . hitpoints = Math . min ( this . hitpoints + amount , this . GetMaxHitpoints ( ) ) ;
2012-04-17 22:22:13 +02:00
if ( this . hitpoints == this . GetMaxHitpoints ( ) )
{
var cmpRangeManager = Engine . QueryInterface ( SYSTEM _ENTITY , IID _RangeManager ) ;
if ( cmpRangeManager )
cmpRangeManager . SetEntityFlag ( this . entity , "injured" , false ) ;
}
2010-10-02 14:41:29 +02:00
Engine . PostMessage ( this . entity , MT _HealthChanged , { "from" : old , "to" : this . hitpoints } ) ;
2012-04-17 22:22:13 +02:00
// We return the old and the actual hp
return { "old" : old , "new" : this . hitpoints } ;
2010-08-01 19:38:01 +02:00
} ;
2010-03-12 22:41:40 +01:00
2010-03-07 21:14:30 +01:00
//// Private functions ////
2012-08-20 03:38:39 +02:00
Health . prototype . CreateCorpse = function ( leaveResources )
2010-03-07 21:14:30 +01:00
{
2011-05-09 04:02:00 +02:00
// 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 ( ) )
2012-08-20 03:38:39 +02:00
return INVALID _ENTITY ;
2011-05-09 04:02:00 +02:00
2012-08-20 03:38:39 +02:00
// Either creates a static local version of the current entity, or a
// persistent corpse retaining the ResourceSupply element of the parent.
2010-03-07 21:14:30 +01:00
var cmpTempMan = Engine . QueryInterface ( SYSTEM _ENTITY , IID _TemplateManager ) ;
var templateName = cmpTempMan . GetCurrentTemplateName ( this . entity ) ;
2012-08-20 03:38:39 +02:00
var corpse ;
if ( leaveResources )
corpse = Engine . AddEntity ( "resource|" + templateName ) ;
else
corpse = Engine . AddLocalEntity ( "corpse|" + templateName ) ;
2010-03-07 21:14:30 +01:00
// 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 ( ) ) ;
2013-02-09 08:10:50 +01:00
var cmpVisual = Engine . QueryInterface ( this . entity , IID _Visual ) ;
2010-03-07 21:14:30 +01:00
var cmpCorpseVisual = Engine . QueryInterface ( corpse , IID _Visual ) ;
2013-02-09 08:10:50 +01:00
cmpCorpseVisual . SetActorSeed ( cmpVisual . GetActorSeed ( ) ) ;
// Make it fall over
2010-05-09 15:56:06 +02:00
cmpCorpseVisual . SelectAnimation ( "death" , true , 1.0 , "" ) ;
2012-08-20 03:38:39 +02:00
return corpse ;
2010-03-07 21:14:30 +01:00
} ;
2012-09-29 00:02:13 +02:00
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 ;
} ;
2010-08-21 23:53:51 +02:00
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 } ) ;
}
} ;
2010-03-07 21:14:30 +01:00
2012-09-23 21:24:43 +02:00
Health . prototype . OnTechnologyModification = function ( msg )
{
if ( msg . component == "Health" )
{
var cmpTechnologyManager = QueryOwnerInterface ( this . entity , IID _TechnologyManager ) ;
if ( cmpTechnologyManager )
{
var oldMaxHitpoints = this . GetMaxHitpoints ( ) ;
2013-01-08 01:00:21 +01:00
var newMaxHitpoints = Math . round ( ApplyTechModificationsToEntity ( "Health/Max" , + this . template . Max , this . entity ) ) ;
2012-09-23 21:24:43 +02:00
if ( oldMaxHitpoints != newMaxHitpoints )
{
var newHitpoints = Math . round ( this . GetHitpoints ( ) * newMaxHitpoints / oldMaxHitpoints ) ;
this . maxHitpoints = newMaxHitpoints ;
this . SetHitpoints ( newHitpoints ) ;
}
}
}
} ;
2010-02-05 23:00:39 +01:00
Engine . RegisterComponentType ( IID _Health , "Health" , Health ) ;