Let entities garrison using cmpGarrisonable.

Followup to f4c9305eee.
Moves the logic for garrisoning from cmpGarrisonHolder to the entity
that is being garrisoned.
Also fixes
https://wildfiregames.com/forum/topic/36863-teleportation-feature-in-a24/?do=findComment&comment=418441
while at it (setting garrisoned before transferring orders on
transform).

Differential revision: D3280
Refs: #5906
Comments by: @Stan, @wraitii
This was SVN commit r24977.
This commit is contained in:
Freagarach 2021-03-02 15:06:16 +00:00
parent a64536b45f
commit e18001e897
9 changed files with 206 additions and 96 deletions

View file

@ -184,14 +184,9 @@ TriggerHelper.SpawnGarrisonedUnits = function(entity, template, count, owner)
if (cmpOwnership)
cmpOwnership.SetOwner(owner);
if (cmpGarrisonHolder.Garrison(ent))
{
let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
cmpUnitAI.Autogarrison(entity);
let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);
if (cmpGarrisonable && cmpGarrisonable.Autogarrison(entity))
entities.push(ent);
}
else
error("failed to garrison entity " + ent + " (" + template + ") inside " + entity);
}

View file

@ -164,11 +164,9 @@ GarrisonHolder.prototype.IsAllowedToGarrison = function(entity)
/**
* @param {number} entity - The entityID to garrison.
* @param {boolean} renamed - Whether the entity was renamed.
*
* @return {boolean} - Whether the entity was garrisoned.
*/
GarrisonHolder.prototype.Garrison = function(entity, renamed = false)
GarrisonHolder.prototype.Garrison = function(entity)
{
if (!this.IsAllowedToGarrison(entity))
return false;
@ -176,10 +174,6 @@ GarrisonHolder.prototype.Garrison = function(entity, renamed = false)
if (!this.HasEnoughHealth())
return false;
let cmpGarrisonable = Engine.QueryInterface(entity, IID_Garrisonable);
if (!cmpGarrisonable || !cmpGarrisonable.Garrison(this.entity))
return false;
if (!this.timer && this.GetHealRate() > 0)
{
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
@ -191,8 +185,7 @@ GarrisonHolder.prototype.Garrison = function(entity, renamed = false)
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {
"added": [entity],
"removed": [],
"renamed": renamed
"removed": []
});
return true;
@ -214,14 +207,13 @@ GarrisonHolder.prototype.Eject = function(entity, forced, renamed = false)
return false;
let cmpGarrisonable = Engine.QueryInterface(entity, IID_Garrisonable);
if (!cmpGarrisonable || !cmpGarrisonable.UnGarrison(forced))
if (!cmpGarrisonable || !cmpGarrisonable.UnGarrison(forced, renamed))
return false;
this.entities.splice(entityIndex, 1);
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {
"added": [],
"removed": [entity],
"renamed": renamed
"removed": [entity]
});
return true;
@ -510,24 +502,6 @@ GarrisonHolder.prototype.OnGlobalOwnershipChanged = function(msg)
*/
GarrisonHolder.prototype.OnGlobalEntityRenamed = function(msg)
{
let entityIndex = this.entities.indexOf(msg.entity);
if (entityIndex != -1)
{
this.Eject(msg.entity, true, true);
this.Garrison(msg.newentity, true);
// TurretHolder is not subscribed to GarrisonChanged, so we must inform it explicitly.
// Otherwise a renaming entity may re-occupy another turret instead of its previous one,
// since the message does not know what turret point whas used, which is not wanted.
// Also ensure the TurretHolder receives the message after we process it.
// If it processes it before us we garrison a turret and subsequently
// are hidden by GarrisonHolder again.
// This could be fixed by not requiring a turret to be 'garrisoned'.
let cmpTurretHolder = Engine.QueryInterface(this.entity, IID_TurretHolder);
if (cmpTurretHolder)
cmpTurretHolder.SwapEntities(msg.entity, msg.newentity);
}
if (!this.initGarrison)
return;
@ -540,7 +514,7 @@ GarrisonHolder.prototype.OnGlobalEntityRenamed = function(msg)
}
else
{
entityIndex = this.initGarrison.indexOf(msg.entity);
let entityIndex = this.initGarrison.indexOf(msg.entity);
if (entityIndex != -1)
this.initGarrison[entityIndex] = msg.newentity;
}
@ -630,9 +604,9 @@ GarrisonHolder.prototype.OnGlobalInitGame = function(msg)
for (let ent of this.initGarrison)
{
let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI && cmpUnitAI.CanGarrison(this.entity) && this.Garrison(ent))
cmpUnitAI.Autogarrison(this.entity);
let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);
if (cmpGarrisonable)
cmpGarrisonable.Autogarrison(this.entity);
}
this.initGarrison = undefined;
};
@ -665,4 +639,3 @@ GarrisonHolder.prototype.OnValueModification = function(msg)
};
Engine.RegisterComponentType(IID_GarrisonHolder, "GarrisonHolder", GarrisonHolder);

View file

@ -44,15 +44,29 @@ Garrisonable.prototype.HolderID = function()
return this.holder || INVALID_ENTITY;
};
/**
* @param {number} - The entity ID to check.
* @return {boolean} - Whether we can garrison.
*/
Garrisonable.prototype.CanGarrison = function(entity)
{
let cmpGarrisonHolder = Engine.QueryInterface(entity, IID_GarrisonHolder);
return cmpGarrisonHolder && cmpGarrisonHolder.IsAllowedToGarrison(this.entity);
};
/**
* @param {number} entity - The entity ID of the entity this entity is being garrisoned in.
* @return {boolean} - Whether garrisoning succeeded.
*/
Garrisonable.prototype.Garrison = function(entity)
Garrisonable.prototype.Garrison = function(entity, renamed = false)
{
if (this.holder)
return false;
let cmpGarrisonHolder = Engine.QueryInterface(entity, IID_GarrisonHolder);
if (!cmpGarrisonHolder || !cmpGarrisonHolder.Garrison(this.entity))
return false;
this.holder = entity;
let cmpProductionQueue = Engine.QueryInterface(this.entity, IID_ProductionQueue);
@ -67,14 +81,38 @@ Garrisonable.prototype.Garrison = function(entity)
if (cmpPosition)
cmpPosition.MoveOutOfWorld();
if (renamed)
return true;
let cmpTurretHolder = Engine.QueryInterface(entity, IID_TurretHolder);
if (cmpTurretHolder)
cmpTurretHolder.OccupyTurret(this.entity);
return true;
};
/**
* Called on game init when the entity was part of init garrison.
* @param {number} entity - The entityID to autogarrison.
* @return {boolean} - Whether garrisoning succeeded.
*/
Garrisonable.prototype.Autogarrison = function(entity)
{
if (!this.Garrison(entity))
return false;
let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
if (cmpUnitAI)
cmpUnitAI.Autogarrison(this.entity);
return true;
};
/**
* @param {boolean} forced - Optionally whether the spawning is forced.
* @param {boolean} renamed - Optionally whether the ungarrisoning is due to renaming.
* @return {boolean} - Whether the ungarrisoning succeeded.
*/
Garrisonable.prototype.UnGarrison = function(forced)
Garrisonable.prototype.UnGarrison = function(forced = false, renamed = false)
{
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (cmpPosition)
@ -107,8 +145,42 @@ Garrisonable.prototype.UnGarrison = function(forced)
if (cmpAura && cmpAura.HasGarrisonAura())
cmpAura.RemoveGarrisonAura(this.holder);
if (renamed)
return true;
let cmpTurretHolder = Engine.QueryInterface(this.holder, IID_TurretHolder);
if (cmpTurretHolder)
cmpTurretHolder.LeaveTurret(this.entity);
delete this.holder;
return true;
};
Garrisonable.prototype.OnEntityRenamed = function(msg)
{
if (!this.holder)
return;
let cmpGarrisonHolder = Engine.QueryInterface(this.holder, IID_GarrisonHolder);
if (cmpGarrisonHolder)
{
// ToDo: Clean this by using cmpGarrisonable to ungarrison.
cmpGarrisonHolder.Eject(msg.entity, true, true);
let cmpGarrisonable = Engine.QueryInterface(msg.newentity, IID_Garrisonable);
if (cmpGarrisonable)
cmpGarrisonable.Garrison(this.holder, true);
}
// We process EntityRenamed of turrets here because we need to be sure that we
// receive it after it is processed by GarrisonHolder.js.
// ToDo: Make this not needed by fully separating TurretHolder from GarrisonHolder.
// That means an entity with TurretHolder should not need a GarrisonHolder
// for e.g. the garrisoning logic.
let cmpTurretHolder = Engine.QueryInterface(this.holder, IID_TurretHolder);
if (cmpTurretHolder)
cmpTurretHolder.SwapEntities(msg.entity, msg.newentity);
delete this.holder;
};
Engine.RegisterComponentType(IID_Garrisonable, "Garrisonable", Garrisonable);

View file

@ -238,12 +238,6 @@ class TurretHolder
}
/**
* We process EntityRenamed here because we need to be sure that we receive
* it after it is processed by GarrisonHolder.js.
* ToDo: Make this not needed by fully separating TurretHolder from GarrisonHolder.
* That means an entity with TurretHolder should not need a GarrisonHolder
* for e.g. the garrisoning logic.
*
* @param {number} from - The entity to substitute.
* @param {number} to - The entity to subtitute with.
*/
@ -251,24 +245,10 @@ class TurretHolder
{
let turretPoint = this.GetOccupiedTurret(from);
if (turretPoint)
{
this.LeaveTurret(from, turretPoint);
let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
if (cmpGarrisonHolder && cmpGarrisonHolder.IsGarrisoned(to))
this.OccupyTurret(to, turretPoint);
}
OnGarrisonedUnitsChanged(msg)
{
// Ignore renaming for that is handled seperately
// (i.e. called directly from GarrisonHolder.js).
if (msg.renamed)
return;
for (let entity of msg.removed)
this.LeaveTurret(entity);
for (let entity of msg.added)
this.OccupyTurret(entity);
}
}
/**

View file

@ -3163,8 +3163,8 @@ UnitAI.prototype.UnitFsmSpec = {
if (this.CanGarrison(target))
if (this.CheckGarrisonRange(target))
{
var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder);
if (cmpGarrisonHolder.Garrison(this.entity))
let cmpGarrisonable = Engine.QueryInterface(this.entity, IID_Garrisonable);
if (cmpGarrisonable.Garrison(target))
{
this.isGarrisoned = true;
this.SetImmobile(true);

View file

@ -1,12 +1,13 @@
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/Garrisonable.js");
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/TurretHolder.js");
Engine.LoadComponentScript("GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/ModifiersManager.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("Garrisonable.js");
Engine.LoadComponentScript("GarrisonHolder.js");
const garrisonedEntitiesList = [25, 26, 27, 28, 29, 30, 31, 32, 33];
const garrisonHolderId = 15;
@ -17,6 +18,16 @@ const player = 1;
const friendlyPlayer = 2;
const enemyPlayer = 3;
let cmpGarrisonHolder = ConstructComponent(garrisonHolderId, "GarrisonHolder", {
"Max": 10,
"List": { "_string": "Infantry+Cavalry" },
"EjectHealth": 0.1,
"EjectClassesOnDestroy": { "_string": "Infantry" },
"BuffHeal": 1,
"LoadingRange": 2.1,
"Pickup": false
});
AddMock(garrisonHolderId, IID_Footprint, {
"PickSpawnPointBothPass": entity => new Vector3D(4, 3, 30),
"PickSpawnPoint": entity => new Vector3D(4, 3, 30)
@ -70,7 +81,7 @@ for (let i = 24; i <= 35; ++i)
AddMock(i, IID_Garrisonable, {
"UnitSize": () => 9,
"TotalSize": () => 9,
"Garrison": entity => true,
"Garrison": (entity, renamed) => cmpGarrisonHolder.Garrison(i, renamed),
"UnGarrison": () => true
});
else
@ -96,16 +107,6 @@ AddMock(33, IID_Identity, {
"GetSelectionGroupName": () => "spart_infantry_archer_a"
});
let cmpGarrisonHolder = ConstructComponent(garrisonHolderId, "GarrisonHolder", {
"Max": 10,
"List": { "_string": "Infantry+Cavalry" },
"EjectHealth": 0.1,
"EjectClassesOnDestroy": { "_string": "Infantry" },
"BuffHeal": 1,
"LoadingRange": 2.1,
"Pickup": false
});
let testGarrisonAllowed = function()
{
TS_ASSERT_EQUALS(cmpGarrisonHolder.HasEnoughHealth(), true);
@ -232,7 +233,7 @@ AddMock(siegeEngineId, IID_Ownership, {
AddMock(siegeEngineId, IID_Garrisonable, {
"UnitSize": () => 1,
"TotalSize": () => 1,
"Garrison": entity => true,
"Garrison": (entity, renamed) => cmpGarrisonHolder.Garrison(siegeEngineId, renamed),
"UnGarrison": () => true
});
let cavalryId = 46;
@ -255,15 +256,10 @@ AddMock(cavalryId, IID_Ownership, {
AddMock(cavalryId, IID_Garrisonable, {
"UnitSize": () => 1,
"TotalSize": () => 1,
"Garrison": entity => true,
"Garrison": (entity, renamed) => cmpGarrisonHolder.Garrison(cavalryId, renamed),
"UnGarrison": () => true
});
TS_ASSERT(cmpGarrisonHolder.Garrison(siegeEngineId));
TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 1);
cmpGarrisonHolder.OnGlobalEntityRenamed({
"entity": siegeEngineId,
"newentity": cavalryId
});
TS_ASSERT(cmpGarrisonHolder.Garrison(cavalryId));
TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 1);
// Eject enemy units.

View file

@ -9,8 +9,11 @@ Engine.RegisterGlobal("ApplyValueModificationsToEntity", (prop, oVal, ent) => oV
const garrisonHolderID = 1;
const garrisonableID = 2;
AddMock(garrisonHolderID, IID_GarrisonHolder, {
"Garrison": () => true
});
let size = 1
let size = 1;
let cmpGarrisonable = ConstructComponent(garrisonableID, "Garrisonable", {
"Size": size
});

View file

@ -0,0 +1,92 @@
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadComponentScript("interfaces/Auras.js");
Engine.LoadComponentScript("interfaces/Garrisonable.js");
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/ModifiersManager.js");
Engine.LoadComponentScript("interfaces/ProductionQueue.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/TurretHolder.js");
Engine.LoadComponentScript("interfaces/UnitAI.js");
Engine.LoadComponentScript("Garrisonable.js");
Engine.LoadComponentScript("GarrisonHolder.js");
const player = 1;
const enemyPlayer = 2;
const friendlyPlayer = 3;
const garrison = 10;
const holder = 11;
AddMock(holder, IID_Footprint, {
"PickSpawnPointBothPass": entity => new Vector3D(4, 3, 30),
"PickSpawnPoint": entity => new Vector3D(4, 3, 30)
});
AddMock(holder, IID_Ownership, {
"GetOwner": () => player
});
AddMock(player, IID_Player, {
"IsAlly": id => id != enemyPlayer,
"IsMutualAlly": id => id != enemyPlayer,
"GetPlayerID": () => player
});
AddMock(friendlyPlayer, IID_Player, {
"IsAlly": id => true,
"IsMutualAlly": id => true,
"GetPlayerID": () => friendlyPlayer
});
AddMock(SYSTEM_ENTITY, IID_Timer, {
"SetTimeout": (ent, iid, funcname, time, data) => 1
});
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": id => id
});
AddMock(garrison, IID_Identity, {
"GetClassesList": () => ["Ranged"],
"GetSelectionGroupName": () => "mace_infantry_archer_a"
});
AddMock(garrison, IID_Ownership, {
"GetOwner": () => player
});
AddMock(garrison, IID_Position, {
"GetHeightOffset": () => 0,
"GetPosition": () => new Vector3D(4, 3, 25),
"GetRotation": () => new Vector3D(4, 0, 6),
"JumpTo": (posX, posZ) => {},
"MoveOutOfWorld": () => {},
"SetHeightOffset": height => {}
});
let cmpGarrisonable = ConstructComponent(garrison, "Garrisonable", {
"Size": "1"
});
let cmpGarrisonHolder = ConstructComponent(holder, "GarrisonHolder", {
"Max": "10",
"List": { "_string": "Ranged" },
"EjectHealth": "0.1",
"EjectClassesOnDestroy": { "_string": "Infantry" },
"BuffHeal": "1",
"LoadingRange": "2.1",
"Pickup": "false"
});
TS_ASSERT(cmpGarrisonable.Garrison(holder));
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [garrison]);
cmpGarrisonable.OnEntityRenamed({
"entity": garrison,
"newentity": -1
});
TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 0);
TS_ASSERT(cmpGarrisonable.Garrison(holder));
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [garrison]);

View file

@ -67,6 +67,8 @@ function ChangeEntityTemplate(oldEnt, newTemplate)
cmpNewUnitAI.SetHeldPosition(pos.x, pos.z);
if (cmpUnitAI.GetStanceName())
cmpNewUnitAI.SwitchToStance(cmpUnitAI.GetStanceName());
if (cmpUnitAI.IsGarrisoned())
cmpNewUnitAI.SetGarrisoned();
cmpNewUnitAI.AddOrders(cmpUnitAI.GetOrders());
if (cmpUnitAI.IsGuardOf())
{
@ -78,8 +80,6 @@ function ChangeEntityTemplate(oldEnt, newTemplate)
cmpNewUnitAI.SetGuardOf(guarded);
}
}
if (cmpUnitAI.IsGarrisoned())
cmpNewUnitAI.SetGarrisoned();
}
let cmpPromotion = Engine.QueryInterface(oldEnt, IID_Promotion);
@ -255,11 +255,10 @@ function TransferGarrisonedUnits(oldEnt, newEnt)
cmpOldGarrison.Eject(ent);
if (!cmpNewGarrison)
continue;
let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (!cmpUnitAI)
let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);
if (!cmpGarrisonable)
continue;
cmpUnitAI.Autogarrison(newEnt);
cmpNewGarrison.Garrison(ent);
cmpGarrisonable.Autogarrison(newEnt);
}
}