From 2f7bf6db016215a86eac85294aae62bc69b5e64d Mon Sep 17 00:00:00 2001 From: BaerMitUmlaut Date: Fri, 30 Sep 2016 17:22:34 +0200 Subject: [PATCH] Added event transitions, manual transitions, current state getter, simplified config state machines --- addons/statemachine/XEH_PREP.hpp | 3 + addons/statemachine/example.hpp | 6 ++ addons/statemachine/example.sqf | 7 ++ .../statemachine/fnc_addEventTransition.sqf | 88 +++++++++++++++++++ addons/statemachine/fnc_addState.sqf | 1 + addons/statemachine/fnc_createFromConfig.sqf | 17 ++-- addons/statemachine/fnc_getCurrentState.sqf | 30 +++++++ addons/statemachine/fnc_manualTransition.sqf | 49 +++++++++++ addons/statemachine/fnc_toString.sqf | 18 +++- addons/statemachine/script_component.hpp | 7 ++ 10 files changed, 219 insertions(+), 7 deletions(-) create mode 100644 addons/statemachine/fnc_addEventTransition.sqf create mode 100644 addons/statemachine/fnc_getCurrentState.sqf create mode 100644 addons/statemachine/fnc_manualTransition.sqf diff --git a/addons/statemachine/XEH_PREP.hpp b/addons/statemachine/XEH_PREP.hpp index 83affde3a..f435d3a7b 100644 --- a/addons/statemachine/XEH_PREP.hpp +++ b/addons/statemachine/XEH_PREP.hpp @@ -1,8 +1,11 @@ +PREP(addEventTransition); PREP(addState); PREP(addTransition); PREP(clockwork); PREP(create); PREP(createFromConfig); PREP(delete); +PREP(getCurrentState); +PREP(manualTransition); PREP(toString); PREP(updateList); diff --git a/addons/statemachine/example.hpp b/addons/statemachine/example.hpp index 7e8b09a65..f61c6fb47 100644 --- a/addons/statemachine/example.hpp +++ b/addons/statemachine/example.hpp @@ -26,6 +26,12 @@ class MyAddon_Statemachine { _x setSkill ['spotTime', ((_x skill 'spotTime') * 1.5) min 1]; \ } forEach (units _this);"; }; + + // Event transitions get triggered by CBA events + class Alarm: InCombat { + events[] = {"MyAddon_AlarmRaised"}; + condition = "true"; + }; }; // Empty classes will also work if the state contains no transitions or onState code. diff --git a/addons/statemachine/example.sqf b/addons/statemachine/example.sqf index 20d9e13a9..0250187b6 100644 --- a/addons/statemachine/example.sqf +++ b/addons/statemachine/example.sqf @@ -24,6 +24,13 @@ private _stateMachine = [{allGroups select {!isPlayer leader _x}}, true] call CB } forEach (units _this); }, "InCombat"] call CBA_statemachine_fnc_addTransition; +[_stateMachine, "Initial", "Alert", ["MyAddon_AlarmRaised"], {true}, { + { + _x setSkill ["spotDistance", ((_x skill "spotDistance") * 1.5) min 1]; + _x setSkill ["spotTime", ((_x skill "spotTime") * 1.5) min 1]; + } forEach (units _this); +}, "Alarm"] call CBA_statemachine_fnc_addEventTransition; + // This makes sure you can execute this through the debug console _stateMachine spawn { sleep 0.1; diff --git a/addons/statemachine/fnc_addEventTransition.sqf b/addons/statemachine/fnc_addEventTransition.sqf new file mode 100644 index 000000000..4887795f7 --- /dev/null +++ b/addons/statemachine/fnc_addEventTransition.sqf @@ -0,0 +1,88 @@ +/* ---------------------------------------------------------------------------- +Function: CBA_statemachine_fnc_addEventTransition + +Description: + Creates a transition between two states. + +Parameters: + _stateMachine - a state machine + _originalState - state the transition origins from + _targetState - state the transition goes to + _events - list of events that can trigger the transition + _condition - additional condition required for the transition to + trigger + _onTransition - code that gets executed once transition happens + (Default: {}) + _name - name for this specific transition + (Default: "NONAME") + +Returns: + _wasCreated - check if the transition was created + +Examples: + (begin example) + [_stateMachine, "initial", "end", ["end_statemachine"], {true}, { + systemChat format [ + "%1 transitioned from %2 to %3 via %4.", + _this, _thisOrigin, _thisTarget, _thisTransition + ]; + }, "dummyTransition"] call CBA_statemachine_fnc_addEventTransition; + (end) + +Author: + BaerMitUmlaut +---------------------------------------------------------------------------- */ +#include "script_component.hpp" +SCRIPT(addEventTransition); +params [ + ["_stateMachine", locationNull, [locationNull]], + ["_originalState", "", [""]], + ["_targetState", "", [""]], + ["_events", [], [[]]], + ["_condition", {}, [{}, ""]], + ["_onTransition", {}, [{}]], + ["_name", "NONAME", [""]] +]; + +private _states = _stateMachine getVariable QGVAR(states); + +if (isNull _stateMachine + || {!(_originalState in _states)} + || {!(_targetState in _states)} + || {!(_events isEqualTypeAll "")} +) exitWith {false}; + +if (_condition isEqualTo {}) then { + _condition = {true}; +}; + +{ + [_x, { + params ["_listItem"]; + // The condition needs to be able to access these variables + _thisArgs params [ + "_condition", + "_stateMachine", + "_thisOrigin", + "_thisTarget", + "", + "_thisTransition" + ]; + private _thisState = _thisOrigin; + + if (([_listItem, _stateMachine] call FUNC(getCurrentState)) != _thisState) exitWith {}; + + if (_listItem call _condition) then { + // Replace condition with listItem for params + private _args =+ _thisArgs; + _args set [0, _listItem]; + _args call FUNC(manualTransition); + }; + }, [_condition, _stateMachine, _originalState, _targetState, _onTransition, _name]] call CBA_fnc_addEventHandlerArgs; +} forEach _events; + +private _eventTransitions = _stateMachine getVariable EVENTTRANSITIONS(_originalState); +_eventTransitions pushBack [_name, _events, _condition, _targetState, _onTransition]; +_stateMachine setVariable [EVENTTRANSITIONS(_originalState), _eventTransitions]; + +true diff --git a/addons/statemachine/fnc_addState.sqf b/addons/statemachine/fnc_addState.sqf index 1311d78e8..b6770e1d0 100644 --- a/addons/statemachine/fnc_addState.sqf +++ b/addons/statemachine/fnc_addState.sqf @@ -57,6 +57,7 @@ _stateMachine setVariable [ONSTATE(_name), _onState]; _stateMachine setVariable [ONSTATEENTERED(_name), _onStateEntered]; _stateMachine setVariable [ONSTATELEAVING(_name), _onStateLeaving]; _stateMachine setVariable [TRANSITIONS(_name), []]; +_stateMachine setVariable [EVENTTRANSITIONS(_name), []]; // First state added is always the intial state if (isNil {_stateMachine getVariable QGVAR(initialState)}) then { diff --git a/addons/statemachine/fnc_createFromConfig.sqf b/addons/statemachine/fnc_createFromConfig.sqf index b88a0f426..db35b98b5 100644 --- a/addons/statemachine/fnc_createFromConfig.sqf +++ b/addons/statemachine/fnc_createFromConfig.sqf @@ -32,9 +32,9 @@ private _stateMachine = [_list, _skipNull] call FUNC(create); { private _state = configName _x; - private _onState = compile getText (_x >> "onState"); - private _onStateEntered = compile getText (_x >> "onStateEntered"); - private _onStateLeaving = compile getText (_x >> "onStateLeaving"); + GET_FUNCTION(_onState,_x >> "onState"); + GET_FUNCTION(_onStateEntered,_x >> "onStateEntered"); + GET_FUNCTION(_onStateLeaving,_x >> "onStateLeaving"); [_stateMachine, _onState, _onStateEntered, _onStateLeaving, _state] call FUNC(addState); false @@ -46,10 +46,15 @@ private _stateMachine = [_list, _skipNull] call FUNC(create); { private _transition = configName _x; private _targetState = getText (_x >> "targetState"); - private _condition = compile getText (_x >> "condition"); - private _onTransition = compile getText (_x >> "onTransition"); + GET_FUNCTION(_condition,_x >> "condition"); + GET_FUNCTION(_onTransition,_x >> "onTransition"); + private _events = getArray (_x >> "events"); - [_stateMachine, _state, _targetState, _condition, _onTransition, _transition] call FUNC(addTransition); + if (_events isEqualTo []) then { + [_stateMachine, _state, _targetState, _condition, _onTransition, _transition] call FUNC(addTransition); + } else { + [_stateMachine, _state, _targetState, _events, _condition, _onTransition, _transition] call FUNC(addEventTransition); + }; false } count (configProperties [_x, "isClass _x", true]); diff --git a/addons/statemachine/fnc_getCurrentState.sqf b/addons/statemachine/fnc_getCurrentState.sqf new file mode 100644 index 000000000..60ba93d75 --- /dev/null +++ b/addons/statemachine/fnc_getCurrentState.sqf @@ -0,0 +1,30 @@ +/* ---------------------------------------------------------------------------- +Function: CBA_statemachine_fnc_getCurrentState + +Description: + Manually triggers a transition. + +Parameters: + _listItem - item to get the state of + _stateMachine - state machine + +Returns: + _currentState - state of the given item + +Examples: + (begin example) + _currentState = [player, _stateMachine] call CBA_statemachine_fnc_getCurrentState; + (end) + +Author: + BaerMitUmlaut +---------------------------------------------------------------------------- */ +#include "script_component.hpp" +SCRIPT(getCurrentState); +params [ + ["_listItem", objNull, [missionNamespace, objNull, grpNull, teamMemberNull, taskNull, locationNull]], + ["_stateMachine", locationNull, [locationNull]] +]; + +private _id = _stateMachine getVariable QGVAR(ID); +[_listItem getVariable (QGVAR(state) + str _id)] param [0, _stateMachine getVariable QGVAR(initialState)]; diff --git a/addons/statemachine/fnc_manualTransition.sqf b/addons/statemachine/fnc_manualTransition.sqf new file mode 100644 index 000000000..9fd232dfe --- /dev/null +++ b/addons/statemachine/fnc_manualTransition.sqf @@ -0,0 +1,49 @@ +/* ---------------------------------------------------------------------------- +Function: CBA_statemachine_fnc_manualTransition + +Description: + Manually triggers a transition. + +Parameters: + _listItem - the item which should transition + _stateMachine - a state machine + _thisOrigin - state the transition origins from + _thisTarget - state the transition goes to + _onTransition - code that gets executed once transition happens + (Default: {}) + _thisTransition - name for this specific transition + (Default: "MANUAL") + +Returns: + Nothing + +Examples: + (begin example) + [_stateMachine, "initial", "end", { + systemChat format [ + "%1 transitioned from %2 to %3 manually.", + _this, _thisOrigin, _thisTarget + ]; + }, "dummyTransition"] call CBA_statemachine_fnc_manualTransition; + (end) + +Author: + BaerMitUmlaut +---------------------------------------------------------------------------- */ +#include "script_component.hpp" +SCRIPT(manualTransition); +params [ + ["_listItem", objNull, [missionNamespace, objNull, grpNull, teamMemberNull, taskNull, locationNull]], + ["_stateMachine", locationNull, [locationNull]], + ["_thisOrigin", "", [""]], + ["_thisTarget", "", [""]], + ["_onTransition", {}, [{}]], + ["_thisTransition", "MANUAL", [""]] +]; +private _thisState = _thisOrigin; +private _id = _stateMachine getVariable QGVAR(ID); + +_listItem call (_stateMachine getVariable ONSTATELEAVING(_thisOrigin)); +_listItem call _onTransition; +_listItem setVariable [QGVAR(state) + str _id, _thisTarget]; +_listItem call (_stateMachine getVariable ONSTATEENTERED(_thisTarget)); diff --git a/addons/statemachine/fnc_toString.sqf b/addons/statemachine/fnc_toString.sqf index d5a8b64fa..845a00f1b 100644 --- a/addons/statemachine/fnc_toString.sqf +++ b/addons/statemachine/fnc_toString.sqf @@ -59,10 +59,11 @@ _output = _output + "States: " + _nli; } else { _output = _output + _x + _nli; }; + { _x params ["_name", "_condition", "_targetState", "_onTransition"]; if (_outputCode) then { - _output = _output + " " + format ["Transition %1:%2", _name, _nli3]; + _output = _output + " " + format ["Transition %1:%2", _name, _nli3]; _output = _output + "Condition: " + str _condition + _nli3; _output = _output + "Target: " + _targetState + _nli3; _output = _output + "onTransition: " + str _onTransition + _nli; @@ -73,6 +74,21 @@ _output = _output + "States: " + _nli; false } count (_stateMachine getVariable TRANSITIONS(_x)); + { + _x params ["_name", "_events", "_condition", "_targetState", "_onTransition"]; + if (_outputCode) then { + _output = _output + " " + format ["Event transition %1:%2", _name, _nli3]; + _output = _output + "Events: " + (_events joinString ", ") + _nli3; + _output = _output + "Condition: " + str _condition + _nli3; + _output = _output + "Target: " + _targetState + _nli3; + _output = _output + "onTransition: " + str _onTransition + _nli; + } else { + _output = _output + " " + format ["%1 -> %2%3", _name, _targetState, _nli]; + }; + + false + } count (_stateMachine getVariable EVENTTRANSITIONS(_x)); + false } count (_stateMachine getVariable QGVAR(states)); diff --git a/addons/statemachine/script_component.hpp b/addons/statemachine/script_component.hpp index 9886f5ab0..887f9cc19 100644 --- a/addons/statemachine/script_component.hpp +++ b/addons/statemachine/script_component.hpp @@ -14,6 +14,13 @@ #include "\x\cba\addons\main\script_macros.hpp" #define TRANSITIONS(var) (var + "_transitions") +#define EVENTTRANSITIONS(var) (var + "_eventTransitions") #define ONSTATE(var) (var + "_onState") #define ONSTATEENTERED(var) (var + "_onStateEntered") #define ONSTATELEAVING(var) (var + "_onStateLeaving") +#define GET_FUNCTION(var,cfg) private var = getText (cfg); \ + if (isNil var) then { \ + var = compile var; \ + } else { \ + var = missionNamespace getVariable var;\ + }