317 lines
18 KiB
Plaintext
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;
|
|
}
|
|
|