forked from electronicarts/CnC_Generals_Zero_Hour
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathStateMachine.h
More file actions
483 lines (400 loc) · 18.2 KB
/
StateMachine.h
File metadata and controls
483 lines (400 loc) · 18.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
/*
** Command & Conquer Generals(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// StateMachine.h
// Finite state machine encapsulation
// Author: Michael S. Booth, January 2002
#pragma once
#include "Common/GameMemory.h"
#include "Common/GameType.h"
#include "Common/ModelState.h"
#include "Common/Snapshot.h"
#include "Common/Xfer.h"
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
class State;
class StateMachine;
class Object;
//#undef STATE_MACHINE_DEBUG
#if defined(RTS_DEBUG)
#define STATE_MACHINE_DEBUG
#endif
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
enum { MACHINE_DONE_STATE_ID = 999998, INVALID_STATE_ID = 999999 };
typedef UnsignedInt StateID; ///< used to denote individual states
typedef Bool (*StateTransFuncPtr)( State *state, void* userData );
/**
* State return codes
*/
enum StateReturnType CPP_11(: Int)
{
// note that all positive values are reserved for STATE_SLEEP!
STATE_CONTINUE = 0, ///< stay in this state (only for update method)
STATE_SUCCESS = -1, ///< state finished successfully, go to next state
STATE_FAILURE = -2, ///< state finished abnormally, go to next state
};
#define STATE_SLEEP(numFrames) ((StateReturnType)(numFrames))
#define IS_STATE_SLEEP(ret) ((Int)(ret) > 0)
#define GET_STATE_SLEEP_FRAMES(ret) ((UnsignedInt)(ret))
// (we use 0x3fffffff so that we can add offsets and not overflow...
// at 30fps that's around ~414 days!)
#define STATE_SLEEP_FOREVER STATE_SLEEP(0x3fffffff)
// this is mainly useful for states that enclose other state machines...
// where, even if the enclosed machine is sleeping, the encloser still needs
// to run every frame.
inline StateReturnType CONVERT_SLEEP_TO_CONTINUE(StateReturnType s)
{
return IS_STATE_SLEEP(s) ? STATE_CONTINUE : s;
}
// this is mainly useful for states that enclose other state machines...
// where the encloser and enclosee both might sleep. we need to choose the min
// sleep time that satisfies both.
inline StateReturnType MIN_SLEEP(UnsignedInt encloserSleep, StateReturnType encloseeResult)
{
if (IS_STATE_SLEEP(encloseeResult))
{
UnsignedInt encloseeSleep = GET_STATE_SLEEP_FRAMES(encloseeResult);
return STATE_SLEEP(min(encloserSleep, encloseeSleep));
}
else
{
// if enclosee needs to stay awake, we better do so, regardless
return encloseeResult;
}
}
/**
* Special argument for onCondition. It means when the given condition
* becomes true, the state machine will exit and return the given status.
*/
enum
{
EXIT_MACHINE_WITH_SUCCESS = 9998,
EXIT_MACHINE_WITH_FAILURE = 9999
};
/**
* Parameters for onExit().
*/
enum StateExitType CPP_11(: Int)
{
EXIT_NORMAL, ///< state exited due to normal state transitioning
EXIT_RESET ///< state exited due to state machine reset
};
struct StateConditionInfo
{
StateTransFuncPtr test;
StateID toStateID;
void* userData;
StateConditionInfo(StateTransFuncPtr t, StateID id, void* ud) : test(t), toStateID(id), userData(ud) { }
};
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
/**
* An abstraction of a machine's "state".
*/
class State : public MemoryPoolObject, public Snapshot
{
MEMORY_POOL_GLUE_ABC( State ) ///< this abstract class needs memory pool hooks
public:
State( StateMachine *machine, AsciiString name);
// already defined by MPO.
//virtual ~State() { }
virtual StateReturnType onEnter() { return STATE_CONTINUE; } ///< executed once when entering state
virtual void onExit( StateExitType status ) { } ///< executed once when leaving state
virtual StateReturnType update() = 0; ///< implements this state's behavior, decides when to change state
virtual Bool isIdle() const { return false; }
virtual Bool isAttack() const { return false; }
//Definition of busy -- when explicitly in the busy state. Moving or attacking is not considered busy!
virtual Bool isBusy() const { return false; }
inline StateMachine* getMachine() { return m_machine; } ///< return the machine this state is part of
inline StateID getID() const { return m_ID; } ///< get this state's id
Object* getMachineOwner();
const Object* getMachineOwner() const;
Object* getMachineGoalObject();
const Object* getMachineGoalObject() const;
const Coord3D* getMachineGoalPosition() const;
#ifdef STATE_MACHINE_DEBUG
virtual AsciiString getName() const {return m_name;}
std::vector<StateID> *getTransitions(void);
#endif
// for internal use by the StateMachine class ---------------------------------------------------------
inline void friend_setID( StateID id ) { m_ID = id; } ///< define this state's id (for use only by StateMachine class)
void friend_onSuccess( StateID toStateID ) { m_successStateID = toStateID; } ///< define which state to move to after successful completion
void friend_onFailure( StateID toStateID ) { m_failureStateID = toStateID; } ///< define which state to move to after failure
void friend_onCondition( StateTransFuncPtr test, StateID toStateID, void* userData, const char* description = NULL ); ///< define when to change state
StateReturnType friend_checkForTransitions( StateReturnType status ); ///< given a return code, handle state transitions
StateReturnType friend_checkForSleepTransitions( StateReturnType status ); ///< given a return code, handle state transitions
protected:
#ifdef STATE_MACHINE_DEBUG
inline void setName(AsciiString n) { m_name = n; }
#endif
protected:
// snapshot interface - pure virtual here.
// Essentially all the member data gets set up on creation and shouldn't change.
// So none of it needs to be saved, and it nicely forces all user states to
// remember to implement crc, xfer & loadPostProcess. jba
virtual void crc( Xfer *xfer )=0;
virtual void xfer( Xfer *xfer )=0;
virtual void loadPostProcess()=0;
private:
struct TransitionInfo
{
StateTransFuncPtr test; ///< the condition evaluation function
StateID toStateID; ///< the state to transition to
void* userData; ///< data passed to transFuncPtr.
#ifdef STATE_MACHINE_DEBUG
const char* description; ///< description (for debugging purposes)
#endif
TransitionInfo(StateTransFuncPtr t, StateID id, void* ud, const char* desc) :
test(t),
toStateID(id),
userData(ud)
#ifdef STATE_MACHINE_DEBUG
, description(desc)
#endif
{ }
};
StateID m_ID; ///< this state's ID
StateID m_successStateID; ///< state to move to upon success
StateID m_failureStateID; ///< state to move to upon failure
std::vector<TransitionInfo> m_transitions; ///< possible transitions from this state
StateMachine *m_machine; ///< the state machine this state is part of
protected:
#ifdef STATE_MACHINE_DEBUG
AsciiString m_name; ///< Human readable name of this state - for debugging. jba.
#endif
};
inline State::~State() { }
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
/**
* A finite state machine.
*/
class StateMachine : public MemoryPoolObject, public Snapshot
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( StateMachine, "StateMachinePool" );
public:
/**
* All of the states used by this machine should be
* instantiated and defined via defineState() in the
* machine's constructor.
*/
StateMachine( Object *owner, AsciiString name );
// virtual destructor defined by MemoryPool
virtual StateReturnType updateStateMachine(); ///< run one step of the machine
virtual void clear(); ///< clear the machine's internals to a known, initialized state
virtual StateReturnType resetToDefaultState(); ///< clear the machine's internals and set to the default state
/** must be called to jump the StateMachine into the default state... this may fail
(return a failure code), so it can't be done via the ctor. you MUST call this before
using the state machine. */
virtual StateReturnType initDefaultState();
virtual StateReturnType setState( StateID newStateID ); ///< change the current state of the machine (which may cause further state changes, due to onEnter)
void Add_Ref() const { m_refCount.Add_Ref(); }
void Release_Ref() const { m_refCount.Release_Ref(MemoryPoolObject::deleteInstanceInternal, this); }
UnsignedByte Num_Refs() const { return m_refCount.Num_Refs(); }
StateID getCurrentStateID() const { return m_currentState ? m_currentState->getID() : INVALID_STATE_ID; } ///< return the id of the current state of the machine
Bool isInIdleState() const { return m_currentState ? m_currentState->isIdle() : true; } // stateless things are considered 'idle'
Bool isInAttackState() const { return m_currentState ? m_currentState->isAttack() : true; } // stateless things are considered 'idle'
Bool isInForceAttackState() const { return m_currentState ? m_currentState->isIdle() : true; } // stateless things are considered 'idle'
//Definition of busy -- when explicitly in the busy state. Moving or attacking is not considered busy!
Bool isInBusyState() const { return m_currentState ? m_currentState->isBusy() : false; } // stateless things are not considered 'busy'
// no, this is now deprecated. you should strive to avoid having to get the current state.
// try to make do with getCurrentStateID() or isInIdleState() instead. (srj)
// State *getCurrentState() { return m_currentState; } ///< return the current state of the machine
/**
* Lock/unlock this state machine.
* If a machine is locked, it cannot be reset, or given external setStates(), etc.
*/
void lock(const char* msg)
{
m_locked = true;
#ifdef STATE_MACHINE_DEBUG
m_lockedby = msg;
#endif
}
void unlock()
{
m_locked = false;
#ifdef STATE_MACHINE_DEBUG
m_lockedby = NULL;
#endif
}
Bool isLocked() const { return m_locked; }
/**
* Get the object that "owns" this machine.
* There need not be an object with a machine, but it is so common
* that it is useful to have this method in the generic state machine.
*/
Object *getOwner() { return m_owner; }
const Object *getOwner() const { return m_owner; }
// common parameters for state machines
void setGoalObject( const Object *obj );
Object *getGoalObject();
const Object *getGoalObject() const;
void setGoalPosition( const Coord3D *pos );
const Coord3D *getGoalPosition() const { return &m_goalPosition; }
Bool isGoalObjectDestroyed() const; ///< Returns true if we had a goal object, but it has been destroyed.
virtual void halt(void); ///< Stops the state machine & disables it in preparation for deleting it.
//
// The following methods are for internal use by the State class
//
StateReturnType internalSetState( StateID newStateID ); ///< for internal use only - change the current state of the machine
#if defined(RTS_DEBUG)
UnsignedInt peekSleepTill() const { return m_sleepTill; }
#endif
#ifdef STATE_MACHINE_DEBUG
Bool getWantsDebugOutput() const;
void setDebugOutput( Bool output ) { m_debugOutput = output; }
void setName( AsciiString name) {m_name = name;}
inline AsciiString getName() const {return m_name;}
virtual AsciiString getCurrentStateName() const { return m_currentState ? m_currentState->getName() : AsciiString::TheEmptyString;}
#else
inline Bool getWantsDebugOutput() const { return false; }
inline AsciiString getCurrentStateName() const { return AsciiString::TheEmptyString;}
#endif
protected:
// snapshot interface
virtual void crc( Xfer *xfer );
virtual void xfer( Xfer *xfer );
virtual void loadPostProcess();
protected:
/**
* Given a unique (to this machine) integer ID representing a state, and an instance
* of that state, the machine records this as a possible state, and
* internally maps the given integer ID to the state instance.
* These state id's are used to change the machine's state via setState().
*/
void defineState( StateID id, State *state,
StateID successID,
StateID failureID,
const StateConditionInfo* conditions = NULL);
State* internalGetState( StateID id );
private:
void internalClear();
void internalSetGoalObject( const Object *obj );
void internalSetGoalPosition( const Coord3D *pos);
std::map<StateID, State *> m_stateMap; ///< the mapping of ids to states
Object* m_owner; ///< object that "owns" this machine
UnsignedInt m_sleepTill; ///< if nonzero, we are sleeping 'till this frame
StateID m_defaultStateID; ///< the default state of the machine
State* m_currentState;
ObjectID m_goalObjectID; ///< the object of interest for this state
Coord3D m_goalPosition; ///< the position of interest for this state
Bool m_locked; ///< whether this machine is locked or not
Bool m_defaultStateInited; ///< if initDefaultState has been called
RefCountValue<UnsignedByte> m_refCount;
#ifdef STATE_MACHINE_DEBUG
Bool m_debugOutput;
AsciiString m_name; ///< Human readable name of this state - for debugging. jba.
const char* m_lockedby;
#endif
};
//-----------------------------------------------------------------------------
inline Object* State::getMachineOwner() { return m_machine->getOwner(); }
inline const Object* State::getMachineOwner() const { return m_machine->getOwner(); }
inline Object* State::getMachineGoalObject() { return m_machine->getGoalObject(); } ///< return the machine this state is part of
inline const Object* State::getMachineGoalObject() const { return m_machine->getGoalObject(); } ///< return the machine this state is part of
inline const Coord3D* State::getMachineGoalPosition() const { return m_machine->getGoalPosition(); } ///< return the machine this state is part of
//-----------------------------------------------------------------------------------------------------------
/**
A utility state that immediately succeeds.
*/
class SuccessState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(SuccessState, "SuccessState")
public:
SuccessState( StateMachine *machine ) : State( machine, "SuccessState") { }
virtual StateReturnType onEnter() { return STATE_SUCCESS; }
virtual StateReturnType update() { return STATE_SUCCESS; }
protected:
// snapshot interface STUBBED.
virtual void crc( Xfer *xfer ){};
virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );}
virtual void loadPostProcess(){};
};
EMPTY_DTOR(SuccessState)
//-----------------------------------------------------------------------------------------------------------
/**
A utility state that immediately fails.
*/
class FailureState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(FailureState, "FailureState")
public:
FailureState( StateMachine *machine ) : State( machine, "FailureState") { }
virtual StateReturnType onEnter() { return STATE_FAILURE; }
virtual StateReturnType update() { return STATE_FAILURE; }
protected:
// snapshot interface STUBBED.
virtual void crc( Xfer *xfer ){};
virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );}
virtual void loadPostProcess(){};
};
EMPTY_DTOR(FailureState)
//-----------------------------------------------------------------------------------------------------------
/**
A utility state that never exits (except due to conditions).
*/
class ContinueState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ContinueState, "ContinueState")
public:
ContinueState( StateMachine *machine ) : State( machine, "ContinueState" ) { }
virtual StateReturnType onEnter() { return STATE_CONTINUE; }
virtual StateReturnType update() { return STATE_CONTINUE; }
protected:
// snapshot interface STUBBED.
virtual void crc( Xfer *xfer ){};
virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );}
virtual void loadPostProcess(){};
};
EMPTY_DTOR(ContinueState)
//-----------------------------------------------------------------------------------------------------------
/**
A utility state that sleeps forever.
*/
class SleepState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(SleepState, "SleepState")
public:
SleepState( StateMachine *machine ) : State( machine, "SleepState" ) { }
virtual StateReturnType onEnter() { return STATE_SLEEP_FOREVER; }
virtual StateReturnType update() { return STATE_SLEEP_FOREVER; }
protected:
// snapshot interface STUBBED.
virtual void crc( Xfer *xfer ){};
virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );}
virtual void loadPostProcess(){};
};
EMPTY_DTOR(SleepState)
//-----------------------------------------------------------------------------------------------------------
// TheSuperHackers @info Misappropriates deleteInstance to call Release_Ref to keep the StateMachine fix small.
// @todo Replace calls to deleteInstance with RefCountPtr<StateMachine> when so appropriate.
inline void deleteInstance(StateMachine* machine)
{
if (machine != NULL)
machine->Release_Ref();
}