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
+
+
+ units/athen_infantry_archer_a
+
+ 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)) { }