historical/toontown-classic.git/panda/direct/fsm/FourState.py

219 lines
6.8 KiB
Python
Raw Normal View History

2024-01-16 11:20:27 -06:00
"""Contains the FourState class."""
__all__ = ['FourState']
from direct.directnotify import DirectNotifyGlobal
#import DistributedObject
from . import ClassicFSM
from . import State
class FourState:
"""
Generic four state ClassicFSM base class.
This is a mix-in class that expects that your derived class
is a DistributedObject.
Inherit from FourStateFSM and pass in your states. Two of
the states should be oposites of each other and the other
two should be the transition states between the first two.
E.g::
+--------+
-->| closed | --
| +--------+ |
| |
| v
+---------+ +---------+
| closing |<----->| opening |
+---------+ +---------+
^ |
| |
| +------+ |
----| open |<---
+------+
There is a fifth off state, but that is an implementation
detail (and that's why it's not called a five state ClassicFSM).
I found that this pattern repeated in several things I was
working on, so this base class was created.
"""
notify = DirectNotifyGlobal.directNotify.newCategory('FourState')
def __init__(self, names, durations = [0, 1, None, 1, 1]):
"""
names is a list of state names
E.g.
['off', 'opening', 'open', 'closing', 'closed',]
e.g. 2:
['off', 'locking', 'locked', 'unlocking', 'unlocked',]
e.g. 3:
['off', 'deactivating', 'deactive', 'activating', 'activated',]
durations is a list of time values (floats) or None values.
Each list must have five entries.
More Details
Here is a diagram showing the where the names from the list
are used:
+---------+
| 0 (off) |----> (any other state and vice versa).
+---------+
+--------+
-->| 4 (on) |---
| +--------+ |
| |
| v
+---------+ +---------+
| 3 (off) |<----->| 1 (off) |
+---------+ +---------+
^ |
| |
| +---------+ |
--| 2 (off) |<--
+---------+
Each states also has an associated on or off value. The only
state that is 'on' is state 4. So, the transition states
between off and on (states 1 and 3) are also considered
off (and so is state 2 which is oposite of 4 and therefore
oposite of 'on').
"""
self.stateIndex = 0
assert self.__debugPrint("FourState(names=%s)"%(names))
self.track = None
self.stateTime = 0.0
self.names = names
self.durations = durations
self.states = {
0: State.State(names[0],
self.enterState0,
self.exitState0,
[names[1],
names[2],
names[3],
names[4]]),
1: State.State(names[1],
self.enterState1,
self.exitState1,
[names[2], names[3]]),
2: State.State(names[2],
self.enterState2,
self.exitState2,
[names[3]]),
3: State.State(names[3],
self.enterState3,
self.exitState3,
[names[4], names[1]]),
4: State.State(names[4],
self.enterState4,
self.exitState4,
[names[1]]),
}
self.fsm = ClassicFSM.ClassicFSM('FourState',
list(self.states.values()),
# Initial State
names[0],
# Final State
names[0],
)
self.fsm.enterInitialState()
def setTrack(self, track):
assert self.__debugPrint("setTrack(track=%s)"%(track,))
if self.track is not None:
self.track.pause()
self.track = None
if track is not None:
track.start(self.stateTime)
self.track = track
def enterStateN(self, stateIndex):
self.stateIndex = stateIndex
self.duration = self.durations[stateIndex] or 0.0
# The AI is the authority on setting the On value.
# If the client wants the state changed it needs to
# send a request to the AI.
#def setIsOn(self, isOn):
# assert self.__debugPrint("setIsOn(isOn=%s)"%(isOn,))
# pass
def isOn(self):
assert self.__debugPrint("isOn() returning %s (stateIndex=%s)"%(self.stateIndex==4, self.stateIndex))
return self.stateIndex==4
def changedOnState(self, isOn):
"""
Allow derived classes to overide this.
"""
assert self.__debugPrint("changedOnState(isOn=%s)"%(isOn,))
##### state 0 #####
def enterState0(self):
assert self.__debugPrint("enter0()")
self.enterStateN(0)
def exitState0(self):
assert self.__debugPrint("exit0()")
# It's important for FourStates to broadcast their state
# when they are generated on the client. Before I put this in,
# if a door was generated and went directly to an 'open' state,
# it would not broadcast its state until it closed.
self.changedOnState(0)
##### state 1 #####
def enterState1(self):
assert self.__debugPrint("enterState1()")
self.enterStateN(1)
def exitState1(self):
assert self.__debugPrint("exitState1()")
##### state 2 #####
def enterState2(self):
assert self.__debugPrint("enterState2()")
self.enterStateN(2)
def exitState2(self):
assert self.__debugPrint("exitState2()")
##### state 3 #####
def enterState3(self):
assert self.__debugPrint("enterState3()")
self.enterStateN(3)
def exitState3(self):
assert self.__debugPrint("exitState3()")
##### state 4 #####
def enterState4(self):
assert self.__debugPrint("enterState4()")
self.enterStateN(4)
self.changedOnState(1)
def exitState4(self):
assert self.__debugPrint("exitState4()")
self.changedOnState(0)
if __debug__:
def __debugPrint(self, message):
"""for debugging"""
return self.notify.debug("%d (%d) %s"%(
id(self), self.stateIndex==4, message))