Index: binaries/data/mods/public/globalscripts/Templates.js
===================================================================
--- binaries/data/mods/public/globalscripts/Templates.js
+++ binaries/data/mods/public/globalscripts/Templates.js
@@ -308,6 +308,13 @@
warn("GetTemplateDataHelper(): Unrecognized Footprint type");
}
+ if (template.Garrisonable)
+ {
+ ret.garrisonable = {
+ "size": getEntityValue("Garrisonable/Size")
+ };
+ }
+
if (template.GarrisonHolder)
{
ret.garrisonHolder = {
Index: binaries/data/mods/public/gui/common/tooltips.js
===================================================================
--- binaries/data/mods/public/gui/common/tooltips.js
+++ binaries/data/mods/public/gui/common/tooltips.js
@@ -390,27 +390,44 @@
function getGarrisonTooltip(template)
{
- if (!template.garrisonHolder)
- return "";
-
- let tooltips = [
- sprintf(translate("%(label)s: %(garrisonLimit)s"), {
- "label": headerFont(translate("Garrison Limit")),
- "garrisonLimit": template.garrisonHolder.capacity
- })
- ];
-
- if (template.garrisonHolder.buffHeal)
- tooltips.push(
- sprintf(translate("%(healRateLabel)s %(value)s %(health)s / %(second)s"), {
- "healRateLabel": headerFont(translate("Heal:")),
- "value": Math.round(template.garrisonHolder.buffHeal),
- "health": unitFont(translate("Health")),
- "second": unitFont(translate("second")),
+ let tooltips = [];
+ if (template.garrisonHolder)
+ {
+ tooltips.push (
+ sprintf(translate("%(label)s: %(garrisonLimit)s"), {
+ "label": headerFont(translate("Garrison Limit")),
+ "garrisonLimit": template.garrisonHolder.capacity
})
);
- return tooltips.join(commaFont(translate(", ")));
+ if (template.garrisonHolder.buffHeal)
+ tooltips.push(
+ sprintf(translate("%(healRateLabel)s %(value)s %(health)s / %(second)s"), {
+ "healRateLabel": headerFont(translate("Heal:")),
+ "value": Math.round(template.garrisonHolder.buffHeal),
+ "health": unitFont(translate("Health")),
+ "second": unitFont(translate("second")),
+ })
+ );
+
+ tooltips.join(commaFont(translate(", ")));
+ }
+ if (template.garrisonable)
+ {
+ let extraSize;
+ if (template.garrisonHolder)
+ extraSize = template.garrisonHolder.garrisonedSlots;
+ if (template.garrisonable.size > 1 || extraSize)
+ tooltips.push (
+ sprintf(translate("%(label)s: %(garrisonSize)s %(extraSize)s"), {
+ "label": headerFont(translate("Garrison Size")),
+ "garrisonSize": template.garrisonable.size,
+ "extraSize": extraSize ? "+ " + extraSize : ""
+ })
+ );
+ }
+
+ return tooltips.join("\n");
}
function getProjectilesTooltip(template)
Index: binaries/data/mods/public/gui/session/selection_details.js
===================================================================
--- binaries/data/mods/public/gui/session/selection_details.js
+++ binaries/data/mods/public/gui/session/selection_details.js
@@ -344,6 +344,7 @@
let playerID = 0;
let totalCarrying = {};
let totalLoot = {};
+ let garrisonSize = 0;
for (let entState of entStates)
{
@@ -373,6 +374,12 @@
totalCarrying[type] = (totalCarrying[type] || 0) + carrying[type];
totalLoot[type] = (totalLoot[type] || 0) + carrying[type];
}
+
+ if (entState.garrisonHolder)
+ garrisonSize += entState.garrisonHolder.garrisonedSlots;
+
+ if (entState.garrisonable)
+ garrisonSize += entState.garrisonable.size;
}
Engine.GetGUIObjectByName("healthMultiple").hidden = averageHealth <= 0;
@@ -426,6 +433,12 @@
numberOfUnits.caption = entStates.length;
numberOfUnits.tooltip = "";
+ if (garrisonSize)
+ numberOfUnits.tooltip = sprintf(translate("%(label)s: %(details)s\n"), {
+ "label": headerFont(translate("Garrison Size")),
+ "details": bodyFont(garrisonSize)
+ });
+
if (Object.keys(totalCarrying).length)
numberOfUnits.tooltip = sprintf(translate("%(label)s %(details)s\n"), {
"label": headerFont(translate("Carrying:")),
Index: binaries/data/mods/public/gui/session/unit_actions.js
===================================================================
--- binaries/data/mods/public/gui/session/unit_actions.js
+++ binaries/data/mods/public/gui/session/unit_actions.js
@@ -630,20 +630,20 @@
},
"getActionInfo": function(entState, targetState)
{
- if (!entState.canGarrison || !targetState.garrisonHolder ||
+ if (!entState.garrisonable || !targetState.garrisonHolder ||
!playerCheck(entState, targetState, ["Player", "MutualAlly"]))
return false;
let tooltip = sprintf(translate("Current garrison: %(garrisoned)s/%(capacity)s"), {
- "garrisoned": targetState.garrisonHolder.garrisonedEntitiesCount,
+ "garrisoned": targetState.garrisonHolder.garrisonedSlots,
"capacity": targetState.garrisonHolder.capacity
});
- let extraCount = 0;
+ let extraCount = entState.garrisonable.size;
if (entState.garrisonHolder)
- extraCount += entState.garrisonHolder.garrisonedEntitiesCount;
+ extraCount += entState.garrisonHolder.garrisonedSlots;
- if (targetState.garrisonHolder.garrisonedEntitiesCount + extraCount >= targetState.garrisonHolder.capacity)
+ if (targetState.garrisonHolder.garrisonedSlots + extraCount > targetState.garrisonHolder.capacity)
tooltip = coloredText(tooltip, "orange");
if (!MatchesClassList(entState.identity.classes, targetState.garrisonHolder.allowedClasses))
@@ -850,11 +850,11 @@
cursor = "action-garrison";
tooltip = sprintf(translate("Current garrison: %(garrisoned)s/%(capacity)s"), {
- "garrisoned": targetState.garrisonHolder.garrisonedEntitiesCount,
+ "garrisoned": targetState.garrisonHolder.garrisonedSlots,
"capacity": targetState.garrisonHolder.capacity
});
- if (targetState.garrisonHolder.garrisonedEntitiesCount >=
+ if (targetState.garrisonHolder.garrisonedSlots >=
targetState.garrisonHolder.capacity)
tooltip = coloredText(tooltip, "orange");
}
Index: binaries/data/mods/public/simulation/ai/common-api/entity.js
===================================================================
--- binaries/data/mods/public/simulation/ai/common-api/entity.js
+++ binaries/data/mods/public/simulation/ai/common-api/entity.js
@@ -401,6 +401,8 @@
"garrisonMax": function() { return this.get("GarrisonHolder/Max"); },
+ "garrisonSize": function() { return this.get("Garrisonable/Size"); },
+
"garrisonEjectHealth": function() { return +this.get("GarrisonHolder/EjectHealth"); },
"getDefaultArrow": function() { return +this.get("BuildingAI/DefaultArrowCount"); },
@@ -723,7 +725,18 @@
},
"garrisoned": function() { return this._entity.garrisoned; },
- "canGarrisonInside": function() { return this._entity.garrisoned.length < this.garrisonMax(); },
+
+ "garrisonedSlots": function() {
+ let count = 0;
+
+ if (this._entity.garrisoned)
+ for (let ent of this._entity.garrisoned)
+ count += +this._ai._entities.get(ent).garrisonSize();
+
+ return count;
+ },
+
+ "canGarrisonInside": function() { return this.garrisonedSlots() < this.garrisonMax(); },
/**
* returns true if the entity can attack (including capture) the given class.
Index: binaries/data/mods/public/simulation/ai/petra/defenseManager.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/defenseManager.js
+++ binaries/data/mods/public/simulation/ai/petra/defenseManager.js
@@ -735,7 +735,7 @@
if (target.hitpoints() < target.garrisonEjectHealth() * target.maxHitpoints())
return false;
let minGarrison = data.min || target.garrisonMax();
- if (gameState.ai.HQ.garrisonManager.numberOfGarrisonedUnits(target) >= minGarrison)
+ if (gameState.ai.HQ.garrisonManager.numberOfGarrisonedSlots(target) >= minGarrison)
return false;
if (data.attacker)
{
@@ -787,7 +787,7 @@
let ret = false;
for (let ent of units.values())
{
- if (garrisonManager.numberOfGarrisonedUnits(target) >= minGarrison)
+ if (garrisonManager.numberOfGarrisonedSlots(target) >= minGarrison)
break;
if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") >= 0)
{
@@ -817,7 +817,7 @@
continue;
if (!MatchesClassList(unit.classes(), ent.garrisonableClasses()))
continue;
- if (garrisonManager.numberOfGarrisonedUnits(ent) >= ent.garrisonMax())
+ if (garrisonManager.numberOfGarrisonedSlots(ent) >= ent.garrisonMax())
continue;
if (ent.hitpoints() < ent.garrisonEjectHealth() * ent.maxHitpoints())
continue;
@@ -853,7 +853,7 @@
continue;
if (!MatchesClassList(unit.classes(), ent.garrisonableClasses()))
continue;
- if (garrisonManager.numberOfGarrisonedUnits(ent) >= ent.garrisonMax() &&
+ if (garrisonManager.numberOfGarrisonedSlots(ent) >= ent.garrisonMax() &&
(!emergency || !ent.garrisoned().length))
continue;
if (ent.hitpoints() < ent.garrisonEjectHealth() * ent.maxHitpoints())
@@ -874,7 +874,7 @@
garrisonManager.garrison(gameState, unit, nearest, "protection");
return true;
}
- if (garrisonManager.numberOfGarrisonedUnits(nearest) >= nearest.garrisonMax()) // make room for this ent
+ if (garrisonManager.numberOfGarrisonedSlots(nearest) >= nearest.garrisonMax()) // make room for this ent
nearest.unload(nearest.garrisoned()[0]);
garrisonManager.garrison(gameState, unit, nearest, nearest.buffHeal() ? "protection" : "emergency");
Index: binaries/data/mods/public/simulation/ai/petra/garrisonManager.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/garrisonManager.js
+++ binaries/data/mods/public/simulation/ai/petra/garrisonManager.js
@@ -179,7 +179,7 @@
}
list.splice(j--, 1);
}
- if (this.numberOfGarrisonedUnits(holder) === 0)
+ if (this.numberOfGarrisonedSlots(holder) === 0)
this.holders.delete(id);
else
holder.setMetadata(PlayerID, "holderTimeUpdate", gameState.ai.elapsedTime);
@@ -193,7 +193,7 @@
let ent = gameState.getEntityById(id);
if (!ent || ent.owner() !== PlayerID)
this.decayingStructures.delete(id);
- else if (this.numberOfGarrisonedUnits(ent) < gmin)
+ else if (this.numberOfGarrisonedSlots(ent) < gmin)
gameState.ai.HQ.defenseManager.garrisonUnitsInside(gameState, ent, { "min": gmin, "type": "decay" });
}
};
@@ -207,6 +207,15 @@
return holder.garrisoned().length + this.holders.get(holder.id()).list.length;
};
+/** TODO should add the units garrisoned inside garrisoned units */
+PETRA.GarrisonManager.prototype.numberOfGarrisonedSlots = function(holder)
+{
+ if (!this.holders.has(holder.id()))
+ return holder.garrisonedSlots();
+
+ return holder.garrisonedSlots() + this.holders.get(holder.id()).list.length;
+};
+
PETRA.GarrisonManager.prototype.allowMelee = function(holder)
{
if (!this.holders.has(holder.id()))
@@ -218,7 +227,7 @@
/** This is just a pre-garrison state, while the entity walk to the garrison holder */
PETRA.GarrisonManager.prototype.garrison = function(gameState, ent, holder, type)
{
- if (this.numberOfGarrisonedUnits(holder) >= holder.garrisonMax() || !ent.canGarrison())
+ if (this.numberOfGarrisonedSlots(holder) >= holder.garrisonMax() || !ent.canGarrison())
return;
this.registerHolder(gameState, holder);
Index: binaries/data/mods/public/simulation/ai/petra/headquarters.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/headquarters.js
+++ binaries/data/mods/public/simulation/ai/petra/headquarters.js
@@ -2107,7 +2107,7 @@
// We will choose randomly ranged and melee units, except when garrisonHolder is full
// in which case we prefer melee units
- let numGarrisoned = this.garrisonManager.numberOfGarrisonedUnits(nearestAnchor);
+ let numGarrisoned = this.garrisonManager.numberOfGarrisonedSlots(nearestAnchor);
if (nearestAnchor._entity.trainingQueue)
{
for (let item of nearestAnchor._entity.trainingQueue)
Index: binaries/data/mods/public/simulation/components/AlertRaiser.js
===================================================================
--- binaries/data/mods/public/simulation/components/AlertRaiser.js
+++ binaries/data/mods/public/simulation/components/AlertRaiser.js
@@ -63,7 +63,7 @@
let cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
if (!reserved.has(ent))
- reserved.set(ent, cmpGarrisonHolder.GetCapacity() - cmpGarrisonHolder.GetGarrisonedEntitiesCount());
+ reserved.set(ent, cmpGarrisonHolder.GetCapacity() - cmpGarrisonHolder.GetGarrisonedSlots());
return cmpGarrisonHolder.IsAllowedToGarrison(unit) && reserved.get(ent);
});
Index: binaries/data/mods/public/simulation/components/GarrisonHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/GarrisonHolder.js
+++ binaries/data/mods/public/simulation/components/GarrisonHolder.js
@@ -121,7 +121,7 @@
GarrisonHolder.prototype.IsFull = function()
{
- return this.GetGarrisonedEntitiesCount() >= this.GetCapacity();
+ return this.GetGarrisonedSlots() >= this.GetCapacity();
};
GarrisonHolder.prototype.GetHealRate = function()
@@ -162,6 +162,21 @@
return count;
};
+GarrisonHolder.prototype.GetGarrisonedSlots = function()
+{
+ let count = 0;
+ for (let ent of this.entities)
+ {
+ let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);
+ if (cmpGarrisonable)
+ count += cmpGarrisonable.GetSize();
+ let cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
+ if (cmpGarrisonHolder)
+ count += cmpGarrisonHolder.GetGarrisonedSlots();
+ }
+ return count;
+};
+
GarrisonHolder.prototype.IsAllowedToGarrison = function(ent)
{
if (!this.IsGarrisoningAllowed())
@@ -244,11 +259,13 @@
return false;
// Check the capacity
- let extraCount = 0;
+ let cmpGarrisonable = Engine.QueryInterface(entity, IID_Garrisonable);
+ let extraCount = cmpGarrisonable.GetSize();
+
let cmpGarrisonHolder = Engine.QueryInterface(entity, IID_GarrisonHolder);
if (cmpGarrisonHolder)
- extraCount += cmpGarrisonHolder.GetGarrisonedEntitiesCount();
- if (this.GetGarrisonedEntitiesCount() + extraCount >= this.GetCapacity())
+ extraCount += cmpGarrisonHolder.GetGarrisonedSlots();
+ if (this.GetGarrisonedSlots() + extraCount > this.GetCapacity())
return false;
if (!this.timer && this.GetHealRate() > 0)
Index: binaries/data/mods/public/simulation/components/Garrisonable.js
===================================================================
--- binaries/data/mods/public/simulation/components/Garrisonable.js
+++ binaries/data/mods/public/simulation/components/Garrisonable.js
@@ -1,6 +1,15 @@
function Garrisonable() {}
-Garrisonable.prototype.Schema = "";
+Garrisonable.prototype.Schema =
+ "Controls the garrisonability of an entity." +
+ "" +
+ "10" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
Garrisonable.prototype.Init = function()
{
@@ -8,4 +17,9 @@
Garrisonable.prototype.Serialize = null;
+Garrisonable.prototype.GetSize = function()
+{
+ return ApplyValueModificationsToEntity("Garrisonable/Size", +this.template.Size, this.entity);
+};
+
Engine.RegisterComponentType(IID_Garrisonable, "Garrisonable", Garrisonable);
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js
+++ binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -360,10 +360,14 @@
"buffHeal": cmpGarrisonHolder.GetHealRate(),
"allowedClasses": cmpGarrisonHolder.GetAllowedClasses(),
"capacity": cmpGarrisonHolder.GetCapacity(),
- "garrisonedEntitiesCount": cmpGarrisonHolder.GetGarrisonedEntitiesCount()
+ "garrisonedSlots": cmpGarrisonHolder.GetGarrisonedSlots()
};
- ret.canGarrison = !!Engine.QueryInterface(ent, IID_Garrisonable);
+ let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);
+ if (cmpGarrisonable)
+ ret.garrisonable = {
+ "size": cmpGarrisonable.GetSize()
+ }
let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
Index: binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
+++ binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
@@ -14,6 +14,7 @@
const garrisonHolderId = 15;
const unitToGarrisonId = 24;
const enemyUnitId = 34;
+const largeUnitId = 35;
const player = 1;
const friendlyPlayer = 2;
const enemyPlayer = 3;
@@ -52,7 +53,7 @@
"GetPlayerByID": id => id
});
-for (let i = 24; i <= 34; ++i)
+for (let i = 24; i <= 35; ++i)
{
AddMock(i, IID_Identity, {
"GetClassesList": () => "Infantry+Cavalry",
@@ -72,7 +73,14 @@
"GetOwner": () => friendlyPlayer
});
- AddMock(i, IID_Garrisonable, {});
+ if (i == 35)
+ AddMock(i, IID_Garrisonable, {
+ "GetSize": () => 9
+ });
+ else
+ AddMock(i, IID_Garrisonable, {
+ "GetSize": () => 1
+ });
AddMock(i, IID_Position, {
"GetHeightOffset": () => 0,
@@ -134,6 +142,7 @@
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.PerformGarrison(unitToGarrisonId), false);
@@ -146,10 +155,14 @@
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.Eject(largeUnitId), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Eject(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);
@@ -166,5 +179,6 @@
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.PerformGarrison(largeUnitId), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.UnloadAll(), true);
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []);
Index: binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
+++ binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
@@ -581,7 +581,6 @@
"needsRepair": false,
"needsHeal": true,
"builder": true,
- "canGarrison": false,
"visibility": "visible",
"isBarterMarket":true,
"resourceTrickle": {
Index: binaries/data/mods/public/simulation/templates/template_unit.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit.xml
+++ binaries/data/mods/public/simulation/templates/template_unit.xml
@@ -28,7 +28,9 @@
2.5
-
+
+ 1
+
corpse