Index: ps/trunk/binaries/data/mods/public/simulation/components/BuildingAI.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/BuildingAI.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/BuildingAI.js
@@ -339,12 +339,34 @@
for (let target of this.targetUnits)
addTarget(target);
+ // The obstruction manager performs approximate range checks
+ // so we need to verify them here.
+ // TODO: perhaps an optional 'precise' mode to range queries would be more performant.
+ let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
+ let range = cmpAttack.GetRange(attackType);
+
+ let thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+ if (!thisCmpPosition.IsInWorld())
+ return;
+ let s = thisCmpPosition.GetPosition();
+
for (let i = 0; i < arrowsToFire; ++i)
{
let selectedIndex = targets.randomIndex();
let selectedTarget = targets.itemAt(selectedIndex);
- if (selectedTarget && this.CheckTargetVisible(selectedTarget))
+ // Copied from UnitAI's MoveToTargetAttackRange.
+ let targetCmpPosition = Engine.QueryInterface(selectedTarget, IID_Position);
+ if (!targetCmpPosition.IsInWorld())
+ continue;
+
+ let t = targetCmpPosition.GetPosition();
+ // h is positive when I'm higher than the target
+ let h = s.y - t.y + range.elevationBonus;
+ let parabolicMaxRange = Math.sqrt(Math.square(range.max) + 2 * range.max * h);
+ if (selectedTarget && this.CheckTargetVisible(selectedTarget) &&
+ h > -range.max / 2 && cmpObstructionManager.IsInTargetRange(
+ this.entity, selectedTarget, range.min, parabolicMaxRange, false))
{
cmpAttack.PerformAttack(attackType, selectedTarget);
PlaySound("attack_" + attackType.toLowerCase(), this.entity);
Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/iber/monument.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/structures/iber/monument.xml
+++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/iber/monument.xml
@@ -7,7 +7,7 @@
Monument
Monument
- 150
+ 135
Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/pillar_ashoka.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/pillar_ashoka.xml
+++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/pillar_ashoka.xml
@@ -7,7 +7,7 @@
Pillar
Pillar
- 75
+ 70
Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/merc_camp_egyptian.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/structures/merc_camp_egyptian.xml
+++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/merc_camp_egyptian.xml
@@ -4,7 +4,7 @@
own neutral
MercenaryCamp
- 100
+ 70
Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/palisades_gate.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/structures/palisades_gate.xml
+++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/palisades_gate.xml
@@ -11,7 +11,7 @@
7.0
- 20
+ 2
0.5
Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/mercenary_camp.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/mercenary_camp.xml
+++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/mercenary_camp.xml
@@ -4,7 +4,7 @@
own neutral
MercenaryCamp
- 100
+ 70
Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/army_camp.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/army_camp.xml
+++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/army_camp.xml
@@ -38,7 +38,7 @@
ArmyCamp
ArmyCamp
- 80
+ 45
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
@@ -44,7 +44,7 @@
CivilCentre
CivilCentre
- 200
+ 160
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre_military_colony.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre_military_colony.xml
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre_military_colony.xml
@@ -8,7 +8,7 @@
Colony
CivilCentre
- 120
+ 80
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_outpost.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_outpost.xml
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_outpost.xml
@@ -7,7 +7,7 @@
own neutral
Outpost
- 50
+ 45
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower.xml
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower.xml
@@ -35,7 +35,7 @@
Tower
Tower
- 60
+ 55
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_gate.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_gate.xml
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_gate.xml
@@ -12,7 +12,7 @@
- 20
+ 2
2500
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml
@@ -37,7 +37,7 @@
Fortress
Fortress
- 80
+ 55
Index: ps/trunk/source/simulation2/components/CCmpRangeManager.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpRangeManager.cpp
+++ ps/trunk/source/simulation2/components/CCmpRangeManager.cpp
@@ -142,22 +142,24 @@
*/
struct Query
{
- bool enabled;
- bool parabolic;
+ std::vector lastMatch;
CEntityHandle source; // TODO: this could crash if an entity is destroyed while a Query is still referencing it
entity_pos_t minRange;
entity_pos_t maxRange;
- entity_pos_t elevationBonus;
+ entity_pos_t elevationBonus; // Used for parabolas only.
u32 ownersMask;
i32 interface;
- std::vector lastMatch;
u8 flagsMask;
+ bool enabled;
+ bool parabolic;
};
/**
* Checks whether v is in a parabolic range of (0,0,0)
* The highest point of the paraboloid is (0,range/2,0)
* and the circle of distance 'range' around (0,0,0) on height y=0 is part of the paraboloid
+ * This equates to computing f(x, z) = y = -(xx + zz)/(2*range) + range/2 > 0,
+ * or alternatively √(xx+zz) <= √(range^2 - 2range*y).
*
* Avoids sqrting and overflowing.
*/
@@ -1206,19 +1208,20 @@
continue;
CFixedVector3D secondPosition = cmpSecondPosition->GetPosition();
- // Restrict based on precise distance
- if (!InParabolicRange(
- CFixedVector3D(it->second.x, secondPosition.Y, it->second.z)
- - pos3d,
- q.maxRange))
+ // Doing an exact check for parabolas with obstruction sizes is not really possible.
+ // However, we can prove that InParabolicRange(d, range + size) > InParabolicRange(d, range)
+ // in the sense that it always returns true when the latter would, which is enough.
+ // To do so, compute the derivative with respect to distance, and notice that
+ // they have an intersection after which the former grows slower, and then use that to prove the above.
+ // Note that this is only true because we do not account for vertical size here,
+ // if we did, we would also need to artificially 'raise' the source over the target.
+ if (!InParabolicRange(CFixedVector3D(it->second.x, secondPosition.Y, it->second.z) - pos3d,
+ q.maxRange + fixed::FromInt(it->second.size)))
continue;
if (!q.minRange.IsZero())
- {
- int distVsMin = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange);
- if (distVsMin < 0)
+ if ((CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange) < 0)
continue;
- }
r.push_back(it->first);
}
@@ -1239,17 +1242,13 @@
if (!TestEntityQuery(q, it->first, it->second))
continue;
- // Restrict based on precise distance
- int distVsMax = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.maxRange);
- if (distVsMax > 0)
+ // Restrict based on approximate circle-circle distance.
+ if ((CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.maxRange + fixed::FromInt(it->second.size)) > 0)
continue;
if (!q.minRange.IsZero())
- {
- int distVsMin = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange);
- if (distVsMin < 0)
+ if ((CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange) < 0)
continue;
- }
r.push_back(it->first);
}
@@ -1377,6 +1376,16 @@
q.maxRange = maxRange;
q.elevationBonus = entity_pos_t::Zero();
+ if (q.source.GetId() != INVALID_ENTITY && q.maxRange != entity_pos_t::FromInt(-1))
+ {
+ EntityMap::const_iterator it = m_EntityData.find(q.source.GetId());
+ ENSURE(it != m_EntityData.end());
+ // Adjust the range query based on the querier's obstruction radius.
+ // The smallest side of the obstruction isn't known here, so we can't safely adjust the min-range, only the max.
+ // 'size' is the diagonal size rounded up so this will cover all possible rotations of the querier.
+ q.maxRange += fixed::FromInt(it->second.size);
+ }
+
q.ownersMask = 0;
for (size_t i = 0; i < owners.size(); ++i)
q.ownersMask |= CalcOwnerMask(owners[i]);
Index: ps/trunk/source/simulation2/components/ICmpRangeManager.h
===================================================================
--- ps/trunk/source/simulation2/components/ICmpRangeManager.h
+++ ps/trunk/source/simulation2/components/ICmpRangeManager.h
@@ -73,7 +73,7 @@
*
* In most cases the users are event-based and want notifications when something
* has entered or left the range, and the query can be set up once and rarely changed.
- * These queries have to be fast. It's fine to approximate an entity as a point.
+ * These queries have to be fast. Entities are approximated as circles.
*
* Current design:
*
Index: ps/trunk/source/simulation2/components/tests/test_RangeManager.h
===================================================================
--- ps/trunk/source/simulation2/components/tests/test_RangeManager.h
+++ ps/trunk/source/simulation2/components/tests/test_RangeManager.h
@@ -17,13 +17,14 @@
#include "simulation2/system/ComponentTest.h"
#include "simulation2/components/ICmpRangeManager.h"
+#include "simulation2/components/ICmpObstruction.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpVision.h"
#include
#include
-class MockVision : public ICmpVision
+class MockVisionRgm : public ICmpVision
{
public:
DEFAULT_MOCK_COMPONENT()
@@ -32,7 +33,7 @@
virtual bool GetRevealShore() const { return false; }
};
-class MockPosition : public ICmpPosition
+class MockPositionRgm : public ICmpPosition
{
public:
DEFAULT_MOCK_COMPONENT()
@@ -56,8 +57,8 @@
virtual void SetFloating(bool UNUSED(flag)) { }
virtual void SetActorFloating(bool UNUSED(flag)) { }
virtual void SetConstructionProgress(fixed UNUSED(progress)) { }
- virtual CFixedVector3D GetPosition() const { return CFixedVector3D(); }
- virtual CFixedVector2D GetPosition2D() const { return CFixedVector2D(); }
+ virtual CFixedVector3D GetPosition() const { return m_Pos; }
+ virtual CFixedVector2D GetPosition2D() const { return CFixedVector2D(m_Pos.X, m_Pos.Z); }
virtual CFixedVector3D GetPreviousPosition() const { return CFixedVector3D(); }
virtual CFixedVector2D GetPreviousPosition2D() const { return CFixedVector2D(); }
virtual void TurnTo(entity_angle_t UNUSED(y)) { }
@@ -67,6 +68,45 @@
virtual fixed GetDistanceTravelled() const { return fixed::Zero(); }
virtual void GetInterpolatedPosition2D(float UNUSED(frameOffset), float& x, float& z, float& rotY) const { x = z = rotY = 0; }
virtual CMatrix3D GetInterpolatedTransform(float UNUSED(frameOffset)) const { return CMatrix3D(); }
+
+ CFixedVector3D m_Pos;
+};
+
+class MockObstructionRgm : public ICmpObstruction
+{
+public:
+ DEFAULT_MOCK_COMPONENT();
+
+ MockObstructionRgm(entity_pos_t s) : m_Size(s) {};
+
+ virtual ICmpObstructionManager::tag_t GetObstruction() const { return {}; };
+ virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare&) const { return false; };
+ virtual bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare&) const { return false; };
+ virtual entity_pos_t GetSize() const { return m_Size; };
+ virtual CFixedVector2D GetStaticSize() const { return {}; };
+ virtual EObstructionType GetObstructionType() const { return {}; };
+ virtual void SetUnitClearance(const entity_pos_t&) {};
+ virtual bool IsControlPersistent() const { return {}; };
+ virtual bool CheckShorePlacement() const { return {}; };
+ virtual EFoundationCheck CheckFoundation(const std::string&) const { return {}; };
+ virtual EFoundationCheck CheckFoundation(const std::string& , bool) const { return {}; };
+ virtual std::string CheckFoundation_wrapper(const std::string&, bool) const { return {}; };
+ virtual bool CheckDuplicateFoundation() const { return {}; };
+ virtual std::vector GetEntitiesByFlags(ICmpObstructionManager::flags_t) const { return {}; };
+ virtual std::vector GetEntitiesBlockingMovement() const { return {}; };
+ virtual std::vector GetEntitiesBlockingConstruction() const { return {}; };
+ virtual std::vector GetEntitiesDeletedUponConstruction() const { return {}; };
+ virtual void ResolveFoundationCollisions() const {};
+ virtual void SetActive(bool) {};
+ virtual void SetMovingFlag(bool) {};
+ virtual void SetDisableBlockMovementPathfinding(bool, bool, int32_t) {};
+ virtual bool GetBlockMovementFlag() const { return {}; };
+ virtual void SetControlGroup(entity_id_t) {};
+ virtual entity_id_t GetControlGroup() const { return {}; };
+ virtual void SetControlGroup2(entity_id_t) {};
+ virtual entity_id_t GetControlGroup2() const { return {}; };
+private:
+ entity_pos_t m_Size;
};
class TestCmpRangeManager : public CxxTest::TestSuite
@@ -91,10 +131,10 @@
ICmpRangeManager* cmp = test.Add(CID_RangeManager, "", SYSTEM_ENTITY);
- MockVision vision;
+ MockVisionRgm vision;
test.AddMock(100, IID_Vision, vision);
- MockPosition position;
+ MockPositionRgm position;
test.AddMock(100, IID_Position, position);
// This tests that the incremental computation produces the correct result
@@ -153,4 +193,76 @@
}
}
}
+
+ void test_queries()
+ {
+ ComponentTestHelper test(g_ScriptContext);
+
+ ICmpRangeManager* cmp = test.Add(CID_RangeManager, "", SYSTEM_ENTITY);
+
+ MockVisionRgm vision, vision2;
+ MockPositionRgm position, position2;
+ MockObstructionRgm obs(fixed::FromInt(2)), obs2(fixed::Zero());
+ test.AddMock(100, IID_Vision, vision);
+ test.AddMock(100, IID_Position, position);
+ test.AddMock(100, IID_Obstruction, obs);
+
+ test.AddMock(101, IID_Vision, vision2);
+ test.AddMock(101, IID_Position, position2);
+ test.AddMock(101, IID_Obstruction, obs2);
+
+ cmp->SetBounds(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0), entity_pos_t::FromInt(512), entity_pos_t::FromInt(512), 512/TERRAIN_TILE_SIZE + 1);
+ cmp->Verify();
+ { CMessageCreate msg(100); cmp->HandleMessage(msg, false); }
+ { CMessageCreate msg(101); cmp->HandleMessage(msg, false); }
+
+ { CMessageOwnershipChanged msg(100, -1, 1); cmp->HandleMessage(msg, false); }
+ { CMessageOwnershipChanged msg(101, -1, 1); cmp->HandleMessage(msg, false); }
+
+ auto move = [&cmp](entity_id_t ent, MockPositionRgm& pos, fixed x, fixed z) {
+ pos.m_Pos = CFixedVector3D(x, fixed::Zero(), z);
+ { CMessagePositionChanged msg(ent, true, x, z, entity_angle_t::Zero()); cmp->HandleMessage(msg, false); }
+ };
+
+ move(100, position, fixed::FromInt(10), fixed::FromInt(10));
+ move(101, position2, fixed::FromInt(10), fixed::FromInt(20));
+
+ std::vector nearby = cmp->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {1}, 0);
+ TS_ASSERT_EQUALS(nearby, std::vector{});
+ nearby = cmp->ExecuteQuery(100, fixed::FromInt(4), fixed::FromInt(50), {1}, 0);
+ TS_ASSERT_EQUALS(nearby, std::vector{101});
+
+ move(101, position2, fixed::FromInt(10), fixed::FromInt(10));
+ nearby = cmp->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {1}, 0);
+ TS_ASSERT_EQUALS(nearby, std::vector{101});
+ nearby = cmp->ExecuteQuery(100, fixed::FromInt(4), fixed::FromInt(50), {1}, 0);
+ TS_ASSERT_EQUALS(nearby, std::vector{});
+
+ move(101, position2, fixed::FromInt(10), fixed::FromInt(13));
+ nearby = cmp->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {1}, 0);
+ TS_ASSERT_EQUALS(nearby, std::vector{101});
+ nearby = cmp->ExecuteQuery(100, fixed::FromInt(4), fixed::FromInt(50), {1}, 0);
+ TS_ASSERT_EQUALS(nearby, std::vector{});
+
+ move(101, position2, fixed::FromInt(10), fixed::FromInt(15));
+ // In range thanks to self obstruction size.
+ nearby = cmp->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {1}, 0);
+ TS_ASSERT_EQUALS(nearby, std::vector{101});
+ // In range thanks to target obstruction size.
+ nearby = cmp->ExecuteQuery(101, fixed::FromInt(0), fixed::FromInt(4), {1}, 0);
+ TS_ASSERT_EQUALS(nearby, std::vector{100});
+
+ // Trickier: min-range is closest-to-closest, but rotation may change the real distance.
+ nearby = cmp->ExecuteQuery(100, fixed::FromInt(2), fixed::FromInt(50), {1}, 0);
+ TS_ASSERT_EQUALS(nearby, std::vector{101});
+ nearby = cmp->ExecuteQuery(100, fixed::FromInt(5), fixed::FromInt(50), {1}, 0);
+ TS_ASSERT_EQUALS(nearby, std::vector{101});
+ nearby = cmp->ExecuteQuery(100, fixed::FromInt(6), fixed::FromInt(50), {1}, 0);
+ TS_ASSERT_EQUALS(nearby, std::vector{});
+ nearby = cmp->ExecuteQuery(101, fixed::FromInt(5), fixed::FromInt(50), {1}, 0);
+ TS_ASSERT_EQUALS(nearby, std::vector{100});
+ nearby = cmp->ExecuteQuery(101, fixed::FromInt(6), fixed::FromInt(50), {1}, 0);
+ TS_ASSERT_EQUALS(nearby, std::vector{});
+
+ }
};