‪Black Ops 3 Source Code Explorer  0.1
‪An script explorer for Black Ops 3 by ZeRoY
statemachine_shared.gsc
Go to the documentation of this file.
1 
2 #using scripts\shared\array_shared;
3 
4 #insert scripts\shared\shared.gsh;
5 #insert scripts\shared\statemachine.gsh;
6 
7 #namespace statemachine;
8 
9 function ‪create( ‪name, owner, change_notify = "change_state" )
10 {
11  state_machine = SpawnStruct();
12  state_machine.name = ‪name;
13  state_machine.states = [];
14  state_machine.previous_state = undefined;
15  state_machine.current_state = undefined;
16  state_machine.next_state = undefined;
17  state_machine.change_note = change_notify;
18 
19  if ( isdefined( owner ) )
20  {
21  state_machine.owner = owner;
22  }
23  else
24  {
25  state_machine.owner = level;
26  }
27 
28  if ( !isdefined( state_machine.owner.state_machines ) )
29  {
30  state_machine.owner.state_machines = [];
31  }
32 
33  state_machine.owner.state_machines[ state_machine.name ] = state_machine;
34 
35  return state_machine;
36 }
37 
38 function ‪clear()
39 {
40  if ( isdefined( self.states ) && IsArray( self.states ) )
41  {
42  foreach( state in self.states )
43  {
44  state.connections_notify = undefined;
45  state.connections_utility = undefined;
46  }
47  }
48 
49  self.states = undefined;
50  self.previous_state = undefined;
51  self.current_state = undefined;
52  self.next_state = undefined;
53  self.owner = undefined;
54 
55  self notify( "_cancel_connections" );
56 }
57 
58 // self == state_machine
59 function ‪add_state( ‪name, enter_func, update_func, exit_func, reenter_func )
60 {
61  // Setup the state
62  if ( !IsDefined( self.states[ ‪name ] ) )
63  {
64  self.states[ ‪name ] = SpawnStruct();
65  }
66 
67  self.states[ ‪name ].name = ‪name;
68  self.states[ ‪name ].enter_func = enter_func;
69  self.states[ ‪name ].exit_func = exit_func;
70  self.states[ ‪name ].update_func = update_func;
71  self.states[ ‪name ].reenter_func = reenter_func;
72  self.states[ ‪name ].connections_notify = [];
73  self.states[ ‪name ].connections_utility = [];
74  self.states[ ‪name ].owner = self;
75 
76  // Pass back so we can add connections
77  return self.states[ ‪name ];
78 }
79 
80 function ‪get_state( ‪name )
81 {
82  return self.states[ ‪name ];
83 }
84 
85 // self == state_machine
86 // interrupt connection: when the notify is received on the owner entity, and checkfunc passes (if defined), the connection is taken
87 // interrupt connection are meant to be interruptive state transition, "when X happens, do Y immediately"
88 // one notify string should only have one connection, otherwise the state transition becomes unpredictable.
89 // checkfunc will be called as: connectionValid = ownerEntity checkfunc( from_state_name, to_state_name, connection_struct, notify_params );
90 function ‪add_interrupt_connection( from_state_name, to_state_name, on_notify, checkfunc )
91 {
92  from_state = ‪get_state( from_state_name );
93  to_state = ‪get_state( to_state_name );
94 
95  connection = SpawnStruct();
96  connection.to_state = to_state;
97  connection.type = ‪CONNECTION_TYPE_NOTIFY;
98  connection.on_notify = on_notify;
99  connection.checkfunc = checkfunc;
100 
101  from_state.connections_notify[ on_notify ] = connection;
102 
103  return from_state.connections_notify[ from_state.connections_notify.size - 1 ];
104 }
105 
106 // self == state_machine
107 // utility connection: the connection only happens from evaluate_connections().
108 // 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.
109 // utility connection are meant to use as behavioral state transition, "what do I do next?"
110 // if checkfunc is defined, it will be called as: connectionScore = ownerEntity checkfunc( from_state_name, to_state_name, connection_struct );
111 // if checkfunc is not defined, connectionScore will be DEFAULT_CONNECTION_SCORE
112 function ‪add_utility_connection( from_state_name, to_state_name, checkfunc, defaultScore )
113 {
114  from_state = ‪get_state( from_state_name );
115  to_state = ‪get_state( to_state_name );
116 
117  connection = SpawnStruct();
118  connection.to_state = to_state;
119  connection.type = ‪CONNECTION_TYPE_UTILITY;
120  connection.checkfunc = checkfunc;
121  connection.score = defaultScore;
122  if ( !isdefined( connection.score ) )
123  {
124  connection.score = ‪DEFAULT_CONNECTION_SCORE;
125  }
126 
127  ‪ARRAY_ADD( from_state.connections_utility, connection );
128 
129  return from_state.connections_utility[ from_state.connections_utility.size - 1 ];
130 }
131 
132 // self == state_machine
133 function ‪set_state( ‪name, state_params )
134 {
135  // Find the state
136  state = self.states[ ‪name ];
137 
138  if ( !isdefined( self.owner ) )
139  {
140  return false;
141  }
142 
143  // Make sure we found a valid state
144  if ( !isdefined( state ) )
145  {
146  AssertMsg( "Could not find state named " + ‪name + " in statemachine: " + self.‪name );
147  return false;
148  }
149 
150  reenter = ( self.current_state === state );
151 
152  // Run reenter function
153  if ( isdefined( state.reenter_func ) && reenter )
154  {
155  shouldReenter = self.owner [[ state.reenter_func ]]( state.state_params );
156  }
157 
158  if ( reenter && shouldReenter !== true )
159  {
160  return false;
161  }
162 
163  // If no current state then assume we are setting the first state
164  if ( isdefined( self.current_state ) )
165  {
166  self.next_state = state;
167 
168  // Run the exit function for previous state
169  if ( isdefined( self.current_state.exit_func ) )
170  {
171  self.owner [[ self.current_state.exit_func ]]( self.current_state.state_params );
172  }
173 
174  if ( !reenter )
175  {
176  self.previous_state = self.current_state;
177  }
178  self.current_state.state_params = undefined;
179  }
180 
181  if ( !isdefined( state_params ) )
182  {
183  state_params = SpawnStruct();
184  }
185  state.state_params = state_params;
186 
187  // End any currently running update threads
188  self.owner notify( self.change_note );
189 
190  // All checks passed switch states
191  self.current_state = state;
192 
193  self ‪threadNotifyConnections( self.current_state );
194 
195  // Run the enter function for the new state
196  if ( isdefined( self.current_state.enter_func ) )
197  {
198  self.owner [[ self.current_state.enter_func ]]( self.current_state.state_params );
199  }
200 
201  // Finally...thread the update function for the current state
202  if ( isdefined( self.current_state.update_func ) )
203  {
204  self.owner thread [[ self.current_state.update_func ]]( self.current_state.state_params );
205  }
206 
207  return true;
208 }
209 
210 function ‪threadNotifyConnections( state )
211 {
212  self notify( "_cancel_connections" );
213 
214  // Thread any notify connections
215  foreach( connection in state.connections_notify )
216  {
217  assert( connection.type == ‪CONNECTION_TYPE_NOTIFY );
218  self.owner thread ‪connection_on_notify( self, connection.on_notify, connection );
219  }
220 }
221 
222 function ‪connection_on_notify( state_machine, notify_name, connection )
223 {
224  self endon( state_machine.change_note );
225  state_machine endon( "_cancel_connections" );
226 
227  while ( 1 )
228  {
229  self waittill( notify_name, param0, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15 );
230  params = SpawnStruct();
231  params.notify_param = [];
232  ‪ARRAY_ADD( params.notify_param, param0 );
233  ‪ARRAY_ADD( params.notify_param, param1 );
234  ‪ARRAY_ADD( params.notify_param, param2 );
235  ‪ARRAY_ADD( params.notify_param, param3 );
236  ‪ARRAY_ADD( params.notify_param, param4 );
237  ‪ARRAY_ADD( params.notify_param, param5 );
238  ‪ARRAY_ADD( params.notify_param, param6 );
239  ‪ARRAY_ADD( params.notify_param, param7 );
240  ‪ARRAY_ADD( params.notify_param, param8 );
241  ‪ARRAY_ADD( params.notify_param, param9 );
242  ‪ARRAY_ADD( params.notify_param, param10 );
243  ‪ARRAY_ADD( params.notify_param, param11 );
244  ‪ARRAY_ADD( params.notify_param, param12 );
245  ‪ARRAY_ADD( params.notify_param, param13 );
246  ‪ARRAY_ADD( params.notify_param, param14 );
247  ‪ARRAY_ADD( params.notify_param, param15 );
248 
249  connectionValid = true;
250  if ( isdefined( connection.checkfunc ) )
251  {
252  connectionValid = self [[connection.checkfunc]]( self.current_state, connection.to_state.name, connection, params );
253  }
254 
255  if ( connectionValid )
256  {
257  state_machine thread ‪set_state( connection.to_state.name, params );
258  }
259  }
260 }
261 
262 // self == statemachine
263 // the responsibility to call evaluate_connections() function is on current state's update function
264 // if function eval_func is not defined, connection with highest positive connectionScore will be taken
265 // 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.
266 function ‪evaluate_connections( eval_func, params )
267 {
268  assert( isdefined( self.current_state ) );
269 
270  connectionArray = [];
271  scoreArray = [];
272 
273  best_connection = undefined;
274  best_score = -1;
275 
276  foreach ( connection in self.current_state.connections_utility )
277  {
278  assert( connection.type == ‪CONNECTION_TYPE_UTILITY );
279 
280  score = connection.score;
281  if ( isdefined( connection.checkfunc ) )
282  {
283  score = self.owner [[ connection.checkfunc ]]( self.current_state.name, connection.to_state.name, connection );
284  }
285 
286  if ( score > 0 )
287  {
288  ‪ARRAY_ADD( connectionArray, connection );
289  ‪ARRAY_ADD( scoreArray, score );
290 
291  if ( score > best_score )
292  {
293  best_connection = connection;
294  best_score = score;
295  }
296  }
297  }
298 
299  if ( isdefined( eval_func ) && connectionArray.size > 0 )
300  {
301  best_connection = self.owner [[eval_func]]( connectionArray, scoreArray, self.current_state );
302  }
303 
304  if ( isdefined( best_connection ) )
305  {
306  self thread ‪set_state( best_connection.to_state.name, params );
307  }
308 }
309 
310 function ‪debugOn()
311 {
312  dvarVal = GetDvarInt( "statemachine_debug" );
313 
314  return dvarVal;
315 }
316 
‪add_interrupt_connection
‪function add_interrupt_connection(from_state_name, to_state_name, on_notify, checkfunc)
Definition: statemachine_shared.gsc:90
‪CONNECTION_TYPE_UTILITY
‪#define CONNECTION_TYPE_UTILITY
Definition: statemachine.gsh:3
‪add_state
‪function add_state(name, enter_func, update_func, exit_func, reenter_func)
Definition: statemachine_shared.gsc:59
‪set_state
‪function set_state(name, state_params)
Definition: statemachine_shared.gsc:133
‪evaluate_connections
‪function evaluate_connections(eval_func, params)
Definition: statemachine_shared.gsc:266
‪DEFAULT_CONNECTION_SCORE
‪#define DEFAULT_CONNECTION_SCORE
Definition: statemachine.gsh:5
‪connection_on_notify
‪function connection_on_notify(state_machine, notify_name, connection)
Definition: statemachine_shared.gsc:222
‪clear
‪function clear()
Definition: statemachine_shared.gsc:38
‪get_state
‪function get_state(name)
Definition: statemachine_shared.gsc:80
‪ARRAY_ADD
‪#define ARRAY_ADD(__array, __item)
Definition: shared.gsh:304
‪threadNotifyConnections
‪function threadNotifyConnections(state)
Definition: statemachine_shared.gsc:210
‪create
‪function create(name, owner, change_notify="change_state")
Definition: statemachine_shared.gsc:9
‪add_utility_connection
‪function add_utility_connection(from_state_name, to_state_name, checkfunc, defaultScore)
Definition: statemachine_shared.gsc:112
‪debugOn
‪function debugOn()
Definition: statemachine_shared.gsc:310
‪CONNECTION_TYPE_NOTIFY
‪#define CONNECTION_TYPE_NOTIFY
Definition: statemachine.gsh:2
‪name
‪class GroundFx name