Index: binaries/data/mods/public/simulation/components/BuildingAI.js =================================================================== --- binaries/data/mods/public/simulation/components/BuildingAI.js +++ binaries/data/mods/public/simulation/components/BuildingAI.js @@ -1,4 +1,4 @@ -//Number of rounds of firing per 2 seconds +// Number of rounds of firing per 2 seconds const roundCount = 10; const attackType = "Ranged"; @@ -36,18 +36,26 @@ for (let ent of msg.added) { + if (msg.visible[ent]) + continue; + let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); if (!cmpIdentity) continue; + if (MatchesClassList(cmpIdentity.GetClassesList(), classes)) ++this.archersGarrisoned; } for (let ent of msg.removed) { + if (msg.visible[ent]) + continue; + let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); if (!cmpIdentity) continue; + if (MatchesClassList(cmpIdentity.GetClassesList(), classes)) --this.archersGarrisoned; } 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 @@ -45,6 +45,14 @@ "" + "" + "" + + "" + + "" + + "" + + "tokens" + + "" + + "" + + "" + + ""+ "" + "" + "" + @@ -68,20 +76,21 @@ this.timer = undefined; this.allowGarrisoning = new Map(); this.visibleGarrisonPoints = []; - if (this.template.VisibleGarrisonPoints) - { - let points = this.template.VisibleGarrisonPoints; - for (let point in points) - this.visibleGarrisonPoints.push({ - "offset": { - "x": +points[point].X, - "y": +points[point].Y, - "z": +points[point].Z - }, - "angle": points[point].Angle ? +points[point].Angle * Math.PI / 180 : null, - "entity": null - }); - } + if (!this.template.VisibleGarrisonPoints) + return; + + let points = this.template.VisibleGarrisonPoints; + for (let point in points) + this.visibleGarrisonPoints.push({ + "offset": { + "x": +points[point].X, + "y": +points[point].Y, + "z": +points[point].Z, + }, + "allowedClasses": points[point].AllowedClasses ? points[point].AllowedClasses : { "_string": "Infantry+Ranged" }, + "angle": points[point].Angle ? +points[point].Angle * Math.PI / 180 : null, + "entity": null + }); }; /** @@ -178,11 +187,25 @@ return MatchesClassList(entityClasses, this.template.List._string) && !!Engine.QueryInterface(ent, IID_Garrisonable); }; +/** + * @param {number} entity - The entity's id. + * @param {Object} visibleGarrisonPoint - The vgp object + * @return {boolean} true if the unit is allowed be visible on that garrison point, false otherwise. + */ +GarrisonHolder.prototype.AllowedToVisibleGarrisoning = function(entity, visibleGarrisonPoint) +{ + let cmpIdentity = Engine.QueryInterface(entity, IID_Identity); + if (!cmpIdentity) + return false; + + return MatchesClassList(cmpIdentity.GetClassesList(), visibleGarrisonPoint ? visibleGarrisonPoint.allowedClasses._string : ""); +}; + /** * Garrison a unit inside. The timer for AutoHeal is started here. * @param {number} vgpEntity - The visual garrison point that will be used. * If vgpEntity is given, this visualGarrisonPoint will be used for the entity. - * @return {boolean} Whether the entity was garrisonned. + * @return {boolean} Whether the entity was garrisoned. */ GarrisonHolder.prototype.Garrison = function(entity, vgpEntity) { @@ -194,14 +217,11 @@ return false; let visibleGarrisonPoint = vgpEntity; + if (!vgpEntity || !this.AllowedToVisibleGarrisoning(entity, vgpEntity)) + visibleGarrisonPoint = undefined; + if (!visibleGarrisonPoint) - for (let vgp of this.visibleGarrisonPoints) - { - if (vgp.entity) - continue; - visibleGarrisonPoint = vgp; - break; - } + this.visibleGarrisonPoints.find(vgp => !vgp.entity && this.AllowedToVisibleGarrisoning(entity, vgp)); if (visibleGarrisonPoint) { @@ -228,6 +248,15 @@ else cmpPosition.MoveOutOfWorld(); + // Should only be called after the garrison has been performed else the visible Garrison Points are not updated yet. + Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { + "added": [entity], + "removed": [], + "visible": { + [entity] : this.IsVisiblyGarrisoned(entity), + } + }); + return true; }; @@ -268,7 +297,6 @@ if (cmpAura && cmpAura.HasGarrisonAura()) cmpAura.ApplyGarrisonAura(this.entity); - Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added": [entity], "removed": [] }); return true; }; @@ -313,6 +341,11 @@ let cmpEntPosition = Engine.QueryInterface(entity, IID_Position); let cmpEntUnitAI = Engine.QueryInterface(entity, IID_UnitAI); + // Needs to be called before the visible garrison points are cleared. + let visible = { + [entity] : this.IsVisiblyGarrisoned(entity) + }; + for (let vgp of this.visibleGarrisonPoints) { if (vgp.entity != entity) @@ -345,7 +378,11 @@ if (cmpPosition) cmpEntPosition.SetYRotation(cmpPosition.GetPosition().horizAngleTo(pos)); - Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added": [], "removed": [entity] }); + Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { + "added": [], + "removed": [entity], + "visible": visible + }); return true; }; @@ -581,7 +618,13 @@ if (cmpHealth && cmpHealth.GetHitpoints() == 0) { this.entities.splice(entityIndex, 1); - Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added": [], "removed": [msg.entity] }); + Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { + "added": [], + "removed": [msg.entity], + "visible": { + [msg.entity]: this.IsVisiblyGarrisoned(msg.entity) + } + }); this.UpdateGarrisonFlag(); for (let point of this.visibleGarrisonPoints) @@ -670,10 +713,29 @@ } if (killedEntities.length) - Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added": [], "removed": killedEntities }); + { + let visibleEntitiesIds = {}; + for (let ent of killedEntities) + visibleEntitiesIds[ent] = this.IsVisiblyGarrisoned(ent); + Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { + "added": [], + "removed": killedEntities, + "visible": visibleEntitiesIds + }); + } this.UpdateGarrisonFlag(); }; +/** +* Gives insight about the unit type of garrisoning. +* @param {number} entity - the entity's id. +* @return {boolean} - Whether the unit is visible on the structure. +*/ +GarrisonHolder.prototype.IsVisiblyGarrisoned = function(entity) +{ + return this.visibleGarrisonPoints.some(point => point.entity == entity); +}; + /** * Whether an entity is ejectable. * @param {number} entity - The entity-ID to be tested. 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 @@ -55,7 +55,7 @@ for (let i = 24; i <= 34; ++i) { AddMock(i, IID_Identity, { - "GetClassesList": () => "Infantry+Cavalry", + "GetClassesList": () => ["Infantry", "Cavalry"], "GetSelectionGroupName": () => "mace_infantry_archer_a" }); @@ -88,7 +88,7 @@ } AddMock(33, IID_Identity, { - "GetClassesList": () => "Infantry+Cavalry", + "GetClassesList": () => ["Infantry", "Cavalry"], "GetSelectionGroupName": () => "spart_infantry_archer_a" }); @@ -168,3 +168,52 @@ TS_ASSERT_EQUALS(cmpGarrisonHolder.IsFull(), false); TS_ASSERT_EQUALS(cmpGarrisonHolder.UnloadAll(), true); TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []); + +let siegeEngineId = 44; +AddMock(siegeEngineId, IID_Identity, { + "GetClassesList": () => ["Siege"] +}); +let archerId = 45; +AddMock(archerId, IID_Identity, { + "GetClassesList": () => ["Infantry", "Ranged"] +}); + +// Test visible garrisoning restrictions. +cmpGarrisonHolder = ConstructComponent(garrisonHolderId, "GarrisonHolder", { + "Max": 10, + "List": { "_string": "Infantry+Ranged Siege" }, + "EjectHealth": 0.1, + "EjectClassesOnDestroy": { "_string": "Infantry" }, + "BuffHeal": 1, + "LoadingRange": 2.1, + "Pickup": false, + "VisibleGarrisonPoints": { + "archer1": { + "X": 12, + "Y": 5, + "Z": 6 + }, + "archer2": { + "X": 15, + "Y": 5, + "Z": 6, + "AllowedClasses": { "_string": "Siege" } + }, + "archer3": { + "X": 15, + "Y": 5, + "Z": 6, + "AllowedClasses": { "_string": "Siege Infantry+Ranged Infantry+Cavalry" } + } + } +}); + +TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(siegeEngineId, cmpGarrisonHolder.visibleGarrisonPoints[0]), false); +TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(siegeEngineId, cmpGarrisonHolder.visibleGarrisonPoints[1]), true); +TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(siegeEngineId, cmpGarrisonHolder.visibleGarrisonPoints[2]), true); +TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(archerId, cmpGarrisonHolder.visibleGarrisonPoints[0]), true); +TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(archerId, cmpGarrisonHolder.visibleGarrisonPoints[1]), false); +TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(archerId, cmpGarrisonHolder.visibleGarrisonPoints[2]), true); +TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(33, cmpGarrisonHolder.visibleGarrisonPoints[0]), false); +TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(33, cmpGarrisonHolder.visibleGarrisonPoints[1]), false); +TS_ASSERT_EQUALS(cmpGarrisonHolder.AllowedToVisibleGarrisoning(33, cmpGarrisonHolder.visibleGarrisonPoints[2]), true);