boiii-scripts/shared/statemachine_shared.gsc
2023-04-13 17:30:38 +02:00

317 lines
18 KiB
Plaintext

#using scripts\shared\array_shared;
#namespace statemachine;
function create( name, owner, change_notify = "change_state" )
{
state_machine = SpawnStruct();
state_machine.name = name;
state_machine.states = [];
state_machine.previous_state = undefined;
state_machine.current_state = undefined;
state_machine.next_state = undefined;
state_machine.change_note = change_notify;
if ( isdefined( owner ) )
{
state_machine.owner = owner;
}
else
{
state_machine.owner = level;
}
if ( !isdefined( state_machine.owner.state_machines ) )
{
state_machine.owner.state_machines = [];
}
state_machine.owner.state_machines[ state_machine.name ] = state_machine;
return state_machine;
}
function clear()
{
if ( isdefined( self.states ) && IsArray( self.states ) )
{
foreach( state in self.states )
{
state.connections_notify = undefined;
state.connections_utility = undefined;
}
}
self.states = undefined;
self.previous_state = undefined;
self.current_state = undefined;
self.next_state = undefined;
self.owner = undefined;
self notify( "_cancel_connections" );
}
// self == state_machine
function add_state( name, enter_func, update_func, exit_func, reenter_func )
{
// Setup the state
if ( !IsDefined( self.states[ name ] ) )
{
self.states[ name ] = SpawnStruct();
}
self.states[ name ].name = name;
self.states[ name ].enter_func = enter_func;
self.states[ name ].exit_func = exit_func;
self.states[ name ].update_func = update_func;
self.states[ name ].reenter_func = reenter_func;
self.states[ name ].connections_notify = [];
self.states[ name ].connections_utility = [];
self.states[ name ].owner = self;
// Pass back so we can add connections
return self.states[ name ];
}
function get_state( name )
{
return self.states[ name ];
}
// self == state_machine
// interrupt connection: when the notify is received on the owner entity, and checkfunc passes (if defined), the connection is taken
// interrupt connection are meant to be interruptive state transition, "when X happens, do Y immediately"
// one notify string should only have one connection, otherwise the state transition becomes unpredictable.
// checkfunc will be called as: connectionValid = ownerEntity checkfunc( from_state_name, to_state_name, connection_struct, notify_params );
function add_interrupt_connection( from_state_name, to_state_name, on_notify, checkfunc )
{
from_state = get_state( from_state_name );
to_state = get_state( to_state_name );
connection = SpawnStruct();
connection.to_state = to_state;
connection.type = 0;
connection.on_notify = on_notify;
connection.checkfunc = checkfunc;
from_state.connections_notify[ on_notify ] = connection;
return from_state.connections_notify[ from_state.connections_notify.size - 1 ];
}
// self == state_machine
// utility connection: the connection only happens from evaluate_connections().
// evaluate_connections() calls checkfunc on all utility connections, collect all the ones with positive scores, and choose the highest one. if all scores are zero or negative, no connection will be taken.
// utility connection are meant to use as behavioral state transition, "what do I do next?"
// if checkfunc is defined, it will be called as: connectionScore = ownerEntity checkfunc( from_state_name, to_state_name, connection_struct );
// if checkfunc is not defined, connectionScore will be DEFAULT_CONNECTION_SCORE
function add_utility_connection( from_state_name, to_state_name, checkfunc, defaultScore )
{
from_state = get_state( from_state_name );
to_state = get_state( to_state_name );
connection = SpawnStruct();
connection.to_state = to_state;
connection.type = 1;
connection.checkfunc = checkfunc;
connection.score = defaultScore;
if ( !isdefined( connection.score ) )
{
connection.score = 100;
}
if ( !isdefined( from_state.connections_utility ) ) from_state.connections_utility = []; else if ( !IsArray( from_state.connections_utility ) ) from_state.connections_utility = array( from_state.connections_utility ); from_state.connections_utility[from_state.connections_utility.size]=connection;;
return from_state.connections_utility[ from_state.connections_utility.size - 1 ];
}
// self == state_machine
function set_state( name, state_params )
{
// Find the state
state = self.states[ name ];
if ( !isdefined( self.owner ) )
{
return false;
}
// Make sure we found a valid state
if ( !isdefined( state ) )
{
AssertMsg( "Could not find state named " + name + " in statemachine: " + self.name );
return false;
}
reenter = ( self.current_state === state );
// Run reenter function
if ( isdefined( state.reenter_func ) && reenter )
{
shouldReenter = self.owner [[ state.reenter_func ]]( state.state_params );
}
if ( reenter && shouldReenter !== true )
{
return false;
}
// If no current state then assume we are setting the first state
if ( isdefined( self.current_state ) )
{
self.next_state = state;
// Run the exit function for previous state
if ( isdefined( self.current_state.exit_func ) )
{
self.owner [[ self.current_state.exit_func ]]( self.current_state.state_params );
}
if ( !reenter )
{
self.previous_state = self.current_state;
}
self.current_state.state_params = undefined;
}
if ( !isdefined( state_params ) )
{
state_params = SpawnStruct();
}
state.state_params = state_params;
// End any currently running update threads
self.owner notify( self.change_note );
// All checks passed switch states
self.current_state = state;
self threadNotifyConnections( self.current_state );
// Run the enter function for the new state
if ( isdefined( self.current_state.enter_func ) )
{
self.owner [[ self.current_state.enter_func ]]( self.current_state.state_params );
}
// Finally...thread the update function for the current state
if ( isdefined( self.current_state.update_func ) )
{
self.owner thread [[ self.current_state.update_func ]]( self.current_state.state_params );
}
return true;
}
function threadNotifyConnections( state )
{
self notify( "_cancel_connections" );
// Thread any notify connections
foreach( connection in state.connections_notify )
{
assert( connection.type == 0 );
self.owner thread connection_on_notify( self, connection.on_notify, connection );
}
}
function connection_on_notify( state_machine, notify_name, connection )
{
self endon( state_machine.change_note );
state_machine endon( "_cancel_connections" );
while ( 1 )
{
self waittill( notify_name, param0, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15 );
params = SpawnStruct();
params.notify_param = [];
if ( !isdefined( params.notify_param ) ) params.notify_param = []; else if ( !IsArray( params.notify_param ) ) params.notify_param = array( params.notify_param ); params.notify_param[params.notify_param.size]=param0;;
if ( !isdefined( params.notify_param ) ) params.notify_param = []; else if ( !IsArray( params.notify_param ) ) params.notify_param = array( params.notify_param ); params.notify_param[params.notify_param.size]=param1;;
if ( !isdefined( params.notify_param ) ) params.notify_param = []; else if ( !IsArray( params.notify_param ) ) params.notify_param = array( params.notify_param ); params.notify_param[params.notify_param.size]=param2;;
if ( !isdefined( params.notify_param ) ) params.notify_param = []; else if ( !IsArray( params.notify_param ) ) params.notify_param = array( params.notify_param ); params.notify_param[params.notify_param.size]=param3;;
if ( !isdefined( params.notify_param ) ) params.notify_param = []; else if ( !IsArray( params.notify_param ) ) params.notify_param = array( params.notify_param ); params.notify_param[params.notify_param.size]=param4;;
if ( !isdefined( params.notify_param ) ) params.notify_param = []; else if ( !IsArray( params.notify_param ) ) params.notify_param = array( params.notify_param ); params.notify_param[params.notify_param.size]=param5;;
if ( !isdefined( params.notify_param ) ) params.notify_param = []; else if ( !IsArray( params.notify_param ) ) params.notify_param = array( params.notify_param ); params.notify_param[params.notify_param.size]=param6;;
if ( !isdefined( params.notify_param ) ) params.notify_param = []; else if ( !IsArray( params.notify_param ) ) params.notify_param = array( params.notify_param ); params.notify_param[params.notify_param.size]=param7;;
if ( !isdefined( params.notify_param ) ) params.notify_param = []; else if ( !IsArray( params.notify_param ) ) params.notify_param = array( params.notify_param ); params.notify_param[params.notify_param.size]=param8;;
if ( !isdefined( params.notify_param ) ) params.notify_param = []; else if ( !IsArray( params.notify_param ) ) params.notify_param = array( params.notify_param ); params.notify_param[params.notify_param.size]=param9;;
if ( !isdefined( params.notify_param ) ) params.notify_param = []; else if ( !IsArray( params.notify_param ) ) params.notify_param = array( params.notify_param ); params.notify_param[params.notify_param.size]=param10;;
if ( !isdefined( params.notify_param ) ) params.notify_param = []; else if ( !IsArray( params.notify_param ) ) params.notify_param = array( params.notify_param ); params.notify_param[params.notify_param.size]=param11;;
if ( !isdefined( params.notify_param ) ) params.notify_param = []; else if ( !IsArray( params.notify_param ) ) params.notify_param = array( params.notify_param ); params.notify_param[params.notify_param.size]=param12;;
if ( !isdefined( params.notify_param ) ) params.notify_param = []; else if ( !IsArray( params.notify_param ) ) params.notify_param = array( params.notify_param ); params.notify_param[params.notify_param.size]=param13;;
if ( !isdefined( params.notify_param ) ) params.notify_param = []; else if ( !IsArray( params.notify_param ) ) params.notify_param = array( params.notify_param ); params.notify_param[params.notify_param.size]=param14;;
if ( !isdefined( params.notify_param ) ) params.notify_param = []; else if ( !IsArray( params.notify_param ) ) params.notify_param = array( params.notify_param ); params.notify_param[params.notify_param.size]=param15;;
connectionValid = true;
if ( isdefined( connection.checkfunc ) )
{
connectionValid = self [[connection.checkfunc]]( self.current_state, connection.to_state.name, connection, params );
}
if ( connectionValid )
{
state_machine thread set_state( connection.to_state.name, params );
}
}
}
// self == statemachine
// the responsibility to call evaluate_connections() function is on current state's update function
// if function eval_func is not defined, connection with highest positive connectionScore will be taken
// if function eval_func is defined, it will be called as: bestConnection = ownerEntity [[eval_func]]( connectionArray, scoreArray, state ); it's the eval_func responsibility to choose and return a connection. undefined means no connection will be taken.
function evaluate_connections( eval_func, params )
{
assert( isdefined( self.current_state ) );
connectionArray = [];
scoreArray = [];
best_connection = undefined;
best_score = -1;
foreach ( connection in self.current_state.connections_utility )
{
assert( connection.type == 1 );
score = connection.score;
if ( isdefined( connection.checkfunc ) )
{
score = self.owner [[ connection.checkfunc ]]( self.current_state.name, connection.to_state.name, connection );
}
if ( score > 0 )
{
if ( !isdefined( connectionArray ) ) connectionArray = []; else if ( !IsArray( connectionArray ) ) connectionArray = array( connectionArray ); connectionArray[connectionArray.size]=connection;;
if ( !isdefined( scoreArray ) ) scoreArray = []; else if ( !IsArray( scoreArray ) ) scoreArray = array( scoreArray ); scoreArray[scoreArray.size]=score;;
if ( score > best_score )
{
best_connection = connection;
best_score = score;
}
}
}
if ( isdefined( eval_func ) && connectionArray.size > 0 )
{
best_connection = self.owner [[eval_func]]( connectionArray, scoreArray, self.current_state );
}
if ( isdefined( best_connection ) )
{
self thread set_state( best_connection.to_state.name, params );
}
}
function debugOn()
{
dvarVal = GetDvarInt( "statemachine_debug" );
return dvarVal;
}