-
Notifications
You must be signed in to change notification settings - Fork 148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
State machine system #389
State machine system #389
Changes from 3 commits
509b0cf
aae332c
b63898f
f45489d
ba3d396
50d17ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
x\cba\addons\statemachine |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
class Extended_PreStart_EventHandlers { | ||
class ADDON { | ||
init = QUOTE(call COMPILE_FILE(XEH_preStart)); | ||
}; | ||
}; | ||
|
||
class Extended_PreInit_EventHandlers { | ||
class ADDON { | ||
init = QUOTE(call COMPILE_FILE(XEH_preInit)); | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
PREP(addState); | ||
PREP(addTransition); | ||
PREP(clockwork); | ||
PREP(create); | ||
PREP(createFromConfig); | ||
PREP(delete); | ||
PREP(toString); | ||
PREP(updateList); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#include "script_component.hpp" | ||
|
||
ADDON = false; | ||
|
||
#include "XEH_PREP.hpp" | ||
|
||
ADDON = true; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#include "script_component.hpp" | ||
|
||
#include "XEH_PREP.hpp" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
#include "script_component.hpp" | ||
|
||
class CfgPatches { | ||
class ADDON { | ||
author = "$STR_CBA_Author"; | ||
name = CSTRING(component); | ||
url = "$STR_CBA_URL"; | ||
units[] = {}; | ||
requiredVersion = REQUIRED_VERSION; | ||
requiredAddons[] = {"CBA_common"}; | ||
version = VERSION; | ||
authors[] = {"BaerMitUmlaut"}; | ||
}; | ||
}; | ||
|
||
#include "CfgEventHandlers.hpp" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* | ||
This is an example for a CBA state machine config that can be read by the | ||
CBA_statemachine_fnc_createFromConfig function. | ||
This example would result in the same state machine as the one from | ||
example.sqf. | ||
*/ | ||
|
||
class MyAddon_Statemachine { | ||
// Class properties have the same name as the corresponding function parameters | ||
// and code goes into strings. | ||
list = "allGroups select {!isPlayer leader _x}"; | ||
|
||
// States are just subclasses of the state machine | ||
class Initial { | ||
onState = ""; | ||
onStateEntered = ""; | ||
onStateLeaving = ""; | ||
|
||
// Transitions are also just subclasses of states | ||
class InCombat { | ||
targetState = "Alert"; | ||
condition = "combatMode _this == 'YELLOW'"; | ||
onTransition = "{ \ | ||
_x setSkill ['spotDistance', ((_x skill 'spotDistance') * 1.5) min 1]; \ | ||
_x setSkill ['spotTime', ((_x skill 'spotTime') * 1.5) min 1]; \ | ||
} forEach (units _this);"; | ||
}; | ||
}; | ||
|
||
// Empty classes will also work if the state contains no transitions or onState code. | ||
class Alert {}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/* | ||
This is an example for a script that uses the CBA state machine system. | ||
In this case, we're "sharpening the senses" of AI units that got attacked before. | ||
Once they had contact, they stay alert. | ||
|
||
This would simulate units that did not expect an attack, but are now aware that | ||
they might come under fire again. | ||
|
||
You can test this example by executing the following code in the debug console: | ||
[] call compile preprocessFileLineNumbers "\x\cba\addons\statemachine\example.sqf" | ||
*/ | ||
|
||
private _stateMachine = [{allGroups select {!isPlayer leader _x}}] call CBA_statemachine_fnc_create; | ||
|
||
[_stateMachine, {}, {}, {}, "Initial"] call CBA_statemachine_fnc_addState; | ||
[_stateMachine, {}, {}, {}, "Alert"] call CBA_statemachine_fnc_addState; | ||
|
||
[_stateMachine, "Initial", "Alert", {combatMode _this == "YELLOW"}, { | ||
// Set skill once on transition | ||
// This could also be done in the onStateEntered function | ||
{ | ||
_x setSkill ["spotDistance", ((_x skill "spotDistance") * 1.5) min 1]; | ||
_x setSkill ["spotTime", ((_x skill "spotTime") * 1.5) min 1]; | ||
} forEach (units _this); | ||
}, "InCombat"] call CBA_statemachine_fnc_addTransition; | ||
|
||
// This makes sure you can execute this through the debug console | ||
_stateMachine spawn { | ||
sleep 0.1; | ||
private _output = [_this, true, true] call CBA_statemachine_fnc_toString; | ||
copyToClipboard _output; | ||
hintC _output; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/* ---------------------------------------------------------------------------- | ||
Function: CBA_statemachine_fnc_addState | ||
|
||
Description: | ||
Adds a state to a state machine. | ||
|
||
Parameters: | ||
_stateMachine - a state machine <LOCATION> | ||
_onState - code that is executed when state is active (frequency | ||
depends on amount of objects active in state machine) | ||
<CODE> | ||
(Default: {}) | ||
_onStateEntered - code that is executed once when state was entered, after | ||
onTransition (also once for the intial state) <CODE> | ||
(Default: {}) | ||
_onStateLeaving - code that is executed once when exiting state, before | ||
onTransition <CODE> | ||
(Default: {}) | ||
_name - unique state name <STRING> | ||
(Default: "stateX" with X being a unique number) | ||
|
||
Returns: | ||
_name - unique state name or empty string on error <STRING> | ||
|
||
Examples: | ||
(begin example) | ||
_name = [_stateMachine, {}] call CBA_statemachine_fnc_addState; | ||
(end) | ||
|
||
Author: | ||
BaerMitUmlaut | ||
---------------------------------------------------------------------------- */ | ||
#include "script_component.hpp" | ||
SCRIPT(addState); | ||
params [ | ||
["_stateMachine", locationNull, [locationNull]], | ||
["_onState", {}, [{}]], | ||
["_onStateEntered", {}, [{}]], | ||
["_onStateLeaving", {}, [{}]], | ||
["_name", "", [""]] | ||
]; | ||
|
||
private _states = _stateMachine getVariable QGVAR(states); | ||
|
||
if (isNull _stateMachine || {_name in _states}) exitWith {""}; | ||
|
||
// Autogenerate unique name | ||
if (_name == "") then { | ||
private _nextUniqueID = _stateMachine getVariable QGVAR(nextUniqueStateID); | ||
_name = "state" + str _nextUniqueID; | ||
_stateMachine setVariable [QGVAR(nextUniqueStateID), _nextUniqueID + 1]; | ||
}; | ||
|
||
_states pushBack _name; | ||
_stateMachine setVariable [QGVAR(states), _states]; | ||
_stateMachine setVariable [ONSTATE(_name), _onState]; | ||
_stateMachine setVariable [ONSTATEENTERED(_name), _onStateEntered]; | ||
_stateMachine setVariable [ONSTATELEAVING(_name), _onStateLeaving]; | ||
_stateMachine setVariable [TRANSITIONS(_name), []]; | ||
|
||
diag_log "derp"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ? |
||
|
||
// First state added is always the intial state | ||
if (isNil {_stateMachine getVariable QGVAR(initialState)}) then { | ||
_stateMachine setVariable [QGVAR(initialState), _name]; | ||
diag_log "initial state set"; | ||
}; | ||
|
||
_name |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/* ---------------------------------------------------------------------------- | ||
Function: CBA_statemachine_fnc_addTransition | ||
|
||
Description: | ||
Creates a transition between two states. | ||
|
||
Parameters: | ||
_stateMachine - a state machine <LOCATION> | ||
_originalState - state the transition origins from <STRING> | ||
_targetState - state the transition goes to <STRING> | ||
_condition - condition under which the transition will happen <CODE> | ||
_onTransition - code that gets executed once transition happens <CODE> | ||
(Default: {}) | ||
_name - name for this specific transition <STRING> | ||
(Default: "NONAME") | ||
|
||
Returns: | ||
_wasCreated - check if the transition was created <BOOL> | ||
|
||
Examples: | ||
(begin example) | ||
[_stateMachine, "initial", "end", {true}, { | ||
systemChat format [ | ||
"%1 transitioned from %2 to %3 via %4.", | ||
_this, _thisOrigin, _thisTarget, _thisTransition | ||
]; | ||
}, "dummyTransition"] call CBA_statemachine_fnc_addTransition; | ||
(end) | ||
|
||
Author: | ||
BaerMitUmlaut | ||
---------------------------------------------------------------------------- */ | ||
#include "script_component.hpp" | ||
SCRIPT(addTransition); | ||
params [ | ||
["_stateMachine", locationNull, [locationNull]], | ||
["_originalState", "", [""]], | ||
["_targetState", "", [""]], | ||
["_condition", {}, [{}]], | ||
["_onTransition", {}, [{}]], | ||
["_name", "NONAME", [""]] | ||
]; | ||
|
||
private _states = _stateMachine getVariable QGVAR(states); | ||
|
||
if (isNull _stateMachine | ||
|| {!(_originalState in _states)} | ||
|| {!(_targetState in _states)} | ||
|| {_condition isEqualTo {}} | ||
) exitWith {false}; | ||
|
||
private _transitions = _stateMachine getVariable TRANSITIONS(_originalState); | ||
_transitions pushBack [_name, _condition, _targetState, _onTransition]; | ||
_stateMachine setVariable [TRANSITIONS(_originalState), _transitions]; | ||
|
||
true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
/* ---------------------------------------------------------------------------- | ||
Function: CBA_statemachine_fnc_clockwork | ||
|
||
Description: | ||
Clockwork which runs all state machines. | ||
|
||
Parameters: | ||
None | ||
|
||
Returns: | ||
Nothing | ||
|
||
Author: | ||
BaerMitUmlaut | ||
---------------------------------------------------------------------------- */ | ||
#include "script_component.hpp" | ||
SCRIPT(clockwork); | ||
|
||
{ | ||
private _stateMachine = _x; | ||
private _list = _stateMachine getVariable QGVAR(list); | ||
private _updateCode = _stateMachine getVariable QGVAR(updateCode); | ||
private _id = _stateMachine getVariable QGVAR(ID); | ||
|
||
// Skip state machine when it has no states yet | ||
if !(isNil {_stateMachine getVariable QGVAR(initialState)}) then { | ||
private _tick = _stateMachine getVariable QGVAR(tick); | ||
|
||
// When the list was iterated through, jump back to start and update it | ||
if (_tick >= count _list) then { | ||
_tick = 0; | ||
if !(_updateCode isEqualTo {}) then { | ||
_list = [] call _updateCode; | ||
_stateMachine setVariable [QGVAR(list), _list]; | ||
}; | ||
}; | ||
|
||
// If the list has no items, we can stop checking this state machine | ||
// No need to set the tick when it will get reset next frame anyways | ||
if (count _list > 0) then { | ||
_stateMachine setVariable [QGVAR(tick), _tick + 1]; | ||
|
||
private _current = _list select _tick; | ||
private _thisState = _current getVariable (QGVAR(state) + str _id); | ||
|
||
if (isNil "_thisState") then { | ||
// Item is new and gets set to the intial state, onStateEntered | ||
// function of initial state gets executed as well. | ||
_thisState = _stateMachine getVariable QGVAR(initialState); | ||
_current setVariable [QGVAR(state) + str _id, _thisState]; | ||
_current call (_stateMachine getVariable ONSTATEENTERED(_thisState)); | ||
}; | ||
|
||
// onState functions can use: | ||
// _stateMachine - the state machine | ||
// _this - the current list item | ||
// _thisState - the current state | ||
_current call (_stateMachine getVariable ONSTATE(_thisState)); | ||
|
||
private _thisOrigin = _thisState; | ||
{ | ||
_x params ["_thisTransition", "_condition", "_thisTarget", "_onTransition"]; | ||
// Transition conditions, onTransition, onStateLeaving and | ||
// onStateEntered functions can use: | ||
// _stateMachine - the state machine | ||
// _this - the current list item | ||
// _thisTransition - the current transition we're in | ||
// _thisOrigin - the state we're coming from | ||
// _thisState - same as _thisOrigin | ||
// _thisTarget - the state we're transitioning to | ||
// Note: onTransition and onStateLeaving functions can change | ||
// the transition target by overwriting the passed | ||
// _thisTarget variable. | ||
// Note: onStateEntered functions of initial states won't have | ||
// some of these variables defined. | ||
if (_current call _condition) exitWith { | ||
_current call (_stateMachine getVariable ONSTATELEAVING(_thisOrigin)); | ||
_current call _onTransition; | ||
_current setVariable [QGVAR(state) + str _id, _thisTarget]; | ||
_current call (_stateMachine getVariable ONSTATEENTERED(_thisTarget)); | ||
}; | ||
} forEach (_stateMachine getVariable TRANSITIONS(_thisState)); | ||
}; | ||
}; | ||
|
||
false | ||
} count GVAR(stateMachines); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/* ---------------------------------------------------------------------------- | ||
Function: CBA_statemachine_fnc_create | ||
|
||
Description: | ||
Creates a state machine. | ||
|
||
Parameters: | ||
_list - list of anything over which the state machine will run | ||
(type needs to support setVariable) <ARRAY> | ||
OR | ||
code that will generate this list, called once the list | ||
has been cycled through <CODE> | ||
|
||
Returns: | ||
_stateMachine - a state machine <LOCATION> | ||
|
||
Examples: | ||
(begin example) | ||
_stateMachine = call CBA_statemachine_fnc_create; | ||
(end) | ||
|
||
Author: | ||
BaerMitUmlaut | ||
---------------------------------------------------------------------------- */ | ||
#include "script_component.hpp" | ||
SCRIPT(create); | ||
params [["_list", [], [[], {}]]]; | ||
|
||
if (isNil QGVAR(stateMachines)) then { | ||
GVAR(stateMachines) = []; | ||
GVAR(nextUniqueID) = 0; | ||
}; | ||
|
||
private _updateCode = {}; | ||
if (typeName _list == "CODE") then { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pls |
||
_updateCode = _list; | ||
_list = [] call _updateCode; | ||
}; | ||
|
||
private _stateMachine = call CBA_fnc_createNamespace; | ||
_stateMachine setVariable [QGVAR(nextUniqueStateID), 0]; // Unique ID for autogenerated state names | ||
_stateMachine setVariable [QGVAR(tick), 0]; // List index ticker | ||
_stateMachine setVariable [QGVAR(states), []]; // State machine states | ||
_stateMachine setVariable [QGVAR(list), _list]; // List state machine iterates over | ||
_stateMachine setVariable [QGVAR(updateCode), _updateCode]; // List update code | ||
_stateMachine setVariable [QGVAR(ID), GVAR(nextUniqueID)]; // Unique state machine ID | ||
INC(GVAR(nextUniqueID)); | ||
GVAR(stateMachines) pushBack _stateMachine; | ||
|
||
if (isNil QGVAR(pfh)) then { | ||
GVAR(pfh) = [FUNC(clockwork), 0, []] call CBA_fnc_addPerFrameHandler; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. optional parameter to set this value for you fsm? |
||
}; | ||
|
||
_stateMachine |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Entered" vs "Leaving". I think you should probably conyugate those two consistently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's on purpose, it follows an event naming convention from Microsoft. The point is that it tells you already exactly when this happens:
More info: https://msdn.microsoft.com/de-de/library/h0eyck3s(v=vs.71).aspx
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very Microsoft