add more flexibility to eject units when garrisonHolder is destroyed, fixes #2242

This was SVN commit r14550.
This commit is contained in:
mimo 2014-01-08 18:27:58 +00:00
parent 2d806f81f0
commit ec36222b20
31 changed files with 210 additions and 38 deletions

View File

@ -13,8 +13,11 @@ GarrisonHolder.prototype.Schema =
"<element name='EjectHealth' a:help='Percentage of maximum health below which this holder no longer allows garrisoning'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='EjectEntitiesOnDestroy' a:help='Whether the entity should eject or kill all garrisoned entities on destroy'>" +
"<data type='boolean'/>" +
"<element name='EjectClassesOnDestroy' a:help='Classes of entities to be ejected on destroy. Others are killed'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>" +
"<element name='BuffHeal' a:help='Number of hit points that will be restored to this holder&apos;s garrisoned units each second'>" +
"<ref name='nonNegativeDecimal'/>" +
@ -105,11 +108,6 @@ GarrisonHolder.prototype.GetHealRate = function()
return ApplyValueModificationsToEntity("GarrisonHolder/BuffHeal", +this.template.BuffHeal, this.entity);
};
GarrisonHolder.prototype.EjectEntitiesOnDestroy = function()
{
return this.template.EjectEntitiesOnDestroy == "true";
};
/**
* Set this entity to allow or disallow garrisoning in
* Every component calling this function should do it with its own ID, and as long as one
@ -270,7 +268,15 @@ GarrisonHolder.prototype.Eject = function(entity, forced)
// Find spawning location
var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
var pos = cmpFootprint.PickSpawnPoint(entity);
var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
// If the garrisonHolder is a sinking ship, restrict the location to the intersection of both passabilities
// TODO: should use passability classes to be more generic
if ((!cmpHealth || cmpHealth.GetHitpoints() == 0) && cmpIdentity && cmpIdentity.HasClass("Ship"))
var pos = cmpFootprint.PickSpawnPointBothPass(entity);
else
var pos = cmpFootprint.PickSpawnPoint(entity);
if (pos.y < 0)
{
// Error: couldn't find space satisfying the unit's passability criteria
@ -602,8 +608,13 @@ GarrisonHolder.prototype.EjectOrKill = function(entities)
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
// Eject the units which can be ejected (if not in world, it generally means this holder
// is inside a holder which kills its entities, so do not eject)
if (cmpPosition.IsInWorld() && this.EjectEntitiesOnDestroy())
this.PerformEject(entities, true);
if (cmpPosition.IsInWorld())
{
var cmpGarrisonHolder = this;
var ejectables = entities.filter(function(ent) { return cmpGarrisonHolder.IsEjectable(ent) });
if (ejectables.length)
this.PerformEject(ejectables, false);
}
// And destroy all remaining entities
for each (var entity in entities)
@ -621,5 +632,20 @@ GarrisonHolder.prototype.EjectOrKill = function(entities)
this.UpdateGarrisonFlag();
};
/**
* Checks if an entity is ejectable on destroy if possible
*/
GarrisonHolder.prototype.IsEjectable = function(entity)
{
var ejectableClasses = this.template.EjectClassesOnDestroy._string;
ejectableClasses = ejectableClasses ? ejectableClasses.split(/\s+/) : [];
var entityClasses = (Engine.QueryInterface(entity, IID_Identity)).GetClassesList();
for each (var ejectableClass in ejectableClasses)
if (entityClasses.indexOf(ejectableClass) != -1)
return true;
return false;
};
Engine.RegisterComponentType(IID_GarrisonHolder, "GarrisonHolder", GarrisonHolder);

View File

@ -24,7 +24,7 @@
<List datatype="tokens">Support Infantry</List>
<BuffHeal>1</BuffHeal>
<LoadingRange>5</LoadingRange>
<EjectEntitiesOnDestroy>false</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens" />
</GarrisonHolder>
<Decay>
<Inactive/>

View File

@ -7,7 +7,7 @@
<GarrisonHolder>
<Max>30</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry Cavalry</List>
<BuffHeal>3</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -7,7 +7,7 @@
<GarrisonHolder>
<Max>30</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry Cavalry</List>
<BuffHeal>3</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -7,7 +7,7 @@
<GarrisonHolder>
<Max>30</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry Cavalry</List>
<BuffHeal>3</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -7,7 +7,7 @@
<GarrisonHolder>
<Max>30</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry Cavalry</List>
<BuffHeal>3</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -7,7 +7,7 @@
<GarrisonHolder>
<Max>30</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry Cavalry</List>
<BuffHeal>3</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -16,7 +16,7 @@
<GarrisonHolder>
<Max>5</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry</List>
<BuffHeal>0</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -7,7 +7,7 @@
<GarrisonHolder>
<Max>30</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry Cavalry</List>
<BuffHeal>3</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -7,7 +7,7 @@
<GarrisonHolder>
<Max>30</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry Cavalry</List>
<BuffHeal>3</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -49,7 +49,7 @@
<GarrisonHolder>
<Max>20</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry Cavalry</List>
<BuffHeal>1</BuffHeal>
<LoadingRange>1</LoadingRange>

View File

@ -18,7 +18,7 @@
<Max>3</Max>
<BuffHeal>0</BuffHeal>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support</List>
<LoadingRange>1</LoadingRange>
</GarrisonHolder>

View File

@ -23,7 +23,7 @@
<GarrisonHolder>
<Max>20</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry Cavalry</List>
<BuffHeal>3</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -46,7 +46,7 @@
<GarrisonHolder>
<Max>5</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry</List>
<BuffHeal>0</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -44,7 +44,7 @@
<GarrisonHolder>
<Max>1</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry</List>
<BuffHeal>0</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -41,7 +41,7 @@
<GarrisonHolder>
<Max>5</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry</List>
<BuffHeal>0</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -16,7 +16,7 @@
<GarrisonHolder>
<Max>10</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Infantry Cavalry</List>
<BuffHeal>0</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -16,7 +16,7 @@
<GarrisonHolder>
<Max>1</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Infantry</List>
<BuffHeal>0</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -46,7 +46,7 @@
<GarrisonHolder>
<Max>20</Max>
<EjectHealth>0.075</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry Cavalry Siege</List>
<BuffHeal>0</BuffHeal>
<LoadingRange>6</LoadingRange>

View File

@ -13,7 +13,7 @@
<GarrisonHolder>
<Max>10</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Animal</List>
<BuffHeal>1</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -29,7 +29,7 @@
<GarrisonHolder>
<Max>5</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry Cavalry</List>
<BuffHeal>1</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -33,7 +33,7 @@
<GarrisonHolder>
<Max>20</Max>
<EjectHealth>0</EjectHealth>
<EjectEntitiesOnDestroy>false</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Female Infantry</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry Cavalry</List>
<BuffHeal>1</BuffHeal>
<LoadingRange>10</LoadingRange>

View File

@ -22,7 +22,7 @@
<GarrisonHolder>
<Max>1</Max>
<EjectHealth>0</EjectHealth>
<EjectEntitiesOnDestroy>false</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Female Infantry</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry</List>
<BuffHeal>1</BuffHeal>
<LoadingRange>10</LoadingRange>

View File

@ -14,7 +14,7 @@
<GarrisonHolder>
<Max>15</Max>
<EjectHealth>0</EjectHealth>
<EjectEntitiesOnDestroy>false</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Female Infantry</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry Cavalry</List>
<BuffHeal>1</BuffHeal>
<LoadingRange>10</LoadingRange>

View File

@ -41,7 +41,7 @@
<GarrisonHolder>
<Max>50</Max>
<EjectHealth>0</EjectHealth>
<EjectEntitiesOnDestroy>false</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Female Infantry</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry Cavalry Siege</List>
<BuffHeal>1</BuffHeal>
<LoadingRange>10</LoadingRange>

View File

@ -33,7 +33,7 @@
<GarrisonHolder>
<Max>30</Max>
<EjectHealth>0</EjectHealth>
<EjectEntitiesOnDestroy>false</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Female Infantry</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry Cavalry Siege</List>
<BuffHeal>1</BuffHeal>
<LoadingRange>10</LoadingRange>

View File

@ -48,7 +48,7 @@
<GarrisonHolder>
<Max>5</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry</List>
<BuffHeal>1</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -46,7 +46,7 @@
<GarrisonHolder>
<Max>20</Max>
<EjectHealth>0.1</EjectHealth>
<EjectEntitiesOnDestroy>true</EjectEntitiesOnDestroy>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry</List>
<BuffHeal>0</BuffHeal>
<LoadingRange>2</LoadingRange>

View File

@ -158,7 +158,7 @@ public:
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
if (!cmpPathfinder)
return error;
CFixedVector2D initialPos = cmpPosition->GetPosition2D();
entity_angle_t initialAngle = cmpPosition->GetRotation().Y;
@ -250,6 +250,144 @@ public:
return error;
}
virtual CFixedVector3D PickSpawnPointBothPass(entity_id_t spawned)
{
// Try to find a free space inside and around this footprint
// at the intersection between the footprint passability and the unit passability.
// (useful for example for destroyed ships where the spawning point should be in the intersection
// of the unit and ship passabilities).
// As the overlap between these passabilities regions may be narrow, we need a small step (1 meter)
const CFixedVector3D error(fixed::FromInt(-1), fixed::FromInt(-1), fixed::FromInt(-1));
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
if (!cmpPosition || !cmpPosition->IsInWorld())
return error;
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
if (!cmpObstructionManager)
return error;
entity_pos_t spawnedRadius;
ICmpObstructionManager::tag_t spawnedTag;
CmpPtr<ICmpObstruction> cmpSpawnedObstruction(GetSimContext(), spawned);
if (cmpSpawnedObstruction)
{
spawnedRadius = cmpSpawnedObstruction->GetUnitRadius();
spawnedTag = cmpSpawnedObstruction->GetObstruction();
}
// else use zero radius
// Get passability class from UnitMotion
CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), spawned);
if (!cmpUnitMotion)
return error;
ICmpPathfinder::pass_class_t spawnedPass = cmpUnitMotion->GetPassabilityClass();
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
if (!cmpPathfinder)
return error;
// Get the Footprint entity passability
CmpPtr<ICmpUnitMotion> cmpEntityMotion(GetEntityHandle());
if (!cmpEntityMotion)
return error;
ICmpPathfinder::pass_class_t entityPass = cmpEntityMotion->GetPassabilityClass();
CFixedVector2D initialPos = cmpPosition->GetPosition2D();
entity_angle_t initialAngle = cmpPosition->GetRotation().Y;
// Max spawning distance + 1 (in meters)
const i32 maxSpawningDistance = 13;
if (m_Shape == CIRCLE)
{
// Expand outwards from foundation with a fixed step of 1 meter
for (i32 dist = 0; dist <= maxSpawningDistance; ++dist)
{
// The spawn point should be far enough from this footprint to fit the unit, plus a little gap
entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(1+dist);
entity_pos_t radius = m_Size0 + clearance;
// Try equally-spaced points around the circle in alternating directions, starting from the front
const i32 numPoints = 31 + 2*dist;
for (i32 i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2]
{
entity_angle_t angle = initialAngle + (entity_angle_t::Pi()*2).Multiply(entity_angle_t::FromInt(i)/(int)numPoints);
fixed s, c;
sincos_approx(angle, s, c);
CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius));
SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Z, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS &&
cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Z, spawnedRadius, entityPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS)
return pos; // this position is okay, so return it
}
}
}
else
{
fixed s, c;
sincos_approx(initialAngle, s, c);
// Expand outwards from foundation with a fixed step of 1 meter
for (i32 dist = 0; dist <= maxSpawningDistance; ++dist)
{
// The spawn point should be far enough from this footprint to fit the unit, plus a little gap
entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(1+dist);
for (i32 edge = 0; edge < 4; ++edge)
{
// Compute the direction and length of the current edge
CFixedVector2D dir;
fixed sx, sy;
switch (edge)
{
case 0:
dir = CFixedVector2D(c, -s);
sx = m_Size0;
sy = m_Size1;
break;
case 1:
dir = CFixedVector2D(-s, -c);
sx = m_Size1;
sy = m_Size0;
break;
case 2:
dir = CFixedVector2D(s, c);
sx = m_Size1;
sy = m_Size0;
break;
case 3:
dir = CFixedVector2D(-c, s);
sx = m_Size0;
sy = m_Size1;
break;
}
sx = sx/2 + clearance;
sy = sy/2 + clearance;
// Try equally-spaced (1 meter) points along the edge in alternating directions, starting from the middle
i32 numPoints = 1 + 2*sx.ToInt_RoundToNearest();
CFixedVector2D center = initialPos - dir.Perpendicular().Multiply(sy);
for (i32 i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2]
{
CFixedVector2D pos (center + dir*i);
SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS &&
cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, entityPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS)
return CFixedVector3D(pos.X, fixed::Zero(), pos.Y); // this position is okay, so return it
}
}
}
}
return error;
}
};
REGISTER_COMPONENT_TYPE(Footprint)

View File

@ -62,5 +62,6 @@ CScriptVal ICmpFootprint::GetShape_wrapper()
BEGIN_INTERFACE_WRAPPER(Footprint)
DEFINE_INTERFACE_METHOD_1("PickSpawnPoint", CFixedVector3D, ICmpFootprint, PickSpawnPoint, entity_id_t)
DEFINE_INTERFACE_METHOD_1("PickSpawnPointBothPass", CFixedVector3D, ICmpFootprint, PickSpawnPointBothPass, entity_id_t)
DEFINE_INTERFACE_METHOD_0("GetShape", CScriptVal, ICmpFootprint, GetShape_wrapper)
END_INTERFACE_WRAPPER(Footprint)

View File

@ -64,6 +64,13 @@ public:
*/
virtual CFixedVector3D PickSpawnPoint(entity_id_t spawned) = 0;
/**
* Pick a sensible position to place a newly-spawned entity near this footprint,
* at the intersection between the footprint passability and the entity one.
* @return the X and Z coordinates of the spawn point, with Y = 0; or the special value (-1, -1, -1) if there's no space
*/
virtual CFixedVector3D PickSpawnPointBothPass(entity_id_t spawned) = 0;
DECLARE_INTERFACE_TYPE(Footprint)
};