Index: ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js (revision 25188)
+++ ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js (revision 25189)
@@ -1,538 +1,547 @@
function GarrisonHolder() {}
GarrisonHolder.prototype.Schema =
"" +
"" +
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
/**
+ * Time between heals.
+ */
+GarrisonHolder.prototype.HEAL_TIMEOUT = 1000;
+
+/**
* Initialize GarrisonHolder Component
* Garrisoning when loading a map is set in the script of the map, by setting initGarrison
* which should contain the array of garrisoned entities.
*/
GarrisonHolder.prototype.Init = function()
{
this.entities = [];
this.allowedClasses = ApplyValueModificationsToEntity("GarrisonHolder/List/_string", this.template.List._string, this.entity);
};
/**
* @param {number} entity - The entity to verify.
* @return {boolean} - Whether the given entity is garrisoned in this GarrisonHolder.
*/
GarrisonHolder.prototype.IsGarrisoned = function(entity)
{
return this.entities.indexOf(entity) != -1;
};
/**
* @return {Object} max and min range at which entities can garrison the holder.
*/
GarrisonHolder.prototype.LoadingRange = function()
{
return { "max": +this.template.LoadingRange, "min": 0 };
};
GarrisonHolder.prototype.CanPickup = function(ent)
{
if (!this.template.Pickup || this.IsFull())
return false;
let cmpOwner = Engine.QueryInterface(this.entity, IID_Ownership);
return !!cmpOwner && IsOwnedByPlayer(cmpOwner.GetOwner(), ent);
};
GarrisonHolder.prototype.GetEntities = function()
{
return this.entities;
};
/**
* @return {Array} unit classes which can be garrisoned inside this
* particular entity. Obtained from the entity's template.
*/
GarrisonHolder.prototype.GetAllowedClasses = function()
{
return this.allowedClasses;
};
GarrisonHolder.prototype.GetCapacity = function()
{
return ApplyValueModificationsToEntity("GarrisonHolder/Max", +this.template.Max, this.entity);
};
GarrisonHolder.prototype.IsFull = function()
{
return this.OccupiedSlots() >= this.GetCapacity();
};
GarrisonHolder.prototype.GetHealRate = function()
{
return ApplyValueModificationsToEntity("GarrisonHolder/BuffHeal", +this.template.BuffHeal, this.entity);
};
/**
* Set this entity to allow or disallow garrisoning in the entity.
* Every component calling this function should do it with its own ID, and as long as one
* component doesn't allow this entity to garrison, it can't be garrisoned
* When this entity already contains garrisoned soldiers,
* these will not be able to ungarrison until the flag is set to true again.
*
* This more useful for modern-day features. For example you can't garrison or ungarrison
* a driving vehicle or plane.
* @param {boolean} allow - Whether the entity should be garrisonable.
*/
GarrisonHolder.prototype.AllowGarrisoning = function(allow, callerID)
{
if (!this.allowGarrisoning)
this.allowGarrisoning = new Map();
this.allowGarrisoning.set(callerID, allow);
};
/**
* @return {boolean} - Whether (un)garrisoning is allowed.
*/
GarrisonHolder.prototype.IsGarrisoningAllowed = function()
{
return !this.allowGarrisoning ||
Array.from(this.allowGarrisoning.values()).every(allow => allow);
};
GarrisonHolder.prototype.GetGarrisonedEntitiesCount = function()
{
let count = this.entities.length;
for (let ent of this.entities)
{
let cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
if (cmpGarrisonHolder)
count += cmpGarrisonHolder.GetGarrisonedEntitiesCount();
}
return count;
};
GarrisonHolder.prototype.OccupiedSlots = function()
{
let count = 0;
for (let ent of this.entities)
{
let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);
if (cmpGarrisonable)
count += cmpGarrisonable.TotalSize();
}
return count;
};
GarrisonHolder.prototype.IsAllowedToGarrison = function(entity)
{
if (!this.IsGarrisoningAllowed())
return false;
let cmpGarrisonable = Engine.QueryInterface(entity, IID_Garrisonable);
if (!cmpGarrisonable || this.OccupiedSlots() + cmpGarrisonable.TotalSize() > this.GetCapacity())
return false;
return this.IsAllowedToBeGarrisoned(entity);
};
GarrisonHolder.prototype.IsAllowedToBeGarrisoned = function(entity)
{
if (!IsOwnedByMutualAllyOfEntity(entity, this.entity))
return false;
let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), this.allowedClasses);
};
/**
* @param {number} entity - The entityID to garrison.
* @return {boolean} - Whether the entity was garrisoned.
*/
GarrisonHolder.prototype.Garrison = function(entity)
{
if (!this.IsAllowedToGarrison(entity))
return false;
if (!this.HasEnoughHealth())
return false;
- if (!this.timer && this.GetHealRate() > 0)
- {
- let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
- this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {});
- }
+ if (!this.timer && this.GetHealRate())
+ this.StartTimer();
this.entities.push(entity);
this.UpdateGarrisonFlag();
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {
"added": [entity],
"removed": []
});
return true;
};
/**
* @param {number} entity - The entity ID of the entity to eject.
* @param {boolean} forced - Whether eject is forced (e.g. if building is destroyed).
* @return {boolean} Whether the entity was ejected.
*/
GarrisonHolder.prototype.Eject = function(entity, forced)
{
if (!this.IsGarrisoningAllowed() && !forced)
return false;
let entityIndex = this.entities.indexOf(entity);
// Error: invalid entity ID, usually it's already been ejected, assume success.
if (entityIndex == -1)
return true;
this.entities.splice(entityIndex, 1);
this.UpdateGarrisonFlag();
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {
"added": [],
"removed": [entity]
});
return true;
};
/**
* Tell unit to unload from this entity.
* @param {number} entity - The entity to unload.
* @return {boolean} Whether the command was successful.
*/
GarrisonHolder.prototype.Unload = function(entity)
{
let cmpGarrisonable = Engine.QueryInterface(entity, IID_Garrisonable);
return cmpGarrisonable && cmpGarrisonable.UnGarrison();
};
/**
* Tell units to unload from this entity.
* @param {number[]} entities - The entities to unload.
* @return {boolean} - Whether all unloads were successful.
*/
GarrisonHolder.prototype.UnloadEntities = function(entities)
{
let success = true;
for (let entity of entities)
if (!this.Unload(entity))
success = false;
return success;
};
/**
* Unload one or all units that match a template and owner from us.
* @param {string} template - Type of units that should be ejected.
* @param {number} owner - Id of the player whose units should be ejected.
* @param {boolean} all - Whether all units should be ejected.
* @return {boolean} Whether the unloading was successful.
*/
GarrisonHolder.prototype.UnloadTemplate = function(template, owner, all)
{
let entities = [];
let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
for (let entity of this.entities)
{
let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
// Units with multiple ranks are grouped together.
let name = cmpIdentity.GetSelectionGroupName() || cmpTemplateManager.GetCurrentTemplateName(entity);
if (name != template || owner != Engine.QueryInterface(entity, IID_Ownership).GetOwner())
continue;
entities.push(entity);
// If 'all' is false, only ungarrison the first matched unit.
if (!all)
break;
}
return this.UnloadEntities(entities);
};
/**
* Unload all units, that belong to certain player
* and order all own units to move to the rally point.
* @param {number} owner - Id of the player whose units should be ejected.
* @return {boolean} Whether the unloading was successful.
*/
GarrisonHolder.prototype.UnloadAllByOwner = function(owner)
{
let entities = this.entities.filter(ent => {
let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
return cmpOwnership && cmpOwnership.GetOwner() == owner;
});
return this.UnloadEntities(entities);
};
/**
* Unload all units from the entity and order them to move to the rally point.
* @return {boolean} Whether the unloading was successful.
*/
GarrisonHolder.prototype.UnloadAll = function()
{
return this.UnloadEntities(this.entities.slice());
};
/**
* Used to check if the garrisoning entity's health has fallen below
* a certain limit after which all garrisoned units are unloaded.
*/
GarrisonHolder.prototype.OnHealthChanged = function(msg)
{
if (!this.HasEnoughHealth() && this.entities.length)
this.EjectOrKill(this.entities.slice());
};
GarrisonHolder.prototype.HasEnoughHealth = function()
{
// 0 is a valid value so explicitly check for undefined.
if (this.template.EjectHealth === undefined)
return true;
let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
return !cmpHealth || cmpHealth.GetHitpoints() > Math.floor(+this.template.EjectHealth * cmpHealth.GetMaxHitpoints());
};
+GarrisonHolder.prototype.StartTimer = function()
+{
+ if (this.timer)
+ return;
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ this.timer = cmpTimer.SetInterval(this.entity, IID_GarrisonHolder, "HealTimeout", this.HEAL_TIMEOUT, this.HEAL_TIMEOUT, null);
+};
+
+GarrisonHolder.prototype.StopTimer = function()
+{
+ if (!this.timer)
+ return;
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ cmpTimer.CancelTimer(this.timer);
+ delete this.timer;
+};
+
/**
- * Called every second. Heals garrisoned units.
+ * @params data and lateness are unused.
*/
-GarrisonHolder.prototype.HealTimeout = function(data)
+GarrisonHolder.prototype.HealTimeout = function(data, lateness)
{
- let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
- if (!this.entities.length)
+ let healRate = this.GetHealRate();
+ if (!this.entities.length || !healRate)
{
- cmpTimer.CancelTimer(this.timer);
- delete this.timer;
+ this.StopTimer();
return;
}
for (let entity of this.entities)
{
let cmpHealth = Engine.QueryInterface(entity, IID_Health);
if (cmpHealth && !cmpHealth.IsUnhealable())
- cmpHealth.Increase(this.GetHealRate());
+ cmpHealth.Increase(healRate);
}
-
- this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {});
};
/**
* Updates the garrison flag depending whether something is garrisoned in the entity.
*/
GarrisonHolder.prototype.UpdateGarrisonFlag = function()
{
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (!cmpVisual)
return;
cmpVisual.SetVariant("garrison", this.entities.length ? "garrisoned" : "ungarrisoned");
};
/**
* Cancel timer when destroyed.
*/
GarrisonHolder.prototype.OnDestroy = function()
{
if (this.timer)
{
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timer);
}
};
/**
* 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)
{
// The ownership change may be on the garrisonholder
if (this.entity == msg.entity)
{
let entities = this.entities.filter(ent => msg.to == INVALID_PLAYER || !IsOwnedByMutualAllyOfEntity(this.entity, ent));
if (entities.length)
this.EjectOrKill(entities);
return;
}
// or on some of its garrisoned units
let entityIndex = this.entities.indexOf(msg.entity);
if (entityIndex != -1 && (msg.to == INVALID_PLAYER || !IsOwnedByMutualAllyOfEntity(this.entity, msg.entity)))
this.EjectOrKill([msg.entity]);
};
/**
* Update list of garrisoned entities when a game inits.
*/
GarrisonHolder.prototype.OnGlobalSkirmishReplacerReplaced = function(msg)
{
if (!this.initGarrison)
return;
if (msg.entity == this.entity)
{
let cmpGarrisonHolder = Engine.QueryInterface(msg.newentity, IID_GarrisonHolder);
if (cmpGarrisonHolder)
cmpGarrisonHolder.initGarrison = this.initGarrison;
}
else
{
let entityIndex = this.initGarrison.indexOf(msg.entity);
if (entityIndex != -1)
this.initGarrison[entityIndex] = msg.newentity;
}
};
/**
* Eject all foreign garrisoned entities which are no more allied.
*/
GarrisonHolder.prototype.OnDiplomacyChanged = function()
{
this.EjectOrKill(this.entities.filter(ent => !IsOwnedByMutualAllyOfEntity(this.entity, ent)));
};
/**
* Eject or kill a garrisoned unit which can no more be garrisoned
* (garrisonholder's health too small or ownership changed).
*/
GarrisonHolder.prototype.EjectOrKill = function(entities)
{
let 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 && cmpPosition.IsInWorld())
{
let ejectables = entities.filter(ent => this.IsEjectable(ent));
if (ejectables.length)
this.UnloadEntities(ejectables);
}
// And destroy all remaining entities
let killedEntities = [];
for (let entity of entities)
{
let entityIndex = this.entities.indexOf(entity);
if (entityIndex == -1)
continue;
let cmpHealth = Engine.QueryInterface(entity, IID_Health);
if (cmpHealth)
cmpHealth.Kill();
else
Engine.DestroyEntity(entity);
this.entities.splice(entityIndex, 1);
killedEntities.push(entity);
}
if (killedEntities.length)
{
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {
"added": [],
"removed": killedEntities
});
this.UpdateGarrisonFlag();
}
};
/**
* Whether an entity is ejectable.
* @param {number} entity - The entity-ID to be tested.
* @return {boolean} - Whether the entity is ejectable.
*/
GarrisonHolder.prototype.IsEjectable = function(entity)
{
if (!this.entities.find(ent => ent == entity))
return false;
let ejectableClasses = this.template.EjectClassesOnDestroy._string;
let entityClasses = Engine.QueryInterface(entity, IID_Identity).GetClassesList();
return MatchesClassList(entityClasses, ejectableClasses);
};
/**
* Sets the intitGarrison to the specified entities. Used by the mapreader.
*
* @param {number[]} entities - The entity IDs to garrison on init.
*/
GarrisonHolder.prototype.SetInitGarrison = function(entities)
{
this.initGarrison = clone(entities);
};
/**
* Initialise the garrisoned units.
*/
GarrisonHolder.prototype.OnGlobalInitGame = function(msg)
{
if (!this.initGarrison)
return;
for (let ent of this.initGarrison)
{
let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);
if (cmpGarrisonable)
cmpGarrisonable.Garrison(this.entity);
}
- this.initGarrison = undefined;
+ delete this.initGarrison;
};
GarrisonHolder.prototype.OnValueModification = function(msg)
{
if (msg.component != "GarrisonHolder")
return;
if (msg.valueNames.indexOf("GarrisonHolder/List/_string") !== -1)
{
this.allowedClasses = ApplyValueModificationsToEntity("GarrisonHolder/List/_string", this.template.List._string, this.entity);
this.EjectOrKill(this.entities.filter(entity => !this.IsAllowedToBeGarrisoned(entity)));
}
if (msg.valueNames.indexOf("GarrisonHolder/BuffHeal") === -1)
return;
- if (this.timer && this.GetHealRate() == 0)
- {
- let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
- cmpTimer.CancelTimer(this.timer);
- delete this.timer;
- }
- else if (!this.timer && this.GetHealRate() > 0)
- {
- let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
- this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {});
- }
+ if (this.timer && !this.GetHealRate())
+ this.StopTimer();
+ else if (!this.timer && this.GetHealRate())
+ this.StartTimer();
};
Engine.RegisterComponentType(IID_GarrisonHolder, "GarrisonHolder", GarrisonHolder);
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js (revision 25188)
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js (revision 25189)
@@ -1,291 +1,291 @@
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadComponentScript("interfaces/Garrisonable.js");
Engine.LoadComponentScript("interfaces/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;
const unitToGarrisonId = 24;
const enemyUnitId = 34;
const largeUnitId = 35;
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_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
+ "SetInterval": (ent, iid, funcname, time, data) => 1
});
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": id => id
});
for (let i = 24; i <= 35; ++i)
{
AddMock(i, IID_Identity, {
"GetClassesList": () => ["Infantry", "Cavalry"],
"GetSelectionGroupName": () => "mace_infantry_archer_a"
});
if (i < 28)
AddMock(i, IID_Ownership, {
"GetOwner": () => player
});
else if (i == 34)
AddMock(i, IID_Ownership, {
"GetOwner": () => enemyPlayer
});
else
AddMock(i, IID_Ownership, {
"GetOwner": () => friendlyPlayer
});
if (i == largeUnitId)
AddMock(i, IID_Garrisonable, {
"UnitSize": () => 9,
"TotalSize": () => 9,
"Garrison": (entity) => cmpGarrisonHolder.Garrison(i),
"UnGarrison": () => cmpGarrisonHolder.Eject(i)
});
else
AddMock(i, IID_Garrisonable, {
"UnitSize": () => 1,
"TotalSize": () => 1,
"Garrison": entity => cmpGarrisonHolder.Garrison(i),
"UnGarrison": () => cmpGarrisonHolder.Eject(i)
});
AddMock(i, IID_Position, {
"GetHeightOffset": () => 0,
"GetPosition": () => new Vector3D(4, 3, 25),
"GetRotation": () => new Vector3D(4, 0, 6),
"JumpTo": (posX, posZ) => {},
"MoveOutOfWorld": () => {},
"SetHeightOffset": height => {}
});
}
AddMock(33, IID_Identity, {
"GetClassesList": () => ["Infantry", "Cavalry"],
"GetSelectionGroupName": () => "spart_infantry_archer_a"
});
let testGarrisonAllowed = function()
{
TS_ASSERT_EQUALS(cmpGarrisonHolder.HasEnoughHealth(), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(enemyUnitId), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(unitToGarrisonId), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(largeUnitId), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsFull(), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Unload(largeUnitId), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Unload(unitToGarrisonId), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(unitToGarrisonId), true);
for (let entity of garrisonedEntitiesList)
TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(entity), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(largeUnitId), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsFull(), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.CanPickup(unitToGarrisonId), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.UnloadTemplate("spart_infantry_archer_a", 2, false), true);
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [24, 25, 26, 27, 28, 29, 30, 31, 32]);
TS_ASSERT_EQUALS(cmpGarrisonHolder.UnloadAllByOwner(friendlyPlayer), true);
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [24, 25, 26, 27]);
TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 4);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsEjectable(25), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Unload(25), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsEjectable(25), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Unload(25), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Eject(null, false), true);
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [24, 26, 27]);
TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 3);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsFull(), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(largeUnitId), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.UnloadAll(), true);
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []);
};
// No health component yet.Pick
testGarrisonAllowed();
AddMock(garrisonHolderId, IID_Health, {
"GetHitpoints": () => 50,
"GetMaxHitpoints": () => 600
});
cmpGarrisonHolder.AllowGarrisoning(true, "callerID1");
cmpGarrisonHolder.AllowGarrisoning(false, 5);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(unitToGarrisonId), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Unload(unitToGarrisonId), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsGarrisoningAllowed(), false);
cmpGarrisonHolder.AllowGarrisoning(true, 5);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsGarrisoningAllowed(), true);
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.LoadingRange(), { "max": 2.1, "min": 0 });
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []);
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetHealRate(), 1);
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetAllowedClasses(), "Infantry+Cavalry");
TS_ASSERT_EQUALS(cmpGarrisonHolder.GetCapacity(), 10);
TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 0);
TS_ASSERT_EQUALS(cmpGarrisonHolder.CanPickup(unitToGarrisonId), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.CanPickup(enemyUnitId), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsFull(), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsAllowedToGarrison(enemyUnitId), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsAllowedToGarrison(largeUnitId), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsAllowedToGarrison(unitToGarrisonId), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.HasEnoughHealth(), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(unitToGarrisonId), false);
AddMock(garrisonHolderId, IID_Health, {
"GetHitpoints": () => 600,
"GetMaxHitpoints": () => 600
});
// No eject health.
cmpGarrisonHolder = ConstructComponent(garrisonHolderId, "GarrisonHolder", {
"Max": 10,
"List": { "_string": "Infantry+Cavalry" },
"EjectClassesOnDestroy": { "_string": "Infantry" },
"BuffHeal": 1,
"LoadingRange": 2.1,
"Pickup": false
});
testGarrisonAllowed();
// Test entity renaming.
let siegeEngineId = 44;
AddMock(siegeEngineId, IID_Identity, {
"GetClassesList": () => ["Siege"]
});
let archerId = 45;
AddMock(archerId, IID_Identity, {
"GetClassesList": () => ["Infantry", "Ranged"]
});
let originalClassList = "Infantry+Ranged Siege Cavalry";
cmpGarrisonHolder = ConstructComponent(garrisonHolderId, "GarrisonHolder", {
"Max": 10,
"List": { "_string": originalClassList },
"EjectHealth": 0.1,
"EjectClassesOnDestroy": { "_string": "Infantry" },
"BuffHeal": 1,
"LoadingRange": 2.1,
"Pickup": false
});
let traderId = 32;
AddMock(traderId, IID_Identity, {
"GetClassesList": () => ["Trader"]
});
AddMock(siegeEngineId, IID_Position, {
"GetHeightOffset": () => 0,
"GetPosition": () => new Vector3D(4, 3, 25),
"GetRotation": () => new Vector3D(4, 0, 6),
"JumpTo": (posX, posZ) => {},
"MoveOutOfWorld": () => {},
"SetHeightOffset": height => {}
});
let currentSiegePlayer = player;
AddMock(siegeEngineId, IID_Ownership, {
"GetOwner": () => currentSiegePlayer
});
AddMock(siegeEngineId, IID_Garrisonable, {
"UnitSize": () => 1,
"TotalSize": () => 1,
"Garrison": (entity, renamed) => cmpGarrisonHolder.Garrison(siegeEngineId, renamed),
"UnGarrison": () => true
});
let cavalryId = 46;
AddMock(cavalryId, IID_Identity, {
"GetClassesList": () => ["Infantry", "Ranged"]
});
AddMock(cavalryId, IID_Position, {
"GetHeightOffset": () => 0,
"GetPosition": () => new Vector3D(4, 3, 25),
"GetRotation": () => new Vector3D(4, 0, 6),
"JumpTo": (posX, posZ) => {},
"MoveOutOfWorld": () => {},
"SetHeightOffset": height => {}
});
let currentCavalryPlayer = player;
AddMock(cavalryId, IID_Ownership, {
"GetOwner": () => currentCavalryPlayer
});
AddMock(cavalryId, IID_Garrisonable, {
"UnitSize": () => 1,
"TotalSize": () => 1,
"Garrison": (entity, renamed) => cmpGarrisonHolder.Garrison(cavalryId, renamed),
"UnGarrison": () => true
});
TS_ASSERT(cmpGarrisonHolder.Garrison(cavalryId));
TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 1);
// Eject enemy units.
currentCavalryPlayer = enemyPlayer;
cmpGarrisonHolder.OnGlobalOwnershipChanged({
"entity": cavalryId,
"to": enemyPlayer
});
TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 0);
let oldApplyValueModificationsToEntity = ApplyValueModificationsToEntity;
TS_ASSERT(cmpGarrisonHolder.Garrison(siegeEngineId));
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [siegeEngineId]);
Engine.RegisterGlobal("ApplyValueModificationsToEntity", (valueName, currentValue, entity) => {
if (valueName !== "GarrisonHolder/List/_string")
return valueName;
return HandleTokens(currentValue, "-Siege Trader");
});
cmpGarrisonHolder.OnValueModification({
"component": "GarrisonHolder",
"valueNames": ["GarrisonHolder/List/_string"],
"entities": [garrisonHolderId]
});
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetAllowedClasses().split(/\s+/), ["Infantry+Ranged", "Cavalry", "Trader"]);
// The new classes are now cached so we can restore the behavior.
Engine.RegisterGlobal("ApplyValueModificationsToEntity", oldApplyValueModificationsToEntity);
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []);
TS_ASSERT(!cmpGarrisonHolder.Garrison(siegeEngineId));
TS_ASSERT(cmpGarrisonHolder.Garrison(traderId));
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Garrisoning.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Garrisoning.js (revision 25188)
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Garrisoning.js (revision 25189)
@@ -1,142 +1,142 @@
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("Position.js");
Engine.LoadComponentScript("interfaces/Garrisonable.js");
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/ModifiersManager.js");
Engine.LoadComponentScript("interfaces/Timer.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;
let createGarrisonCmp = entity => {
AddMock(entity, IID_Identity, {
"GetClassesList": () => ["Ranged"],
"GetSelectionGroupName": () => "mace_infantry_archer_a"
});
AddMock(entity, IID_Ownership, {
"GetOwner": () => player
});
AddMock(entity, IID_Position, {
"GetHeightOffset": () => 0,
"GetPosition": () => new Vector3D(4, 3, 25),
"GetRotation": () => new Vector3D(4, 0, 6),
"JumpTo": (posX, posZ) => {},
"MoveOutOfWorld": () => {},
"SetHeightOffset": height => {},
"SetYRotation": angle => {}
});
return ConstructComponent(entity, "Garrisonable", {
"Size": "1"
});
};
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
+ "SetInterval": (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 => {},
"SetYRotation": angle => {}
});
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]);
// Can't garrison twice.
TS_ASSERT(!cmpGarrisonable.Garrison(holder));
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [garrison]);
TS_ASSERT(cmpGarrisonHolder.Unload(garrison));
TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 0);
// Test initGarrison.
let entities = [21, 22, 23, 24];
for (let entity of entities)
createGarrisonCmp(entity);
cmpGarrisonHolder.SetInitGarrison(entities);
cmpGarrisonHolder.OnGlobalInitGame();
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), entities);
// They turned against us!
AddMock(entities[0], IID_Ownership, {
"GetOwner": () => enemyPlayer
});
cmpGarrisonHolder.OnDiplomacyChanged();
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), entities.length - 1);
TS_ASSERT(cmpGarrisonHolder.UnloadAll());
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []);