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

363 lines
15 KiB
Plaintext

// Created and Implemented by Sumeet Jakatdar
#namespace BehaviorStateMachine;
// ------------- Behavior State Machine Script API registration ----------- //
function RegisterBSMScriptAPIInternal( functionName, scriptFunction )
{
if ( !IsDefined( level._bsmscriptfunctions ) )
{
level._bsmscriptfunctions = [];
}
// SUMEET TODO - remove the need of ToLower and have the functionNames defined in .gsh
functionName = ToLower( functionName );
Assert( IsDefined( scriptFunction ) && IsDefined( scriptFunction ), "BT - (RegisterBSMScriptAPI) No functionPtr defined or no functionName defined for BSMScript." );
Assert( !IsDefined( level._bsmscriptfunctions[functionName] ), "BT - (RegisterBSMScriptAPI) functionName is already defined for BSMScript." );
level._bsmscriptfunctions[functionName] = scriptFunction;
}
/*
class BehaviorState
{
var stateName;
var asmStateName;
var initFunc;
var updateFunc;
var terminateFunc;
var connections;
constructor()
{
connections = [];
}
destructor()
{
}
function GetConnections()
{
return connections;
}
}
class BehaviorConnection
{
var connectionName;
var toState;
var conditionFunc;
var endOfStateConnection;
var asmState;
constructor()
{
toState = NULL;
}
destructor()
{
}
}
class BSM
{
var stateMachineName;
var states;
var miscData;
var currentState;
var previousState;
constructor()
{
states = [];
}
destructor()
{
}
function AddState( stateName, asmStateName, initFunc, updateFunc, terminateFunc )
{
assert( !IsDefined( states[stateName] ), "BSM - State is already defined " + stateName );
states[stateName] = new BehaviorState();
states[stateName].initFunc = initFunc;
states[stateName].updateFunc = updateFunc;
states[stateName].terminateFunc = terminateFunc;
states[stateName].asmStateName = asmStateName;
states[stateName].stateName = stateName;
}
function AddConnection( connectionName, fromState, toState, conditionFunc, endOfStateConnection, asmState )
{
assert( IsDefined( states[fromState] ), "BSM - State is not defined " + fromState );
assert( IsDefined( states[toState] ), "BSM - State is not defined " + toState );
assert( !IsDefined( states[fromState].connections[connectionName] ), "BSM - Connection is already defined " + connectionName );
states[fromState].connections[connectionName] = new BehaviorConnection();
states[fromState].connections[connectionName].toState = toState;
states[fromState].connections[connectionName].conditionFunc = conditionFunc;
states[fromState].connections[connectionName].connectionName = connectionName;
states[fromState].connections[connectionName].endOfStateConnection = endOfStateConnection;
states[fromState].connections[connectionName].asmState = asmState;
}
function GetStates()
{
return states;
}
}
function CreateBSM( archetypeName, stateMachineName )
{
if( !IsDefined( level.__BSMs ) )
{
level.__BSMs = [];
}
if( !IsDefined( level.__BSMs[archetypeName] ) )
{
level.__BSMs[archetypeName] = [];
}
assert( !IsDefined( level.__BSMs[archetypeName][stateMachineName] ) );
newStateMachine = new BSM();
newStateMachine.stateMachineName = stateMachineName;
level.__BSMs[archetypeName][stateMachineName] = newStateMachine;
return level.__BSMs[archetypeName][stateMachineName];
}
function StartBSM( entity, stateMachineName )
{
assert( IsDefined( level.__BSMs[entity.archetype][stateMachineName] ) );
stateMachine = level.__BSMs[entity.archetype][stateMachineName];
assert( !IsDefined( stateMachine.currentState ) );
assert( !IsDefined( stateMachine.previousState ) );
// go through the states, find the first fit
foreach ( state in stateMachine.states )
{
if( !IsDefined( state.initFunc ) )
{
if( IsDefined( state.asmStateName ) )
{
newState = state;
break;
}
}
else
{
validState = [[state.initFunc]]( entity );
if( validState )
{
// found one state which has a valid ASM, set it as a current substate
if( IsDefined( state.asmStateName ) )
{
newState = state;
break;
}
}
}
}
if( IsDefined( newState ) )
{
stateMachine.currentState = newState;
AnimationStateNetworkUtility::RequestState( entity, newState.asmStateName );
return BHTN_RUNNING;
}
return BHTN_SUCCESS;
}
function UpdateBSM( entity, stateMachineName )
{
assert( IsDefined( level.__BSMs[entity.archetype][stateMachineName] ) );
stateMachine = level.__BSMs[entity.archetype][stateMachineName];
assert( IsDefined( stateMachine.currentState ) );
// run the update function for the current state
currentStateUpdatedResult = true;
if( IsDefined( stateMachine.currentState.updateFunc ) )
{
currentStateUpdatedResult = [[stateMachine.currentState.updateFunc]]( entity );
}
// We have a valid running state, go through all the connections ( if there are any )
connections = [[stateMachine.currentState]]->GetConnections();
if( !connections.size )
{
if( entity ASMGetStatus() == ASM_STATE_COMPLETE )
{
TerminateBSM( entity, stateMachineName );
return BHTN_SUCCESS;
}
}
else
{
nextValidState = undefined;
foreach ( connection in connections )
{
// execute endOfStateConnection only when current state is complete or is not valid anymore
if( IS_TRUE( connection.endOfStateConnection )
&& ( entity ASMGetStatus() != ASM_STATE_COMPLETE || !currentStateUpdatedResult )
)
{
/#RecordEntText( "EndOfStateConnection :" + connection.connectionName, entity, RED, "Animscript" );#/
continue;
}
if( IsDefined( connection.conditionFunc ) )
{
// check if the connection is valid
validConnection = [[connection.conditionFunc]]( entity );
if( !IsDefined( validConnection ) )
validConnection = true;
// Connection is valid, check if the toState has any condition associated with it, and execute it
if( validConnection )
{
/#RecordEntText( "Connection :" + connection.connectionName, entity, GREEN, "Animscript" );#/
if( !IsDefined( stateMachine.states[connection.toState].initFunc ) )
{
nextValidState = stateMachine.states[connection.toState];
break;
}
else
{
validToState = [[stateMachine.states[connection.toState].initFunc]]( entity );
if( validToState )
{
nextValidState = stateMachine.states[connection.toState];
break;
}
else
{
/#RecordEntText( "ToState :" + stateMachine.states[connection.toState].stateName, entity, RED, "Animscript" );#/
}
}
}
else
{
/#RecordEntText( "Connection :" + connection.connectionName, entity, RED, "Animscript" );#/
}
}
else
{
/#RecordEntText( "Connection :" + connection.connectionName, entity, GREEN, "Animscript" );#/
if( !IsDefined( stateMachine.states[connection.toState].initFunc ) )
{
nextValidState = stateMachine.states[connection.toState];
break;
}
else
{
validToState = [[stateMachine.states[connection.toState].initFunc]]( entity );
if( validToState )
{
nextValidState = stateMachine.states[connection.toState];
break;
}
else
{
/#RecordEntText( "ToState :" + stateMachine.states[connection.toState].stateName, entity, RED, "Animscript" );#/
}
}
}
}
// found a validNextState with ValidConnection, go there now
if( IsDefined( nextValidState ) )
{
// found a new valid state, execute terminateFunc of the current state
if( IsDefined( stateMachine.currentState.terminateFunc ) )
{
[[stateMachine.currentState.terminateFunc]]( entity );
}
/#RecordEntText( "ToState :" + stateMachine.states[connection.toState].stateName, entity, GREEN, "Animscript" );#/
stateMachine.previousState = stateMachine.currentState;
stateMachine.currentState = nextValidState;
assert( IsDefined( nextValidState.asmStateName ) );
AnimationStateNetworkUtility::RequestState( entity, nextValidState.asmStateName );
return BHTN_RUNNING;
}
else
{
/#RecordEntText( "ToState :" + stateMachine.states[connection.toState].stateName, entity, RED, "Animscript" );#/
}
}
// if there is no better connection, just update the current substate
if( entity ASMGetStatus() == ASM_STATE_COMPLETE )
{
TerminateBSM( entity, stateMachineName );
return BHTN_SUCCESS;
}
else
{
/#RecordEntText( "CurrentState :" + stateMachine.currentState.stateName, entity, BLUE, "Animscript" );#/
}
return BHTN_RUNNING;
}
function TerminateBSM( entity, stateMachineName )
{
assert( IsDefined( level.__BSMs[entity.archetype][stateMachineName] ) );
stateMachine = level.__BSMs[entity.archetype][stateMachineName];
// state machine is terminating and returning control to behavior tree, call terminateFunc of the current state
if( IsDefined( stateMachine.currentState ) && IsDefined( stateMachine.currentState.terminateFunc ) )
{
[[stateMachine.currentState.terminateFunc]]( entity );
}
stateMachine.currentState = undefined;
stateMachine.previousState = undefined;
return BHTN_SUCCESS;
}
*/