# Add auto-attacking of nearby enemies.
Add general range-detection code. Avoid unnecessarily computing 3D entity positions. This was SVN commit r7817.
This commit is contained in:
parent
913404e4b2
commit
0cd0a1f584
@ -64,7 +64,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Dev/cheat commands -->
|
<!-- Dev/cheat commands -->
|
||||||
<object name="devCommands" size="100%-155 50 100%-16 130" z="200" type="image" sprite="devCommandsBackground"
|
<object name="devCommands" size="100%-155 50 100%-16 146" z="200" type="image" sprite="devCommandsBackground"
|
||||||
hidden="true" hotkey="session.devcommands.toggle">
|
hidden="true" hotkey="session.devcommands.toggle">
|
||||||
<action on="Press">
|
<action on="Press">
|
||||||
this.hidden = !this.hidden;
|
this.hidden = !this.hidden;
|
||||||
@ -90,6 +90,11 @@
|
|||||||
<object size="100%-16 64 100% 80" type="checkbox" style="wheatCrossBox">
|
<object size="100%-16 64 100% 80" type="checkbox" style="wheatCrossBox">
|
||||||
<action on="Press">g_Selection.SetMotionDebugOverlay(this.checked);</action>
|
<action on="Press">g_Selection.SetMotionDebugOverlay(this.checked);</action>
|
||||||
</object>
|
</object>
|
||||||
|
|
||||||
|
<object size="0 80 100%-18 96" type="text" style="devCommandsText">Range overlay</object>
|
||||||
|
<object size="100%-16 80 100% 96" type="checkbox" style="wheatCrossBox">
|
||||||
|
<action on="Press">Engine.GuiInterfaceCall("SetRangeDebugOverlay", this.checked);</action>
|
||||||
|
</object>
|
||||||
</object>
|
</object>
|
||||||
|
|
||||||
<!-- ================================ ================================ -->
|
<!-- ================================ ================================ -->
|
||||||
|
@ -248,6 +248,12 @@ GuiInterface.prototype.SetMotionDebugOverlay = function(player, data)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
GuiInterface.prototype.SetRangeDebugOverlay = function(player, enabled)
|
||||||
|
{
|
||||||
|
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||||
|
cmpRangeManager.SetDebugOverlay(enabled);
|
||||||
|
};
|
||||||
|
|
||||||
// List the GuiInterface functions that can be safely called by GUI scripts.
|
// List the GuiInterface functions that can be safely called by GUI scripts.
|
||||||
// (GUI scripts are non-deterministic and untrusted, so these functions must be
|
// (GUI scripts are non-deterministic and untrusted, so these functions must be
|
||||||
// appropriately careful. They are called with a first argument "player", which is
|
// appropriately careful. They are called with a first argument "player", which is
|
||||||
@ -262,6 +268,7 @@ var exposedFunctions = {
|
|||||||
"SetPathfinderDebugOverlay": 1,
|
"SetPathfinderDebugOverlay": 1,
|
||||||
"SetObstructionDebugOverlay": 1,
|
"SetObstructionDebugOverlay": 1,
|
||||||
"SetMotionDebugOverlay": 1,
|
"SetMotionDebugOverlay": 1,
|
||||||
|
"SetRangeDebugOverlay": 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
GuiInterface.prototype.ScriptCall = function(player, name, args)
|
GuiInterface.prototype.ScriptCall = function(player, name, args)
|
||||||
|
@ -19,6 +19,10 @@ var UnitFsmSpec = {
|
|||||||
// ignore uninteresting construction messages
|
// ignore uninteresting construction messages
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"LosRangeUpdate": function(msg) {
|
||||||
|
// ignore newly-seen units by default
|
||||||
|
},
|
||||||
|
|
||||||
"Attacked": function(msg) {
|
"Attacked": function(msg) {
|
||||||
// Default behaviour: attack back at our attacker
|
// Default behaviour: attack back at our attacker
|
||||||
if (this.CanAttack(msg.data.attacker))
|
if (this.CanAttack(msg.data.attacker))
|
||||||
@ -30,11 +34,35 @@ var UnitFsmSpec = {
|
|||||||
|
|
||||||
"IDLE": {
|
"IDLE": {
|
||||||
"enter": function() {
|
"enter": function() {
|
||||||
|
// If we entered the idle state we must have nothing better to do,
|
||||||
|
// so immediately check whether there's anybody nearby to attack.
|
||||||
|
// (If anyone approaches later, it'll be handled via LosRangeUpdate.)
|
||||||
|
if (this.losRangeQuery)
|
||||||
|
{
|
||||||
|
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||||
|
var ents = rangeMan.ResetActiveQuery(this.losRangeQuery);
|
||||||
|
if (this.AttackVisibleEntity(ents))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nobody to attack - switch to idle
|
||||||
this.SelectAnimation("idle");
|
this.SelectAnimation("idle");
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
"leave": function() {
|
||||||
|
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||||
|
rangeMan.DisableActiveQuery(this.losRangeQuery);
|
||||||
|
},
|
||||||
|
|
||||||
|
"LosRangeUpdate": function(msg) {
|
||||||
|
// TODO: implement stances (ignore this message if hold-fire stance)
|
||||||
|
|
||||||
|
// Start attacking one of the newly-seen enemy (if any)
|
||||||
|
this.AttackVisibleEntity(msg.data.added);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
"Order.Walk": function(msg) {
|
"Order.Walk": function(msg) {
|
||||||
var ok;
|
var ok;
|
||||||
if (this.order.data.target)
|
if (this.order.data.target)
|
||||||
@ -328,14 +356,56 @@ UnitAI.prototype.Init = function()
|
|||||||
this.order = undefined; // always == this.orderQueue[0]
|
this.order = undefined; // always == this.orderQueue[0]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
//// FSM linkage functions ////
|
|
||||||
|
|
||||||
UnitAI.prototype.OnCreate = function()
|
UnitAI.prototype.OnCreate = function()
|
||||||
{
|
{
|
||||||
UnitFsm.Init(this, "INDIVIDUAL.IDLE");
|
UnitFsm.Init(this, "INDIVIDUAL.IDLE");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UnitAI.prototype.OnOwnershipChanged = function(msg)
|
||||||
|
{
|
||||||
|
this.SetupRangeQuery(msg.to);
|
||||||
|
};
|
||||||
|
|
||||||
|
UnitAI.prototype.OnDestroy = function()
|
||||||
|
{
|
||||||
|
// Clean up any timers that are now obsolete
|
||||||
|
this.StopTimer();
|
||||||
|
|
||||||
|
// Clean up range queries
|
||||||
|
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||||
|
if (this.losRangeQuery)
|
||||||
|
rangeMan.DestroyActiveQuery(this.losRangeQuery);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up a range query for all enemy units within LOS range
|
||||||
|
// which can be attacked.
|
||||||
|
// This should be called whenever our ownership changes.
|
||||||
|
UnitAI.prototype.SetupRangeQuery = function(owner)
|
||||||
|
{
|
||||||
|
var cmpVision = Engine.QueryInterface(this.entity, IID_Vision);
|
||||||
|
if (!cmpVision)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||||
|
var playerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
|
||||||
|
|
||||||
|
if (this.losRangeQuery)
|
||||||
|
rangeMan.DestroyActiveQuery(this.losRangeQuery);
|
||||||
|
|
||||||
|
var range = cmpVision.GetRange();
|
||||||
|
|
||||||
|
// Find all enemy players (i.e. exclude Gaia and ourselves)
|
||||||
|
var players = [];
|
||||||
|
for (var i = 1; i < playerMan.GetNumPlayers(); ++i)
|
||||||
|
if (i != owner)
|
||||||
|
players.push(i);
|
||||||
|
|
||||||
|
this.losRangeQuery = rangeMan.CreateActiveQuery(this.entity, range, players, IID_DamageReceiver);
|
||||||
|
rangeMan.EnableActiveQuery(this.losRangeQuery);
|
||||||
|
};
|
||||||
|
|
||||||
|
//// FSM linkage functions ////
|
||||||
|
|
||||||
UnitAI.prototype.SetNextState = function(state)
|
UnitAI.prototype.SetNextState = function(state)
|
||||||
{
|
{
|
||||||
UnitFsm.SetNextState(this, state);
|
UnitFsm.SetNextState(this, state);
|
||||||
@ -438,12 +508,6 @@ UnitAI.prototype.StopTimer = function()
|
|||||||
|
|
||||||
//// Message handlers /////
|
//// Message handlers /////
|
||||||
|
|
||||||
UnitAI.prototype.OnDestroy = function()
|
|
||||||
{
|
|
||||||
// Clean up any timers that are now obsolete
|
|
||||||
this.StopTimer();
|
|
||||||
};
|
|
||||||
|
|
||||||
UnitAI.prototype.OnMotionChanged = function(msg)
|
UnitAI.prototype.OnMotionChanged = function(msg)
|
||||||
{
|
{
|
||||||
if (!msg.speed)
|
if (!msg.speed)
|
||||||
@ -463,6 +527,12 @@ UnitAI.prototype.OnAttacked = function(msg)
|
|||||||
UnitFsm.ProcessMessage(this, {"type": "Attacked", "data": msg});
|
UnitFsm.ProcessMessage(this, {"type": "Attacked", "data": msg});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UnitAI.prototype.OnRangeUpdate = function(msg)
|
||||||
|
{
|
||||||
|
if (msg.tag == this.losRangeQuery)
|
||||||
|
UnitFsm.ProcessMessage(this, {"type": "LosRangeUpdate", "data": msg});
|
||||||
|
};
|
||||||
|
|
||||||
//// Helper functions to be called by the FSM ////
|
//// Helper functions to be called by the FSM ////
|
||||||
|
|
||||||
UnitAI.prototype.GetWalkSpeed = function()
|
UnitAI.prototype.GetWalkSpeed = function()
|
||||||
@ -562,6 +632,23 @@ UnitAI.prototype.GetBestAttack = function()
|
|||||||
return cmpAttack.GetBestAttack();
|
return cmpAttack.GetBestAttack();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to find one of the given entities which can be attacked,
|
||||||
|
* and start attacking it.
|
||||||
|
* Returns true if it found something to attack.
|
||||||
|
*/
|
||||||
|
UnitAI.prototype.AttackVisibleEntity = function(ents)
|
||||||
|
{
|
||||||
|
for each (var target in ents)
|
||||||
|
{
|
||||||
|
if (this.CanAttack(target))
|
||||||
|
{
|
||||||
|
this.PushOrderFront("Attack", { "target": target });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
//// External interface functions ////
|
//// External interface functions ////
|
||||||
|
|
||||||
|
@ -14,4 +14,9 @@ Vision.prototype.Schema =
|
|||||||
* TODO: this all needs to be designed and implemented
|
* TODO: this all needs to be designed and implemented
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
Vision.prototype.GetRange = function()
|
||||||
|
{
|
||||||
|
return +this.template.Range;
|
||||||
|
};
|
||||||
|
|
||||||
Engine.RegisterComponentType(IID_Vision, "Vision", Vision);
|
Engine.RegisterComponentType(IID_Vision, "Vision", Vision);
|
||||||
|
@ -220,6 +220,11 @@ FSM.prototype.SwitchToNextState = function(obj, nextStateName)
|
|||||||
if (!toState)
|
if (!toState)
|
||||||
error("Tried to change to non-existent state '" + nextState + "'");
|
error("Tried to change to non-existent state '" + nextState + "'");
|
||||||
|
|
||||||
|
// Find the set of states in the hierarchy tree to leave then enter,
|
||||||
|
// to traverse from the old state to the new one.
|
||||||
|
// If any enter/leave function returns true then abort the process
|
||||||
|
// (this lets them intercept the transition and start a new transition)
|
||||||
|
|
||||||
for (var equalPrefix = 0; fromState[equalPrefix] === toState[equalPrefix]; ++equalPrefix)
|
for (var equalPrefix = 0; fromState[equalPrefix] === toState[equalPrefix]; ++equalPrefix)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -228,14 +233,22 @@ FSM.prototype.SwitchToNextState = function(obj, nextStateName)
|
|||||||
{
|
{
|
||||||
var leave = this.states[fromState[i]].leave;
|
var leave = this.states[fromState[i]].leave;
|
||||||
if (leave)
|
if (leave)
|
||||||
leave.apply(obj);
|
{
|
||||||
|
obj.fsmStateName = fromState[i];
|
||||||
|
if (leave.apply(obj))
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = equalPrefix; i < toState.length; ++i)
|
for (var i = equalPrefix; i < toState.length; ++i)
|
||||||
{
|
{
|
||||||
var enter = this.states[toState[i]].enter;
|
var enter = this.states[toState[i]].enter;
|
||||||
if (enter)
|
if (enter)
|
||||||
enter.apply(obj);
|
{
|
||||||
|
obj.fsmStateName = toState[i];
|
||||||
|
if (enter.apply(obj))
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.fsmStateName = nextStateName;
|
obj.fsmStateName = nextStateName;
|
||||||
|
@ -111,6 +111,52 @@ public:
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns -1, 0, +1 depending on whether length is less/equal/greater
|
||||||
|
* than the argument.
|
||||||
|
* Avoids sqrting and overflowing.
|
||||||
|
*/
|
||||||
|
int CompareLength(fixed cmp) const
|
||||||
|
{
|
||||||
|
i64 x = (i64)X.GetInternalValue(); // abs(x) <= 2^31
|
||||||
|
i64 y = (i64)Y.GetInternalValue();
|
||||||
|
u64 xx = (u64)(x * x); // xx <= 2^62
|
||||||
|
u64 yy = (u64)(y * y);
|
||||||
|
u64 d2 = xx + yy; // d2 <= 2^63 (no overflow)
|
||||||
|
|
||||||
|
i64 c = (i64)cmp.GetInternalValue();
|
||||||
|
u64 c2 = (u64)(c * c);
|
||||||
|
if (d2 < c2)
|
||||||
|
return -1;
|
||||||
|
else if (d2 > c2)
|
||||||
|
return +1;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns -1, 0, +1 depending on whether length is less/equal/greater
|
||||||
|
* than the argument's length.
|
||||||
|
* Avoids sqrting and overflowing.
|
||||||
|
*/
|
||||||
|
int CompareLength(const CFixedVector2D& other) const
|
||||||
|
{
|
||||||
|
i64 x = (i64)X.GetInternalValue();
|
||||||
|
i64 y = (i64)Y.GetInternalValue();
|
||||||
|
u64 d2 = (u64)(x * x) + (u64)(y * y);
|
||||||
|
|
||||||
|
i64 ox = (i64)other.X.GetInternalValue();
|
||||||
|
i64 oy = (i64)other.Y.GetInternalValue();
|
||||||
|
u64 od2 = (u64)(ox * ox) + (u64)(oy * oy);
|
||||||
|
|
||||||
|
if (d2 < od2)
|
||||||
|
return -1;
|
||||||
|
else if (d2 > od2)
|
||||||
|
return +1;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool IsZero() const
|
bool IsZero() const
|
||||||
{
|
{
|
||||||
return (X.IsZero() && Y.IsZero());
|
return (X.IsZero() && Y.IsZero());
|
||||||
|
@ -117,30 +117,6 @@ template<> bool ScriptInterface::FromJSVal<std::string>(JSContext* cx, jsval v,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
template<typename T> bool ScriptInterface::FromJSVal<std::vector<T> >(JSContext* cx, jsval v, std::vector<T>& out)
|
|
||||||
{
|
|
||||||
JSObject* obj;
|
|
||||||
if (!JS_ValueToObject(cx, v, &obj) || obj == NULL || !JS_IsArrayObject(cx, obj))
|
|
||||||
FAIL("Argument must be an array");
|
|
||||||
jsuint length;
|
|
||||||
if (!JS_GetArrayLength(cx, obj, &length))
|
|
||||||
FAIL("Failed to get array length");
|
|
||||||
out.reserve(length);
|
|
||||||
for (jsuint i = 0; i < length; ++i)
|
|
||||||
{
|
|
||||||
jsval el;
|
|
||||||
if (!JS_GetElement(cx, obj, i, &el))
|
|
||||||
FAIL("Failed to read array element");
|
|
||||||
T el2;
|
|
||||||
if (!FromJSVal<T>::Convert(cx, el, el2))
|
|
||||||
return false;
|
|
||||||
out.push_back(el2);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
// Primitive types:
|
// Primitive types:
|
||||||
|
|
||||||
@ -235,6 +211,28 @@ template<typename T> static jsval ToJSVal_vector(JSContext* cx, const std::vecto
|
|||||||
return OBJECT_TO_JSVAL(obj);
|
return OBJECT_TO_JSVAL(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T> static bool FromJSVal_vector(JSContext* cx, jsval v, std::vector<T>& out)
|
||||||
|
{
|
||||||
|
JSObject* obj;
|
||||||
|
if (!JS_ValueToObject(cx, v, &obj) || obj == NULL || !JS_IsArrayObject(cx, obj))
|
||||||
|
FAIL("Argument must be an array");
|
||||||
|
jsuint length;
|
||||||
|
if (!JS_GetArrayLength(cx, obj, &length))
|
||||||
|
FAIL("Failed to get array length");
|
||||||
|
out.reserve(length);
|
||||||
|
for (jsuint i = 0; i < length; ++i)
|
||||||
|
{
|
||||||
|
jsval el;
|
||||||
|
if (!JS_GetElement(cx, obj, i, &el))
|
||||||
|
FAIL("Failed to read array element");
|
||||||
|
T el2;
|
||||||
|
if (!ScriptInterface::FromJSVal<T>(cx, el, el2))
|
||||||
|
return false;
|
||||||
|
out.push_back(el2);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
template<> jsval ScriptInterface::ToJSVal<std::vector<int> >(JSContext* cx, const std::vector<int>& val)
|
template<> jsval ScriptInterface::ToJSVal<std::vector<int> >(JSContext* cx, const std::vector<int>& val)
|
||||||
{
|
{
|
||||||
return ToJSVal_vector(cx, val);
|
return ToJSVal_vector(cx, val);
|
||||||
@ -249,3 +247,8 @@ template<> jsval ScriptInterface::ToJSVal<std::vector<std::wstring> >(JSContext*
|
|||||||
{
|
{
|
||||||
return ToJSVal_vector(cx, val);
|
return ToJSVal_vector(cx, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<> bool ScriptInterface::FromJSVal<std::vector<int> >(JSContext* cx, jsval v, std::vector<int>& out)
|
||||||
|
{
|
||||||
|
return FromJSVal_vector(cx, v, out);
|
||||||
|
}
|
||||||
|
@ -87,7 +87,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is send immediately after a new entity's components have all be created
|
* This is sent immediately after a new entity's components have all been created
|
||||||
* and initialised.
|
* and initialised.
|
||||||
*/
|
*/
|
||||||
class CMessageCreate : public CMessage
|
class CMessageCreate : public CMessage
|
||||||
@ -149,11 +149,12 @@ class CMessagePositionChanged : public CMessage
|
|||||||
public:
|
public:
|
||||||
DEFAULT_MESSAGE_IMPL(PositionChanged)
|
DEFAULT_MESSAGE_IMPL(PositionChanged)
|
||||||
|
|
||||||
CMessagePositionChanged(bool inWorld, entity_pos_t x, entity_pos_t z, entity_angle_t a) :
|
CMessagePositionChanged(entity_id_t entity, bool inWorld, entity_pos_t x, entity_pos_t z, entity_angle_t a) :
|
||||||
inWorld(inWorld), x(x), z(z), a(a)
|
entity(entity), inWorld(inWorld), x(x), z(z), a(a)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entity_id_t entity;
|
||||||
bool inWorld;
|
bool inWorld;
|
||||||
entity_pos_t x, z;
|
entity_pos_t x, z;
|
||||||
entity_angle_t a;
|
entity_angle_t a;
|
||||||
@ -192,4 +193,45 @@ public:
|
|||||||
ssize_t i0, j0, i1, j1; // inclusive lower bound, exclusive upper bound, in tiles
|
ssize_t i0, j0, i1, j1; // inclusive lower bound, exclusive upper bound, in tiles
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sent by CCmpRangeManager at most once per turn, when an active range query
|
||||||
|
* has had matching units enter/leave the range since the last RangeUpdate.
|
||||||
|
*/
|
||||||
|
class CMessageRangeUpdate : public CMessage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DEFAULT_MESSAGE_IMPL(RangeUpdate)
|
||||||
|
|
||||||
|
CMessageRangeUpdate(u32 tag, const std::vector<entity_id_t>& added, const std::vector<entity_id_t>& removed) :
|
||||||
|
tag(tag), added(added), removed(removed)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 tag;
|
||||||
|
std::vector<entity_id_t> added;
|
||||||
|
std::vector<entity_id_t> removed;
|
||||||
|
|
||||||
|
// CCmpRangeManager wants to store a vector of messages and wants to
|
||||||
|
// swap vectors instead of copying (to save on memory allocations),
|
||||||
|
// so add some constructors for it:
|
||||||
|
|
||||||
|
CMessageRangeUpdate(u32 tag) :
|
||||||
|
tag(tag)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CMessageRangeUpdate(const CMessageRangeUpdate& other) :
|
||||||
|
CMessage(), tag(other.tag), added(other.added), removed(other.removed)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CMessageRangeUpdate& operator=(const CMessageRangeUpdate& other)
|
||||||
|
{
|
||||||
|
tag = other.tag;
|
||||||
|
added = other.added;
|
||||||
|
removed = other.removed;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
#endif // INCLUDED_MESSAGETYPES
|
#endif // INCLUDED_MESSAGETYPES
|
||||||
|
@ -95,6 +95,7 @@ public:
|
|||||||
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_ObstructionManager, noParam);
|
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_ObstructionManager, noParam);
|
||||||
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Pathfinder, LoadXML(L"special/pathfinder.xml").GetChild("Pathfinder"));
|
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Pathfinder, LoadXML(L"special/pathfinder.xml").GetChild("Pathfinder"));
|
||||||
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_ProjectileManager, noParam);
|
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_ProjectileManager, noParam);
|
||||||
|
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_RangeManager, noParam);
|
||||||
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_SoundManager, noParam);
|
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_SoundManager, noParam);
|
||||||
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Terrain, noParam);
|
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Terrain, noParam);
|
||||||
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_WaterManager, noParam);
|
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_WaterManager, noParam);
|
||||||
|
@ -39,6 +39,7 @@ MESSAGE(Destroy)
|
|||||||
MESSAGE(OwnershipChanged)
|
MESSAGE(OwnershipChanged)
|
||||||
MESSAGE(PositionChanged)
|
MESSAGE(PositionChanged)
|
||||||
MESSAGE(MotionChanged)
|
MESSAGE(MotionChanged)
|
||||||
|
MESSAGE(RangeUpdate)
|
||||||
MESSAGE(TerrainChanged)
|
MESSAGE(TerrainChanged)
|
||||||
|
|
||||||
// TemplateManager must come before all other (non-test) components,
|
// TemplateManager must come before all other (non-test) components,
|
||||||
@ -92,6 +93,9 @@ COMPONENT(Position) // must be before VisualActor
|
|||||||
INTERFACE(ProjectileManager)
|
INTERFACE(ProjectileManager)
|
||||||
COMPONENT(ProjectileManager)
|
COMPONENT(ProjectileManager)
|
||||||
|
|
||||||
|
INTERFACE(RangeManager)
|
||||||
|
COMPONENT(RangeManager)
|
||||||
|
|
||||||
INTERFACE(Selectable)
|
INTERFACE(Selectable)
|
||||||
COMPONENT(Selectable)
|
COMPONENT(Selectable)
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ public:
|
|||||||
// The spawn point should be far enough from this footprint to fit the unit, plus a little gap
|
// The spawn point should be far enough from this footprint to fit the unit, plus a little gap
|
||||||
entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2);
|
entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2);
|
||||||
|
|
||||||
CFixedVector3D initialPos = cmpPosition->GetPosition();
|
CFixedVector2D initialPos = cmpPosition->GetPosition2D();
|
||||||
entity_angle_t initialAngle = cmpPosition->GetRotation().Y;
|
entity_angle_t initialAngle = cmpPosition->GetRotation().Y;
|
||||||
|
|
||||||
if (m_Shape == CIRCLE)
|
if (m_Shape == CIRCLE)
|
||||||
@ -164,7 +164,7 @@ public:
|
|||||||
fixed s, c;
|
fixed s, c;
|
||||||
sincos_approx(angle, s, c);
|
sincos_approx(angle, s, c);
|
||||||
|
|
||||||
CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Z + c.Multiply(radius));
|
CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius));
|
||||||
|
|
||||||
SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
|
SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
|
||||||
if (!cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Z, spawnedRadius))
|
if (!cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Z, spawnedRadius))
|
||||||
@ -207,9 +207,7 @@ public:
|
|||||||
sy = m_Size1;
|
sy = m_Size1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
CFixedVector2D center;
|
CFixedVector2D center = initialPos - dir.Perpendicular().Multiply(sy/2 + clearance);
|
||||||
center.X = initialPos.X + (-dir.Y).Multiply(sy/2 + clearance);
|
|
||||||
center.Y = initialPos.Z + dir.X.Multiply(sy/2 + clearance);
|
|
||||||
dir = dir.Multiply((sx + clearance*2) / (int)(numPoints-1));
|
dir = dir.Multiply((sx + clearance*2) / (int)(numPoints-1));
|
||||||
|
|
||||||
for (ssize_t i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2]
|
for (ssize_t i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2]
|
||||||
|
@ -208,16 +208,16 @@ public:
|
|||||||
if (cmpPosition.null())
|
if (cmpPosition.null())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
CFixedVector3D pos = cmpPosition->GetPosition();
|
CFixedVector2D pos = cmpPosition->GetPosition2D();
|
||||||
|
|
||||||
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
|
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
|
||||||
|
|
||||||
SkipTagObstructionFilter filter(m_Tag); // ignore collisions with self
|
SkipTagObstructionFilter filter(m_Tag); // ignore collisions with self
|
||||||
|
|
||||||
if (m_Type == STATIC)
|
if (m_Type == STATIC)
|
||||||
return cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Z, cmpPosition->GetRotation().Y, m_Size0, m_Size1);
|
return cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1);
|
||||||
else
|
else
|
||||||
return cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Z, m_Size0);
|
return cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Size0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -246,12 +246,20 @@ public:
|
|||||||
baseY = std::max(baseY, cmpWaterMan->GetWaterLevel(m_X, m_Z));
|
baseY = std::max(baseY, cmpWaterMan->GetWaterLevel(m_X, m_Z));
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: most callers don't actually care about Y; if this is a performance
|
|
||||||
// issue then we could add a new method that simply returns X/Z
|
|
||||||
|
|
||||||
return CFixedVector3D(m_X, baseY + m_YOffset, m_Z);
|
return CFixedVector3D(m_X, baseY + m_YOffset, m_Z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual CFixedVector2D GetPosition2D()
|
||||||
|
{
|
||||||
|
if (!m_InWorld)
|
||||||
|
{
|
||||||
|
LOGERROR(L"CCmpPosition::GetPosition2D called on entity when IsInWorld is false");
|
||||||
|
return CFixedVector2D();
|
||||||
|
}
|
||||||
|
|
||||||
|
return CFixedVector2D(m_X, m_Z);
|
||||||
|
}
|
||||||
|
|
||||||
virtual void TurnTo(entity_angle_t y)
|
virtual void TurnTo(entity_angle_t y)
|
||||||
{
|
{
|
||||||
m_RotY = y;
|
m_RotY = y;
|
||||||
@ -381,12 +389,12 @@ private:
|
|||||||
{
|
{
|
||||||
if (m_InWorld)
|
if (m_InWorld)
|
||||||
{
|
{
|
||||||
CMessagePositionChanged msg(true, m_X, m_Z, m_RotY);
|
CMessagePositionChanged msg(GetEntityId(), true, m_X, m_Z, m_RotY);
|
||||||
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
|
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CMessagePositionChanged msg(false, entity_pos_t(), entity_pos_t(), entity_angle_t());
|
CMessagePositionChanged msg(GetEntityId(), false, entity_pos_t::Zero(), entity_pos_t::Zero(), entity_angle_t::Zero());
|
||||||
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
|
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
527
source/simulation2/components/CCmpRangeManager.cpp
Normal file
527
source/simulation2/components/CCmpRangeManager.cpp
Normal file
@ -0,0 +1,527 @@
|
|||||||
|
/* Copyright (C) 2010 Wildfire Games.
|
||||||
|
* This file is part of 0 A.D.
|
||||||
|
*
|
||||||
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* 0 A.D. is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "precompiled.h"
|
||||||
|
|
||||||
|
#include "simulation2/system/Component.h"
|
||||||
|
#include "ICmpRangeManager.h"
|
||||||
|
|
||||||
|
#include "ICmpPosition.h"
|
||||||
|
#include "simulation2/MessageTypes.h"
|
||||||
|
#include "simulation2/helpers/Render.h"
|
||||||
|
|
||||||
|
#include "graphics/Overlay.h"
|
||||||
|
#include "maths/FixedVector2D.h"
|
||||||
|
#include "ps/CLogger.h"
|
||||||
|
#include "ps/Overlay.h"
|
||||||
|
#include "ps/Profile.h"
|
||||||
|
#include "renderer/Scene.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a range query.
|
||||||
|
*/
|
||||||
|
struct Query
|
||||||
|
{
|
||||||
|
bool enabled;
|
||||||
|
entity_id_t source;
|
||||||
|
entity_pos_t maxRange;
|
||||||
|
u32 ownersMask;
|
||||||
|
int interface;
|
||||||
|
std::vector<entity_id_t> lastMatch;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an owner ID (-1 = unowned, 0 = gaia, 1..30 = players)
|
||||||
|
* into a 31-bit mask for quick set-membership tests.
|
||||||
|
*/
|
||||||
|
static u32 CalcOwnerMask(i32 owner)
|
||||||
|
{
|
||||||
|
if (owner >= -1 && owner < 30)
|
||||||
|
return 1 << (1+owner);
|
||||||
|
else
|
||||||
|
return 0; // owner was invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of an entity, with the data needed for queries.
|
||||||
|
*/
|
||||||
|
struct EntityData
|
||||||
|
{
|
||||||
|
EntityData() : ownerMask(CalcOwnerMask(-1)), inWorld(0) { }
|
||||||
|
entity_pos_t x, z;
|
||||||
|
u32 ownerMask : 31;
|
||||||
|
u32 inWorld : 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
cassert(sizeof(EntityData) == 12);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functor for sorting entities by distance from a source point.
|
||||||
|
* It must only be passed entities that has a Position component
|
||||||
|
* and are currently in the world.
|
||||||
|
*/
|
||||||
|
struct EntityDistanceOrdering
|
||||||
|
{
|
||||||
|
EntityDistanceOrdering(const CSimContext& context, const CFixedVector2D& source) :
|
||||||
|
context(context), source(source)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(entity_id_t a, entity_id_t b)
|
||||||
|
{
|
||||||
|
CmpPtr<ICmpPosition> cmpPositionA(context, a);
|
||||||
|
CmpPtr<ICmpPosition> cmpPositionB(context, b);
|
||||||
|
// (these positions will be valid and in the world, else ExecuteQuery wouldn't have returned it)
|
||||||
|
CFixedVector2D vecA = cmpPositionA->GetPosition2D() - source;
|
||||||
|
CFixedVector2D vecB = cmpPositionB->GetPosition2D() - source;
|
||||||
|
return (vecA.CompareLength(vecB) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const CSimContext& context;
|
||||||
|
CFixedVector2D source;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic range manager implementation.
|
||||||
|
* Maintains a list of all entities (and their positions and owners), which is used for
|
||||||
|
* queries.
|
||||||
|
*
|
||||||
|
* TODO: Ideally this would use a quadtree or something for more efficient spatial queries,
|
||||||
|
* since it's about O(n^2) in the total number of entities on the map.
|
||||||
|
*/
|
||||||
|
class CCmpRangeManager : public ICmpRangeManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void ClassInit(CComponentManager& componentManager)
|
||||||
|
{
|
||||||
|
componentManager.SubscribeGloballyToMessageType(MT_Create);
|
||||||
|
componentManager.SubscribeGloballyToMessageType(MT_PositionChanged);
|
||||||
|
componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged);
|
||||||
|
componentManager.SubscribeGloballyToMessageType(MT_Destroy);
|
||||||
|
|
||||||
|
componentManager.SubscribeToMessageType(MT_Update);
|
||||||
|
|
||||||
|
componentManager.SubscribeToMessageType(MT_RenderSubmit); // for debug overlays
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_COMPONENT_ALLOCATOR(RangeManager)
|
||||||
|
|
||||||
|
bool m_DebugOverlayEnabled;
|
||||||
|
bool m_DebugOverlayDirty;
|
||||||
|
std::vector<SOverlayLine> m_DebugOverlayLines;
|
||||||
|
|
||||||
|
tag_t m_QueryNext; // next allocated id
|
||||||
|
std::map<tag_t, Query> m_Queries;
|
||||||
|
std::map<entity_id_t, EntityData> m_EntityData;
|
||||||
|
|
||||||
|
static std::string GetSchema()
|
||||||
|
{
|
||||||
|
return "<a:component type='system'/><empty/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Init(const CSimContext& UNUSED(context), const CParamNode& UNUSED(paramNode))
|
||||||
|
{
|
||||||
|
m_QueryNext = 1;
|
||||||
|
|
||||||
|
m_DebugOverlayEnabled = false;
|
||||||
|
m_DebugOverlayDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Deinit(const CSimContext& UNUSED(context))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Serialize(ISerializer& UNUSED(serialize))
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
|
||||||
|
{
|
||||||
|
Init(context, paramNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void HandleMessage(const CSimContext& UNUSED(context), const CMessage& msg, bool UNUSED(global))
|
||||||
|
{
|
||||||
|
switch (msg.GetType())
|
||||||
|
{
|
||||||
|
case MT_Create:
|
||||||
|
{
|
||||||
|
const CMessageCreate& msgData = static_cast<const CMessageCreate&> (msg);
|
||||||
|
entity_id_t ent = msgData.entity;
|
||||||
|
|
||||||
|
// Ignore local entities - we shouldn't let them influence anything
|
||||||
|
if (ENTITY_IS_LOCAL(ent))
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Ignore non-positional entities
|
||||||
|
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), ent);
|
||||||
|
if (cmpPosition.null())
|
||||||
|
break;
|
||||||
|
|
||||||
|
// The newly-created entity will have owner -1 and position out-of-world
|
||||||
|
// (any initialisation of those values will happen later), so we can just
|
||||||
|
// use the default-constructed EntityData here
|
||||||
|
EntityData entdata;
|
||||||
|
|
||||||
|
// Remember this entity
|
||||||
|
m_EntityData.insert(std::make_pair(ent, entdata));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MT_PositionChanged:
|
||||||
|
{
|
||||||
|
const CMessagePositionChanged& msgData = static_cast<const CMessagePositionChanged&> (msg);
|
||||||
|
entity_id_t ent = msgData.entity;
|
||||||
|
|
||||||
|
std::map<u32, EntityData>::iterator it = m_EntityData.find(ent);
|
||||||
|
|
||||||
|
// Ignore if we're not already tracking this entity
|
||||||
|
if (it == m_EntityData.end())
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (msgData.inWorld)
|
||||||
|
{
|
||||||
|
it->second.inWorld = 1;
|
||||||
|
it->second.x = msgData.x;
|
||||||
|
it->second.z = msgData.z;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
it->second.inWorld = 0;
|
||||||
|
it->second.x = entity_pos_t::Zero();
|
||||||
|
it->second.z = entity_pos_t::Zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MT_OwnershipChanged:
|
||||||
|
{
|
||||||
|
const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
|
||||||
|
entity_id_t ent = msgData.entity;
|
||||||
|
|
||||||
|
std::map<u32, EntityData>::iterator it = m_EntityData.find(ent);
|
||||||
|
|
||||||
|
// Ignore if we're not already tracking this entity
|
||||||
|
if (it == m_EntityData.end())
|
||||||
|
break;
|
||||||
|
|
||||||
|
it->second.ownerMask = CalcOwnerMask(msgData.to);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MT_Destroy:
|
||||||
|
{
|
||||||
|
const CMessageDestroy& msgData = static_cast<const CMessageDestroy&> (msg);
|
||||||
|
entity_id_t ent = msgData.entity;
|
||||||
|
|
||||||
|
m_EntityData.erase(ent);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MT_Update:
|
||||||
|
{
|
||||||
|
m_DebugOverlayDirty = true;
|
||||||
|
ExecuteActiveQueries();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MT_RenderSubmit:
|
||||||
|
{
|
||||||
|
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
|
||||||
|
RenderSubmit(msgData.collector);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
virtual tag_t CreateActiveQuery(entity_id_t source, entity_pos_t maxRange,
|
||||||
|
std::vector<int> owners, int requiredInterface)
|
||||||
|
{
|
||||||
|
size_t id = m_QueryNext++;
|
||||||
|
m_Queries[id] = ConstructQuery(source, maxRange, owners, requiredInterface);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void DestroyActiveQuery(tag_t tag)
|
||||||
|
{
|
||||||
|
if (m_Queries.find(tag) == m_Queries.end())
|
||||||
|
{
|
||||||
|
LOGERROR(L"CCmpRangeManager: DestroyActiveQuery called with invalid tag %d", tag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Queries.erase(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void EnableActiveQuery(tag_t tag)
|
||||||
|
{
|
||||||
|
std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
|
||||||
|
if (it == m_Queries.end())
|
||||||
|
{
|
||||||
|
LOGERROR(L"CCmpRangeManager: EnableActiveQuery called with invalid tag %d", tag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Query& q = it->second;
|
||||||
|
q.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void DisableActiveQuery(tag_t tag)
|
||||||
|
{
|
||||||
|
std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
|
||||||
|
if (it == m_Queries.end())
|
||||||
|
{
|
||||||
|
LOGERROR(L"CCmpRangeManager: DisableActiveQuery called with invalid tag %d", tag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Query& q = it->second;
|
||||||
|
q.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::vector<entity_id_t> ExecuteQuery(entity_id_t source, entity_pos_t maxRange,
|
||||||
|
std::vector<int> owners, int requiredInterface)
|
||||||
|
{
|
||||||
|
PROFILE("ExecuteQuery");
|
||||||
|
|
||||||
|
Query q = ConstructQuery(source, maxRange, owners, requiredInterface);
|
||||||
|
|
||||||
|
std::vector<entity_id_t> r;
|
||||||
|
|
||||||
|
CmpPtr<ICmpPosition> cmpSourcePosition(GetSimContext(), q.source);
|
||||||
|
if (cmpSourcePosition.null() || !cmpSourcePosition->IsInWorld())
|
||||||
|
{
|
||||||
|
// If the source doesn't have a position, then the result is just the empty list
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
PerformQuery(q, r);
|
||||||
|
|
||||||
|
// Return the list sorted by distance from the entity
|
||||||
|
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
|
||||||
|
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(GetSimContext(), pos));
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::vector<entity_id_t> ResetActiveQuery(tag_t tag)
|
||||||
|
{
|
||||||
|
PROFILE("ResetActiveQuery");
|
||||||
|
|
||||||
|
std::vector<entity_id_t> r;
|
||||||
|
|
||||||
|
std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
|
||||||
|
if (it == m_Queries.end())
|
||||||
|
{
|
||||||
|
LOGERROR(L"CCmpRangeManager: ResetActiveQuery called with invalid tag %d", tag);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Query& q = it->second;
|
||||||
|
q.enabled = true;
|
||||||
|
|
||||||
|
CmpPtr<ICmpPosition> cmpSourcePosition(GetSimContext(), q.source);
|
||||||
|
if (cmpSourcePosition.null() || !cmpSourcePosition->IsInWorld())
|
||||||
|
{
|
||||||
|
// If the source doesn't have a position, then the result is just the empty list
|
||||||
|
q.lastMatch = r;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
PerformQuery(q, r);
|
||||||
|
|
||||||
|
q.lastMatch = r;
|
||||||
|
|
||||||
|
// Return the list sorted by distance from the entity
|
||||||
|
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
|
||||||
|
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(GetSimContext(), pos));
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void SetDebugOverlay(bool enabled)
|
||||||
|
{
|
||||||
|
m_DebugOverlayEnabled = enabled;
|
||||||
|
m_DebugOverlayDirty = true;
|
||||||
|
if (!enabled)
|
||||||
|
m_DebugOverlayLines.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update all currently-enabled active queries.
|
||||||
|
*/
|
||||||
|
void ExecuteActiveQueries()
|
||||||
|
{
|
||||||
|
PROFILE("ExecuteActiveQueries");
|
||||||
|
|
||||||
|
// Store a queue of all messages before sending any, so we can assume
|
||||||
|
// no entities will move until we've finished checking all the ranges
|
||||||
|
std::vector<std::pair<entity_id_t, CMessageRangeUpdate> > messages;
|
||||||
|
|
||||||
|
for (std::map<tag_t, Query>::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it)
|
||||||
|
{
|
||||||
|
Query& q = it->second;
|
||||||
|
|
||||||
|
if (!q.enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
CmpPtr<ICmpPosition> cmpSourcePosition(GetSimContext(), q.source);
|
||||||
|
if (cmpSourcePosition.null() || !cmpSourcePosition->IsInWorld())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::vector<entity_id_t> r;
|
||||||
|
r.reserve(q.lastMatch.size());
|
||||||
|
|
||||||
|
PerformQuery(q, r);
|
||||||
|
|
||||||
|
// Compute the changes vs the last match
|
||||||
|
std::vector<entity_id_t> added;
|
||||||
|
std::vector<entity_id_t> removed;
|
||||||
|
std::set_difference(r.begin(), r.end(), q.lastMatch.begin(), q.lastMatch.end(), std::back_inserter(added));
|
||||||
|
std::set_difference(q.lastMatch.begin(), q.lastMatch.end(), r.begin(), r.end(), std::back_inserter(removed));
|
||||||
|
|
||||||
|
if (added.empty() && removed.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Return the 'added' list sorted by distance from the entity
|
||||||
|
// (Don't bother sorting 'removed' because they might not even have positions or exist any more)
|
||||||
|
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
|
||||||
|
std::stable_sort(added.begin(), added.end(), EntityDistanceOrdering(GetSimContext(), pos));
|
||||||
|
|
||||||
|
messages.push_back(std::make_pair(q.source, CMessageRangeUpdate(it->first)));
|
||||||
|
messages.back().second.added.swap(added);
|
||||||
|
messages.back().second.removed.swap(removed);
|
||||||
|
|
||||||
|
it->second.lastMatch.swap(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < messages.size(); ++i)
|
||||||
|
GetSimContext().GetComponentManager().PostMessage(messages[i].first, messages[i].second);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of distinct entity IDs that match the given query, sorted by ID.
|
||||||
|
*/
|
||||||
|
void PerformQuery(const Query& q, std::vector<entity_id_t>& r)
|
||||||
|
{
|
||||||
|
CmpPtr<ICmpPosition> cmpSourcePosition(GetSimContext(), q.source);
|
||||||
|
if (cmpSourcePosition.null() || !cmpSourcePosition->IsInWorld())
|
||||||
|
return;
|
||||||
|
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
|
||||||
|
|
||||||
|
for (std::map<entity_id_t, EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
|
||||||
|
{
|
||||||
|
// Quick filter to ignore entities with the wrong owner
|
||||||
|
if (!(it->second.ownerMask & q.ownersMask))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Restrict based on location
|
||||||
|
// (TODO: this bit ought to use a quadtree or something)
|
||||||
|
if (!it->second.inWorld)
|
||||||
|
continue;
|
||||||
|
int distVsMax = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.maxRange);
|
||||||
|
if (distVsMax > 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Ignore self
|
||||||
|
if (it->first == q.source)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Ignore if it's missing the required interface
|
||||||
|
if (q.interface && !GetSimContext().GetComponentManager().QueryInterface(it->first, q.interface))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
r.push_back(it->first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Query ConstructQuery(entity_id_t source, entity_pos_t maxRange, std::vector<int> owners, int requiredInterface)
|
||||||
|
{
|
||||||
|
Query q;
|
||||||
|
q.enabled = false;
|
||||||
|
q.source = source;
|
||||||
|
q.maxRange = maxRange;
|
||||||
|
|
||||||
|
q.ownersMask = 0;
|
||||||
|
for (size_t i = 0; i < owners.size(); ++i)
|
||||||
|
q.ownersMask |= CalcOwnerMask(owners[i]);
|
||||||
|
|
||||||
|
q.interface = requiredInterface;
|
||||||
|
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderSubmit(SceneCollector& collector)
|
||||||
|
{
|
||||||
|
if (!m_DebugOverlayEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CColor enabledRingColour(0, 1, 0, 1);
|
||||||
|
CColor disabledRingColour(1, 0, 0, 1);
|
||||||
|
CColor rayColour(1, 1, 0, 0.2);
|
||||||
|
|
||||||
|
if (m_DebugOverlayDirty)
|
||||||
|
{
|
||||||
|
m_DebugOverlayLines.clear();
|
||||||
|
|
||||||
|
for (std::map<tag_t, Query>::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it)
|
||||||
|
{
|
||||||
|
Query& q = it->second;
|
||||||
|
|
||||||
|
CmpPtr<ICmpPosition> cmpSourcePosition(GetSimContext(), q.source);
|
||||||
|
if (cmpSourcePosition.null() || !cmpSourcePosition->IsInWorld())
|
||||||
|
continue;
|
||||||
|
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
|
||||||
|
|
||||||
|
// Draw the range circle
|
||||||
|
m_DebugOverlayLines.push_back(SOverlayLine());
|
||||||
|
m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColour : disabledRingColour);
|
||||||
|
SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToDouble(), q.maxRange.ToFloat(), m_DebugOverlayLines.back(), true);
|
||||||
|
|
||||||
|
// Draw a ray from the source to each matched entity
|
||||||
|
for (size_t i = 0; i < q.lastMatch.size(); ++i)
|
||||||
|
{
|
||||||
|
CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), q.lastMatch[i]);
|
||||||
|
if (cmpTargetPosition.null() || !cmpTargetPosition->IsInWorld())
|
||||||
|
continue;
|
||||||
|
CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
|
||||||
|
|
||||||
|
std::vector<float> coords;
|
||||||
|
coords.push_back(pos.X.ToFloat());
|
||||||
|
coords.push_back(pos.Y.ToFloat());
|
||||||
|
coords.push_back(targetPos.X.ToFloat());
|
||||||
|
coords.push_back(targetPos.Y.ToFloat());
|
||||||
|
|
||||||
|
m_DebugOverlayLines.push_back(SOverlayLine());
|
||||||
|
m_DebugOverlayLines.back().m_Color = rayColour;
|
||||||
|
SimRender::ConstructLineOnGround(GetSimContext(), coords, m_DebugOverlayLines.back(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_DebugOverlayDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < m_DebugOverlayLines.size(); ++i)
|
||||||
|
collector.Submit(&m_DebugOverlayLines[i]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
REGISTER_COMPONENT_TYPE(RangeManager)
|
@ -354,8 +354,7 @@ void CCmpUnitMotion::Move(fixed dt)
|
|||||||
if (cmpPosition.null())
|
if (cmpPosition.null())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
CFixedVector3D pos3 = cmpPosition->GetPosition();
|
CFixedVector2D pos = cmpPosition->GetPosition2D();
|
||||||
CFixedVector2D pos (pos3.X, pos3.Z);
|
|
||||||
|
|
||||||
// We want to move (at most) m_Speed*dt units from pos towards the next waypoint
|
// We want to move (at most) m_Speed*dt units from pos towards the next waypoint
|
||||||
|
|
||||||
@ -491,8 +490,7 @@ bool CCmpUnitMotion::MoveToPoint(entity_pos_t x, entity_pos_t z)
|
|||||||
if (cmpPosition.null() || !cmpPosition->IsInWorld())
|
if (cmpPosition.null() || !cmpPosition->IsInWorld())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
CFixedVector3D pos3 = cmpPosition->GetPosition();
|
CFixedVector2D pos = cmpPosition->GetPosition2D();
|
||||||
CFixedVector2D pos (pos3.X, pos3.Z);
|
|
||||||
|
|
||||||
// Reset any current movement
|
// Reset any current movement
|
||||||
m_HasTarget = false;
|
m_HasTarget = false;
|
||||||
@ -560,8 +558,7 @@ bool CCmpUnitMotion::MoveToAttackRange(entity_id_t target, entity_pos_t minRange
|
|||||||
if (cmpPosition.null() || !cmpPosition->IsInWorld())
|
if (cmpPosition.null() || !cmpPosition->IsInWorld())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
CFixedVector3D pos3 = cmpPosition->GetPosition();
|
CFixedVector2D pos = cmpPosition->GetPosition2D();
|
||||||
CFixedVector2D pos (pos3.X, pos3.Z);
|
|
||||||
|
|
||||||
// Reset any current movement
|
// Reset any current movement
|
||||||
m_HasTarget = false;
|
m_HasTarget = false;
|
||||||
@ -690,9 +687,9 @@ bool CCmpUnitMotion::MoveToAttackRange(entity_id_t target, entity_pos_t minRange
|
|||||||
if (cmpTargetPosition.null() || !cmpTargetPosition->IsInWorld())
|
if (cmpTargetPosition.null() || !cmpTargetPosition->IsInWorld())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
CFixedVector3D targetPos = cmpTargetPosition->GetPosition();
|
CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
|
||||||
|
|
||||||
return MoveToPointRange(targetPos.X, targetPos.Z, minRange, maxRange);
|
return MoveToPointRange(targetPos.X, targetPos.Y, minRange, maxRange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -704,8 +701,7 @@ bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos
|
|||||||
if (cmpPosition.null() || !cmpPosition->IsInWorld())
|
if (cmpPosition.null() || !cmpPosition->IsInWorld())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
CFixedVector3D pos3 = cmpPosition->GetPosition();
|
CFixedVector2D pos = cmpPosition->GetPosition2D();
|
||||||
CFixedVector2D pos (pos3.X, pos3.Z);
|
|
||||||
|
|
||||||
entity_pos_t distance = (pos - CFixedVector2D(x, z)).Length();
|
entity_pos_t distance = (pos - CFixedVector2D(x, z)).Length();
|
||||||
|
|
||||||
@ -750,8 +746,7 @@ bool CCmpUnitMotion::IsInAttackRange(entity_id_t target, entity_pos_t minRange,
|
|||||||
if (cmpPosition.null() || !cmpPosition->IsInWorld())
|
if (cmpPosition.null() || !cmpPosition->IsInWorld())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
CFixedVector3D pos3 = cmpPosition->GetPosition();
|
CFixedVector2D pos = cmpPosition->GetPosition2D();
|
||||||
CFixedVector2D pos (pos3.X, pos3.Z);
|
|
||||||
|
|
||||||
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
|
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
|
||||||
if (cmpObstructionManager.null())
|
if (cmpObstructionManager.null())
|
||||||
@ -801,9 +796,9 @@ bool CCmpUnitMotion::IsInAttackRange(entity_id_t target, entity_pos_t minRange,
|
|||||||
if (cmpTargetPosition.null() || !cmpTargetPosition->IsInWorld())
|
if (cmpTargetPosition.null() || !cmpTargetPosition->IsInWorld())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
CFixedVector3D targetPos = cmpTargetPosition->GetPosition();
|
CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
|
||||||
|
|
||||||
entity_pos_t distance = (pos - CFixedVector2D(targetPos.X, targetPos.Z)).Length();
|
entity_pos_t distance = (pos - targetPos).Length();
|
||||||
|
|
||||||
if (minRange <= distance && distance <= maxRange)
|
if (minRange <= distance && distance <= maxRange)
|
||||||
return true;
|
return true;
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include "simulation2/helpers/Position.h"
|
#include "simulation2/helpers/Position.h"
|
||||||
#include "maths/FixedVector3D.h"
|
#include "maths/FixedVector3D.h"
|
||||||
|
#include "maths/FixedVector2D.h"
|
||||||
|
|
||||||
class CMatrix3D;
|
class CMatrix3D;
|
||||||
|
|
||||||
@ -95,6 +96,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual CFixedVector3D GetPosition() = 0;
|
virtual CFixedVector3D GetPosition() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current x,z position (no interpolation).
|
||||||
|
* Must not be called unless IsInWorld is true.
|
||||||
|
*/
|
||||||
|
virtual CFixedVector2D GetPosition2D() = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rotate smoothly to the given angle around the upwards axis.
|
* Rotate smoothly to the given angle around the upwards axis.
|
||||||
* @param y clockwise radians from the +Z axis.
|
* @param y clockwise radians from the +Z axis.
|
||||||
|
32
source/simulation2/components/ICmpRangeManager.cpp
Normal file
32
source/simulation2/components/ICmpRangeManager.cpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/* Copyright (C) 2010 Wildfire Games.
|
||||||
|
* This file is part of 0 A.D.
|
||||||
|
*
|
||||||
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* 0 A.D. is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "precompiled.h"
|
||||||
|
|
||||||
|
#include "ICmpRangeManager.h"
|
||||||
|
|
||||||
|
#include "simulation2/system/InterfaceScripted.h"
|
||||||
|
|
||||||
|
BEGIN_INTERFACE_WRAPPER(RangeManager)
|
||||||
|
DEFINE_INTERFACE_METHOD_4("ExecuteQuery", std::vector<entity_id_t>, ICmpRangeManager, ExecuteQuery, entity_id_t, entity_pos_t, std::vector<int>, int)
|
||||||
|
DEFINE_INTERFACE_METHOD_4("CreateActiveQuery", ICmpRangeManager::tag_t, ICmpRangeManager, CreateActiveQuery, entity_id_t, entity_pos_t, std::vector<int>, int)
|
||||||
|
DEFINE_INTERFACE_METHOD_1("DestroyActiveQuery", void, ICmpRangeManager, DestroyActiveQuery, ICmpRangeManager::tag_t)
|
||||||
|
DEFINE_INTERFACE_METHOD_1("EnableActiveQuery", void, ICmpRangeManager, EnableActiveQuery, ICmpRangeManager::tag_t)
|
||||||
|
DEFINE_INTERFACE_METHOD_1("DisableActiveQuery", void, ICmpRangeManager, DisableActiveQuery, ICmpRangeManager::tag_t)
|
||||||
|
DEFINE_INTERFACE_METHOD_1("ResetActiveQuery", std::vector<entity_id_t>, ICmpRangeManager, ResetActiveQuery, ICmpRangeManager::tag_t)
|
||||||
|
DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpRangeManager, SetDebugOverlay, bool)
|
||||||
|
END_INTERFACE_WRAPPER(RangeManager)
|
122
source/simulation2/components/ICmpRangeManager.h
Normal file
122
source/simulation2/components/ICmpRangeManager.h
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/* Copyright (C) 2010 Wildfire Games.
|
||||||
|
* This file is part of 0 A.D.
|
||||||
|
*
|
||||||
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* 0 A.D. is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_ICMPRANGEMANAGER
|
||||||
|
#define INCLUDED_ICMPRANGEMANAGER
|
||||||
|
|
||||||
|
#include "simulation2/system/Interface.h"
|
||||||
|
|
||||||
|
#include "simulation2/helpers/Position.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides efficient range-based queries of the game world.
|
||||||
|
*
|
||||||
|
* Possible use cases:
|
||||||
|
* - combat units need to detect targetable enemies entering LOS, so they can choose
|
||||||
|
* to auto-attack.
|
||||||
|
* - auras let a unit have some effect on all units (or those of the same player, or of enemies)
|
||||||
|
* within a certain range.
|
||||||
|
* - capturable animals need to detect when a player-owned unit is nearby and no units of other
|
||||||
|
* players are in range.
|
||||||
|
* - scenario triggers may want to detect when units enter a given area.
|
||||||
|
* - units gathering from a resource that is exhausted need to find a new resource of the
|
||||||
|
* same type, near the old one and reachable.
|
||||||
|
* - projectile weapons with splash damage need to find all units within some distance
|
||||||
|
* of the target point.
|
||||||
|
* - ...
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Current design:
|
||||||
|
*
|
||||||
|
* This class handles just the most common parts of range queries:
|
||||||
|
* distance, target interface, and player ownership.
|
||||||
|
* The caller can then apply any more complex filtering that it needs.
|
||||||
|
*
|
||||||
|
* There are two types of query:
|
||||||
|
* Passive queries are performed by ExecuteQuery and immediately return the matching entities.
|
||||||
|
* Active queries are set up by CreateActiveQuery, and then a CMessageRangeUpdate message will be
|
||||||
|
* sent to the entity once per turn if anybody has entered or left the range since the last RangeUpdate.
|
||||||
|
* Queries can be disabled, in which case no message will be sent.
|
||||||
|
*/
|
||||||
|
class ICmpRangeManager : public IComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* External identifiers for active queries.
|
||||||
|
*/
|
||||||
|
typedef u32 tag_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a passive query.
|
||||||
|
* @param source the entity around which the range will be computed.
|
||||||
|
* @param maxRange maximum distance in metres (inclusive).
|
||||||
|
* @param owners list of player IDs that matching entities may have; -1 matches entities with no owner.
|
||||||
|
* @param requiredInterface if non-zero, an interface ID that matching entities must implement.
|
||||||
|
* @return list of entities matching the query, ordered by increasing distance from the source entity.
|
||||||
|
*/
|
||||||
|
virtual std::vector<entity_id_t> ExecuteQuery(entity_id_t source, entity_pos_t maxRange, std::vector<int> owners, int requiredInterface) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an active query. The query will be disabled by default.
|
||||||
|
* @param source the entity around which the range will be computed.
|
||||||
|
* @param maxRange maximum distance in metres (inclusive).
|
||||||
|
* @param owners list of player IDs that matching entities may have; -1 matches entities with no owner.
|
||||||
|
* @param requiredInterface if non-zero, an interface ID that matching entities must implement.
|
||||||
|
* @return unique non-zero identifier of query.
|
||||||
|
*/
|
||||||
|
virtual tag_t CreateActiveQuery(entity_id_t source, entity_pos_t maxRange, std::vector<int> owners, int requiredInterface) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy a query and clean up resources. This must be called when an entity no longer needs its
|
||||||
|
* query (e.g. when the entity is destroyed).
|
||||||
|
* @param tag identifier of query.
|
||||||
|
*/
|
||||||
|
virtual void DestroyActiveQuery(tag_t tag) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-enable the processing of a query.
|
||||||
|
* @param tag identifier of query.
|
||||||
|
*/
|
||||||
|
virtual void EnableActiveQuery(tag_t tag) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable the processing of a query (no RangeUpdate messages will be sent).
|
||||||
|
* @param tag identifier of query.
|
||||||
|
*/
|
||||||
|
virtual void DisableActiveQuery(tag_t tag) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately execute a query, and re-enable it if disabled.
|
||||||
|
* The next RangeUpdate message will say who has entered/left since this call,
|
||||||
|
* so you won't miss any notifications.
|
||||||
|
* @param tag identifier of query.
|
||||||
|
* @return list of entities matching the query, ordered by increasing distance from the source entity.
|
||||||
|
*/
|
||||||
|
virtual std::vector<entity_id_t> ResetActiveQuery(tag_t tag) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the rendering of debug info.
|
||||||
|
*/
|
||||||
|
virtual void SetDebugOverlay(bool enabled) = 0;
|
||||||
|
|
||||||
|
DECLARE_INTERFACE_TYPE(RangeManager)
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // INCLUDED_ICMPRANGEMANAGER
|
@ -26,7 +26,6 @@
|
|||||||
#include "graphics/Terrain.h"
|
#include "graphics/Terrain.h"
|
||||||
#include "maths/MathUtil.h"
|
#include "maths/MathUtil.h"
|
||||||
|
|
||||||
static const size_t RENDER_CIRCLE_POINTS = 16;
|
|
||||||
static const float RENDER_HEIGHT_DELTA = 0.25f; // distance above terrain
|
static const float RENDER_HEIGHT_DELTA = 0.25f; // distance above terrain
|
||||||
|
|
||||||
void SimRender::ConstructLineOnGround(const CSimContext& context, std::vector<float> xz,
|
void SimRender::ConstructLineOnGround(const CSimContext& context, std::vector<float> xz,
|
||||||
@ -79,11 +78,14 @@ void SimRender::ConstructCircleOnGround(const CSimContext& context, float x, flo
|
|||||||
water = cmpWaterMan->GetExactWaterLevel(x, z);
|
water = cmpWaterMan->GetExactWaterLevel(x, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
overlay.m_Coords.reserve((RENDER_CIRCLE_POINTS + 1) * 3);
|
// Adapt the circle resolution to look reasonable for small and largeish radiuses
|
||||||
|
size_t numPoints = clamp((size_t)(radius*4.0f), (size_t)12, (size_t)48);
|
||||||
|
|
||||||
for (size_t i = 0; i <= RENDER_CIRCLE_POINTS; ++i) // use '<=' so it's a closed loop
|
overlay.m_Coords.reserve((numPoints + 1) * 3);
|
||||||
|
|
||||||
|
for (size_t i = 0; i <= numPoints; ++i) // use '<=' so it's a closed loop
|
||||||
{
|
{
|
||||||
float a = i * 2 * (float)M_PI / RENDER_CIRCLE_POINTS;
|
float a = i * 2 * (float)M_PI / numPoints;
|
||||||
float px = x + radius * sin(a);
|
float px = x + radius * sin(a);
|
||||||
float pz = z + radius * cos(a);
|
float pz = z + radius * cos(a);
|
||||||
float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + RENDER_HEIGHT_DELTA;
|
float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + RENDER_HEIGHT_DELTA;
|
||||||
|
@ -200,6 +200,22 @@ CMessage* CMessageTerrainChanged::FromJSVal(ScriptInterface& UNUSED(scriptInterf
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
|
||||||
|
jsval CMessageRangeUpdate::ToJSVal(ScriptInterface& scriptInterface) const
|
||||||
|
{
|
||||||
|
TOJSVAL_SETUP();
|
||||||
|
SET_MSG_PROPERTY(tag);
|
||||||
|
SET_MSG_PROPERTY(added);
|
||||||
|
SET_MSG_PROPERTY(removed);
|
||||||
|
return OBJECT_TO_JSVAL(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
CMessage* CMessageRangeUpdate::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val))
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
CMessage* CMessageFromJSVal(int mtid, ScriptInterface& scriptingInterface, jsval val)
|
CMessage* CMessageFromJSVal(int mtid, ScriptInterface& scriptingInterface, jsval val)
|
||||||
|
@ -73,4 +73,10 @@
|
|||||||
4, \
|
4, \
|
||||||
JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT, 0 },
|
JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT, 0 },
|
||||||
|
|
||||||
|
#define DEFINE_INTERFACE_METHOD_5(scriptname, rettype, classname, methodname, arg1, arg2, arg3, arg4, arg5) \
|
||||||
|
{ scriptname, \
|
||||||
|
ScriptInterface::callMethod<rettype, arg1, arg2, arg3, arg4, arg5, &class_##classname, classname, &classname::methodname>, \
|
||||||
|
5, \
|
||||||
|
JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT, 0 },
|
||||||
|
|
||||||
#endif // INCLUDED_INTERFACE_SCRIPTED
|
#endif // INCLUDED_INTERFACE_SCRIPTED
|
||||||
|
Loading…
Reference in New Issue
Block a user