hostile-takeover/game/StateMachine.cpp
2016-01-03 23:19:26 -08:00

555 lines
13 KiB
C++

#include "game/ht.h"
#include "game/stateframe.h"
namespace wi {
// Portions Copyright (C) Steve Rabin, 2000
// StateMachine methods
StateMachine::StateMachine()
{
m_fForceStateChange = false;
m_st = m_stNext = kstReservedGlobal;
#ifdef DEBUG_HELPERS
m_fDebug = false;
#endif
}
int StateMachine::ProcessStateMachineMessage(State st, Message *pmsg)
{
return knHandled;
}
// StateMachineMgr methods
StateMachineMgr::StateMachineMgr()
{
m_pdmsgHead = NULL;
}
StateMachineMgr::~StateMachineMgr()
{
ClearDelayedMessages();
}
bool StateMachineMgr::Init(TimerMgr *ptimm)
{
m_ptimm = ptimm;
ClearDelayedMessages();
return true;
}
#define knVerStateMachineMgrState 1
bool StateMachineMgr::LoadState(Stream *pstm)
{
// Version check
byte nVer = pstm->ReadByte();
if (nVer != knVerStateMachineMgrState)
return false;
// Read msg count
int cmsgs = pstm->ReadWord();
// Read in messages, restore delivery time
long tCurrent = m_ptimm->GetTickCount();
while (cmsgs-- != 0) {
Message msg;
if (pstm->Read(&msg, sizeof(msg)) == 0)
return false;
dword ctDelivery = pstm->ReadDword();
msg.tDelivery = (int)(tCurrent + ctDelivery);
StoreDelayedMessage(&msg);
}
return pstm->IsSuccess();
}
bool StateMachineMgr::SaveState(Stream *pstm)
{
// Save version
pstm->WriteByte(knVerStateMachineMgrState);
// Save count of messages
int cmsgs = 0;
DelayedMessage *pdmsg;
for (pdmsg = m_pdmsgHead; pdmsg != NULL; pdmsg = pdmsg->pdmsgNext)
cmsgs++;
pstm->WriteWord(cmsgs);
// Save messages and time delta remaining for each message.
long tCurrent = m_ptimm->GetTickCount();
for (pdmsg = m_pdmsgHead; pdmsg != NULL; pdmsg = pdmsg->pdmsgNext) {
pstm->Write(&pdmsg->msg, sizeof(pdmsg->msg));
pstm->WriteDword((int)(pdmsg->msg.tDelivery - tCurrent));
}
return pstm->IsSuccess();
}
void StateMachineMgr::ClearDelayedMessages()
{
DelayedMessage *pdmsg = m_pdmsgHead;
while (pdmsg != NULL) {
DelayedMessage *pdmsgNext = pdmsg->pdmsgNext;
delete pdmsg;
pdmsg = pdmsgNext;
}
m_pdmsgHead = NULL;
}
void StateMachineMgr::SendMsg(Message *pmsg)
{
pmsg->tDelivery = 0;
RouteMessage(pmsg);
}
void StateMachineMgr::SendMsg(MessageId mid, StateMachineId smidReceiver)
{
Message msg;
memset(&msg, 0, sizeof(msg));
msg.mid = mid;
msg.smidReceiver = smidReceiver;
msg.smidSender = ksmidNull;
RouteMessage(&msg);
}
void StateMachineMgr::SendMsg(MessageId mid, StateMachineId smidSender, StateMachineId smidReceiver)
{
Message msg;
memset(&msg, 0, sizeof(msg));
msg.mid = mid;
msg.smidSender = smidSender;
msg.smidReceiver = smidReceiver;
RouteMessage(&msg);
}
void StateMachineMgr::PostMsg(Message *pmsg)
{
pmsg->tDelivery = 0;
StoreDelayedMessage(pmsg);
}
void StateMachineMgr::PostMsg(MessageId mid, StateMachineId smidSender, StateMachineId smidReceiver)
{
Message msg;
memset(&msg, 0, sizeof(msg));
msg.mid = mid;
msg.smidSender = smidSender;
msg.smidReceiver = smidReceiver;
msg.tDelivery = 0;
StoreDelayedMessage(&msg);
}
void StateMachineMgr::SendDelayedMsg(Message *pmsg, long tDelay)
{
pmsg->tDelivery = (int)(m_ptimm->GetTickCount() + tDelay);
RouteMessage(pmsg);
}
void StateMachineMgr::SendDelayedMsg(MessageId mid, long tDelay, StateMachineId smidSender, StateMachineId smidReceiver)
{
Message msg;
memset(&msg, 0, sizeof(msg));
msg.mid = mid;
msg.smidSender = smidSender;
msg.smidReceiver = smidReceiver;
msg.tDelivery = (int)(m_ptimm->GetTickCount() + tDelay);
RouteMessage(&msg);
}
void StateMachineMgr::RouteMessage(Message *pmsg)
{
extern int gcMessagesPerUpdate;
gcMessagesPerUpdate++;
#ifdef TRACKSTATE
// Track that this message has been sent
extern StateFrame *gpframeCurrent;
if (gpframeCurrent != NULL) {
TrackState(gpframeCurrent, pmsg);
}
#endif
StateMachine *psm = GetStateMachine(pmsg->smidReceiver);
// If receiver doesn't exist anymore, discard the message
if (psm == NULL)
return;
if (pmsg->tDelivery > m_ptimm->GetTickCount()) {
// This message needs to be stored until it's time to send it
// UNDONE: this can fail due to out of memory
StoreDelayedMessage(pmsg);
return;
}
// If SetState() was called outside of ProcessStateMachineMessage()
// handling, change the state before processing this message so that the
// message is processed in the correct state
if (psm->m_fForceStateChange)
ProcessStateChange(psm);
// See if this specific state handles this message
#if 0 // def DEBUG_HELPERS
if (psm->m_fDebug && pmsg->mid != kmidReservedUpdate)
DebugBreak();
#endif
int nRet = psm->ProcessMessage(psm->m_st, pmsg);
if (nRet == knDeleted)
return;
// If not, try the "global" handler
if (nRet == knNotHandled)
nRet = psm->ProcessMessage(kstReservedGlobal, pmsg);
// If this state machine has been deleted, no further processing
if (nRet == knDeleted)
return;
// Process state change if a state change occured as part of the previous
// message handling.
if (psm->m_fForceStateChange)
ProcessStateChange(psm);
}
void StateMachineMgr::ProcessStateChange(StateMachine *psm)
{
#ifdef DEBUG
int cStateChanges = 0;
#endif
// Check for a state change
while (psm->m_fForceStateChange) {
// NOTE: Circular logic (state changes causing state changes) could cause
// an infinite loop here. We don't attempt to protect against this.
#ifdef DEBUG
Assert(cStateChanges < 3);
cStateChanges++;
#endif
// Create a general msg for initializing and cleaning up the state change
Message msgT;
msgT.smidSender = msgT.smidReceiver = GetId(psm);
msgT.tDelivery = 0;
psm->m_fForceStateChange = false;
// Let the last state clean-up.
// NOTE: this purposefully does NOT bubble up to the Global state handler
msgT.mid = kmidReservedExit;
int nRet = psm->ProcessMessage(psm->m_st, &msgT);
if (nRet == knDeleted)
return;
// We don't allow Exit handlers to change the state.
// Assert(!psm->m_fForceStateChange);
psm->m_fForceStateChange = false;
// Set the new state
psm->m_st = psm->m_stNext;
// Let the new state initialize
// NOTE: this purposefully does NOT bubble up to the Global state
// handler
msgT.mid = kmidReservedEnter;
nRet = psm->ProcessMessage(psm->m_st, &msgT);
if (nRet == knDeleted)
return;
}
}
// This function is called every game tick
void StateMachineMgr::DispatchDelayedMessages()
{
DelayedMessage **ppdmsg = &m_pdmsgHead;
while (*ppdmsg != NULL) {
DelayedMessage *pdmsg = *ppdmsg;
if (pdmsg->msg.tDelivery <= m_ptimm->GetTickCount()) {
*ppdmsg = pdmsg->pdmsgNext;
RouteMessage(&pdmsg->msg);
delete pdmsg;
} else {
ppdmsg = &(*ppdmsg)->pdmsgNext;
}
}
}
bool StateMachineMgr::StoreDelayedMessage(Message *pmsg)
{
// Store this message (in some data structure) for later routing
// A priority queue would be the ideal data structure (but not required)
// to store the delayed messages - Check out Mark Nelson's article
// "Priority Queues and the STL" in the January 1996 Dr. Dobb's Journal
// http://www.dogma.net/markn/articles/pq_stl/priority.htm
// Note: In main game loop call SendDelayedMessages() every game
// tick to check if it's time to send the stored messages
DelayedMessage *pdmsg = new DelayedMessage;
Assert(pdmsg != NULL, "out of memory!");
if (pdmsg == NULL)
return false;
pdmsg->msg = *pmsg;
pdmsg->pdmsgNext = NULL;
DelayedMessage **ppdmsg = &m_pdmsgHead;
while (*ppdmsg != NULL)
ppdmsg = &(*ppdmsg)->pdmsgNext;
*ppdmsg = pdmsg;
return true;
}
#ifdef TRACKSTATE
dword gmpmidQuad[] = {
'MRNL', // kmidReservedNull,
'MREN', // kmidReservedEnter,
'MREX', // kmidReservedExit,
'MRUP', // kmidReservedUpdate,
'MHIT', // kmidHit,
'MNAH', // kmidNearbyAllyHit,
'MDEL', // kmidDelete,
'MENB', // kmidEnemyNearby,
'MPLH', // kmidPowerLowHigh,
'MMWN', // kmidMoveWaitingNearby,
'MBUP', // kmidBeingUpgraded,
'MUPC', // kmidUpgradeComplete,
'MMVC', // kmidMoveCommand,
'MATC', // kmidAttackCommand,
'MANC', // kmidAnimationComplete,
'MBOC', // kmidBuildOtherCommand,
'MABO', // kmidAbortBuildOtherCommand,
'MBDC', // kmidBuildComplete,
'MFRC', // kmidFireComplete,
'MSSM', // kmidSpawnSmoke,
'MSDC', // kmidSelfDestructCommand,
'MREC', // kmidRepairCommand,
'MUPC', // kmidUpgradeCommand,
'MAUC', // kmidAbortUpgradeCommand,
'MTFC', // kmidTransformCommand,
'MDLC', // kmidDeliverCommand,
'MGXD', // kmidGalaxiteDelivery,
'MMIC', // kmidMineCommand,
'MMAC', // kmidMoveAction,
'MAAC', // kmidAttackAction,
'MGUA', // kmidGuardAction,
'MGVA', // kmidGuardVicinityAction,
'MGAA', // kmidGuardAreaAction,
'MHEA', // kmidHuntEnemiesAction,
'MFIR', // kmidFire,
'MHET', // kmidHeal,
'MSFX', // kmidPlaySfx,
'PDIS', // kmidPlayerDisconnect,
};
void StateMachineMgr::TrackState(StateFrame *frame, Message *pmsg) {
int i = frame->AddCountedValue(gmpmidQuad[pmsg->mid]);
frame->AddValue('SMIS', (dword)pmsg->smidSender, i);
frame->AddValue('SMIR', (dword)pmsg->smidReceiver, i);
switch (pmsg->mid) {
case kmidHit:
frame->AddValue('GIDA', (dword)pmsg->Hit.gidAssailant, i);
frame->AddValue('SIDA', (dword)pmsg->Hit.sideAssailant, i);
frame->AddValue('NDAM', (dword)pmsg->Hit.nDamage, i);
break;
case kmidPlaySfx:
// Output 0 so it is always the same, because some units that
// send kmidPlaySfx select the sfx using SfxFromCategory, which
// uses a random # generator that is not in sync with MP.
// bug causing this.
//frame->AddValue('SFX ', (dword)pmsg->PlaySfx.sfx, i);
frame->AddValue('SFX ', 0, i);
break;
case kmidEnemyNearby:
frame->AddValue('ENNB', (dword)pmsg->EnemyNearby.gidEnemy, i);
break;
case kmidMoveCommand:
frame->AddValue('GIDT', (dword)pmsg->MoveCommand.gidTarget, i);
frame->AddValue('TRWX', (dword)pmsg->MoveCommand.wptTarget.wx, i);
frame->AddValue('TRWY', (dword)pmsg->MoveCommand.wptTarget.wy, i);
frame->AddValue('TCWX', (dword)pmsg->MoveCommand.wptTargetCenter.wx, i);
frame->AddValue('TCWY', (dword)pmsg->MoveCommand.wptTargetCenter.wy, i);
frame->AddValue('TCRA', (dword)pmsg->MoveCommand.tcTargetRadius, i);
frame->AddValue('MDPU', (dword)pmsg->MoveCommand.wcMoveDistPerUpdate,
i);
break;
case kmidAttackCommand:
frame->AddValue('GIDT', (dword)pmsg->AttackCommand.gidTarget, i);
frame->AddValue('TRWX', (dword)pmsg->AttackCommand.wptTarget.wx, i);
frame->AddValue('TRWY', (dword)pmsg->AttackCommand.wptTarget.wy, i);
frame->AddValue('TCWX', (dword)pmsg->AttackCommand.wptTargetCenter.wx,
i);
frame->AddValue('TCWY', (dword)pmsg->AttackCommand.wptTargetCenter.wy,
i);
frame->AddValue('TCRA', (dword)pmsg->AttackCommand.tcTargetRadius, i);
frame->AddValue('MDPU', (dword)pmsg->AttackCommand.wcMoveDistPerUpdate,
i);
break;
case kmidUpgradeCommand:
frame->AddValue('WFUP', (dword)pmsg->UpgradeCommand.wfUpgrade, i);
break;
case kmidBuildOtherCommand:
frame->AddValue('UNTT', (dword)pmsg->BuildOtherCommand.ut, i);
frame->AddValue('WPWX', (dword)pmsg->BuildOtherCommand.wpt.wx, i);
frame->AddValue('WPWY', (dword)pmsg->BuildOtherCommand.wpt.wy, i);
break;
case kmidAbortBuildOtherCommand:
frame->AddValue('UNTT', (dword)pmsg->AbortBuildOtherCommand.ut, i);
break;
case kmidDeliverCommand:
frame->AddValue('GTAR', (dword)pmsg->DeliverCommand.gidTarget, i);
break;
case kmidMineCommand:
frame->AddValue('GTAR', (dword)pmsg->MineCommand.gidTarget, i);
frame->AddValue('TRWX', (dword)pmsg->MineCommand.wptTarget.wx, i);
frame->AddValue('TRWY', (dword)pmsg->MineCommand.wptTarget.wy, i);
break;
case kmidGuardAreaAction:
frame->AddValue('AREA', (dword)pmsg->GuardAreaCommand.nArea, i);
break;
case kmidHuntEnemiesAction:
frame->AddValue('UMSK', (dword)pmsg->HuntEnemiesCommand.um, i);
break;
case kmidPlayerDisconnect:
frame->AddValue('PID ', (dword)pmsg->PlayerDisconnect.pid, i);
frame->AddValue('REAS', (dword)pmsg->PlayerDisconnect.nReason, i);
break;
}
}
#endif
#if defined(DEBUG_HELPERS)
// NOTE: these must be in the same order as the MessageId enum
// Note: Use LABEL macros instead of this
char *gaszMessageNames[] = {
"Null",
"Enter",
"Exit",
"Update",
"Hit",
"NearbyAllyHit",
"Delete",
"EnemyNearby",
"PowerLowHigh",
"MoveWaitingNearby",
"BeginUpgraded",
"UpgradeComplete",
"MoveCommand",
"AttackCommand",
"AnimationComplete",
"BuildOtherCommand",
"AbortBuildOtherCommand",
"BuildComplete",
"FireComplete",
"SpawnSmoke",
"SelfDestructCommand",
"RepairCommand",
"UpgradeCommand",
"AbortUpgradeCommand",
"TransformCommand",
"DeliverCommand",
"GalaxiteDelivery",
"MineCommand",
"MoveAction",
"AttackAction",
"GuardAction",
"GuardVicinityAction",
"GuardAreaAction",
"HuntEnemiesAction",
"Fire",
"Heal",
};
// NOTE: these must be in the same order as the State enum
// Note: Use LABEL macros instead of this
char *gaszStateNames[] = {
"Null",
"Zombie",
"Global",
"Guard",
"Move",
"Attack",
"Chase",
"Idle",
"Dying",
"BuildOtherCompleting",
"BeingBuilt",
"HuntEnemies",
"ProcessorGetMiner",
"ProcessorPutMiner",
"ProcessorTakeGalaxite",
"MinerMoveToProcessor",
"MinerRotateForEntry",
"MinerMine",
"MinerFindGalaxite",
"MinerFaceGalaxite",
"MinerApproachGalaxite",
"MinerSuck",
"MinerStepAside",
"ChangeStatePendingFireComplete",
"ContinueActionPendingFireComplete",
};
#endif
} // namespace wi