mirror of
https://github.com/spiffcode/hostile-takeover.git
synced 2025-12-16 12:08:36 +00:00
555 lines
13 KiB
C++
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
|