2010-05-15 23:07:52 +02:00
function GarrisonHolder ( ) { }
GarrisonHolder . prototype . Schema =
2012-03-10 00:33:55 +01:00
"<element name='Max' a:help='Maximum number of entities which can be garrisoned inside this holder'>" +
2010-05-15 23:07:52 +02:00
"<data type='positiveInteger'/>" +
2010-10-24 00:43:15 +02:00
"</element>" +
2012-03-10 00:33:55 +01:00
"<element name='List' a:help='Classes of entities which are allowed to garrison inside this holder (from Identity)'>" +
2010-10-24 00:43:15 +02:00
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>" +
2012-03-10 00:33:55 +01:00
"<element name='EjectHealth' a:help='Percentage of maximum health below which this holder no longer allows garrisoning'>" +
2011-08-17 03:43:23 +02:00
"<ref name='nonNegativeDecimal'/>" +
2010-10-24 00:43:15 +02:00
"</element>" +
2012-03-10 00:33:55 +01:00
"<element name='BuffHeal' a:help='Number of hit points that will be restored to this holder's garrisoned units each second'>" +
2012-03-10 05:08:15 +01:00
"<data type='nonNegativeInteger'/>" +
2012-03-10 00:33:55 +01:00
"</element>" +
"<element name='LoadingRange' a:help='The maximum distance from this holder at which entities are allowed to garrison. Should be about 2.0 for land entities and preferably greater for ships'>" +
"<ref name='nonNegativeDecimal'/>" +
2010-05-15 23:07:52 +02:00
"</element>" ;
2010-10-24 00:43:15 +02:00
/ * *
* Initialize GarrisonHolder Component
* /
GarrisonHolder . prototype . Init = function ( )
{
2011-01-16 00:35:20 +01:00
// Garrisoned Units
2010-10-24 00:43:15 +02:00
this . entities = [ ] ;
this . spaceOccupied = 0 ;
this . timer = undefined ;
} ;
2012-03-10 00:33:55 +01:00
/ * *
* Return range at which entities can garrison here
* /
GarrisonHolder . prototype . GetLoadingRange = function ( )
{
var max = + this . template . LoadingRange ;
return { "max" : max , "min" : 0 } ;
} ;
2010-10-24 00:43:15 +02:00
/ * *
* Return the list of entities garrisoned inside
* /
GarrisonHolder . prototype . GetEntities = function ( )
{
return this . entities ;
2011-05-09 04:02:00 +02:00
} ;
2010-10-24 00:43:15 +02:00
/ * *
* Returns an array of unit classes which can be garrisoned inside this
* particualar entity . Obtained from the entity ' s template
* /
GarrisonHolder . prototype . GetAllowedClassesList = function ( )
{
var string = this . template . List . _string ;
return string . split ( /\s+/ ) ;
} ;
/ * *
* Get Maximum pop which can be garrisoned
* /
GarrisonHolder . prototype . GetCapacity = function ( )
{
2012-05-05 00:51:14 +02:00
var max = + this . template . Max ;
2012-04-26 23:58:05 +02:00
var cmpTechMan = QueryOwnerInterface ( this . entity , IID _TechnologyManager ) ;
2012-05-05 00:51:14 +02:00
if ( cmpTechMan )
max = cmpTechMan . ApplyModifications ( "GarrisonHolder/Max" , max , this . entity ) ;
return max ;
2012-04-26 23:58:05 +02:00
} ;
/ * *
* Get the heal rate with which garrisoned units will be healed
* /
GarrisonHolder . prototype . GetHealRate = function ( )
{
2012-05-05 00:51:14 +02:00
var rate = + this . template . BuffHeal ;
2012-04-26 23:58:05 +02:00
var cmpTechMan = QueryOwnerInterface ( this . entity , IID _TechnologyManager ) ;
2012-05-05 00:51:14 +02:00
if ( cmpTechMan )
rate = cmpTechMan . ApplyModifications ( "GarrisonHolder/BuffHeal" , rate , this . entity ) ;
return rate ;
2010-10-24 00:43:15 +02:00
} ;
2010-11-17 08:30:25 +01:00
/ * *
* Get number of garrisoned units capable of shooting arrows
* Not necessarily archers
* /
GarrisonHolder . prototype . GetGarrisonedArcherCount = function ( )
{
var count = 0 ;
for each ( var entity in this . entities )
{
var cmpIdentity = Engine . QueryInterface ( entity , IID _Identity ) ;
var classes = cmpIdentity . GetClassesList ( ) ;
if ( classes . indexOf ( "Infantry" ) != - 1 || classes . indexOf ( "Ranged" ) != - 1 )
count ++ ;
}
return count ;
2011-05-09 04:02:00 +02:00
} ;
2010-11-17 08:30:25 +01:00
/ * *
* Checks if an entity can be allowed to garrison in the building
2011-05-09 04:02:00 +02:00
* based on its class
2010-11-17 08:30:25 +01:00
* /
GarrisonHolder . prototype . AllowedToGarrison = function ( entity )
{
var allowedClasses = this . GetAllowedClassesList ( ) ;
var entityClasses = ( Engine . QueryInterface ( entity , IID _Identity ) ) . GetClassesList ( ) ;
// Check if the unit is allowed to be garrisoned inside the building
for each ( var allowedClass in allowedClasses )
{
if ( entityClasses . indexOf ( allowedClass ) != - 1 )
{
return true ;
}
}
return false ;
} ;
2010-10-24 00:43:15 +02:00
/ * *
* Garrison a unit inside .
* Returns true if successful , false if not
* The timer for AutoHeal is started here
* /
GarrisonHolder . prototype . Garrison = function ( entity )
{
var entityPopCost = ( Engine . QueryInterface ( entity , IID _Cost ) ) . GetPopCost ( ) ;
var entityClasses = ( Engine . QueryInterface ( entity , IID _Identity ) ) . GetClassesList ( ) ;
2010-10-24 03:14:34 +02:00
2010-10-24 00:43:15 +02:00
if ( ! this . HasEnoughHealth ( ) )
return false ;
2010-10-24 03:14:34 +02:00
// Check if the unit is allowed to be garrisoned inside the building
2010-11-17 08:30:25 +01:00
if ( ! this . AllowedToGarrison ( entity ) )
2010-10-24 00:43:15 +02:00
return false ;
2010-10-24 03:14:34 +02:00
if ( this . GetCapacity ( ) < this . spaceOccupied + 1 )
2010-10-24 00:43:15 +02:00
return false ;
2010-10-24 03:14:34 +02:00
2012-04-26 23:58:05 +02:00
if ( ! this . timer && this . GetHealRate ( ) > 0 )
2010-10-24 00:43:15 +02:00
{
var cmpTimer = Engine . QueryInterface ( SYSTEM _ENTITY , IID _Timer ) ;
this . timer = cmpTimer . SetTimeout ( this . entity , IID _GarrisonHolder , "HealTimeout" , 1000 , { } ) ;
}
2010-10-24 03:14:34 +02:00
var cmpPosition = Engine . QueryInterface ( entity , IID _Position ) ;
2010-10-24 00:43:15 +02:00
if ( cmpPosition )
{
2010-10-24 03:14:34 +02:00
// Actual garrisoning happens here
2010-10-24 00:43:15 +02:00
this . entities . push ( entity ) ;
2010-10-24 03:14:34 +02:00
this . spaceOccupied += 1 ;
2010-10-24 00:43:15 +02:00
cmpPosition . MoveOutOfWorld ( ) ;
2010-12-08 21:12:24 +01:00
this . UpdateGarrisonFlag ( ) ;
2011-11-14 00:23:58 +01:00
Engine . PostMessage ( this . entity , MT _GarrisonedUnitsChanged , { } ) ;
2010-10-24 00:43:15 +02:00
return true ;
}
2010-12-08 21:12:24 +01:00
return false ;
2010-10-24 00:43:15 +02:00
} ;
/ * *
2010-12-08 21:12:24 +01:00
* Simply eject the unit from the garrisoning entity without
* moving it
2011-08-17 03:43:23 +02:00
* Returns true if successful , false if not
2010-10-24 00:43:15 +02:00
* /
2011-08-17 03:43:23 +02:00
GarrisonHolder . prototype . Eject = function ( entity , forced )
2010-10-24 00:43:15 +02:00
{
var entityIndex = this . entities . indexOf ( entity ) ;
2011-09-06 05:11:16 +02:00
if ( entityIndex == - 1 )
{ // Error: invalid entity ID, usually it's already been ejected
return false ; // Fail
}
2011-08-17 03:43:23 +02:00
// Find spawning location
var cmpFootprint = Engine . QueryInterface ( this . entity , IID _Footprint ) ;
var pos = cmpFootprint . PickSpawnPoint ( entity ) ;
if ( pos . y < 0 )
{
// Error: couldn't find space satisfying the unit's passability criteria
if ( forced )
{ // If ejection is forced, we need to continue, so use center of the building
var cmpPosition = Engine . QueryInterface ( this . entity , IID _Position ) ;
pos = cmpPosition . GetPosition ( ) ;
}
else
{ // Fail
return false ;
}
}
2010-10-24 03:14:34 +02:00
this . spaceOccupied -= 1 ;
2010-10-24 00:43:15 +02:00
this . entities . splice ( entityIndex , 1 ) ;
2011-05-11 06:05:05 +02:00
var cmpUnitAI = Engine . QueryInterface ( entity , IID _UnitAI ) ;
if ( cmpUnitAI )
{
cmpUnitAI . Ungarrison ( ) ;
}
2010-10-24 00:43:15 +02:00
var cmpNewPosition = Engine . QueryInterface ( entity , IID _Position ) ;
cmpNewPosition . JumpTo ( pos . x , pos . z ) ;
// TODO: what direction should they face in?
2011-08-17 03:43:23 +02:00
2011-11-14 00:23:58 +01:00
Engine . PostMessage ( this . entity , MT _GarrisonedUnitsChanged , { } ) ;
2011-08-17 03:43:23 +02:00
return true ;
2011-05-09 04:02:00 +02:00
} ;
2010-10-24 00:43:15 +02:00
2010-12-08 21:12:24 +01:00
/ * *
* Order entities to walk to the Rally Point
* /
GarrisonHolder . prototype . OrderWalkToRallyPoint = function ( entities )
{
var cmpOwnership = Engine . QueryInterface ( this . entity , IID _Ownership ) ;
var cmpRallyPoint = Engine . QueryInterface ( this . entity , IID _RallyPoint ) ;
if ( cmpRallyPoint )
2010-10-24 00:43:15 +02:00
{
2012-05-24 20:25:31 +02:00
var rallyPos = cmpRallyPoint . GetPositions ( ) [ 0 ] ;
2010-10-24 00:43:15 +02:00
if ( rallyPos )
{
2012-05-24 20:25:31 +02:00
var commands = GetRallyPointCommands ( cmpRallyPoint , entities ) ;
for each ( var com in commands )
{
ProcessCommand ( cmpOwnership . GetOwner ( ) , com ) ;
}
2010-10-24 00:43:15 +02:00
}
}
2011-05-09 04:02:00 +02:00
} ;
2010-10-24 00:43:15 +02:00
/ * *
2010-12-08 21:12:24 +01:00
* Unload units from the garrisoning entity and order them
* to move to the Rally Point
2011-08-17 03:43:23 +02:00
* Returns true if successful , false if not
2010-10-24 00:43:15 +02:00
* /
2011-08-17 03:43:23 +02:00
GarrisonHolder . prototype . Unload = function ( entity , forced )
2010-10-24 00:43:15 +02:00
{
2011-08-17 03:43:23 +02:00
if ( this . Eject ( entity , forced ) )
{
this . OrderWalkToRallyPoint ( [ entity ] ) ;
this . UpdateGarrisonFlag ( ) ;
return true ;
}
return false ;
2010-10-24 00:43:15 +02:00
} ;
/ * *
* Unload all units from the entity
2011-08-17 03:43:23 +02:00
* Returns true if all successful , false if not
2010-10-24 00:43:15 +02:00
* /
2011-08-17 03:43:23 +02:00
GarrisonHolder . prototype . UnloadAll = function ( forced )
2010-10-24 00:43:15 +02:00
{
2011-08-17 03:43:23 +02:00
// Make copy of entity list
var entities = [ ] ;
for each ( var entity in this . entities )
{
entities . push ( entity ) ;
}
var ejectedEntities = [ ] ;
var success = true ;
2010-10-24 00:43:15 +02:00
for each ( var entity in entities )
{
2011-08-17 03:43:23 +02:00
if ( this . Eject ( entity , forced ) )
{
ejectedEntities . push ( entity ) ;
}
else
{
success = false ;
}
2010-10-24 00:43:15 +02:00
}
2011-08-17 03:43:23 +02:00
this . OrderWalkToRallyPoint ( ejectedEntities ) ;
2010-12-08 21:12:24 +01:00
this . UpdateGarrisonFlag ( ) ;
2011-08-17 03:43:23 +02:00
return success ;
2010-10-24 00:43:15 +02:00
} ;
2010-12-08 20:03:57 +01:00
/ * *
2010-12-08 21:12:24 +01:00
* Used to check if the garrisoning entity ' s health has fallen below
* a certain limit after which all garrisoned units are unloaded
2010-12-08 20:03:57 +01:00
* /
2010-12-08 21:12:24 +01:00
GarrisonHolder . prototype . OnHealthChanged = function ( msg )
2010-12-08 20:03:57 +01:00
{
2010-12-08 21:12:24 +01:00
if ( ! this . HasEnoughHealth ( ) )
{
2011-08-17 03:43:23 +02:00
// We have to be careful of our passability
// ships: not land passable, so assume units have drowned in a shipwreck
// building: land passable, so units can be ejected freely
var classes = ( Engine . QueryInterface ( this . entity , IID _Identity ) ) . GetClassesList ( ) ;
if ( classes . indexOf ( "Ship" ) != - 1 )
{ // Ship - kill all units
for each ( var entity in this . entities )
{
var cmpHealth = Engine . QueryInterface ( entity , IID _Health ) ;
if ( cmpHealth )
{
cmpHealth . Kill ( ) ;
}
}
2012-05-24 20:25:31 +02:00
this . entities = [ ] ;
2011-11-14 00:23:58 +01:00
Engine . PostMessage ( this . entity , MT _GarrisonedUnitsChanged , { } ) ;
2011-08-17 03:43:23 +02:00
}
else
{ // Building - force ejection
this . UnloadAll ( true ) ;
}
2010-12-08 21:12:24 +01:00
}
} ;
2010-12-08 20:03:57 +01:00
2010-12-08 21:12:24 +01:00
/ * *
* Check if this entity has enough health to garrison units inside it
* /
GarrisonHolder . prototype . HasEnoughHealth = function ( )
{
var cmpHealth = Engine . QueryInterface ( this . entity , IID _Health )
var hitpoints = cmpHealth . GetHitpoints ( ) ;
var maxHitpoints = cmpHealth . GetMaxHitpoints ( ) ;
2012-03-10 05:08:15 +01:00
var ejectHitpoints = Math . floor ( ( + this . template . EjectHealth ) * maxHitpoints ) ;
return hitpoints > ejectHitpoints ;
2010-12-08 20:03:57 +01:00
} ;
2010-10-24 00:43:15 +02:00
/ * *
* Called every second . Heals garrisoned units
* /
GarrisonHolder . prototype . HealTimeout = function ( data )
{
if ( this . entities . length == 0 )
{
var cmpTimer = Engine . QueryInterface ( SYSTEM _ENTITY , IID _Timer ) ;
cmpTimer . CancelTimer ( this . timer ) ;
this . timer = undefined ;
}
else
{
for each ( var entity in this . entities )
{
var cmpHealth = Engine . QueryInterface ( entity , IID _Health ) ;
if ( cmpHealth )
{
2012-04-17 22:22:13 +02:00
// We do not want to heal unhealable units
if ( ! cmpHealth . IsUnhealable ( ) )
2012-04-26 23:58:05 +02:00
cmpHealth . Increase ( this . GetHealRate ( ) ) ;
2010-10-24 00:43:15 +02:00
}
}
var cmpTimer = Engine . QueryInterface ( SYSTEM _ENTITY , IID _Timer ) ;
this . timer = cmpTimer . SetTimeout ( this . entity , IID _GarrisonHolder , "HealTimeout" , 1000 , { } ) ;
}
} ;
2010-12-08 21:12:24 +01:00
GarrisonHolder . prototype . UpdateGarrisonFlag = function ( )
{
var cmpVisual = Engine . QueryInterface ( this . entity , IID _Visual ) ;
if ( ! cmpVisual )
return ;
cmpVisual . SelectAnimation ( "garrisoned" , true , 0 , "" ) ;
// TODO: ought to extend ICmpVisual to let us just select variant
// keywords without changing the animation too
if ( this . entities . length )
cmpVisual . SelectAnimation ( "garrisoned" , false , 1.0 , "" ) ;
else
cmpVisual . SelectAnimation ( "idle" , false , 1.0 , "" ) ;
2010-10-24 00:43:15 +02:00
} ;
/ * *
* Cancel timer when destroyed
2010-05-15 23:07:52 +02:00
* /
2010-10-24 00:43:15 +02:00
GarrisonHolder . prototype . OnDestroy = function ( )
{
if ( this . timer )
{
var cmpTimer = Engine . QueryInterface ( SYSTEM _ENTITY , IID _Timer ) ;
cmpTimer . CancelTimer ( this . timer ) ;
}
} ;
2010-05-15 23:07:52 +02:00
2011-05-09 04:02:00 +02:00
/ * *
* If a garrisoned entity is captured , or about to be killed ( so its owner
* changes to '-1' ) , remove it from the building so we only ever contain valid
* entities
* /
GarrisonHolder . prototype . OnGlobalOwnershipChanged = function ( msg )
{
2011-08-17 03:43:23 +02:00
var entityIndex = this . entities . indexOf ( msg . entity ) ;
if ( entityIndex != - 1 )
2011-05-09 04:02:00 +02:00
{
// If the entity is dead, remove it directly instead of ejecting the corpse
var cmpHealth = Engine . QueryInterface ( msg . entity , IID _Health ) ;
if ( cmpHealth && cmpHealth . GetHitpoints ( ) == 0 )
{
2011-08-17 03:43:23 +02:00
this . entities . splice ( entityIndex , 1 ) ;
2011-11-14 00:23:58 +01:00
Engine . PostMessage ( this . entity , MT _GarrisonedUnitsChanged , { } ) ;
2011-05-09 04:02:00 +02:00
}
else
{
2011-08-17 03:43:23 +02:00
// We have to be careful of our passability
// ships: not land passable, assume unit was thrown overboard or something
// building: land passable, unit can be ejected freely
var classes = ( Engine . QueryInterface ( this . entity , IID _Identity ) ) . GetClassesList ( ) ;
if ( classes . indexOf ( "Ship" ) != - 1 )
{ // Ship - kill unit
var cmpHealth = Engine . QueryInterface ( msg . entity , IID _Health ) ;
if ( cmpHealth )
{
cmpHealth . Kill ( ) ;
}
this . entities . splice ( entityIndex , 1 ) ;
2011-11-14 00:23:58 +01:00
Engine . PostMessage ( this . entity , MT _GarrisonedUnitsChanged , { } ) ;
2011-08-17 03:43:23 +02:00
}
else
{ // Building - force ejection
this . Eject ( msg . entity , true ) ;
}
2011-05-09 04:02:00 +02:00
}
}
} ;
/ * *
* Update list of garrisoned entities if one gets renamed ( e . g . by promotion )
* /
2011-05-02 17:03:01 +02:00
GarrisonHolder . prototype . OnGlobalEntityRenamed = function ( msg )
{
2011-08-17 03:43:23 +02:00
var entityIndex = this . entities . indexOf ( msg . entity ) ;
if ( entityIndex != - 1 )
2011-05-02 17:03:01 +02:00
{
2011-08-17 03:43:23 +02:00
this . entities [ entityIndex ] = msg . newentity ;
2011-11-14 00:23:58 +01:00
Engine . PostMessage ( this . entity , MT _GarrisonedUnitsChanged , { } ) ;
2011-05-02 17:03:01 +02:00
}
2011-05-09 04:02:00 +02:00
} ;
2011-05-02 17:03:01 +02:00
2010-05-15 23:07:52 +02:00
Engine . RegisterComponentType ( IID _GarrisonHolder , "GarrisonHolder" , GarrisonHolder ) ;
2011-11-14 00:23:58 +01:00