# Added AI for chickens.
Add scripted HFSM system. Add very basic animal AI. Support script-only message types. Add shift+D hotkey to toggle dev command panel. This was SVN commit r7763.
This commit is contained in:
parent
5f366d798f
commit
e19146cf25
@ -176,6 +176,7 @@ hotkey.resourcepool.toggle = "Shift+R" ; Toggle Resource Pool.
|
|||||||
hotkey.grouppane.toggle = "Shift+G" ; Toggle Group Pane.
|
hotkey.grouppane.toggle = "Shift+G" ; Toggle Group Pane.
|
||||||
hotkey.teamtray.toggle = "Shift+T" ; Toggle Team Tray.
|
hotkey.teamtray.toggle = "Shift+T" ; Toggle Team Tray.
|
||||||
hotkey.session.ShowPlayersList = "Shift+P" ; Toggle Players List
|
hotkey.session.ShowPlayersList = "Shift+P" ; Toggle Players List
|
||||||
|
hotkey.session.devcommands.toggle = "Shift+D"
|
||||||
|
|
||||||
; > SESSION ORIENTATION KEYS
|
; > SESSION ORIENTATION KEYS
|
||||||
hotkey.session.gui.flip = "Alt+G" ; Toggle GUI to top/bottom/left/right of screen.
|
hotkey.session.gui.flip = "Alt+G" ; Toggle GUI to top/bottom/left/right of screen.
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<Threshold>5</Threshold>
|
<Threshold>5</Threshold>
|
||||||
<Decay>3</Decay>
|
<Decay>3</Decay>
|
||||||
<Replacement>chicken_10.ogg</Replacement>
|
<Replacement>chicken_10.ogg</Replacement>
|
||||||
<Path>/audio/actor/fauna/animal</Path>
|
<Path>audio/actor/fauna/animal</Path>
|
||||||
<Sound>chicken_13.ogg</Sound>
|
<Sound>chicken_13.ogg</Sound>
|
||||||
<Sound>chicken_10.ogg</Sound>
|
<Sound>chicken_10.ogg</Sound>
|
||||||
<Sound>chicken_11.ogg</Sound>
|
<Sound>chicken_11.ogg</Sound>
|
||||||
|
@ -64,7 +64,12 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Dev/cheat commands -->
|
<!-- Dev/cheat commands -->
|
||||||
<object z="200" size="100%-155 50 100%-16 130" type="image" name="devCommands" sprite="devCommandsBackground" hidden="true">
|
<object name="devCommands" size="100%-155 50 100%-16 130" z="200" type="image" sprite="devCommandsBackground"
|
||||||
|
hidden="true" hotkey="session.devcommands.toggle">
|
||||||
|
<action on="Press">
|
||||||
|
this.hidden = !this.hidden;
|
||||||
|
</action>
|
||||||
|
|
||||||
<object size="0 0 100%-18 16" type="text" style="devCommandsText">Control all units</object>
|
<object size="0 0 100%-18 16" type="text" style="devCommandsText">Control all units</object>
|
||||||
<object size="100%-16 0 100% 16" type="checkbox" name="devControlAll" style="wheatCrossBox"/>
|
<object size="100%-16 0 100% 16" type="checkbox" name="devControlAll" style="wheatCrossBox"/>
|
||||||
|
|
||||||
|
241
binaries/data/mods/public/simulation/components/AnimalAI.js
Normal file
241
binaries/data/mods/public/simulation/components/AnimalAI.js
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
function AnimalAI() {}
|
||||||
|
|
||||||
|
AnimalAI.prototype.Schema =
|
||||||
|
"<a:example/>" +
|
||||||
|
"<element name='NaturalBehaviour' a:help='Behaviour of the unit in the absence of player commands (intended for animals)'>" +
|
||||||
|
"<choice>" +
|
||||||
|
"<value a:help='Will actively attack any unit it encounters, even if not threatened'>violent</value>" +
|
||||||
|
"<value a:help='Will attack nearby units if it feels threatened (if they linger within LOS for too long)'>aggressive</value>" +
|
||||||
|
"<value a:help='Will attack nearby units if attacked'>defensive</value>" +
|
||||||
|
"<value a:help='Will never attack units'>passive</value>" +
|
||||||
|
"<value a:help='Will never attack units. Will typically attempt to flee for short distances when units approach'>skittish</value>" +
|
||||||
|
"</choice>" +
|
||||||
|
"</element>";
|
||||||
|
|
||||||
|
var AnimalFsmSpec = {
|
||||||
|
|
||||||
|
"SKITTISH": {
|
||||||
|
|
||||||
|
"ResourceGather": function(msg) {
|
||||||
|
// If someone's carving chunks of meat off us, then run away
|
||||||
|
this.MoveAwayFrom(msg.gatherer, 12);
|
||||||
|
this.SetNextState("FLEEING");
|
||||||
|
this.PlaySound("panic");
|
||||||
|
},
|
||||||
|
|
||||||
|
"ROAMING": {
|
||||||
|
"enter": function() {
|
||||||
|
// Walk in a random direction
|
||||||
|
this.SelectAnimation("walk", false);
|
||||||
|
this.MoveRandomly();
|
||||||
|
// Set a random timer to switch to feeding state
|
||||||
|
this.StartTimer(RandomInt(2000, 8000));
|
||||||
|
},
|
||||||
|
|
||||||
|
"leave": function() {
|
||||||
|
this.StopTimer();
|
||||||
|
},
|
||||||
|
|
||||||
|
"Timer": function(msg) {
|
||||||
|
this.SetNextState("FEEDING");
|
||||||
|
},
|
||||||
|
|
||||||
|
"MoveStopped": function() {
|
||||||
|
this.MoveRandomly();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"FEEDING": {
|
||||||
|
"enter": function() {
|
||||||
|
// Stop and eat for a while
|
||||||
|
this.SelectAnimation("idle");
|
||||||
|
this.StopMoving();
|
||||||
|
this.StartTimer(RandomInt(1000, 4000));
|
||||||
|
},
|
||||||
|
|
||||||
|
"leave": function() {
|
||||||
|
this.StopTimer();
|
||||||
|
},
|
||||||
|
|
||||||
|
"MoveStopped": function() { },
|
||||||
|
|
||||||
|
"Timer": function(msg) {
|
||||||
|
this.SetNextState("ROAMING");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"FLEEING": {
|
||||||
|
"enter": function() {
|
||||||
|
// Run quickly
|
||||||
|
this.SelectAnimation("run", false);
|
||||||
|
this.SetMoveSpeedFactor(6.0);
|
||||||
|
},
|
||||||
|
|
||||||
|
"leave": function() {
|
||||||
|
// Reset normal speed
|
||||||
|
this.SetMoveSpeedFactor(1.0);
|
||||||
|
},
|
||||||
|
|
||||||
|
"MoveStopped": function() {
|
||||||
|
// When we've run far enough, go back to the roaming state
|
||||||
|
this.SetNextState("ROAMING");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var AnimalFsm = new FSM(AnimalFsmSpec);
|
||||||
|
|
||||||
|
AnimalAI.prototype.Init = function()
|
||||||
|
{
|
||||||
|
this.messageQueue = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// FSM linkage functions:
|
||||||
|
|
||||||
|
AnimalAI.prototype.OnCreate = function()
|
||||||
|
{
|
||||||
|
AnimalFsm.Init(this, "SKITTISH.ROAMING");
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimalAI.prototype.SetNextState = function(state)
|
||||||
|
{
|
||||||
|
AnimalFsm.SetNextState(this, state);
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimalAI.prototype.DeferMessage = function(msg)
|
||||||
|
{
|
||||||
|
AnimalFsm.DeferMessage(this, msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimalAI.prototype.PushMessage = function(msg)
|
||||||
|
{
|
||||||
|
this.messageQueue.push(msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimalAI.prototype.OnUpdate = function()
|
||||||
|
{
|
||||||
|
var mq = this.messageQueue;
|
||||||
|
this.messageQueue = [];
|
||||||
|
for each (var msg in mq)
|
||||||
|
AnimalFsm.ProcessMessage(this, msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimalAI.prototype.OnMotionChanged = function(msg)
|
||||||
|
{
|
||||||
|
if (!msg.speed)
|
||||||
|
this.PushMessage({"type": "MoveStopped"});
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimalAI.prototype.OnResourceGather = function(msg)
|
||||||
|
{
|
||||||
|
this.PushMessage({"type": "ResourceGather", "gatherer": msg.gatherer});
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimalAI.prototype.TimerHandler = function(data, lateness)
|
||||||
|
{
|
||||||
|
this.PushMessage({"type": "Timer", "data": data, "lateness": lateness});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Functions to be called by the FSM:
|
||||||
|
|
||||||
|
AnimalAI.prototype.PlaySound = function(name)
|
||||||
|
{
|
||||||
|
PlaySound(name, this.entity);
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimalAI.prototype.SelectAnimation = function(name, once, speed, sound)
|
||||||
|
{
|
||||||
|
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
||||||
|
if (!cmpVisual)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var soundgroup;
|
||||||
|
if (sound)
|
||||||
|
{
|
||||||
|
var cmpSound = Engine.QueryInterface(this.entity, IID_Sound);
|
||||||
|
if (cmpSound)
|
||||||
|
soundgroup = cmpSound.GetSoundGroup(sound);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default values if unspecified
|
||||||
|
if (typeof once == "undefined")
|
||||||
|
once = false;
|
||||||
|
if (typeof speed == "undefined")
|
||||||
|
speed = 1.0;
|
||||||
|
if (typeof soundgroup == "undefined")
|
||||||
|
soundgroup = "";
|
||||||
|
|
||||||
|
cmpVisual.SelectAnimation(name, once, speed, soundgroup);
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimalAI.prototype.MoveRandomly = function()
|
||||||
|
{
|
||||||
|
// We want to walk in a random direction, but avoid getting stuck
|
||||||
|
// in obstacles or narrow spaces.
|
||||||
|
// So pick a circular range from approximately our current position,
|
||||||
|
// and move outwards to the nearest point on that circle, which will
|
||||||
|
// lead to us avoiding obstacles and moving towards free space.
|
||||||
|
|
||||||
|
// TODO: we probably ought to have a 'home' point, and drift towards
|
||||||
|
// that, so we don't spread out all across the whole map
|
||||||
|
|
||||||
|
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||||
|
if (!cmpPosition)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!cmpPosition.IsInWorld())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var pos = cmpPosition.GetPosition();
|
||||||
|
|
||||||
|
var distance = 4;
|
||||||
|
var jitter = 0.5;
|
||||||
|
|
||||||
|
// Randomly adjust the range's center a bit, so we tend to prefer
|
||||||
|
// moving in random directions (if there's nothing in the way)
|
||||||
|
var tx = pos.x + (2*Math.random()-1)*jitter;
|
||||||
|
var tz = pos.z + (2*Math.random()-1)*jitter;
|
||||||
|
|
||||||
|
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
|
||||||
|
cmpMotion.MoveToPointRange(tx, tz, distance, distance);
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimalAI.prototype.MoveAwayFrom = function(ent, distance)
|
||||||
|
{
|
||||||
|
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
|
||||||
|
cmpMotion.MoveToAttackRange(ent, distance, distance);
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimalAI.prototype.StopMoving = function()
|
||||||
|
{
|
||||||
|
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
|
||||||
|
cmpMotion.StopMoving();
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimalAI.prototype.SetMoveSpeedFactor = function(factor)
|
||||||
|
{
|
||||||
|
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
|
||||||
|
cmpMotion.SetSpeedFactor(factor);
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimalAI.prototype.StartTimer = function(interval, data)
|
||||||
|
{
|
||||||
|
if (this.timer)
|
||||||
|
error("Called StartTimer when there's already an active timer");
|
||||||
|
|
||||||
|
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||||
|
this.timer = cmpTimer.SetTimeout(this.entity, IID_AnimalAI, "TimerHandler", interval, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimalAI.prototype.StopTimer = function()
|
||||||
|
{
|
||||||
|
if (!this.timer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||||
|
cmpTimer.CancelTimer(this.timer);
|
||||||
|
this.timer = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
Engine.RegisterComponentType(IID_AnimalAI, "AnimalAI", AnimalAI);
|
@ -301,6 +301,9 @@ UnitAI.prototype.StartGather = function()
|
|||||||
|
|
||||||
// Start the gather animation and sound
|
// Start the gather animation and sound
|
||||||
this.SelectAnimation(typename, false, 1.0, typename);
|
this.SelectAnimation(typename, false, 1.0, typename);
|
||||||
|
|
||||||
|
// Tell the target we're gathering from it
|
||||||
|
Engine.PostMessage(this.gatherTarget, MT_ResourceGather, { "entity": this.gatherTarget, "gatherer": this.entity });
|
||||||
};
|
};
|
||||||
|
|
||||||
UnitAI.prototype.CancelTimers = function()
|
UnitAI.prototype.CancelTimers = function()
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
Engine.RegisterInterface("AnimalAI");
|
@ -1 +1,5 @@
|
|||||||
Engine.RegisterInterface("ResourceSupply");
|
Engine.RegisterInterface("ResourceSupply");
|
||||||
|
|
||||||
|
// Message sent from gatherers to ResourceSupply entities
|
||||||
|
// when beginning to gather
|
||||||
|
Engine.RegisterMessageType("ResourceGather");
|
||||||
|
244
binaries/data/mods/public/simulation/helpers/FSM.js
Normal file
244
binaries/data/mods/public/simulation/helpers/FSM.js
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
// Hierarchical finite state machine implementation.
|
||||||
|
//
|
||||||
|
// FSMs are specified as a JS data structure;
|
||||||
|
// see e.g. AnimalAI.js for an example of the syntax.
|
||||||
|
//
|
||||||
|
// FSMs are implicitly linked with an external object.
|
||||||
|
// That object stores all FSM-related state.
|
||||||
|
// (This means we can serialise FSM-based components as
|
||||||
|
// plain old JS objects, with no need to serialise the complex
|
||||||
|
// FSM structure itself or to add custom serialisation code.)
|
||||||
|
|
||||||
|
function FSM(spec)
|
||||||
|
{
|
||||||
|
// The (relatively) human-readable FSM specification needs to get
|
||||||
|
// compiled into a more-efficient-to-execute version.
|
||||||
|
//
|
||||||
|
// In particular, message handling should require minimal
|
||||||
|
// property lookups in the common case (even when the FSM has
|
||||||
|
// a deeply nested hierarchy), and there should never be any
|
||||||
|
// string manipulation at run-time.
|
||||||
|
|
||||||
|
this.decompose = { "": [] };
|
||||||
|
/* 'decompose' will store:
|
||||||
|
{
|
||||||
|
"": [],
|
||||||
|
"A": ["A"],
|
||||||
|
"A.B": ["A", "A.B"],
|
||||||
|
"A.B.C": ["A", "A.B", "A.B.C"],
|
||||||
|
"A.B.D": ["A", "A.B", "A.B.D"],
|
||||||
|
...
|
||||||
|
};
|
||||||
|
This is used when switching between states in different branches
|
||||||
|
of the hierarchy, to determine the list of sub-states to leave/enter
|
||||||
|
*/
|
||||||
|
|
||||||
|
this.states = { };
|
||||||
|
/* 'states' will store:
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"A": {
|
||||||
|
"_name": "A",
|
||||||
|
"_parent": "",
|
||||||
|
"_refs": { // local -> global name lookups (for SetNextState)
|
||||||
|
"B": "A.B",
|
||||||
|
"B.C": "A.B.C",
|
||||||
|
"B.D": "A.B.D",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"A.B": {
|
||||||
|
"_name": "A.B",
|
||||||
|
"_parent": "A",
|
||||||
|
"_refs": {
|
||||||
|
"C": "A.B.C",
|
||||||
|
"D": "A.B.D",
|
||||||
|
},
|
||||||
|
"MessageType": function(msg) { ... },
|
||||||
|
},
|
||||||
|
"A.B.C": {
|
||||||
|
"_name": "A.B.C",
|
||||||
|
"_parent": "A.B",
|
||||||
|
"_refs": {},
|
||||||
|
"enter": function() { ... },
|
||||||
|
"MessageType": function(msg) { ... },
|
||||||
|
},
|
||||||
|
"A.B.D": {
|
||||||
|
"_name": "A.B.D",
|
||||||
|
"_parent": "A.B",
|
||||||
|
"_refs": {},
|
||||||
|
"enter": function() { ... },
|
||||||
|
"leave": function() { ... },
|
||||||
|
"MessageType": function(msg) { ... },
|
||||||
|
},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function process(fsm, node, path, handlers)
|
||||||
|
{
|
||||||
|
var state = {};
|
||||||
|
fsm.states[path.join(".")] = state;
|
||||||
|
|
||||||
|
var newhandlers = {};
|
||||||
|
for (var e in handlers)
|
||||||
|
newhandlers[e] = handlers[e];
|
||||||
|
|
||||||
|
state._name = path.join(".");
|
||||||
|
state._parent = path.slice(0, -1).join(".");
|
||||||
|
state._refs = {};
|
||||||
|
|
||||||
|
for (var key in node)
|
||||||
|
{
|
||||||
|
if (key === "enter" || key === "leave")
|
||||||
|
{
|
||||||
|
state[key] = node[key];
|
||||||
|
}
|
||||||
|
else if (key.match(/^[A-Z]+$/))
|
||||||
|
{
|
||||||
|
state._refs[key] = (state._name ? state._name + "." : "") + key;
|
||||||
|
|
||||||
|
// (the rest of this will be handled later once we've grabbed
|
||||||
|
// all the event handlers)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newhandlers[key] = node[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var e in newhandlers)
|
||||||
|
state[e] = newhandlers[e];
|
||||||
|
|
||||||
|
for (var key in node)
|
||||||
|
{
|
||||||
|
if (key.match(/^[A-Z]+$/))
|
||||||
|
{
|
||||||
|
var newpath = path.concat([key]);
|
||||||
|
|
||||||
|
var decomposed = [newpath[0]];
|
||||||
|
for (var i = 1; i < newpath.length; ++i)
|
||||||
|
decomposed.push(decomposed[i-1] + "." + newpath[i]);
|
||||||
|
fsm.decompose[newpath.join(".")] = decomposed;
|
||||||
|
|
||||||
|
var childstate = process(fsm, node[key], newpath, newhandlers);
|
||||||
|
|
||||||
|
for (var r in childstate._refs)
|
||||||
|
{
|
||||||
|
var cname = key + "." + r;
|
||||||
|
state._refs[cname] = childstate._refs[r];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
process(this, spec, [], {});
|
||||||
|
}
|
||||||
|
|
||||||
|
FSM.prototype.Init = function(obj, initialState)
|
||||||
|
{
|
||||||
|
this.deferFromState = undefined;
|
||||||
|
|
||||||
|
obj.fsmStateName = "";
|
||||||
|
obj.fsmNextState = undefined;
|
||||||
|
this.SwitchToNextState(obj, initialState);
|
||||||
|
};
|
||||||
|
|
||||||
|
FSM.prototype.SetNextState = function(obj, state)
|
||||||
|
{
|
||||||
|
obj.fsmNextState = state;
|
||||||
|
};
|
||||||
|
|
||||||
|
FSM.prototype.ProcessMessage = function(obj, msg)
|
||||||
|
{
|
||||||
|
// print("ProcessMessage(obj, "+uneval(msg)+")\n");
|
||||||
|
|
||||||
|
var func = this.states[obj.fsmStateName][msg.type];
|
||||||
|
if (!func)
|
||||||
|
{
|
||||||
|
error("Tried to process unhandled event '" + msg.type + "' in state '" + obj.fsmStateName + "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
func.apply(obj, [msg]);
|
||||||
|
|
||||||
|
while (obj.fsmNextState)
|
||||||
|
{
|
||||||
|
var nextStateName = this.LookupState(obj.fsmStateName, obj.fsmNextState);
|
||||||
|
obj.fsmNextState = undefined;
|
||||||
|
|
||||||
|
if (nextStateName != obj.fsmStateName)
|
||||||
|
this.SwitchToNextState(obj, nextStateName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FSM.prototype.DeferMessage = function(obj, msg)
|
||||||
|
{
|
||||||
|
// We need to work out which sub-state we were running the message handler from,
|
||||||
|
// and then try again in its parent state.
|
||||||
|
var old = this.deferFromState;
|
||||||
|
var from;
|
||||||
|
if (old) // if we're recursively deferring and saved the last used state, use that
|
||||||
|
from = old;
|
||||||
|
else // if this is the first defer then we must have last processed the message in the current FSM state
|
||||||
|
from = obj.fsmStateName;
|
||||||
|
|
||||||
|
// Find and save the parent, for use in recursive defers
|
||||||
|
this.deferFromState = this.states[from]._parent;
|
||||||
|
|
||||||
|
// Run the function from the parent state
|
||||||
|
var state = this.states[this.deferFromState];
|
||||||
|
var func = state[msg.type];
|
||||||
|
if (!func)
|
||||||
|
error("Failed to defer event '" + msg.type + "' from state '" + obj.fsmStateName + "'");
|
||||||
|
func.apply(obj, [msg]);
|
||||||
|
|
||||||
|
// Restore the changes we made
|
||||||
|
this.deferFromState = old;
|
||||||
|
|
||||||
|
// TODO: if an inherited handler defers, it calls exactly the same handler
|
||||||
|
// on the parent state, which is probably useless and inefficient
|
||||||
|
|
||||||
|
// NOTE: this will break if two units try to execute AI at the same time;
|
||||||
|
// as long as AI messages are queue and processed asynchronously it should be fine
|
||||||
|
};
|
||||||
|
|
||||||
|
FSM.prototype.LookupState = function(currentStateName, stateName)
|
||||||
|
{
|
||||||
|
// print("LookupState("+currentStateName+", "+stateName+")\n");
|
||||||
|
for (var s = currentStateName; s; s = this.states[s]._parent)
|
||||||
|
if (stateName in this.states[s]._refs)
|
||||||
|
return this.states[s]._refs[stateName];
|
||||||
|
return stateName;
|
||||||
|
};
|
||||||
|
|
||||||
|
FSM.prototype.SwitchToNextState = function(obj, nextStateName)
|
||||||
|
{
|
||||||
|
var fromState = this.decompose[obj.fsmStateName];
|
||||||
|
var toState = this.decompose[nextStateName];
|
||||||
|
|
||||||
|
if (!toState)
|
||||||
|
error("Tried to change to non-existent state '" + nextState + "'");
|
||||||
|
|
||||||
|
for (var equalPrefix = 0; fromState[equalPrefix] === toState[equalPrefix]; ++equalPrefix)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = fromState.length-1; i >= equalPrefix; --i)
|
||||||
|
{
|
||||||
|
var leave = this.states[fromState[i]].leave;
|
||||||
|
if (leave)
|
||||||
|
leave.apply(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = equalPrefix; i < toState.length; ++i)
|
||||||
|
{
|
||||||
|
var enter = this.states[toState[i]].enter;
|
||||||
|
if (enter)
|
||||||
|
enter.apply(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.fsmStateName = nextStateName;
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine.RegisterGlobal("FSM", FSM);
|
9
binaries/data/mods/public/simulation/helpers/Random.js
Normal file
9
binaries/data/mods/public/simulation/helpers/Random.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Returns a random integer from min (inclusive) to max (exclusive)
|
||||||
|
*/
|
||||||
|
function RandomInt(min, max)
|
||||||
|
{
|
||||||
|
return Math.floor(min + Math.random() * (max-min))
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine.RegisterGlobal("RandomInt", RandomInt);
|
@ -15,4 +15,17 @@
|
|||||||
<Circle radius="0.5"/>
|
<Circle radius="0.5"/>
|
||||||
<Height>1.5</Height>
|
<Height>1.5</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
|
<AnimalAI>
|
||||||
|
<NaturalBehaviour>skittish</NaturalBehaviour>
|
||||||
|
</AnimalAI>
|
||||||
|
<UnitMotion>
|
||||||
|
<WalkSpeed>1</WalkSpeed>
|
||||||
|
<PassabilityClass>default</PassabilityClass>
|
||||||
|
<CostClass>default</CostClass>
|
||||||
|
</UnitMotion>
|
||||||
|
<Sound>
|
||||||
|
<SoundGroups>
|
||||||
|
<panic>actor/fauna/animal/chickens.xml</panic>
|
||||||
|
</SoundGroups>
|
||||||
|
</Sound>
|
||||||
</Entity>
|
</Entity>
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
#include "simulation2/helpers/Position.h"
|
#include "simulation2/helpers/Position.h"
|
||||||
|
|
||||||
#define DEFAULT_MESSAGE_IMPL(name) \
|
#define DEFAULT_MESSAGE_IMPL(name) \
|
||||||
virtual EMessageTypeId GetType() const { return MT_##name; } \
|
virtual int GetType() const { return MT_##name; } \
|
||||||
virtual const char* GetScriptHandlerName() const { return "On" #name; } \
|
virtual const char* GetScriptHandlerName() const { return "On" #name; } \
|
||||||
virtual const char* GetScriptGlobalHandlerName() const { return "OnGlobal" #name; } \
|
virtual const char* GetScriptGlobalHandlerName() const { return "OnGlobal" #name; } \
|
||||||
virtual jsval ToJSVal(ScriptInterface& scriptInterface) const; \
|
virtual jsval ToJSVal(ScriptInterface& scriptInterface) const; \
|
||||||
@ -86,6 +86,23 @@ public:
|
|||||||
bool culling;
|
bool culling;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is send immediately after a new entity's components have all be created
|
||||||
|
* and initialised.
|
||||||
|
*/
|
||||||
|
class CMessageCreate : public CMessage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DEFAULT_MESSAGE_IMPL(Create)
|
||||||
|
|
||||||
|
CMessageCreate(entity_id_t entity) :
|
||||||
|
entity(entity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_id_t entity;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is sent immediately before a destroyed entity is flushed and really destroyed.
|
* This is sent immediately before a destroyed entity is flushed and really destroyed.
|
||||||
* (That is, after CComponentManager::DestroyComponentsSoon and inside FlushDestroyedComponents).
|
* (That is, after CComponentManager::DestroyComponentsSoon and inside FlushDestroyedComponents).
|
||||||
|
@ -34,6 +34,7 @@ MESSAGE(TurnStart)
|
|||||||
MESSAGE(Update)
|
MESSAGE(Update)
|
||||||
MESSAGE(Interpolate) // non-deterministic (use with caution)
|
MESSAGE(Interpolate) // non-deterministic (use with caution)
|
||||||
MESSAGE(RenderSubmit) // non-deterministic (use with caution)
|
MESSAGE(RenderSubmit) // non-deterministic (use with caution)
|
||||||
|
MESSAGE(Create)
|
||||||
MESSAGE(Destroy)
|
MESSAGE(Destroy)
|
||||||
MESSAGE(OwnershipChanged)
|
MESSAGE(OwnershipChanged)
|
||||||
MESSAGE(PositionChanged)
|
MESSAGE(PositionChanged)
|
||||||
|
@ -59,7 +59,7 @@ public:
|
|||||||
|
|
||||||
// Template state:
|
// Template state:
|
||||||
|
|
||||||
fixed m_Speed; // in metres per second
|
fixed m_WalkSpeed; // in metres per second
|
||||||
fixed m_RunSpeed;
|
fixed m_RunSpeed;
|
||||||
entity_pos_t m_Radius;
|
entity_pos_t m_Radius;
|
||||||
u8 m_PassClass;
|
u8 m_PassClass;
|
||||||
@ -67,6 +67,7 @@ public:
|
|||||||
|
|
||||||
// Dynamic state:
|
// Dynamic state:
|
||||||
|
|
||||||
|
fixed m_Speed;
|
||||||
bool m_HasTarget; // whether we currently have valid paths and targets
|
bool m_HasTarget; // whether we currently have valid paths and targets
|
||||||
// These values contain undefined junk if !HasTarget:
|
// These values contain undefined junk if !HasTarget:
|
||||||
ICmpPathfinder::Path m_Path;
|
ICmpPathfinder::Path m_Path;
|
||||||
@ -121,7 +122,8 @@ public:
|
|||||||
{
|
{
|
||||||
m_HasTarget = false;
|
m_HasTarget = false;
|
||||||
|
|
||||||
m_Speed = paramNode.GetChild("WalkSpeed").ToFixed();
|
m_WalkSpeed = paramNode.GetChild("WalkSpeed").ToFixed();
|
||||||
|
m_Speed = m_WalkSpeed;
|
||||||
|
|
||||||
if (paramNode.GetChild("Run").IsOk())
|
if (paramNode.GetChild("Run").IsOk())
|
||||||
{
|
{
|
||||||
@ -129,7 +131,7 @@ public:
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_RunSpeed = m_Speed;
|
m_RunSpeed = m_WalkSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
CmpPtr<ICmpObstruction> cmpObstruction(context, GetEntityId());
|
CmpPtr<ICmpObstruction> cmpObstruction(context, GetEntityId());
|
||||||
@ -202,9 +204,9 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual fixed GetSpeed()
|
virtual fixed GetWalkSpeed()
|
||||||
{
|
{
|
||||||
return m_Speed;
|
return m_WalkSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual fixed GetRunSpeed()
|
virtual fixed GetRunSpeed()
|
||||||
@ -212,6 +214,11 @@ public:
|
|||||||
return m_RunSpeed;
|
return m_RunSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void SetSpeedFactor(fixed factor)
|
||||||
|
{
|
||||||
|
m_Speed = m_WalkSpeed.Multiply(factor);
|
||||||
|
}
|
||||||
|
|
||||||
virtual void SetDebugOverlay(bool enabled)
|
virtual void SetDebugOverlay(bool enabled)
|
||||||
{
|
{
|
||||||
m_DebugOverlayEnabled = enabled;
|
m_DebugOverlayEnabled = enabled;
|
||||||
@ -225,6 +232,12 @@ public:
|
|||||||
virtual bool MoveToPoint(entity_pos_t x, entity_pos_t z);
|
virtual bool MoveToPoint(entity_pos_t x, entity_pos_t z);
|
||||||
virtual bool MoveToAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
|
virtual bool MoveToAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
|
||||||
virtual bool IsInAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
|
virtual bool IsInAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
|
||||||
|
virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange);
|
||||||
|
|
||||||
|
virtual void StopMoving()
|
||||||
|
{
|
||||||
|
SwitchState(IDLE);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
@ -537,6 +550,8 @@ bool CCmpUnitMotion::ShouldTreatTargetAsCircle(entity_pos_t range, entity_pos_t
|
|||||||
return (errCircle < errSquare);
|
return (errCircle < errSquare);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const entity_pos_t g_GoalDelta = entity_pos_t::FromInt(CELL_SIZE)/4; // for extending the goal outwards/inwards a little bit
|
||||||
|
|
||||||
bool CCmpUnitMotion::MoveToAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
|
bool CCmpUnitMotion::MoveToAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
|
||||||
{
|
{
|
||||||
PROFILE("MoveToAttackRange");
|
PROFILE("MoveToAttackRange");
|
||||||
@ -551,8 +566,6 @@ bool CCmpUnitMotion::MoveToAttackRange(entity_id_t target, entity_pos_t minRange
|
|||||||
// Reset any current movement
|
// Reset any current movement
|
||||||
m_HasTarget = false;
|
m_HasTarget = false;
|
||||||
|
|
||||||
ICmpPathfinder::Goal goal;
|
|
||||||
|
|
||||||
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
|
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
|
||||||
if (cmpObstructionManager.null())
|
if (cmpObstructionManager.null())
|
||||||
return false;
|
return false;
|
||||||
@ -588,13 +601,12 @@ bool CCmpUnitMotion::MoveToAttackRange(entity_id_t target, entity_pos_t minRange
|
|||||||
* (Those units should set minRange to 0 so they'll never be considered *too* close.)
|
* (Those units should set minRange to 0 so they'll never be considered *too* close.)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const entity_pos_t goalDelta = entity_pos_t::FromInt(CELL_SIZE)/4; // for extending the goal outwards/inwards a little bit
|
|
||||||
|
|
||||||
if (tag.valid())
|
if (tag.valid())
|
||||||
{
|
{
|
||||||
ICmpObstructionManager::ObstructionSquare obstruction = cmpObstructionManager->GetObstruction(tag);
|
ICmpObstructionManager::ObstructionSquare obstruction = cmpObstructionManager->GetObstruction(tag);
|
||||||
|
|
||||||
CFixedVector2D halfSize(obstruction.hw, obstruction.hh);
|
CFixedVector2D halfSize(obstruction.hw, obstruction.hh);
|
||||||
|
ICmpPathfinder::Goal goal;
|
||||||
goal.x = obstruction.x;
|
goal.x = obstruction.x;
|
||||||
goal.z = obstruction.z;
|
goal.z = obstruction.z;
|
||||||
|
|
||||||
@ -604,7 +616,7 @@ bool CCmpUnitMotion::MoveToAttackRange(entity_id_t target, entity_pos_t minRange
|
|||||||
{
|
{
|
||||||
// Too close to the square - need to move away
|
// Too close to the square - need to move away
|
||||||
|
|
||||||
entity_pos_t goalDistance = minRange + goalDelta;
|
entity_pos_t goalDistance = minRange + g_GoalDelta;
|
||||||
|
|
||||||
goal.type = ICmpPathfinder::Goal::SQUARE;
|
goal.type = ICmpPathfinder::Goal::SQUARE;
|
||||||
goal.u = obstruction.u;
|
goal.u = obstruction.u;
|
||||||
@ -642,7 +654,7 @@ bool CCmpUnitMotion::MoveToAttackRange(entity_id_t target, entity_pos_t minRange
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
entity_pos_t goalDistance = maxRange - goalDelta;
|
entity_pos_t goalDistance = maxRange - g_GoalDelta;
|
||||||
|
|
||||||
goal.type = ICmpPathfinder::Goal::CIRCLE;
|
goal.type = ICmpPathfinder::Goal::CIRCLE;
|
||||||
goal.hw = circleRadius + goalDistance;
|
goal.hw = circleRadius + goalDistance;
|
||||||
@ -652,7 +664,7 @@ bool CCmpUnitMotion::MoveToAttackRange(entity_id_t target, entity_pos_t minRange
|
|||||||
// The target is large relative to our range, so treat it as a square and
|
// The target is large relative to our range, so treat it as a square and
|
||||||
// get close enough that the diagonals come within range
|
// get close enough that the diagonals come within range
|
||||||
|
|
||||||
entity_pos_t goalDistance = (maxRange - goalDelta)*2 / 3; // multiply by slightly less than 1/sqrt(2)
|
entity_pos_t goalDistance = (maxRange - g_GoalDelta)*2 / 3; // multiply by slightly less than 1/sqrt(2)
|
||||||
|
|
||||||
goal.type = ICmpPathfinder::Goal::SQUARE;
|
goal.type = ICmpPathfinder::Goal::SQUARE;
|
||||||
goal.u = obstruction.u;
|
goal.u = obstruction.u;
|
||||||
@ -662,6 +674,13 @@ bool CCmpUnitMotion::MoveToAttackRange(entity_id_t target, entity_pos_t minRange
|
|||||||
goal.hh = obstruction.hh + delta;
|
goal.hh = obstruction.hh + delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_FinalGoal = goal;
|
||||||
|
if (!RegeneratePath(pos, false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SwitchState(WALKING);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -673,31 +692,46 @@ bool CCmpUnitMotion::MoveToAttackRange(entity_id_t target, entity_pos_t minRange
|
|||||||
|
|
||||||
CFixedVector3D targetPos = cmpTargetPosition->GetPosition();
|
CFixedVector3D targetPos = cmpTargetPosition->GetPosition();
|
||||||
|
|
||||||
entity_pos_t distance = (pos - CFixedVector2D(targetPos.X, targetPos.Z)).Length();
|
return MoveToPointRange(targetPos.X, targetPos.Z, minRange, maxRange);
|
||||||
|
|
||||||
entity_pos_t goalDistance;
|
|
||||||
if (distance < minRange)
|
|
||||||
{
|
|
||||||
goalDistance = minRange + goalDelta;
|
|
||||||
}
|
|
||||||
else if (distance > maxRange)
|
|
||||||
{
|
|
||||||
goalDistance = maxRange - goalDelta;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// We're already in range - no need to move anywhere
|
|
||||||
FaceTowardsPoint(pos, goal.x, goal.z);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: what happens if goalDistance < 0? (i.e. we probably can never get close enough to the target)
|
|
||||||
|
|
||||||
goal.type = ICmpPathfinder::Goal::CIRCLE;
|
|
||||||
goal.x = targetPos.X;
|
|
||||||
goal.z = targetPos.Z;
|
|
||||||
goal.hw = m_Radius + goalDistance;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange)
|
||||||
|
{
|
||||||
|
PROFILE("MoveToPointRange");
|
||||||
|
|
||||||
|
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId());
|
||||||
|
if (cmpPosition.null() || !cmpPosition->IsInWorld())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
CFixedVector3D pos3 = cmpPosition->GetPosition();
|
||||||
|
CFixedVector2D pos (pos3.X, pos3.Z);
|
||||||
|
|
||||||
|
entity_pos_t distance = (pos - CFixedVector2D(x, z)).Length();
|
||||||
|
|
||||||
|
entity_pos_t goalDistance;
|
||||||
|
if (distance < minRange)
|
||||||
|
{
|
||||||
|
goalDistance = minRange + g_GoalDelta;
|
||||||
|
}
|
||||||
|
else if (distance > maxRange)
|
||||||
|
{
|
||||||
|
goalDistance = maxRange - g_GoalDelta;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We're already in range - no need to move anywhere
|
||||||
|
FaceTowardsPoint(pos, x, z);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: what happens if goalDistance < 0? (i.e. we probably can never get close enough to the target)
|
||||||
|
|
||||||
|
ICmpPathfinder::Goal goal;
|
||||||
|
goal.type = ICmpPathfinder::Goal::CIRCLE;
|
||||||
|
goal.x = x;
|
||||||
|
goal.z = z;
|
||||||
|
goal.hw = m_Radius + goalDistance;
|
||||||
|
|
||||||
m_FinalGoal = goal;
|
m_FinalGoal = goal;
|
||||||
if (!RegeneratePath(pos, false))
|
if (!RegeneratePath(pos, false))
|
||||||
|
@ -25,6 +25,8 @@ BEGIN_INTERFACE_WRAPPER(UnitMotion)
|
|||||||
DEFINE_INTERFACE_METHOD_2("MoveToPoint", bool, ICmpUnitMotion, MoveToPoint, entity_pos_t, entity_pos_t)
|
DEFINE_INTERFACE_METHOD_2("MoveToPoint", bool, ICmpUnitMotion, MoveToPoint, entity_pos_t, entity_pos_t)
|
||||||
DEFINE_INTERFACE_METHOD_3("IsInAttackRange", bool, ICmpUnitMotion, IsInAttackRange, entity_id_t, entity_pos_t, entity_pos_t)
|
DEFINE_INTERFACE_METHOD_3("IsInAttackRange", bool, ICmpUnitMotion, IsInAttackRange, entity_id_t, entity_pos_t, entity_pos_t)
|
||||||
DEFINE_INTERFACE_METHOD_3("MoveToAttackRange", bool, ICmpUnitMotion, MoveToAttackRange, entity_id_t, entity_pos_t, entity_pos_t)
|
DEFINE_INTERFACE_METHOD_3("MoveToAttackRange", bool, ICmpUnitMotion, MoveToAttackRange, entity_id_t, entity_pos_t, entity_pos_t)
|
||||||
DEFINE_INTERFACE_METHOD_0("GetSpeed", fixed, ICmpUnitMotion, GetSpeed)
|
DEFINE_INTERFACE_METHOD_4("MoveToPointRange", bool, ICmpUnitMotion, MoveToPointRange, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t)
|
||||||
|
DEFINE_INTERFACE_METHOD_0("StopMoving", void, ICmpUnitMotion, StopMoving)
|
||||||
|
DEFINE_INTERFACE_METHOD_1("SetSpeedFactor", void, ICmpUnitMotion, SetSpeedFactor, fixed)
|
||||||
DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpUnitMotion, SetDebugOverlay, bool)
|
DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpUnitMotion, SetDebugOverlay, bool)
|
||||||
END_INTERFACE_WRAPPER(UnitMotion)
|
END_INTERFACE_WRAPPER(UnitMotion)
|
||||||
|
@ -51,7 +51,7 @@ public:
|
|||||||
virtual bool IsInAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;
|
virtual bool IsInAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to walk into range of a given target, or as close as possible.
|
* Attempt to walk into range of a given target entity, or as close as possible.
|
||||||
* If the unit is already in range, or cannot move anywhere at all, or if there is
|
* If the unit is already in range, or cannot move anywhere at all, or if there is
|
||||||
* some other error, then returns false.
|
* some other error, then returns false.
|
||||||
* Otherwise, sends a MotionChanged message and returns true; it will send another
|
* Otherwise, sends a MotionChanged message and returns true; it will send another
|
||||||
@ -60,10 +60,25 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual bool MoveToAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;
|
virtual bool MoveToAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See MoveToAttackRange, but the target is the given point.
|
||||||
|
*/
|
||||||
|
virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop moving immediately.
|
||||||
|
*/
|
||||||
|
virtual void StopMoving() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current movement speed to be the default multiplied by the given factor.
|
||||||
|
*/
|
||||||
|
virtual void SetSpeedFactor(fixed factor) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the default speed that this unit will have when walking, in metres per second.
|
* Get the default speed that this unit will have when walking, in metres per second.
|
||||||
*/
|
*/
|
||||||
virtual fixed GetSpeed() = 0;
|
virtual fixed GetWalkSpeed() = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the default speed that this unit will have when running, in metres per second.
|
* Get the default speed that this unit will have when running, in metres per second.
|
||||||
|
@ -110,6 +110,22 @@ CMessage* CMessageRenderSubmit::FromJSVal(ScriptInterface& UNUSED(scriptInterfac
|
|||||||
|
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
|
|
||||||
|
jsval CMessageCreate::ToJSVal(ScriptInterface& scriptInterface) const
|
||||||
|
{
|
||||||
|
TOJSVAL_SETUP();
|
||||||
|
SET_MSG_PROPERTY(entity);
|
||||||
|
return OBJECT_TO_JSVAL(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
CMessage* CMessageCreate::FromJSVal(ScriptInterface& scriptInterface, jsval val)
|
||||||
|
{
|
||||||
|
FROMJSVAL_SETUP();
|
||||||
|
GET_MSG_PROPERTY(entity_id_t, entity);
|
||||||
|
return new CMessageCreate(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
|
||||||
jsval CMessageDestroy::ToJSVal(ScriptInterface& scriptInterface) const
|
jsval CMessageDestroy::ToJSVal(ScriptInterface& scriptInterface) const
|
||||||
{
|
{
|
||||||
TOJSVAL_SETUP();
|
TOJSVAL_SETUP();
|
||||||
|
@ -29,6 +29,28 @@
|
|||||||
#include "ps/CLogger.h"
|
#include "ps/CLogger.h"
|
||||||
#include "ps/Filesystem.h"
|
#include "ps/Filesystem.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for script-only message types.
|
||||||
|
*/
|
||||||
|
class CMessageScripted : public CMessage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual int GetType() const { return mtid; }
|
||||||
|
virtual const char* GetScriptHandlerName() const { return handlerName.c_str(); }
|
||||||
|
virtual const char* GetScriptGlobalHandlerName() const { return globalHandlerName.c_str(); }
|
||||||
|
virtual jsval ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const { return msg.get(); }
|
||||||
|
|
||||||
|
CMessageScripted(int mtid, const std::string& name, const CScriptValRooted& msg) :
|
||||||
|
mtid(mtid), handlerName("On" + name), globalHandlerName("OnGlobal" + name), msg(msg)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int mtid;
|
||||||
|
std::string handlerName;
|
||||||
|
std::string globalHandlerName;
|
||||||
|
CScriptValRooted msg;
|
||||||
|
};
|
||||||
|
|
||||||
CComponentManager::CComponentManager(CSimContext& context, bool skipScriptFunctions) :
|
CComponentManager::CComponentManager(CSimContext& context, bool skipScriptFunctions) :
|
||||||
m_NextScriptComponentTypeId(CID__LastNative), m_ScriptInterface("Engine"), m_SimContext(context), m_CurrentlyHotloading(false)
|
m_NextScriptComponentTypeId(CID__LastNative), m_ScriptInterface("Engine"), m_SimContext(context), m_CurrentlyHotloading(false)
|
||||||
{
|
{
|
||||||
@ -45,6 +67,7 @@ CComponentManager::CComponentManager(CSimContext& context, bool skipScriptFuncti
|
|||||||
{
|
{
|
||||||
m_ScriptInterface.RegisterFunction<void, int, std::string, CScriptVal, CComponentManager::Script_RegisterComponentType> ("RegisterComponentType");
|
m_ScriptInterface.RegisterFunction<void, int, std::string, CScriptVal, CComponentManager::Script_RegisterComponentType> ("RegisterComponentType");
|
||||||
m_ScriptInterface.RegisterFunction<void, std::string, CComponentManager::Script_RegisterInterface> ("RegisterInterface");
|
m_ScriptInterface.RegisterFunction<void, std::string, CComponentManager::Script_RegisterInterface> ("RegisterInterface");
|
||||||
|
m_ScriptInterface.RegisterFunction<void, std::string, CComponentManager::Script_RegisterMessageType> ("RegisterMessageType");
|
||||||
m_ScriptInterface.RegisterFunction<void, std::string, CScriptVal, CComponentManager::Script_RegisterGlobal> ("RegisterGlobal");
|
m_ScriptInterface.RegisterFunction<void, std::string, CScriptVal, CComponentManager::Script_RegisterGlobal> ("RegisterGlobal");
|
||||||
m_ScriptInterface.RegisterFunction<IComponent*, int, int, CComponentManager::Script_QueryInterface> ("QueryInterface");
|
m_ScriptInterface.RegisterFunction<IComponent*, int, int, CComponentManager::Script_QueryInterface> ("QueryInterface");
|
||||||
m_ScriptInterface.RegisterFunction<void, int, int, CScriptVal, CComponentManager::Script_PostMessage> ("PostMessage");
|
m_ScriptInterface.RegisterFunction<void, int, int, CScriptVal, CComponentManager::Script_PostMessage> ("PostMessage");
|
||||||
@ -281,7 +304,27 @@ void CComponentManager::Script_RegisterInterface(void* cbdata, std::string name)
|
|||||||
// IIDs start at 1, so size+1 is the next unused one
|
// IIDs start at 1, so size+1 is the next unused one
|
||||||
size_t id = componentManager->m_InterfaceIdsByName.size() + 1;
|
size_t id = componentManager->m_InterfaceIdsByName.size() + 1;
|
||||||
componentManager->m_InterfaceIdsByName[name] = id;
|
componentManager->m_InterfaceIdsByName[name] = id;
|
||||||
componentManager->m_ScriptInterface.SetGlobal(("IID_" + name).c_str(), (int )id);
|
componentManager->m_ScriptInterface.SetGlobal(("IID_" + name).c_str(), (int)id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CComponentManager::Script_RegisterMessageType(void* cbdata, std::string name)
|
||||||
|
{
|
||||||
|
CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata);
|
||||||
|
|
||||||
|
std::map<std::string, MessageTypeId>::iterator it = componentManager->m_MessageTypeIdsByName.find(name);
|
||||||
|
if (it != componentManager->m_MessageTypeIdsByName.end())
|
||||||
|
{
|
||||||
|
// Redefinitions are fine (and just get ignored) when hotloading; otherwise
|
||||||
|
// they're probably unintentional and should be reported
|
||||||
|
if (!componentManager->m_CurrentlyHotloading)
|
||||||
|
componentManager->m_ScriptInterface.ReportError("Registering message type with already-registered name"); // TODO: report the actual name
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MTIDs start at 1, so size+1 is the next unused one
|
||||||
|
size_t id = componentManager->m_MessageTypeIdsByName.size() + 1;
|
||||||
|
componentManager->RegisterMessageType(id, name.c_str());
|
||||||
|
componentManager->m_ScriptInterface.SetGlobal(("MT_" + name).c_str(), (int)id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CComponentManager::Script_RegisterGlobal(void* cbdata, std::string name, CScriptVal value)
|
void CComponentManager::Script_RegisterGlobal(void* cbdata, std::string name, CScriptVal value)
|
||||||
@ -300,10 +343,27 @@ IComponent* CComponentManager::Script_QueryInterface(void* cbdata, int ent, int
|
|||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CMessage* CComponentManager::ConstructMessage(int mtid, CScriptVal data)
|
||||||
|
{
|
||||||
|
if (mtid == MT__Invalid || mtid > (int)m_MessageTypeIdsByName.size()) // (IDs start at 1 so use '>' here)
|
||||||
|
LOGERROR(L"PostMessage with invalid message type ID '%d'", mtid);
|
||||||
|
|
||||||
|
if (mtid < MT__LastNative)
|
||||||
|
{
|
||||||
|
return CMessageFromJSVal(mtid, m_ScriptInterface, data.get());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new CMessageScripted(mtid, m_MessageTypeNamesById[mtid],
|
||||||
|
CScriptValRooted(m_ScriptInterface.GetContext(), data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CComponentManager::Script_PostMessage(void* cbdata, int ent, int mtid, CScriptVal data)
|
void CComponentManager::Script_PostMessage(void* cbdata, int ent, int mtid, CScriptVal data)
|
||||||
{
|
{
|
||||||
CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata);
|
CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata);
|
||||||
CMessage* msg = CMessageFromJSVal(mtid, componentManager->m_ScriptInterface, data.get());
|
|
||||||
|
CMessage* msg = componentManager->ConstructMessage(mtid, data);
|
||||||
if (!msg)
|
if (!msg)
|
||||||
return; // error
|
return; // error
|
||||||
|
|
||||||
@ -315,7 +375,8 @@ void CComponentManager::Script_PostMessage(void* cbdata, int ent, int mtid, CScr
|
|||||||
void CComponentManager::Script_BroadcastMessage(void* cbdata, int mtid, CScriptVal data)
|
void CComponentManager::Script_BroadcastMessage(void* cbdata, int mtid, CScriptVal data)
|
||||||
{
|
{
|
||||||
CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata);
|
CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata);
|
||||||
CMessage* msg = CMessageFromJSVal(mtid, componentManager->m_ScriptInterface, data.get());
|
|
||||||
|
CMessage* msg = componentManager->ConstructMessage(mtid, data);
|
||||||
if (!msg)
|
if (!msg)
|
||||||
return; // error
|
return; // error
|
||||||
|
|
||||||
@ -399,6 +460,7 @@ void CComponentManager::RegisterComponentTypeScriptWrapper(InterfaceId iid, Comp
|
|||||||
void CComponentManager::RegisterMessageType(MessageTypeId mtid, const char* name)
|
void CComponentManager::RegisterMessageType(MessageTypeId mtid, const char* name)
|
||||||
{
|
{
|
||||||
m_MessageTypeIdsByName[name] = mtid;
|
m_MessageTypeIdsByName[name] = mtid;
|
||||||
|
m_MessageTypeNamesById[mtid] = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CComponentManager::SubscribeToMessageType(MessageTypeId mtid)
|
void CComponentManager::SubscribeToMessageType(MessageTypeId mtid)
|
||||||
@ -587,6 +649,9 @@ entity_id_t CComponentManager::AddEntity(const std::wstring& templateName, entit
|
|||||||
// TODO: maybe we should delete already-constructed components if one of them fails?
|
// TODO: maybe we should delete already-constructed components if one of them fails?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CMessageCreate msg(ent);
|
||||||
|
PostMessage(ent, msg);
|
||||||
|
|
||||||
return ent;
|
return ent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,6 +216,7 @@ private:
|
|||||||
// Implementations of functions exposed to scripts
|
// Implementations of functions exposed to scripts
|
||||||
static void Script_RegisterComponentType(void* cbdata, int iid, std::string cname, CScriptVal ctor);
|
static void Script_RegisterComponentType(void* cbdata, int iid, std::string cname, CScriptVal ctor);
|
||||||
static void Script_RegisterInterface(void* cbdata, std::string name);
|
static void Script_RegisterInterface(void* cbdata, std::string name);
|
||||||
|
static void Script_RegisterMessageType(void* cbdata, std::string name);
|
||||||
static void Script_RegisterGlobal(void* cbdata, std::string name, CScriptVal value);
|
static void Script_RegisterGlobal(void* cbdata, std::string name, CScriptVal value);
|
||||||
static IComponent* Script_QueryInterface(void* cbdata, int ent, int iid);
|
static IComponent* Script_QueryInterface(void* cbdata, int ent, int iid);
|
||||||
static void Script_PostMessage(void* cbdata, int ent, int mtid, CScriptVal data);
|
static void Script_PostMessage(void* cbdata, int ent, int mtid, CScriptVal data);
|
||||||
@ -224,6 +225,7 @@ private:
|
|||||||
static int Script_AddLocalEntity(void* cbdata, std::string templateName);
|
static int Script_AddLocalEntity(void* cbdata, std::string templateName);
|
||||||
static void Script_DestroyEntity(void* cbdata, int ent);
|
static void Script_DestroyEntity(void* cbdata, int ent);
|
||||||
|
|
||||||
|
CMessage* ConstructMessage(int mtid, CScriptVal data);
|
||||||
void SendGlobalMessage(const CMessage& msg) const;
|
void SendGlobalMessage(const CMessage& msg) const;
|
||||||
|
|
||||||
ComponentTypeId GetScriptWrapper(InterfaceId iid);
|
ComponentTypeId GetScriptWrapper(InterfaceId iid);
|
||||||
@ -242,7 +244,9 @@ private:
|
|||||||
std::map<MessageTypeId, std::vector<ComponentTypeId> > m_GlobalMessageSubscriptions;
|
std::map<MessageTypeId, std::vector<ComponentTypeId> > m_GlobalMessageSubscriptions;
|
||||||
std::map<std::string, ComponentTypeId> m_ComponentTypeIdsByName;
|
std::map<std::string, ComponentTypeId> m_ComponentTypeIdsByName;
|
||||||
std::map<std::string, MessageTypeId> m_MessageTypeIdsByName;
|
std::map<std::string, MessageTypeId> m_MessageTypeIdsByName;
|
||||||
|
std::map<MessageTypeId, std::string> m_MessageTypeNamesById;
|
||||||
std::map<std::string, InterfaceId> m_InterfaceIdsByName;
|
std::map<std::string, InterfaceId> m_InterfaceIdsByName;
|
||||||
|
|
||||||
// TODO: maintaining both ComponentsBy* is nasty; can we get rid of one,
|
// TODO: maintaining both ComponentsBy* is nasty; can we get rid of one,
|
||||||
// while keeping QueryInterface and PostMessage sufficiently efficient?
|
// while keeping QueryInterface and PostMessage sufficiently efficient?
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ protected:
|
|||||||
CMessage() { }
|
CMessage() { }
|
||||||
public:
|
public:
|
||||||
virtual ~CMessage() { }
|
virtual ~CMessage() { }
|
||||||
virtual EMessageTypeId GetType() const = 0;
|
virtual int GetType() const = 0;
|
||||||
virtual const char* GetScriptHandlerName() const = 0;
|
virtual const char* GetScriptHandlerName() const = 0;
|
||||||
virtual const char* GetScriptGlobalHandlerName() const = 0;
|
virtual const char* GetScriptGlobalHandlerName() const = 0;
|
||||||
virtual jsval ToJSVal(ScriptInterface&) const = 0;
|
virtual jsval ToJSVal(ScriptInterface&) const = 0;
|
||||||
|
@ -187,7 +187,7 @@ void ActorViewer::SetActor(const CStrW& name, const CStrW& animation)
|
|||||||
{
|
{
|
||||||
CmpPtr<ICmpUnitMotion> cmpUnitMotion(m.Simulation2, m.Entity);
|
CmpPtr<ICmpUnitMotion> cmpUnitMotion(m.Simulation2, m.Entity);
|
||||||
if (!cmpUnitMotion.null())
|
if (!cmpUnitMotion.null())
|
||||||
speed = cmpUnitMotion->GetSpeed().ToFloat();
|
speed = cmpUnitMotion->GetWalkSpeed().ToFloat();
|
||||||
else
|
else
|
||||||
speed = 7.f; // typical unit speed
|
speed = 7.f; // typical unit speed
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user