Index: binaries/data/mods/public/art/actors/units/britons/chariot_javelinist_c_m.xml =================================================================== --- binaries/data/mods/public/art/actors/units/britons/chariot_javelinist_c_m.xml +++ binaries/data/mods/public/art/actors/units/britons/chariot_javelinist_c_m.xml @@ -18,7 +18,6 @@ - Index: binaries/data/mods/public/art/actors/units/britons/hero_chariot_javelinist_boudicca_m.xml =================================================================== --- binaries/data/mods/public/art/actors/units/britons/hero_chariot_javelinist_boudicca_m.xml +++ binaries/data/mods/public/art/actors/units/britons/hero_chariot_javelinist_boudicca_m.xml @@ -17,7 +17,6 @@ - Index: binaries/data/mods/public/gui/session/selection_panels.js =================================================================== --- binaries/data/mods/public/gui/session/selection_panels.js +++ binaries/data/mods/public/gui/session/selection_panels.js @@ -350,7 +350,7 @@ for (let state of unitEntStates) if (state.garrisonHolder) - groups.add(state.garrisonHolder.entities); + groups.add(state.garrisonHolder.hiddenEntities); return groups.getEntsGrouped(); }, 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 @@ -199,7 +199,7 @@ }, "getActionInfo": function(entState, targetState) { - if (!entState.attack || !targetState.hitpoints) + if (!targetState.hitpoints) return false; return { @@ -1020,7 +1020,12 @@ let count = 0; for (let entState of entStates) if (entState.garrisonHolder) - count += entState.garrisonHolder.entities.length; + for (let entity of entState.garrisonHolder.entities) + { + let state = GetEntityState(entity); + if (state.canGarrison && state.canGarrison.unloadable) + ++count; + } if (!count) return false; @@ -1118,7 +1123,7 @@ "getInfo": function(entStates) { if (entStates.every(entState => { - if (!entState.unitAI || !entState.turretParent) + if (!entState.unitAI || !entState.turretParent || !entState.canGarrison.unloadable) return true; let parent = GetEntityState(entState.turretParent); return !parent || !parent.garrisonHolder || parent.garrisonHolder.entities.indexOf(entState.id) == -1; Index: binaries/data/mods/public/simulation/components/Attack.js =================================================================== --- binaries/data/mods/public/simulation/components/Attack.js +++ binaries/data/mods/public/simulation/components/Attack.js @@ -329,12 +329,13 @@ */ Attack.prototype.GetFullAttackRange = function() { - let ret = { "min": Infinity, "max": 0 }; + let ret = { "min": Infinity, "max": 0, "elevationBonus": Infinity }; for (let type of this.GetAttackTypes()) { let range = this.GetRange(type); ret.min = Math.min(ret.min, range.min); ret.max = Math.max(ret.max, range.max); + ret.elevationBonus = Math.min(ret.elevationBonus, range.elevationBonus); } return ret; }; 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 @@ -36,6 +36,16 @@ "" + "" + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + "" + "" + "" + @@ -72,6 +82,7 @@ { let points = this.template.VisibleGarrisonPoints; for (let point in points) + { this.visibleGarrisonPoints.push({ "offset": { "x": +points[point].X, @@ -79,8 +90,43 @@ "z": +points[point].Z }, "angle": points[point].Angle ? +points[point].Angle * Math.PI / 180 : null, - "entity": null + "entity": null, + "template": points[point].Template, + "forced": points[point].Forced && points[point].Forced == "true" }); + } + } + // A timeout is needed because the garrison holder first has to change ownership. + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "AutogarrisonTurrets", 100, null); +}; + +/** + * Autogarrison the turrets as specified in the template. + * This function has to be called from a timer as it requires the entity to have ownership. + */ +GarrisonHolder.prototype.AutogarrisonTurrets = function() +{ + if (!this.visibleGarrisonPoints) + return; + + let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + if (!cmpOwnership) + return; + + let owner = cmpOwnership.GetOwner(); + let points = this.visibleGarrisonPoints; + for (let point in points) + { + if (!points[point].template) + continue; + + let filter = points[point].forced ? "forcedgarrison|" : ""; + let ent = Engine.AddEntity(filter + points[point].template); + let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); + if (cmpOwnership) + cmpOwnership.SetOwner(owner); + this.Garrison(ent, points[point], points[point].forced); } }; @@ -106,6 +152,39 @@ }; /** + * Get the entity IDs of the entities which are not visibly garrisoned. + * Used in the GUI to display in the garrison panel. + * + * @return {number[]} - An array containing entity IDs. + */ +GarrisonHolder.prototype.GetNonvisibleGarrisonedEntities = function() +{ + return this.entities.filter(entity => !this.IsVisiblyGarrisoned(entity)); +}; + +/** + * Test whether the entity is visibly garrisoned. + * + * @param {number} entity - The entity ID of the entity to check. + * + * @return {boolean} - Whether the entity is visibly garrisoned. + */ +GarrisonHolder.prototype.IsVisiblyGarrisoned = function(entity) +{ + return this.visibleGarrisonPoints.some(vgp => vgp.entity == entity); +}; + +/** + * Get the visible garrison points of this entity. + * + * @return {Object[]} - An array containing visibly garrison points. + */ +GarrisonHolder.prototype.GetVisibleGarrisonPoints = function() +{ + return this.visibleGarrisonPoints; +}; + +/** * @return {Array} unit classes which can be garrisoned inside this * particular entity. Obtained from the entity's template. */ @@ -182,15 +261,15 @@ * 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) +GarrisonHolder.prototype.Garrison = function(entity, vgpEntity, forced) { let cmpPosition = Engine.QueryInterface(entity, IID_Position); if (!cmpPosition) return false; - if (!this.PerformGarrison(entity)) + if (!this.PerformGarrison(entity, forced)) return false; let visibleGarrisonPoint = vgpEntity; @@ -223,7 +302,10 @@ cmpPosition.SetTurretParent(this.entity, visibleGarrisonPoint.offset); let cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI); if (cmpUnitAI) + { cmpUnitAI.SetTurretStance(); + cmpUnitAI.SetGarrisoned(); + } } else cmpPosition.MoveOutOfWorld(); @@ -232,15 +314,15 @@ }; /** - * @return {boolean} Whether the entity was garrisonned. + * @return {boolean} Whether the entity was garrisoned. */ -GarrisonHolder.prototype.PerformGarrison = function(entity) +GarrisonHolder.prototype.PerformGarrison = function(entity, forced) { if (!this.HasEnoughHealth()) return false; // Check if the unit is allowed to be garrisoned inside the building - if (!this.IsAllowedToGarrison(entity)) + if (!forced && !this.IsAllowedToGarrison(entity)) return false; // Check the capacity @@ -431,7 +513,7 @@ */ GarrisonHolder.prototype.Unload = function(entity, forced) { - return this.PerformEject([entity], forced); + return this.IsUnloadable(entity) && this.PerformEject([entity], forced); }; /** @@ -477,7 +559,7 @@ { let entities = this.entities.filter(ent => { let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); - return cmpOwnership && cmpOwnership.GetOwner() == owner; + return cmpOwnership && cmpOwnership.GetOwner() == owner && this.IsUnloadable(ent); }); return this.PerformEject(entities, forced); }; @@ -489,7 +571,7 @@ */ GarrisonHolder.prototype.UnloadAll = function(forced) { - return this.PerformEject(this.entities.slice(), forced); + return this.PerformEject(this.entities.slice().filter(ent => this.IsUnloadable(ent)), forced); }; /** @@ -681,7 +763,7 @@ */ GarrisonHolder.prototype.IsEjectable = function(entity) { - if (!this.entities.find(ent => ent == entity)) + if (!this.IsUnloadable(entity)) return false; let ejectableClasses = this.template.EjectClassesOnDestroy._string; @@ -691,6 +773,20 @@ }; /** + * Whether an entity is unloadable. + * @param {number} entity - The entity-ID to be tested. + * @return {boolean} - Whether the entity is unloadable. + */ +GarrisonHolder.prototype.IsUnloadable = function(entity) +{ + if (!this.entities.find(ent => ent == entity)) + return false; + + let cmpGarrisonable = Engine.QueryInterface(entity, IID_Garrisonable); + return cmpGarrisonable && cmpGarrisonable.IsUnloadable(); +}; + +/** * Initialise the garrisoned units. */ GarrisonHolder.prototype.OnGlobalInitGame = function(msg) 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,11 +1,37 @@ function Garrisonable() {} -Garrisonable.prototype.Schema = ""; +Garrisonable.prototype.Schema = + "Controls the garrisonability of an entity." + + "" + + "true" + + "" + + "" + + "" + + ""; Garrisonable.prototype.Init = function() { + this.SetUnloadable(this.template.Unloadable && this.template.Unloadable == "true"); }; -Garrisonable.prototype.Serialize = null; +/** + * Gets the ability to ungarrison. + * + * @return {boolean} Whether ungarrisoning is allowed. + */ +Garrisonable.prototype.IsUnloadable = function() +{ + return this.Unloadable; +}; + +/** + * Sets the ability to ungarrison. + * + * @param {boolean} unloadable - Whether ungarrisoning is allowed. + */ +Garrisonable.prototype.SetUnloadable = function(unloadable) +{ + this.Unloadable = unloadable; +}; 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 @@ -357,13 +357,18 @@ if (cmpGarrisonHolder) ret.garrisonHolder = { "entities": cmpGarrisonHolder.GetEntities(), + "hiddenEntities": cmpGarrisonHolder.GetNonvisibleGarrisonedEntities(), "buffHeal": cmpGarrisonHolder.GetHealRate(), "allowedClasses": cmpGarrisonHolder.GetAllowedClasses(), "capacity": cmpGarrisonHolder.GetCapacity(), "garrisonedEntitiesCount": cmpGarrisonHolder.GetGarrisonedEntitiesCount() }; - ret.canGarrison = !!Engine.QueryInterface(ent, IID_Garrisonable); + let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable); + if (cmpGarrisonable) + ret.canGarrison = { + "unloadable": cmpGarrisonable.IsUnloadable() + } let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); if (cmpUnitAI) @@ -1829,8 +1834,8 @@ GuiInterface.prototype.CanAttack = function(player, data) { - let cmpAttack = Engine.QueryInterface(data.entity, IID_Attack); - return cmpAttack && cmpAttack.CanAttack(data.target, data.types || undefined); + let cmpUnitAI = Engine.QueryInterface(data.entity, IID_UnitAI); + return cmpUnitAI && cmpUnitAI.CanAttack(data.target, data.types || undefined); }; /* Index: binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/UnitAI.js +++ binaries/data/mods/public/simulation/components/UnitAI.js @@ -1513,23 +1513,36 @@ "WALKING": { "enter": function() { - if (!this.MoveTo(this.order.data)) + if (!this.MoveTo(this.order.data, this.order.data.iid || undefined)) { this.FinishOrder(); return true; } + this.StartTimer(0, 1000); }, "leave": function() { this.StopMoving(); + this.StopTimer(); + }, + + "Timer": function() { + if (this.CheckRange(this.order.data, this.order.data.iid || undefined)) + { + this.DelegateOrder(this.order.data); + this.FinishOrder(); + } }, "MovementUpdate": function(msg) { // If it looks like the path is failing, and we are close enough (3 tiles) // stop anyways. This avoids pathing for an unreachable goal and reduces lag considerably. if (msg.likelyFailure || msg.obstructed && this.RelaxedMaxRangeCheck(this.order.data, this.DefaultRelaxedMaxRange) || - this.CheckRange(this.order.data)) + this.CheckRange(this.order.data, this.order.data.iid || undefined)) + { + this.DelegateOrder(this.order.data); this.FinishOrder(); + } }, }, @@ -4293,12 +4306,11 @@ if (!this.CheckTargetVisible(target) || this.IsTurret()) return false; - var cmpRanged = Engine.QueryInterface(this.entity, iid); - if (!cmpRanged) + let range = this.GetRange(iid, type); + if (!range) return false; - var range = cmpRanged.GetRange(type); - var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); + let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); }; @@ -4327,8 +4339,9 @@ if (!this.CheckTargetVisible(target)) return false; - let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); - let range = cmpAttack.GetRange(type); + let range = this.GetRange(IID_Attack, type); + if (!range) + return false; let thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position); if (!thisCmpPosition.IsInWorld()) @@ -4409,10 +4422,9 @@ UnitAI.prototype.CheckTargetRange = function(target, iid, type) { - var cmpRanged = Engine.QueryInterface(this.entity, iid); - if (!cmpRanged) + let range = this.GetRange(iid, type); + if (!range) return false; - var range = cmpRanged.GetRange(type); let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, false); @@ -4446,8 +4458,9 @@ if (!targetCmpPosition || !targetCmpPosition.IsInWorld()) return false; - let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); - let range = cmpAttack.GetRange(type); + let range = this.GetRange(IID_Attack, type); + if (!range) + return false; let thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position); if (!thisCmpPosition.IsInWorld()) @@ -4576,8 +4589,9 @@ UnitAI.prototype.CheckTargetDistanceFromHeldPosition = function(target, iid, type) { - var cmpRanged = Engine.QueryInterface(this.entity, iid); - var range = iid !== IID_Attack ? cmpRanged.GetRange() : cmpRanged.GetRange(type); + let range = this.GetRange(iid, type); + if (!range) + return false; var cmpPosition = Engine.QueryInterface(target, IID_Position); if (!cmpPosition || !cmpPosition.IsInWorld()) @@ -5093,6 +5107,18 @@ "allowCapture": allowCapture, }; + // This is the case when an entity that has visibly garrisoned entities that can + // attack, but the entity itself can't attack. + let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack) + if (!cmpAttack) + { + order.type = "Attack"; + order.iid = IID_Attack; + + this.AddOrder("WalkToTarget", order, queued); + return; + } + this.RememberTargetPosition(order); this.AddOrder("Attack", order, queued); @@ -5618,14 +5644,13 @@ UnitAI.prototype.GetQueryRange = function(iid) { - var ret = { "min": 0, "max": 0 }; + let ret = { "min": 0, "max": 0 }; if (this.GetStance().respondStandGround) { - var cmpRanged = Engine.QueryInterface(this.entity, iid); - if (!cmpRanged) + let range = this.GetRange(iid); + if (!range) return ret; - var range = iid !== IID_Attack ? cmpRanged.GetRange() : cmpRanged.GetFullAttackRange(); - var cmpVision = Engine.QueryInterface(this.entity, IID_Vision); + let cmpVision = Engine.QueryInterface(this.entity, IID_Vision); if (!cmpVision) return ret; ret.min = range.min; @@ -5633,32 +5658,31 @@ } else if (this.GetStance().respondChase) { - var cmpVision = Engine.QueryInterface(this.entity, IID_Vision); + let cmpVision = Engine.QueryInterface(this.entity, IID_Vision); if (!cmpVision) return ret; - var range = cmpVision.GetRange(); + let range = cmpVision.GetRange(); ret.max = range; } else if (this.GetStance().respondHoldGround) { - var cmpRanged = Engine.QueryInterface(this.entity, iid); - if (!cmpRanged) + let range = this.GetRange(iid); + if (!range) return ret; - var range = iid !== IID_Attack ? cmpRanged.GetRange() : cmpRanged.GetFullAttackRange(); - var cmpVision = Engine.QueryInterface(this.entity, IID_Vision); + let cmpVision = Engine.QueryInterface(this.entity, IID_Vision); if (!cmpVision) return ret; - var vision = cmpVision.GetRange(); + let vision = cmpVision.GetRange(); ret.max = Math.min(range.max + vision / 2, vision); } // We probably have stance 'passive' and we wouldn't have a range, // but as it is the default for healers we need to set it to something sane. else if (iid === IID_Heal) { - var cmpVision = Engine.QueryInterface(this.entity, IID_Vision); + let cmpVision = Engine.QueryInterface(this.entity, IID_Vision); if (!cmpVision) return ret; - var range = cmpVision.GetRange(); + let range = cmpVision.GetRange(); ret.max = range; } return ret; @@ -5743,7 +5767,7 @@ //// Helper functions //// -UnitAI.prototype.CanAttack = function(target) +UnitAI.prototype.CanAttack = function(target, types) { // Formation controllers should always respond to commands // (then the individual units can make up their own minds) @@ -5751,7 +5775,68 @@ return true; let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); - return cmpAttack && cmpAttack.CanAttack(target); + if (cmpAttack) + return cmpAttack.CanAttack(target, types); + + let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); + if (!cmpGarrisonHolder) + return false; + + let visibleGarrisonPoints = cmpGarrisonHolder.GetVisibleGarrisonPoints(); + if (!visibleGarrisonPoints) + return false; + + for (let point of visibleGarrisonPoints) + { + if (!point.entity) + continue; + + let cmpAttack = Engine.QueryInterface(point.entity, IID_Attack); + if (cmpAttack && cmpAttack.CanAttack(target, types)) + return true; + } + + return false; +}; + +UnitAI.prototype.GetRange = function(iid, type) +{ + let cmpRanged = Engine.QueryInterface(this.entity, iid); + if (cmpRanged) + return iid === IID_Attack ? (type ? cmpRanged.GetRange(type) : cmpRanged.GetFullAttackRange()) : cmpRanged.GetRange(); + + let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); + if (cmpGarrisonHolder) + { + let result = { + "min": 0, + "max": -1, + "elevationBonus": 0 + }; + let visibleGarrisonPoints = cmpGarrisonHolder.GetVisibleGarrisonPoints(); + if (!visibleGarrisonPoints) + return false; + + let atLeastOne = false; + for (let point of visibleGarrisonPoints) + { + if (!point.entity) + continue; + + cmpRanged = Engine.QueryInterface(point.entity, iid); + if (cmpRanged) + { + atLeastOne = true; + let range = iid === IID_Attack ? (type ? cmpRanged.GetRange(type) : cmpRanged.GetFullAttackRange()) : cmpRanged.GetRange(); + result.min = Math.min(result.min, range.min); + result.max = Math.max(result.max, range.max) + point.offset.z; + result.elevationBonus = Math.min(result.elevationBonus, range.elevationBonus); + } + } + if (atLeastOne) + return result; + } + return false; }; UnitAI.prototype.CanGarrison = function(target) @@ -6079,6 +6164,25 @@ }); }; +/** + * Add order to UnitAI components of all visibly garrisoned members. + */ +UnitAI.prototype.DelegateOrder = function(order) +{ + if (!order || !order.type) + return; + + let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); + if (!cmpGarrisonHolder) + return; + + cmpGarrisonHolder.GetVisibleGarrisonPoints().forEach(point => { + let cmpUnitAI = Engine.QueryInterface(point.entity, IID_UnitAI); + if (cmpUnitAI) + cmpUnitAI.AddOrder(order.type, order, false); + }); +}; + UnitAI.prototype.UnitFsm = new FSM(UnitAI.prototype.UnitFsmSpec); Engine.RegisterComponentType(IID_UnitAI, "UnitAI", UnitAI); Index: binaries/data/mods/public/simulation/components/tests/test_Attack.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Attack.js +++ binaries/data/mods/public/simulation/components/tests/test_Attack.js @@ -149,7 +149,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetPreferredClasses("Melee"), ["FemaleCitizen"]); TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetRestrictedClasses("Melee"), ["Elephant", "Archer"]); - TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetFullAttackRange(), { "min": 0, "max": 80 }); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetFullAttackRange(), { "min": 0, "max": 80, "elevationBonus": 0 }); TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("Capture"), { "Capture": 8 }); TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("Ranged"), { 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 @@ -72,7 +72,9 @@ "GetOwner": () => friendlyPlayer }); - AddMock(i, IID_Garrisonable, {}); + AddMock(i, IID_Garrisonable, { + "IsUnloadable": () => true + }); AddMock(i, IID_Position, { "GetHeightOffset": () => 0, 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/special/filter/forcedgarrison.xml =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/templates/special/filter/forcedgarrison.xml @@ -0,0 +1,9 @@ + + + + true + + + false + + 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 - + + true + corpse Index: binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca.xml =================================================================== --- binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca.xml +++ binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca.xml @@ -1,20 +1,36 @@ - - units/heroes/brit_hero_boudicca - + 5.0 brit - Chariot + Chariot -Hero Boudicca (Chariot) Boadicea female units/brit_hero_boudicca.png + + 1 + Infantry + 0 + Infantry + 0 + 2 + + + + true + 0 + 1.4 + -2.5 + 0 + + + units/britons/hero_chariot_javelinist_boudicca_m.xml Index: source/simulation2/components/CCmpPosition.cpp =================================================================== --- source/simulation2/components/CCmpPosition.cpp +++ source/simulation2/components/CCmpPosition.cpp @@ -21,6 +21,7 @@ #include "ICmpPosition.h" #include "simulation2/MessageTypes.h" +#include "simulation2/serialization/SerializeTemplates.h" #include "ICmpTerrain.h" #include "ICmpTerritoryManager.h" @@ -226,6 +227,7 @@ serialize.NumberFixed_Unbounded("y", m_TurretPosition.Y); serialize.NumberFixed_Unbounded("z", m_TurretPosition.Z); } + SerializeSet()(serialize, "turrets", m_Turrets); } virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) @@ -262,6 +264,7 @@ deserialize.NumberFixed_Unbounded("y", m_TurretPosition.Y); deserialize.NumberFixed_Unbounded("z", m_TurretPosition.Z); } + SerializeSet()(deserialize, "turrets", m_Turrets); if (m_InWorld) UpdateXZRotation();