Index: binaries/data/mods/public/simulation/components/TurretHolder.js =================================================================== --- binaries/data/mods/public/simulation/components/TurretHolder.js +++ binaries/data/mods/public/simulation/components/TurretHolder.js @@ -0,0 +1,148 @@ +function TurretHolder() {} + +TurretHolder.prototype.Schema = + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + +TurretHolder.prototype.Init = function() +{ + this.turrets = {}; + var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.SetTimeout(this.entity, IID_TurretHolder, "InitReal", 0, null); + +}; + +/** + * The real init function. But this should not be loaded in Atlas, + * unless a simulation is started. + */ +TurretHolder.prototype.InitReal = function(msg) +{ + var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + for (var positionCode in this.template) + { + if (!this.template[positionCode].Template) + continue; + var ent = Engine.AddEntity(this.template[positionCode].Template); + var cmpTurretOwnership = Engine.QueryInterface(ent, IID_Ownership); + cmpTurretOwnership.SetOwner(cmpOwnership.GetOwner()); + this.AddTurret(positionCode, ent); + } +}; + +/** + * Bind an existing entity to a positionCode of this turretHolder + * Some components will be notified that this entity shouldn't move on its own anymore + */ +TurretHolder.prototype.AddTurret = function(positionCode, ent) +{ + if (!this.template[positionCode]) + { + warn("TurretHolder.JS: entity assigned to unknown turret positionCode"); + return; + } + if (this.turrets[positionCode]) + { + warn("TurretHolder.JS: entity assigned to occupied turret positionCode"); + return; + } + + var cmpPosition = Engine.QueryInterface(ent, IID_Position); + if (cmpPosition) + { + var offset = this.template[positionCode].Offset; + var pos = new Vector3D(+offset.X, +offset.Y, +offset.Z); + cmpPosition.SetTurretParent(this.entity, pos); + } + + var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); + if (cmpUnitAI) + cmpUnitAI.SetTurret(true); + this.turrets[positionCode] = ent; +}; + +/** + * Remove a turret from the turret holder + * This does not reset its position, nor does it kill the turret + * It just unbinds it from its turret holder + */ +TurretHolder.prototype.RemoveTurret = function(positionCode) +{ + var ent = this.turrets[positionCode]; + if (!ent) + return; + var cmpPosition = Engine.QueryInterface(ent, IID_Position); + if (cmpPosition) + cmpPosition.SetTurretParent(INVALID_ENTITY, new Vector3D()); + var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); + if (cmpUnitAI) + cmpUnitAI.SetTurret(false); + delete this.turrets[positionCode]; +}; + +TurretHolder.prototype.GetAllTurretPositions = function() +{ + return Object.keys(this.template); +}; + +TurretHolder.prototype.GetOccupiedTurretPositions = function() +{ + return Object.keys(this.turrets); +}; + +TurretHolder.prototype.GetFreeTurretPositions = function() +{ + var r = []; + for (var positionCode in this.template) + if (!(positionCode in this.turrets)) + r.push(positionCode); + return r; +}; + +TurretHolder.prototype.GetTurretOnPosition = function(positionCode) +{ + return this.turrets[positionCode]; +}; + +TurretHolder.prototype.OnOwnershipChanged = function(msg) +{ + for each (var ent in this.turrets) + { + var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); + cmpOwnership.SetOwner(msg.to); + } +} + +/** + * Also destroy all turrets + */ +TurretHolder.prototype.OnDestroy = function(msg) +{ + for each (var ent in this.turrets) + Engine.DestroyEntity(ent); +} + +Engine.RegisterComponentType(IID_TurretHolder, "TurretHolder", TurretHolder); 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 @@ -179,7 +179,7 @@ // Called when being told to walk as part of a formation "Order.FormationWalk": function(msg) { // Let players move captured domestic animals around - if (this.IsAnimal() && !this.IsDomestic()) + if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) { this.FinishOrder(); return; @@ -207,7 +207,7 @@ "Order.LeaveFoundation": function(msg) { // If foundation is not ally of entity, or if entity is unpacked siege, // ignore the order - if (!IsOwnedByAllyOfEntity(this.entity, msg.data.target) || this.IsPacking() || this.CanPack()) + if (!IsOwnedByAllyOfEntity(this.entity, msg.data.target) || this.IsPacking() || this.CanPack() || this.IsTurret()) { this.FinishOrder(); return; @@ -252,7 +252,7 @@ "Order.Walk": function(msg) { // Let players move captured domestic animals around - if (this.IsAnimal() && !this.IsDomestic()) + if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) { this.FinishOrder(); return; @@ -278,7 +278,7 @@ "Order.WalkAndFight": function(msg) { // Let players move captured domestic animals around - if (this.IsAnimal() && !this.IsDomestic()) + if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) { this.FinishOrder(); return; @@ -305,7 +305,7 @@ "Order.WalkToTarget": function(msg) { // Let players move captured domestic animals around - if (this.IsAnimal() && !this.IsDomestic()) + if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) { this.FinishOrder(); return; @@ -486,7 +486,7 @@ // If we can't reach the target, but are standing ground, then abandon this attack order. // Unless we're hunting, that's a special case where we should continue attacking our target. - if (this.GetStance().respondStandGround && !this.order.data.force && !this.order.data.hunting) + if (this.GetStance().respondStandGround && !this.order.data.force && !this.order.data.hunting || this.IsTurret()) { this.FinishOrder(); return; @@ -685,6 +685,11 @@ }, "Order.Garrison": function(msg) { + if (this.IsTurret()) + { + this.FinishOrder(); + return; + } // For packable units: // 1. If packed, we can move to the garrison target. // 2. If unpacked, we first need to pack, then follow case 1. @@ -1242,7 +1247,7 @@ "Order.LeaveFoundation": function(msg) { // If foundation is not ally of entity, or if entity is unpacked siege, // ignore the order - if (!IsOwnedByAllyOfEntity(this.entity, msg.data.target) || this.IsPacking() || this.CanPack()) + if (!IsOwnedByAllyOfEntity(this.entity, msg.data.target) || this.IsPacking() || this.CanPack() || this.IsTurret()) { this.FinishOrder(); return; @@ -3119,8 +3124,23 @@ this.lastHealed = undefined; this.SetStance(this.template.DefaultStance); + this.SetTurret(false); }; +/** + * Set the flag to true to use this unit as a turret + * This means no moving is allowed, only turning + */ +UnitAI.prototype.SetTurret = function(flag) +{ + this.isTurret = flag; +}; + +UnitAI.prototype.IsTurret = function() +{ + return this.isTurret; +}; + UnitAI.prototype.ReactsToAlert = function(level) { return this.template.AlertReactiveLevel <= level; @@ -4152,7 +4172,7 @@ UnitAI.prototype.MoveToTargetRange = function(target, iid, type) { - if (!this.CheckTargetVisible(target)) + if (!this.CheckTargetVisible(target) || this.IsTurret()) return false; var cmpRanged = Engine.QueryInterface(this.entity, iid); @@ -4583,6 +4603,9 @@ */ UnitAI.prototype.ShouldChaseTargetedEntity = function(target, force) { + if (this.IsTurret()) + return false; + // TODO: use special stances instead? var cmpPack = Engine.QueryInterface(this.entity, IID_Pack); if (cmpPack) Index: binaries/data/mods/public/simulation/components/interfaces/TurretHolder.js =================================================================== --- binaries/data/mods/public/simulation/components/interfaces/TurretHolder.js +++ binaries/data/mods/public/simulation/components/interfaces/TurretHolder.js @@ -0,0 +1,2 @@ +Engine.RegisterInterface("TurretHolder"); + Index: binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml +++ binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml @@ -69,6 +69,16 @@ 20 40000 + + + + + 5 + 5 + 5 + + + 20 Index: source/simulation2/components/CCmpPosition.cpp =================================================================== --- source/simulation2/components/CCmpPosition.cpp +++ source/simulation2/components/CCmpPosition.cpp @@ -75,10 +75,16 @@ entity_pos_t m_YOffset, m_LastYOffset; bool m_RelativeToGround; // whether m_YOffset is relative to terrain/water plane, or an absolute height + // when the entity is a turret, only m_RotY is used, and this is the rotation + // relative to the parent entity entity_angle_t m_RotX, m_RotY, m_RotZ; player_id_t m_Territory; + entity_id_t m_TurretParent; + CFixedVector3D m_TurretPosition; + std::set m_Turrets; + // not serialized: float m_InterpolatedRotX, m_InterpolatedRotY, m_InterpolatedRotZ; float m_LastInterpolatedRotX, m_LastInterpolatedRotZ; // not serialized @@ -140,6 +146,8 @@ m_Territory = INVALID_PLAYER; m_NeedInitialXZRotation = false; + m_TurretParent = INVALID_ENTITY; + m_TurretPosition = CFixedVector3D(); } virtual void Deinit() @@ -217,6 +225,59 @@ UpdateXZRotation(); } + virtual void UpdateTurretPosition() + { + if (m_TurretParent == INVALID_ENTITY) + return; + CmpPtr cmpPosition(GetSimContext(), m_TurretParent); + if (!cmpPosition) + { + LOGERROR(L"Turret with parent without position component"); + return; + } + if (!cmpPosition->IsInWorld()) + MoveOutOfWorld(); + else + { + CFixedVector2D rotatedPosition = CFixedVector2D(m_TurretPosition.X, m_TurretPosition.Z); + rotatedPosition = rotatedPosition.Rotate(cmpPosition->GetRotation().Y); + CFixedVector2D rootPosition = cmpPosition->GetPosition2D(); + entity_pos_t x = rootPosition.X + rotatedPosition.X; + entity_pos_t z = rootPosition.Y + rotatedPosition.Y; + if (!m_InWorld || m_X != x || m_Z != z) + MoveTo(x, z); + entity_pos_t y = cmpPosition->GetHeightOffset() + m_TurretPosition.Y; + if (!m_InWorld || m_YOffset != y) + SetHeightOffset(y); + } + } + + virtual std::set* GetTurrets() + { + return &m_Turrets; + } + + virtual void SetTurretParent(entity_id_t id, CFixedVector3D offset) + { + if (m_TurretParent != INVALID_ENTITY) + { + CmpPtr cmpPosition(GetSimContext(), m_TurretParent); + if (cmpPosition) + cmpPosition->GetTurrets()->erase(GetEntityId()); + } + + m_TurretParent = id; + m_TurretPosition = offset; + + if (m_TurretParent != INVALID_ENTITY) + { + CmpPtr cmpPosition(GetSimContext(), m_TurretParent); + if (cmpPosition) + cmpPosition->GetTurrets()->insert(GetEntityId()); + } + UpdateTurretPosition(); + } + virtual bool IsInWorld() { return m_InWorld; @@ -249,7 +310,6 @@ { m_X = x; m_Z = z; - m_RotY = ry; if (!m_InWorld) { @@ -259,7 +319,8 @@ m_LastYOffset = m_YOffset; } - AdvertisePositionChanges(); + // TurnTo will advertise the position changes + TurnTo(ry); } virtual void JumpTo(entity_pos_t x, entity_pos_t z) @@ -392,6 +453,12 @@ virtual void TurnTo(entity_angle_t y) { + if (m_TurretParent != INVALID_ENTITY) + { + CmpPtr cmpPosition(GetSimContext(), m_TurretParent); + if (cmpPosition) + y -= cmpPosition->GetRotation().Y; + } m_RotY = y; AdvertisePositionChanges(); @@ -399,6 +466,12 @@ virtual void SetYRotation(entity_angle_t y) { + if (m_TurretParent != INVALID_ENTITY) + { + CmpPtr cmpPosition(GetSimContext(), m_TurretParent); + if (cmpPosition) + y -= cmpPosition->GetRotation().Y; + } m_RotY = y; m_InterpolatedRotY = m_RotY.ToFloat(); @@ -431,6 +504,13 @@ virtual CFixedVector3D GetRotation() { + entity_angle_t y = m_RotY; + if (m_TurretParent != INVALID_ENTITY) + { + CmpPtr cmpPosition(GetSimContext(), m_TurretParent); + if (cmpPosition) + y += cmpPosition->GetRotation().Y; + } return CFixedVector3D(m_RotX, m_RotY, m_RotZ); } @@ -461,6 +541,32 @@ virtual CMatrix3D GetInterpolatedTransform(float frameOffset, bool forceFloating) { + if (m_TurretParent != INVALID_ENTITY) + { + CmpPtr cmpPosition(GetSimContext(), m_TurretParent); + if (!cmpPosition) + { + LOGERROR(L"Turret with parent without position component"); + CMatrix3D m; + m.SetIdentity(); + return m; + } + if (!cmpPosition->IsInWorld()) + { + LOGERROR(L"CCmpPosition::GetInterpolatedTransform called on turret entity when IsInWorld is false"); + CMatrix3D m; + m.SetIdentity(); + return m; + } + else + { + CMatrix3D parentTransformMatrix = cmpPosition->GetInterpolatedTransform(frameOffset, forceFloating); + CMatrix3D ownTransformation = CMatrix3D(); + ownTransformation.SetYRotation(m_InterpolatedRotY); + ownTransformation.Translate(-m_TurretPosition.X.ToFloat(), m_TurretPosition.Y.ToFloat(), -m_TurretPosition.Z.ToFloat()); + return parentTransformMatrix * ownTransformation; + } + } if (!m_InWorld) { LOGERROR(L"CCmpPosition::GetInterpolatedTransform called on entity when IsInWorld is false"); @@ -548,6 +654,7 @@ } case MT_TurnStart: { + m_LastInterpolatedRotX = m_InterpolatedRotX; m_LastInterpolatedRotZ = m_InterpolatedRotZ; @@ -592,6 +699,12 @@ private: void AdvertisePositionChanges() { + for (std::set::const_iterator it = m_Turrets.begin(); it != m_Turrets.end(); ++it) + { + CmpPtr cmpPosition(GetSimContext(), *it); + if (cmpPosition) + cmpPosition->UpdateTurretPosition(); + } if (m_InWorld) { CMessagePositionChanged msg(GetEntityId(), true, m_X, m_Z, m_RotY); Index: source/simulation2/components/ICmpPosition.h =================================================================== --- source/simulation2/components/ICmpPosition.h +++ source/simulation2/components/ICmpPosition.h @@ -59,6 +59,21 @@ { public: /** + * Set this as a turret of an other entity + */ + virtual void SetTurretParent(entity_id_t parent, CFixedVector3D offset) = 0; + + /** + * Has to be called to update the simulation position of the turret + */ + virtual void UpdateTurretPosition() = 0; + + /** + * Get the list of turrets to read or edit + */ + virtual std::set* GetTurrets() = 0; + + /** * Returns true if the entity currently exists at a defined position in the world. */ virtual bool IsInWorld() = 0; Index: source/simulation2/components/ICmpPosition.cpp =================================================================== --- source/simulation2/components/ICmpPosition.cpp +++ source/simulation2/components/ICmpPosition.cpp @@ -22,6 +22,7 @@ #include "simulation2/system/InterfaceScripted.h" BEGIN_INTERFACE_WRAPPER(Position) +DEFINE_INTERFACE_METHOD_2("SetTurretParent", void, ICmpPosition, SetTurretParent, entity_id_t, CFixedVector3D) DEFINE_INTERFACE_METHOD_0("IsInWorld", bool, ICmpPosition, IsInWorld) DEFINE_INTERFACE_METHOD_0("MoveOutOfWorld", void, ICmpPosition, MoveOutOfWorld) DEFINE_INTERFACE_METHOD_2("MoveTo", void, ICmpPosition, MoveTo, entity_pos_t, entity_pos_t) Index: source/simulation2/components/tests/test_RangeManager.h =================================================================== --- source/simulation2/components/tests/test_RangeManager.h +++ source/simulation2/components/tests/test_RangeManager.h @@ -40,6 +40,9 @@ public: DEFAULT_MOCK_COMPONENT() + virtual void SetTurretParent(entity_id_t UNUSED(id), CFixedVector3D UNUSED(pos)) {} + virtual void UpdateTurretPosition() {} + virtual std::set* GetTurrets() { return NULL; } virtual bool IsInWorld() { return true; } virtual void MoveOutOfWorld() { } virtual void MoveTo(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) { }