hostile-takeover/game/MobileUnit.cpp

3049 lines
82 KiB
C++

#include "ht.h"
namespace wi {
// Each element of this table was calculated as:
// gawcDiagonalDist[i] = floor((cos(PI / 4) * i) + 0.5)
/* jscript to generate the table:
for (j = 0; j < 4; j++) {
var str = "";
for (i = 0; i < 16; i++)
str += Math.floor((Math.cos(Math.PI / 4) * ((j * 16) + i) + .5)) + ", ";
WScript.Echo(str);
}
*/
static byte gawcDiagonalDist[64] = {
0, 1, 1, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 10, 11,
11, 12, 13, 13, 14, 15, 16, 16, 17, 18, 18, 19, 20, 21, 21, 22,
23, 23, 24, 25, 25, 26, 27, 28, 28, 29, 30, 30, 31, 32, 33, 33,
34, 35, 35, 36, 37, 37, 38, 39, 40, 40, 41, 42, 42, 43, 44, 45,
};
// Each element of this table was calculated as:
// gaDiv256byNWithRounding[i] = (256 + (i / 2)) / i
// The "+ (i / 2)" part of this rounds the # of steps rather than
// truncating it which gives gives better results when there is a partial
// step remainder.
/* javascript to generate the table:
for (j = 0; j < 4; j++) {
var str = "";
for (i = 0; i < 16; i++) {
var wcMoveDist = ((j * 16) + i);
str += Math.floor((256 + (wcMoveDist / 2)) / wcMoveDist) + ", ";
}
WScript.Echo(str);
}
*/
static byte gaDiv256byNWithRounding[64] = {
0, 255, // Fudge to keep within 8-bit range
128, 85, 64, 51, 43, 37, 32, 28, 26, 23, 21, 20, 18, 17,
16, 15, 14, 13, 13, 12, 12, 11, 11, 10, 10, 9, 9, 9, 9, 8,
8, 8, 8, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4,
};
int g_mpDirToDx[8] = { 0, 1, 1, 1, 0, -1, -1, -1 };
int g_mpDirToDy[8] = { -1, -1, 0, 1, 1, 1, 0, -1 };
AnimationData *g_panidMoveTarget;
static AnimationData *s_panidVehicleExplosion;
#define GetIdleCountdown() ((GetRandom() % 50) + 50) // somewhere between 4 & 8 seconds
const int kcFireCountdown = 6; // 6 updates (~.5 secs)
Path *MobileUnitGob::s_apathCached[kcPathsCache];
int MobileUnitGob::s_cpathCached;
//===========================================================================
// MobileUnitGob implementation
bool MobileUnitGob::InitClass(MobileUnitConsts *pmuntc, IniReader *pini)
{
if (!UnitGob::InitClass(pmuntc, pini))
return false;
char szTemplate[10];
itoa(pmuntc->gt, szTemplate, 10);
// Required properties
int nT;
if (pini->GetPropertyValue(szTemplate, "MoveRate", "%d", &nT) != 1)
return false;
pmuntc->wcMoveDistPerUpdate = (WCoord)nT;
Assert(pmuntc->wcMoveDistPerUpdate < 64); // gawcDiagonalDist only has 64 entries
gwcMoveDistPerUpdateMin = _min(gwcMoveDistPerUpdateMin, pmuntc->wcMoveDistPerUpdate);
gwcMoveDistPerUpdateMax = _max(gwcMoveDistPerUpdateMax, pmuntc->wcMoveDistPerUpdate);
int nArmorStrength;
if (pini->GetPropertyValue(szTemplate, "ArmorStrength", "%d", &nArmorStrength) != 1)
return false;
Assert(nArmorStrength < 1 << 10);
pmuntc->fxArmorStrength = itofx(nArmorStrength);
gfxMobileUnitArmorStrengthMin = _min(gfxMobileUnitArmorStrengthMin, pmuntc->fxArmorStrength);
gfxMobileUnitArmorStrengthMax = _max(gfxMobileUnitArmorStrengthMax, pmuntc->fxArmorStrength);
// Optional properties
if (pini->GetPropertyValue(szTemplate, "ArmorStrengthMP", "%d", &nArmorStrength) != 1) {
pmuntc->fxArmorStrengthMP = pmuntc->fxArmorStrength;
} else {
Assert(nArmorStrength < 1 << 10);
pmuntc->fxArmorStrengthMP = itofx(nArmorStrength);
}
if (pini->GetPropertyValue(szTemplate, "MoveRateMP", "%d", &nT) != 1)
pmuntc->wcMoveDistPerUpdateMP = pmuntc->wcMoveDistPerUpdate;
else
pmuntc->wcMoveDistPerUpdateMP = (WCoord)nT;
Assert(pmuntc->wcMoveDistPerUpdateMP < 64); // gawcDiagonalDist only has 64 entries
// Preload the unit's menu form
if (!LoadMenu(pmuntc, pini, szTemplate, kidfUnitMenu))
return false;
// MobileUnitGob owns the 'move target' and 'vehicle explosion' AnimationData
if (g_panidMoveTarget == NULL) {
g_panidMoveTarget = LoadAnimationData("movetarget.anir");
if (g_panidMoveTarget == NULL)
return false;
}
if (s_panidVehicleExplosion == NULL) {
s_panidVehicleExplosion = LoadAnimationData("vexplosion.anir");
if (s_panidVehicleExplosion == NULL)
return false;
}
return true;
}
void MobileUnitGob::ExitClass(MobileUnitConsts *pmuntc)
{
// Clear this out so MobileUnitGob derivatives can safely call MobileUnitGob::ExitClass
delete s_panidVehicleExplosion;
s_panidVehicleExplosion = NULL;
// Clear this out so MobileUnitGob derivatives can safely call MobileUnitGob::ExitClass
delete g_panidMoveTarget;
g_panidMoveTarget = NULL;
UnitGob::ExitClass(pmuntc);
}
MobileUnitGob::MobileUnitGob(MobileUnitConsts *pmuntc) : UnitGob(pmuntc)
{
m_ff |= kfGobMobileUnit;
m_tLastFire = 0;
m_gidTarget = kgidNull;
m_wptTarget.wx = kwxInvalid; // kxInvalid = no target location
m_cMoveStepsRemaining = 0;
m_txDst = 0;
m_tyDst = 0;
m_wcMoveDistPerUpdate = 0;
m_mua = kmuaNone;
m_muaPending = kmuaNone;
m_wfMunt = kfMuntReturnFire | kfMuntAttackEnemiesWhenGuarding | kfMuntAttackEnemiesWhenMoving;
m_stPending = kstReservedNull;
m_itptPath = 0;
m_ppathUnit = NULL;
m_ppathAvoid = NULL;
m_nSeqMoveAside = 0;
// Just to be clear this Unit or its nearby allies hasn't been hit for awhile
m_cupdLastHitOrNearbyAllyHit = -1000000;
}
MobileUnitGob::~MobileUnitGob()
{
delete m_ppathUnit;
}
void MobileUnitGob::Activate()
{
TCoord tx = TcFromWc(m_wx);
TCoord ty = TcFromWc(m_wy);
Assert(!IsTileReserved(tx, ty));
ReserveTile(tx, ty, true);
UnitGob::Activate();
ggobm.MoveGobBetweenAreas(m_gid, 0, ggobm.CalcAreaMask(tx, ty));
}
void MobileUnitGob::Deactivate()
{
if (m_wfMunt & kfMuntDestinationReserved) {
Assert(IsTileReserved(m_txDst, m_tyDst));
ReserveTile(m_txDst, m_tyDst, false);
} else {
#ifndef MP_DEBUG_SHAREDMEM
// DWM: We always hit it when a Processor containing a Bullpup is destroyed.
// We should fix that but in the meantime I need to release a DEBUG build
// that won't trip up on this case so I'm commenting it out for now.
// Assert(IsTileReserved(TcFromWc(m_wx), TcFromWc(m_wy)));
#endif
ReserveTile(TcFromWc(m_wx), TcFromWc(m_wy), false);
}
UnitGob::Deactivate();
ggobm.MoveGobBetweenAreas(m_gid, ggobm.CalcAreaMask(TcFromWc(m_wx), TcFromWc(m_wy)), 0);
}
word AggBitsFromAgg(int nAggressiveness)
{
switch (nAggressiveness) {
case knAggressivenessCoward:
return kfMuntRunAwayWhenHit;
case knAggressivenessSelfDefense:
return kfMuntReturnFire | kfMuntStayPut;
case knAggressivenessDefender:
return kfMuntReturnFire | kfMuntAttackEnemiesWhenGuarding | kfMuntAttackEnemiesWhenMoving;
case knAggressivenessPitbull:
return kfMuntReturnFire | kfMuntAttackEnemiesWhenGuarding | kfMuntAttackEnemiesWhenMoving | kfMuntChaseEnemies;
// Implicit
// case knAggressivenessPacifist:
// break; // non-aggressive
}
return 0;
}
bool MobileUnitGob::Init(IniReader *pini, FindProp *pfind, const char *pszName)
{
// UnitGob::Init(pini, ...) calls the overridden UnitGob::Init(wx, ...) below
if (!UnitGob::Init(pini, pfind, pszName))
return false;
// Note that this MobileUnitGob has already been Activated by this point
// Translate an aggressiveness type into the MobileUnit flags that actually
// determine its behaviour.
int nAggressiveness;
char szAction[100];
int cArgs = pini->GetPropertyValue(pfind, "%*d ,%*d ,%*d ,%*d ,%*d ,%*d, %d ,%a", &nAggressiveness, szAction);
if (cArgs > 0)
m_wfMunt = (m_wfMunt & ~kfMuntAggressivenessBits) | AggBitsFromAgg(nAggressiveness);
// Only computer-controlled units respond to their initial Action
if (m_pplr->GetFlags() & kfPlrComputer) {
if (cArgs > 1)
PerformAction(szAction);
// Human-controlled units don't attack while executing a move command
} else {
m_wfMunt &= ~kfMuntAttackEnemiesWhenMoving;
}
return true;
}
bool MobileUnitGob::Init(WCoord wx, WCoord wy, Player *pplr, fix fxHealth, dword ff, const char *pszName)
{
if (!UnitGob::Init(wx, wy, pplr, fxHealth, ff, pszName))
return false;
// Center Unit within tile
m_wx += kwcTileHalf;
m_wy += kwcTileHalf;
Activate();
// Human-controlled units don't attack while executing a move command
if (IsHumanOrGodControlled())
m_wfMunt &= ~kfMuntAttackEnemiesWhenMoving;
// Notify nearby enemy gobs that they might want to attack this gob
NotifyEnemyNearby();
return true;
}
#define knVerMobileUnitGobState 10
bool MobileUnitGob::LoadState(Stream *pstm)
{
byte nVer = pstm->ReadByte();
if (nVer != knVerMobileUnitGobState)
return false;
m_dir = (Direction)pstm->ReadByte();
m_dirNext = (Direction)pstm->ReadByte();
m_gidTarget = pstm->ReadWord();
m_tLastFire = gsim.GetTickCount() - pstm->ReadDword();
pstm->Read(&m_msgPending, sizeof(m_msgPending));
pstm->Read(&m_msgAction, sizeof(m_msgAction));
m_cCountdown = pstm->ReadWord();
m_cMoveStepsRemaining = pstm->ReadWord();
m_tptChaseInitial.tx = pstm->ReadWord();
m_tptChaseInitial.ty = pstm->ReadWord();
#ifdef DRAW_PATHS
m_wxDst = pstm->ReadWord();
m_wyDst = pstm->ReadWord();
#endif
m_txDst = pstm->ReadWord();
m_tyDst = pstm->ReadWord();
m_wptTarget.wx = pstm->ReadWord();
m_wptTarget.wy = pstm->ReadWord();
if (pstm->ReadByte() != 0) {
m_ppathUnit = new Path;
if (m_ppathUnit == NULL)
return false;
if (!m_ppathUnit->LoadState(gsim.GetLevel()->GetTerrainMap(), pstm))
return false;
m_itptPath = pstm->ReadWord();
}
if (pstm->ReadByte() != 0) {
m_ppathAvoid = new Path;
if (m_ppathAvoid == NULL)
return false;
if (!m_ppathAvoid->LoadState(gsim.GetLevel()->GetTerrainMap(), pstm))
return false;
}
m_mua = (MobileUnitAction)pstm->ReadByte();
m_muaPending = (MobileUnitAction)pstm->ReadByte();
m_wfMunt = pstm->ReadWord();
m_stPending = (State)pstm->ReadByte();
m_wptTargetCenter.wx = pstm->ReadWord();
m_wptTargetCenter.wy = pstm->ReadWord();
m_tcTargetRadius = pstm->ReadWord();
m_wcMoveDistPerUpdate = pstm->ReadWord();
m_cupdLastHitOrNearbyAllyHit = pstm->ReadDword();
return UnitGob::LoadState(pstm);
}
bool MobileUnitGob::SaveState(Stream *pstm)
{
pstm->WriteByte(knVerMobileUnitGobState);
pstm->WriteByte(m_dir);
pstm->WriteByte(m_dirNext);
pstm->WriteWord(m_gidTarget);
pstm->WriteDword((dword)(gsim.GetTickCount() - m_tLastFire));
pstm->Write(&m_msgPending, sizeof(m_msgPending));
pstm->Write(&m_msgAction, sizeof(m_msgAction));
pstm->WriteWord(m_cCountdown);
pstm->WriteWord(m_cMoveStepsRemaining);
pstm->WriteWord(m_tptChaseInitial.tx);
pstm->WriteWord(m_tptChaseInitial.ty);
#ifdef DRAW_PATHS
pstm->WriteWord(m_wxDst);
pstm->WriteWord(m_wyDst);
#endif
pstm->WriteWord(m_txDst);
pstm->WriteWord(m_tyDst);
pstm->WriteWord(m_wptTarget.wx);
pstm->WriteWord(m_wptTarget.wy);
if (m_ppathUnit == NULL) {
pstm->WriteByte(0);
} else {
pstm->WriteByte(1);
m_ppathUnit->SaveState(pstm);
pstm->WriteWord(m_itptPath);
}
if (m_ppathAvoid == NULL) {
pstm->WriteByte(0);
} else {
pstm->WriteByte(1);
m_ppathAvoid->SaveState(pstm);
}
pstm->WriteByte(m_mua);
pstm->WriteByte(m_muaPending);
pstm->WriteWord(m_wfMunt);
pstm->WriteByte(m_stPending);
pstm->WriteWord(m_wptTargetCenter.wx);
pstm->WriteWord(m_wptTargetCenter.wy);
pstm->WriteWord(m_tcTargetRadius);
pstm->WriteWord(m_wcMoveDistPerUpdate);
pstm->WriteDword(m_cupdLastHitOrNearbyAllyHit);
return UnitGob::SaveState(pstm);
}
void MobileUnitGob::PerformAction(char *szAction)
{
int nUnitAction;
if (IniScanf(szAction, "%d", &nUnitAction) == 0) {
Assert(false);
return;
}
switch (nUnitAction) {
case knMoveUnitAction:
{
int nArea;
if (IniScanf(szAction, "%*d ,%d", &nArea) == 0) {
Assert(false);
return;
}
TRect trc;
ggobm.GetAreaRect(nArea, &trc);
Point ptCenter;
trc.GetCenter(&ptCenter);
SendMoveAction(m_gid, WcFromTc(ptCenter.x), WcFromTc(ptCenter.y), 1, m_pmuntc->GetMoveDistPerUpdate());
}
break;
case knGuardUnitAction:
break;
case knGuardVicinityUnitAction:
SendGuardVicinityAction(m_gid);
break;
case knGuardAreaUnitAction:
{
int nArea;
if (IniScanf(szAction, "%*d ,%d", &nArea) == 0) {
Assert(false);
return;
}
SendGuardAreaAction(m_gid, nArea);
}
break;
case knHuntEnemiesUnitAction:
{
UnitMask um;
if (IniScanf(szAction, "%*d ,%d", &um) == 0) {
Assert(false);
return;
}
SendHuntEnemiesAction(m_gid, um);
}
break;
}
}
#ifdef DRAW_PATHS
void MobileUnitGob::DrawPath(DibBitmap *pbm, WCoord wxViewOrigin, WCoord wyViewOrigin)
{
if (m_ppathUnit != NULL)
m_ppathUnit->Draw(pbm, PcFromWc(wxViewOrigin), PcFromWc(wyViewOrigin), GetSide());
}
#endif
#ifdef DRAW_LINES
void MobileUnitGob::DrawTargetLine(DibBitmap *pbm, int xViewOrigin, int yViewOrigin)
{
if (m_wptTarget.wx == kwxInvalid)
return;
pbm->DrawLine(PcFromUwc(m_wx) - xViewOrigin, PcFromUwc(m_wy) - yViewOrigin,
PcFromUwc(m_wptTarget.wx) - xViewOrigin, PcFromUwc(m_wptTarget.wy) - yViewOrigin,
GetSideColor(m_pplr->GetSide()));
}
#endif
bool MobileUnitGob::IsIdle()
{
// UNDONE: introduce kstIdle?
return (m_wfMunt & kfMuntCommandPending) == 0 && m_st == kstGuard;
}
// Handle rotating toward the target and limiting the firing rate
bool MobileUnitGob::Fire(UnitGob *puntTarget, WCoord wx, WCoord wy, WCoord wdx, WCoord wdy)
{
// Make sure we're facing the way we want to fire before we try to fire
Direction dirFire = CalcDir(wdx, wdy);
if (m_dir != dirFire) {
m_dir = TurnToward(dirFire, m_dir);
SetAnimationStrip(&m_ani, m_pmuntc->anMovingStripIndices[m_dir]);
m_unvl.MinSkip();
return false;
}
// Firing rate is limited by ctFiringRate
long t = gsim.GetTickCount();
int ctWait = m_pmuntc->ctFiringRate;
int ctRemaining = ctWait - (int)(t - m_tLastFire);
if (ctRemaining > 0) {
m_unvl.MinSkip((ctRemaining + (kctUpdate / 2)) / kctUpdate - 1);
return false;
}
m_tLastFire = t;
// Play firing animation (start on frame 1 where the action is)
StartAnimation(&m_ani, m_pmuntc->anFiringStripIndices[m_dir], 1, kfAniIgnoreFirstAdvance | kfAniResetWhenDone);
m_wfMunt |= kfMuntFiring;
gsmm.SendDelayedMsg(kmidFireComplete, m_ani.GetRemainingStripTime(), m_gid, m_gid);
return true;
}
void MobileUnitGob::Idle()
{
}
void MobileUnitGob::SetTarget(Gid gid, WCoord wx, WCoord wy, WCoord wxCenter, WCoord wyCenter, TCoord tcRadius, WCoord wcMoveDistPerUpdate)
{
if (gid == kgidNull) {
// Send formed command
Message msgT;
msgT.mid = kmidMoveCommand;
msgT.smidSender = kgidNull;
msgT.smidReceiver = m_gid;
msgT.MoveCommand.wptTarget.wx = wx;
msgT.MoveCommand.wptTarget.wy = wy;
msgT.MoveCommand.gidTarget = kgidNull;
msgT.MoveCommand.wptTargetCenter.wx = wxCenter;
msgT.MoveCommand.wptTargetCenter.wy = wyCenter;
msgT.MoveCommand.tcTargetRadius = tcRadius;
msgT.MoveCommand.wcMoveDistPerUpdate = wcMoveDistPerUpdate;
gcmdq.Enqueue(&msgT);
return;
}
// If the target no longer exists, discard the command
Gob *pgobTarget = ggobm.GetGob(gid);
if (pgobTarget == NULL)
return;
// If the target is a Replicator move to its input
if (pgobTarget->GetType() == kgtReplicator) {
pgobTarget->SetFlags(pgobTarget->GetFlags() | kfGobFlashing);
// Send formed command
ReplicatorGob *prep = (ReplicatorGob *)pgobTarget;
TPoint tpt;
prep->GetInputTilePosition(&tpt);
Message msgT;
msgT.mid = kmidMoveCommand;
msgT.smidSender = kgidNull;
msgT.smidReceiver = m_gid;
msgT.MoveCommand.wptTarget.wx = WcFromTc(tpt.tx) + kwcTileHalf;
msgT.MoveCommand.wptTarget.wy = WcFromTc(tpt.ty) + kwcTileHalf;
msgT.MoveCommand.gidTarget = kgidNull;
msgT.MoveCommand.wptTargetCenter.wx = msgT.MoveCommand.wptTarget.wx;
msgT.MoveCommand.wptTargetCenter.wy = msgT.MoveCommand.wptTarget.wy;
msgT.MoveCommand.tcTargetRadius = 0;
msgT.MoveCommand.wcMoveDistPerUpdate = wcMoveDistPerUpdate;
gcmdq.Enqueue(&msgT);
return;
}
// Get target coords if none passed in
if (wx == 0 && wy == 0) {
Assert(pgobTarget->GetFlags() & kfGobUnit);
UnitGob *puntTarget = (UnitGob *)pgobTarget;
WPoint wpt;
puntTarget->GetAttackPoint(&wpt);
wx = wpt.wx;
wy = wpt.wy;
}
// Friend or Foe? No special action for friends
if (IsAlly(pgobTarget->GetSide()))
return;
// Foe -- attack!
// Flash the target Gob
if (m_pplr == gpplrLocal)
pgobTarget->Flash();
// Queue attack command
Message msgT;
msgT.mid = kmidAttackCommand;
msgT.smidSender = m_gid;
msgT.smidReceiver = m_gid;
msgT.AttackCommand.wptTarget.wx = wx;
msgT.AttackCommand.wptTarget.wy = wy;
msgT.AttackCommand.gidTarget = gid;
msgT.AttackCommand.wptTargetCenter.wx = wxCenter;
msgT.AttackCommand.wptTargetCenter.wy = wyCenter;
msgT.AttackCommand.tcTargetRadius = tcRadius;
msgT.AttackCommand.wcMoveDistPerUpdate = wcMoveDistPerUpdate;
gcmdq.Enqueue(&msgT);
}
void SendAttackCommand(Gid gidReceiver, Gid gidTarget)
{
// Bail if target is already gone
Gob *pgobTarget = ggobm.GetGob(gidTarget);
if (pgobTarget == NULL)
return;
Assert(pgobTarget->GetFlags() & kfGobUnit);
UnitGob *puntTarget = (UnitGob *)pgobTarget;
Message msgT;
msgT.mid = kmidAttackCommand;
msgT.smidSender = gidReceiver;
msgT.smidReceiver = gidReceiver;
puntTarget->GetAttackPoint(&msgT.AttackCommand.wptTarget);
msgT.AttackCommand.gidTarget = gidTarget;
msgT.AttackCommand.tcTargetRadius = 0;
msgT.AttackCommand.wptTargetCenter = msgT.AttackCommand.wptTarget;
msgT.AttackCommand.wcMoveDistPerUpdate = 0;
gsmm.SendMsg(&msgT);
}
void MobileUnitGob::GetAttackPoint(WPoint *pwpt)
{
// The attack point must be terrain accessible.
if (m_wfMunt & kfMuntDestinationReserved) {
pwpt->wx = WcFromTc(m_txDst);
pwpt->wy = WcFromTc(m_tyDst);
} else {
pwpt->wx = m_wx;
pwpt->wy = m_wy;
}
}
// UNDONE: so far behavior can be easily parameterized
// Parameters:
// target hit animation (piff) (optional)
// move animation (w/ table for 8 directions)
// fire animation (w/ table for 8 directions)
// idle animation (w/ table for 8 directions)
// death animation
// ShotGob (optional) -- NOTE: may handle target hit animation
// other stuff already in MobileUnitConsts
// If a path is being followed we have to wait until a tile center is
// reached before acting on a new move command.
// Returns true if command is processed (new state is set)
bool MobileUnitGob::PendOrProcessCommand(Message *pmsg, State stNew)
{
if (!IsReadyForCommand()) {
m_msgPending = *pmsg;
m_wfMunt |= kfMuntCommandPending;
return false;
} else {
m_gidTarget = kgidNull;
SetState(stNew);
return true;
}
}
bool MobileUnitGob::PendOrProcessAction(Message *pmsg, State stNew, MobileUnitAction mua)
{
if (!IsReadyForCommand()) {
m_muaPending = mua;
m_msgPending = *pmsg;
m_wfMunt |= kfMuntCommandPending;
return false;
} else {
m_mua = mua;
m_gidTarget = kgidNull;
m_msgAction = *pmsg;
SetState(stNew);
return true;
}
}
bool MobileUnitGob::IsTargetInRange()
{
Gob *pgobTarget = ggobm.GetGob(m_gidTarget);
if (pgobTarget == NULL)
return false;
return IsGobWithinRange(pgobTarget, m_pmuntc->tcFiringRange);
}
bool MobileUnitGob::IsStandingOnActivator()
{
for (Gid gid = ggobm.GetFirstGid(TcFromWc(m_wx), TcFromWc(m_wy)); gid != kgidNull; gid = ggobm.GetNextGid(gid)) {
Gob *pgob = ggobm.GetGob(gid, false);
if (pgob == NULL)
continue;
if (pgob->GetType() == kgtActivator)
return true;
}
return false;
}
void MobileUnitGob::SetStatePendingFireComplete(State st)
{
if (m_wfMunt & kfMuntFiring) {
m_stPending = st;
SetState(kstChangeStatePendingFireComplete);
} else {
SetState(st);
}
}
void MobileUnitGob::ContinueActionPendingFireComplete()
{
if (m_wfMunt & kfMuntFiring) {
SetState(kstContinueActionPendingFireComplete);
} else {
gsmm.SendMsg(&m_msgAction);
}
}
bool MobileUnitGob::IsAttackPointWithinFiringRangeOfTarget(UnitGob *puntTarget)
{
WPoint wptAttack;
puntTarget->GetAttackPoint(&wptAttack);
return IsTargetWithinRange(&wptAttack, puntTarget, m_pmuntc->tcFiringRange);
}
// TUNE:
const TCoord ktcVicinity = 5;
const long kcupdAggressivenessBoost = 63; // 5 seconds
int MobileUnitGob::ProcessStateMachineMessage(State st, Message *pmsg)
{
BeginStateMachine
OnEnter
m_dir = kdirS;
SetState(kstGuard);
// Actions are sent by Trigger/UnitActions or the Overmind
OnMsg(kmidMoveAction)
if (PendOrProcessAction(pmsg, kstMove, kmuaMove))
goto lbMoveCommand;
OnMsg(kmidAttackAction)
if (PendOrProcessAction(pmsg, kstAttack, kmuaAttack))
goto lbAttackCommand;
OnMsg(kmidGuardAction)
PendOrProcessAction(pmsg, kstGuard, kmuaGuard);
OnMsg(kmidGuardVicinityAction)
PendOrProcessAction(pmsg, kstGuard, kmuaGuardVicinity);
OnMsg(kmidGuardAreaAction)
PendOrProcessAction(pmsg, kstGuard, kmuaGuardArea);
OnMsg(kmidHuntEnemiesAction)
PendOrProcessAction(pmsg, kstHuntEnemies, kmuaHuntEnemies);
// Commands are the result of player interactions with the SimUI
OnMsg(kmidMoveCommand)
if (PendOrProcessCommand(pmsg, kstMove)) {
lbMoveCommand:
// Stash move parameters to be picked up by the move state
// Adjust target x/y to point to the closest tile center. We like tile centers.
m_wptTarget.wx = WcTrunc(pmsg->MoveCommand.wptTarget.wx) + kwcTileHalf;
m_wptTarget.wy = WcTrunc(pmsg->MoveCommand.wptTarget.wy) + kwcTileHalf;
m_wptTargetCenter.wx = WcTrunc(pmsg->MoveCommand.wptTargetCenter.wx) + kwcTileHalf;
m_wptTargetCenter.wy = WcTrunc(pmsg->MoveCommand.wptTargetCenter.wy) + kwcTileHalf;
m_tcTargetRadius = pmsg->MoveCommand.tcTargetRadius;
m_wcMoveDistPerUpdate = pmsg->MoveCommand.wcMoveDistPerUpdate;
}
OnMsg(kmidAttackCommand)
if (PendOrProcessCommand(pmsg, kstAttack)) {
lbAttackCommand:
// stash attack parameters to be picked up by the attack state
m_gidTarget = pmsg->AttackCommand.gidTarget;
m_wptTarget.wx = WcTrunc(pmsg->AttackCommand.wptTarget.wx) + kwcTileHalf;
m_wptTarget.wy = WcTrunc(pmsg->AttackCommand.wptTarget.wy) + kwcTileHalf;
m_wptTargetCenter.wx = WcTrunc(pmsg->AttackCommand.wptTargetCenter.wx) + kwcTileHalf;
m_wptTargetCenter.wy = WcTrunc(pmsg->AttackCommand.wptTargetCenter.wy) + kwcTileHalf;
m_tcTargetRadius = pmsg->AttackCommand.tcTargetRadius;
m_wcMoveDistPerUpdate = pmsg->AttackCommand.wcMoveDistPerUpdate;
}
// Other messages are inter and intra-Unit communication, sometimes delayed
OnMsg(kmidNearbyAllyHit)
m_cupdLastHitOrNearbyAllyHit = gsim.GetUpdateCount();
// Respond pretty much as if self was the one hit
goto lbHitNoDamage;
OnMsg(kmidHit)
m_cupdLastHitOrNearbyAllyHit = gsim.GetUpdateCount();
// apply damage
// NOTE: This scoping is for the benefit of gcc which is concerned about
// the above goto skipping the initialization of fxDamage
{
fix fxDamage = itofx(pmsg->Hit.nDamage);
if (m_pplr->GetHandicap() & kfHcapIncreasedArmor)
fxDamage = (fix)mulfx(fxDamage, (itofx(knDecreasedDamagePercent) / 100));
SetHealth(subfx(m_fxHealth, fxDamage));
}
if (m_fxHealth <= 0) {
Player *pplr = gplrm.GetPlayer(pmsg->Hit.sideAssailant);
pplr->IncEnemyMobileUnitsKilled();
m_pplr->IncMobileUnitsLost();
SetState(kstDying);
// Think about fighting back
} else {
ShowDamageIndicator();
lbHitNoDamage:
// UNDONE: knAggressivenessCoward
if (m_wfMunt & kfMuntRunAwayWhenHit) {
// If we're supposed to return fire and we know who fired at us...
} else if ((m_wfMunt & kfMuntReturnFire) && pmsg->Hit.gidAssailant != kgidNull) {
// and we're not already attacking an in-range target...
if (m_gidTarget == kgidNull || !IsTargetInRange()) {
// and we're not on a human directed mission...
if ((m_pplr->GetFlags() & kfPlrComputer) || (m_st == kstGuard)) {
// Fight back!
SendAttackCommand(m_gid, pmsg->Hit.gidAssailant);
}
}
}
}
// Because this logic is reused by the kmidNearbyAllyHit handler
if (pmsg->mid != kmidNearbyAllyHit)
NotifyNearbyAlliesOfHit(pmsg->Hit.gidAssailant);
OnMsg(kmidEnemyNearby)
// Notification that an enemy is nearby
RememberEnemyNearby(pmsg->EnemyNearby.gidEnemy);
// Wake up and check out the enemy
m_unvl.MinSkip();
OnMsg(kmidMoveWaitingNearby)
// We're waiting on a gob that has decided to either go into transition
// or stop Wake up and check what to do
m_unvl.MinSkip();
OnMsg(kmidFireComplete)
m_wfMunt &= ~kfMuntFiring;
m_unvl.MinSkip();
OnMsg(kmidDelete)
Assert("Shouldn't receive kmidDelete when not in kstDying state");
//-----------------------------------------------------------------------
State(kstGuard)
OnEnter
// Play idle animation
StartAnimation(&m_ani, m_pmuntc->anIdleStripIndices[m_dir], 0, kfAniDone);
m_wptTarget.wx = kwxInvalid;
m_gidTarget = kgidNull;
m_cCountdown = GetIdleCountdown();
if (m_wfMunt & kfMuntMoveWaitingNearby)
NotifyMoveWaitingNearby(m_wx, m_wy);
Assert(!(m_wfMunt & (kfMuntPathPending | kfMuntMoveWaitingNearby)));
OnUpdate
// Note if an enemy is nearby the update interval is min-ed
// by the enemy nearby notification, causing this path
// to execute right away. Otherwise most of the time the
// state machine is sleeping while in guard state.
// A command might have been pended while we waited for the
// firing animation to complete.
if (m_wfMunt & kfMuntCommandPending && IsReadyForCommand()) {
m_wfMunt &= ~kfMuntCommandPending;
gsmm.SendMsg(&m_msgPending);
return knHandled;
}
// if enemy in range and we're not already firing at something
if ((m_wfMunt & (kfMuntAttackEnemiesWhenGuarding | kfMuntFiring)) == kfMuntAttackEnemiesWhenGuarding) {
TCoord tcSightRange;
if (m_mua == kmuaGuardVicinity)
tcSightRange = ktcVicinity;
else
tcSightRange = m_pmuntc->tcFiringRange;
UnitGob *puntTarget = FindEnemyNearby(tcSightRange);
if (puntTarget != NULL) {
if (m_pplr->GetFlags() & kfPlrComputer) {
puntTarget->GetAttackPoint(&m_wptTarget);
m_gidTarget = puntTarget->GetId();
m_wptTargetCenter = m_wptTarget;
m_tcTargetRadius = 0;
m_wcMoveDistPerUpdate = m_pmuntc->GetMoveDistPerUpdate();
SetStatePendingFireComplete(kstAttack);
// Pretend we're hit so nearby units will come help
NotifyNearbyAlliesOfHit(m_gidTarget);
return knHandled;
}
// Play firing animation
puntTarget->GetCenter(&m_wptTarget);
Fire(puntTarget, m_wptTarget.wx, m_wptTarget.wy, m_wptTarget.wx - m_wx, m_wptTarget.wy - m_wy);
m_cCountdown = GetIdleCountdown();
// Pretend we're hit so nearby units will come help
NotifyNearbyAlliesOfHit(puntTarget->GetId());
} else if (m_mua == kmuaGuardArea) {
// Find a valid target
UnitGob *puntTarget = FindValidTargetInArea(m_msgAction.GuardAreaCommand.nArea);
if (puntTarget != NULL) {
puntTarget->GetAttackPoint(&m_wptTarget);
m_gidTarget = puntTarget->GetId();
m_wptTargetCenter = m_wptTarget;
m_tcTargetRadius = 0;
m_wcMoveDistPerUpdate = m_pmuntc->GetMoveDistPerUpdate();
SetStatePendingFireComplete(kstAttack);
return knHandled;
}
} else {
m_wptTarget.wx = kwxInvalid;
m_gidTarget = kgidNull;
}
}
// Animate (idle or firing)
AdvanceAnimation(&m_ani);
m_cCountdown -= m_unvl.GetUpdateCount();
if (m_cCountdown < 0) {
Idle();
m_cCountdown = GetIdleCountdown();
}
m_unvl.MinSkip(m_cCountdown);
// Handle flashing
DefUpdate();
//-----------------------------------------------------------------------
State(kstMove)
OnEnter
MoveEnter();
OnExit
MoveExit();
OnUpdate
if (!(m_wfMunt & kfMuntCommandPending) && !InTransition()) {
// If the Unit is sufficiently aggressive and sees an enemy
// unit it should attack it.
if (m_wfMunt & kfMuntAttackEnemiesWhenMoving) {
UnitGob *puntTarget = FindEnemyNearby(m_pmuntc->tcFiringRange);
if (puntTarget != NULL) {
m_gidTarget = puntTarget->GetId();
puntTarget->GetAttackPoint(&m_wptTarget);
m_wptTargetCenter = m_wptTarget;
m_tcTargetRadius = 0;
m_wcMoveDistPerUpdate = m_pmuntc->GetMoveDistPerUpdate();
SetState(kstAttack);
return knHandled;
}
}
}
switch (MoveUpdate()) {
case knMoveTargetReached:
if (m_mua == kmuaMove)
m_mua = kmuaNone; // Move action complete
SetState(kstGuard);
break;
case knMoveStuck:
// UNDONE: what to do? Doing nothing means we repath every
// Update (jogging in place) until a valid path can be determined.
// For now, give up and return to Guard state
if (m_mua == kmuaMove)
m_mua = kmuaNone; // Move action complete
SetState(kstGuard);
break;
}
//-----------------------------------------------------------------------
State(kstAttack)
OnExit
// Send notifications to any gobs waiting on this gob to get out
// of attack state
if (m_wfMunt & kfMuntMoveWaitingNearby)
NotifyMoveWaitingNearby(m_wx, m_wy);
OnUpdate
Assert(m_gidTarget != kgidNull);
// A command might have been pended while we waited for the
// firing animation to complete.
if (m_wfMunt & kfMuntCommandPending && IsReadyForCommand()) {
m_wfMunt &= ~kfMuntCommandPending;
gsmm.SendMsg(&m_msgPending);
return knHandled;
}
// if enemy dead/gone or taken over, go to guard mode
UnitGob *puntTarget = (UnitGob *)ggobm.GetGob(m_gidTarget);
if (puntTarget == NULL || !IsValidTarget(puntTarget)) {
// If an Attack action is being carried out and successfully
// completed, indicate that it is done.
if (m_mua == kmuaAttack && m_gidTarget == m_msgAction.AttackCommand.gidTarget) {
m_mua = kmuaNone; // Action complete
// If some other action was interrupted to attack, return to it
} else if (m_mua != kmuaNone) {
m_gidTarget = kgidNull;
ContinueActionPendingFireComplete();
return knHandled;
}
m_gidTarget = kgidNull;
SetStatePendingFireComplete(kstGuard);
} else {
// if enemy not in range think about chasing it
if (!IsGobWithinRange(puntTarget, m_pmuntc->tcFiringRange)) {
// Test actions and situations under which it is a good idea to chase
if (!(m_wfMunt & kfMuntStayPut) && (m_mua != kmuaNone || !IsStandingOnActivator()) &&
(m_wfMunt & kfMuntChaseEnemies ||
(gsim.GetUpdateCount() - m_cupdLastHitOrNearbyAllyHit <= kcupdAggressivenessBoost) ||
(m_mua == kmuaAttack && m_gidTarget == m_msgAction.AttackCommand.gidTarget) ||
(m_mua == kmuaGuardArea && ggobm.IsGobWithinArea(puntTarget, m_msgAction.GuardAreaCommand.nArea)) ||
(m_mua == kmuaGuardVicinity || m_mua == kmuaHuntEnemies) ||
IsHumanOrGodControlled()) && IsAttackPointWithinFiringRangeOfTarget(puntTarget)) {
SetStatePendingFireComplete(kstChase);
// For all other situations we return to the action in-progress
} else if (m_mua != kmuaNone) {
ContinueActionPendingFireComplete();
return knHandled;
// Or if no action, just fall back to guard state
} else {
SetStatePendingFireComplete(kstGuard);
}
} else {
// Check for assumptions
Assert(m_ppathUnit == NULL);
Assert(!IsMoveWaiting());
// Otherwise, fire at the enemy
if (!(m_wfMunt & kfMuntFiring)) {
puntTarget->GetAttackPoint(&m_wptTarget);
WPoint wptFire;
puntTarget->GetCenter(&wptFire);
Fire(puntTarget, wptFire.wx, wptFire.wy, wptFire.wx - m_wx, wptFire.wy - m_wy);
}
}
}
AdvanceAnimation(&m_ani);
// Handle flashing
DefUpdate();
//-----------------------------------------------------------------------
State(kstChase)
OnEnter
MoveEnter();
// Remember where we found the target last. If it moves position we'll repath
Gob *pgobTarget = ggobm.GetGob(m_gidTarget);
if (pgobTarget != NULL) {
Assert(pgobTarget->GetFlags() & kfGobUnit);
UnitGob *puntTarget = (UnitGob *)pgobTarget;
WPoint wptTarget;
puntTarget->GetAttackPoint(&wptTarget);
m_tptChaseInitial.tx = TcFromWc(wptTarget.wx);
m_tptChaseInitial.ty = TcFromWc(wptTarget.wy);
}
OnExit
MoveExit();
OnUpdate
if (IsReadyForCommand()) {
if (m_wfMunt & kfMuntCommandPending) {
m_wfMunt &= ~kfMuntCommandPending;
gsmm.SendMsg(&m_msgPending);
return knHandled;
}
// If the Unit is executing the AttackAction and is sufficiently
// aggressive and sees an enemy unit it should attack it.
if (m_mua == kmuaAttack) {
if (m_wfMunt & kfMuntAttackEnemiesWhenMoving) {
UnitGob *puntTarget = FindEnemyNearby(m_pmuntc->tcFiringRange);
if (puntTarget != NULL) {
m_gidTarget = puntTarget->GetId();
puntTarget->GetAttackPoint(&m_wptTarget);
SetState(kstAttack);
return knHandled;
}
}
}
// Verify the target still exists
Gob *pgobTarget = ggobm.GetGob(m_gidTarget);
if (pgobTarget == NULL || !IsValidTarget(pgobTarget)) {
// If some other action was interrupted to attack/chase, return to it
if (m_mua != kmuaNone) {
m_gidTarget = kgidNull;
gsmm.SendMsg(&m_msgAction);
return knHandled;
}
m_gidTarget = kgidNull;
SetState(kstGuard);
return knHandled;
}
// if enemy is in range, drop back into Attack mode
if (IsGobWithinRange(pgobTarget, m_pmuntc->tcFiringRange)) {
SetState(kstAttack);
return knHandled;
} else if (!(m_wfMunt & kfMuntChaseEnemies) && !IsHumanOrGodControlled()) {
// If we're trying to guard an area and the enemy has left it let it go.
if (m_mua == kmuaGuardArea) {
if (!ggobm.IsGobWithinArea(pgobTarget, m_msgAction.GuardAreaCommand.nArea)) {
SetState(kstGuard);
return knHandled;
}
// If we're trying to guard an expanded radius and the enemy has left it let it go.
} else if (m_mua == kmuaGuardVicinity) {
if (!IsGobWithinRange(pgobTarget, ktcVicinity) && gsim.GetUpdateCount() - m_cupdLastHitOrNearbyAllyHit > kcupdAggressivenessBoost) {
SetState(kstGuard);
return knHandled;
}
// These are inherently chase modes. Stick with it.
} else if (m_mua == kmuaHuntEnemies || m_mua == kmuaAttack) {
// If we're not acting under an aggressiveness boost anymore drop back to the
// prior action or Guard
} else if (gsim.GetUpdateCount() - m_cupdLastHitOrNearbyAllyHit > kcupdAggressivenessBoost) {
if (m_mua != kmuaNone)
gsmm.SendMsg(&m_msgAction);
else
SetState(kstGuard);
return knHandled;
}
}
// If the target moved, force a new path to get calced
Assert(pgobTarget->GetFlags() & kfGobUnit);
UnitGob *puntTarget = (UnitGob *)pgobTarget;
WPoint wptTarget;
puntTarget->GetAttackPoint(&wptTarget);
TCoord tx = TcFromWc(wptTarget.wx);
TCoord ty = TcFromWc(wptTarget.wy);
if (tx != m_tptChaseInitial.tx || ty != m_tptChaseInitial.ty) {
m_tptChaseInitial.tx = tx;
m_tptChaseInitial.ty = ty;
m_wptTarget = wptTarget;
MoveExit();
}
}
switch (MoveUpdate()) {
case knMoveTargetReached:
SetState(kstAttack);
return knHandled;
case knMoveStuck:
// Next few updates, use the target gob's center
{
Gob *pgobTarget = ggobm.GetGob(m_gidTarget);
Assert(pgobTarget->GetFlags() & kfGobUnit);
UnitGob *puntTarget = (UnitGob *)pgobTarget;
if (puntTarget != NULL) {
WPoint wptTarget;
puntTarget->GetAttackPoint(&wptTarget);
if (WcTrunc(wptTarget.wx) != WcTrunc(m_wptTarget.wx) || WcTrunc(wptTarget.wy) != WcTrunc(m_wptTarget.wy)) {
m_wptTarget = wptTarget;
m_unvl.MinSkip(12);
break;
}
}
}
// UNDONE: Put in waiting code so a new path doesn't get searched
// every update
m_gidTarget = kgidNull;
#if 0
// Causes looping problems (kstChase->kstAttack->kstChase). Plus "stuck" cases happen less often, so try
// taking this out and revert to guard mode to see how it feels.
// Return to any action in progress
if (m_mua != kmuaNone) {
gsmm.SendMsg(&m_msgAction);
return knHandled;
}
#endif
// UNDONE: Perhaps in this case try searching around waiting gobs.
SetState(kstGuard);
return knHandled;
}
//-----------------------------------------------------------------------
// Hunting units sit idle if they can't find a target and won't do
// anything except return fire (same as knAggressivenessSelfDefense)
State(kstHuntEnemies)
OnEnter
// Play idle animation
StartAnimation(&m_ani, m_pmuntc->anIdleStripIndices[m_dir], 0, kfAniDone);
m_wptTarget.wx = kwxInvalid;
m_gidTarget = kgidNull;
m_cCountdown = GetIdleCountdown();
OnUpdate
Gob *pgobTarget = ggobm.GetGob(m_gidTarget);
if (pgobTarget == NULL || !IsValidTarget(pgobTarget)) {
int cpuntFound = 0;
Assert(gcbScratch >= kcpgobMax * sizeof(UnitGob *));
UnitGob **apuntFound = (UnitGob **)gpbScratch;
// UNDONE: only do this every few updates
// Find an enemy to attack
Gob *pgob = ggobm.GetFirstGob();
for (; pgob != NULL; pgob = ggobm.GetNextGob(pgob)) {
// Is it an active enemy that this unit type is allowed to attack?
if (!IsValidTarget(pgob))
continue; // no
// Only Units pass the IsValidTarget test
UnitGob *punt = (UnitGob *)pgob;
// Is it one of the type of units we're supposed to hunt?
if (!(punt->GetConsts()->um & m_msgAction.HuntEnemiesCommand.um))
continue;
apuntFound[cpuntFound++] = punt;
}
if (cpuntFound > 0) {
UnitGob *punt = apuntFound[GetRandom() % cpuntFound];
punt->GetAttackPoint(&m_wptTarget);
m_gidTarget = punt->GetId();
m_wptTargetCenter = m_wptTarget;
m_tcTargetRadius = 0;
m_wcMoveDistPerUpdate = m_pmuntc->GetMoveDistPerUpdate();
SetState(kstAttack);
return knHandled;
}
}
AdvanceAnimation(&m_ani); // idle animation
m_cCountdown -= m_unvl.GetUpdateCount();
if (m_cCountdown < 0) {
Idle();
m_cCountdown = GetIdleCountdown();
}
m_unvl.MinSkip(m_cCountdown);
// Handle flashing
DefUpdate();
//-----------------------------------------------------------------------
State(kstDying)
OnEnter
TRect trc;
GetTileRect(&trc);
Deactivate();
// Redraw this part of minimap. It will skip inactive munts
gpmm->RedrawTRect(&trc);
#ifdef DEBUG
if (m_wfMunt & kfMuntDestinationReserved) {
Assert(!IsTileReserved(m_txDst, m_tyDst));
} else {
Assert(!IsTileReserved(TcFromWc(m_wx), TcFromWc(m_wy)));
}
#endif
m_ff ^= kfGobLayerDepthSorted | kfGobLayerSurfaceDecal;
gsndm.PlaySfx(SfxFromCategory(m_pmuntc->sfxcDestroyed));
// remove corpse in .2 seconds
gsmm.SendDelayedMsg(kmidDelete, 20, m_gid, m_gid);
Gob *pgobExpl = CreateAnimGob(m_wx, m_wy, kfAnmDeleteWhenDone | kfAnmSmokeFireLayer, NULL, s_panidVehicleExplosion);
if (pgobExpl != NULL)
pgobExpl->SetOwner(m_pplr);
OnUpdate
// Handle flashing
DefUpdate();
OnMsg(kmidDelete)
Delete();
return knDeleted;
// Eat all other messages (e.g., kmidHit, kmidNearbyAllyHit)
DiscardMsgs
//-----------------------------------------------------------------------
State(kstChangeStatePendingFireComplete)
OnUpdate
if (!(m_wfMunt & kfMuntFiring)) {
SetState(m_stPending);
m_stPending = kstReservedNull;
}
// Will advance any ongoing firing animation
DefUpdate();
State(kstContinueActionPendingFireComplete)
OnUpdate
if (!(m_wfMunt & kfMuntFiring)) {
gsmm.SendMsg(&m_msgAction);
return knHandled;
}
// Will advance any ongoing firing animation
DefUpdate();
EndStateMachine
}
void MobileUnitGob::ReserveTile(TCoord tx, TCoord ty, bool fReserve)
{
if (fReserve) {
gsim.GetLevel()->GetTerrainMap()->SetFlags(tx, ty, 1, 1, kbfMobileUnit);
} else {
gsim.GetLevel()->GetTerrainMap()->ClearFlags(tx, ty, 1, 1, kbfMobileUnit);
}
}
bool MobileUnitGob::IsTileReserved(TCoord tx, TCoord ty)
{
return gsim.GetLevel()->GetTerrainMap()->TestFlags(tx, ty, 1, 1, kbfMobileUnit);
}
byte MobileUnitGob::GetTileFlags(TCoord tx, TCoord ty)
{
byte bf;
bool f = gsim.GetLevel()->GetTerrainMap()->GetFlags(tx, ty, &bf);
if (!f)
bf = kbfStructure;
return bf;
}
bool MobileUnitGob::InTransition()
{
if (m_ppathUnit == NULL)
return false;
return m_cMoveStepsRemaining != 0;
}
bool MobileUnitGob::IsReadyForCommand()
{
if (m_wfMunt & kfMuntFiring)
return false;
return !InTransition();
}
void MobileUnitGob::NotifyMoveWaitingNearby(WCoord wx, WCoord wy)
{
Assert(m_wfMunt & kfMuntMoveWaitingNearby);
m_wfMunt &= ~kfMuntMoveWaitingNearby;
// Look around our dst to see if there are units trying to get in to our dst tile
TCoord txDst = TcFromWc(wx);
TCoord tyDst = TcFromWc(wy);
TCoord ctx, cty;
ggobm.GetMapSize(&ctx, &cty);
for (int n = 0; n < ARRAYSIZE(g_mpDirToDx); n++) {
// Get valid TCoord to check
TCoord tx = txDst + g_mpDirToDx[n];
TCoord ty = tyDst + g_mpDirToDy[n];
if (tx < 0 || tx >= ctx)
continue;
if (ty < 0 || ty >= cty)
continue;
// Check it for gobs that are waiting on this gob
for (Gid gid = ggobm.GetFirstGid(tx, ty); gid != kgidNull; gid = ggobm.GetNextGid(gid)) {
// Any mobile units in this tile?
MobileUnitGob *pgob = (MobileUnitGob *)ggobm.GetGob(gid);
if (pgob == NULL)
continue;
if (!(pgob->m_ff & kfGobMobileUnit))
continue;
// See if this mobile unit is in transition towards our desired tile
if (pgob->m_txDst == txDst && pgob->m_tyDst == tyDst) {
if (pgob->IsMoveWaiting()) {
gsmm.SendMsg(kmidMoveWaitingNearby, pgob->GetId());
}
}
}
}
}
MobileUnitGob *MobileUnitGob::GetReservingUnit(TCoord tx, TCoord ty)
{
// Need to enumerate all mobile units in this tile, there can be more than one
// in certain transition situations
for (Gid gid = ggobm.GetFirstGid(tx, ty); gid != kgidNull; gid = ggobm.GetNextGid(gid)) {
// Any mobile units in this tile?
MobileUnitGob *pmunt = (MobileUnitGob *)ggobm.GetGob(gid);
if (pmunt == NULL)
continue;
if (!(pmunt->m_ff & kfGobMobileUnit))
continue;
// It is the reserving gob if it either isn't in transition or
// is in transition to this tile
if (!pmunt->InTransition() || (pmunt->m_txDst == tx && pmunt->m_tyDst == ty)) {
Assert(IsTileReserved(tx, ty));
return pmunt;
}
}
// Now look for units transitioning into this tile
MobileUnitGob *pmunt = AnyTransitionsIntoTile(tx, ty, NULL);
Assert((pmunt != NULL) == IsTileReserved(tx, ty));
return pmunt;
}
MobileUnitGob *MobileUnitGob::AnyTransitionsIntoTile(TCoord txDst, TCoord tyDst, Gob *pgobIgnore)
{
// Look around our dst to see if there are units trying to get in to our dst tile
TCoord ctx, cty;
ggobm.GetMapSize(&ctx, &cty);
for (int n = 0; n < ARRAYSIZE(g_mpDirToDx); n++) {
// Get valid TCoord to check
TCoord tx = txDst + g_mpDirToDx[n];
TCoord ty = tyDst + g_mpDirToDy[n];
if (tx < 0 || tx >= ctx)
continue;
if (ty < 0 || ty >= cty)
continue;
// Check it for gobs that are entering our dst
for (Gid gid = ggobm.GetFirstGid(tx, ty); gid != kgidNull; gid = ggobm.GetNextGid(gid)) {
// Any mobile units in this tile?
MobileUnitGob *pgob = (MobileUnitGob *)ggobm.GetGob(gid);
if (pgob == NULL || pgob == pgobIgnore)
continue;
if (!(pgob->m_ff & kfGobMobileUnit))
continue;
// See if this mobile unit is in transition towards our desired tile
if (pgob->m_txDst == txDst && pgob->m_tyDst == tyDst) {
if (pgob->InTransition()) {
return pgob;
}
}
}
}
return NULL;
}
void MobileUnitGob::MoveEnter(bool fReset)
{
// Catch movewait bits that are set but shouldn't be
Assert(!IsMoveWaiting());
// Make sure we're not in transition
Assert(!InTransition());
StartAnimation(&m_ani, m_pmuntc->anMovingStripIndices[m_dir], 0, kfAniLoop);
// These get reset when this move isn't aware of group behavior
if (fReset) {
m_wptTargetCenter = m_wptTarget;
m_tcTargetRadius = 0;
}
if (m_wcMoveDistPerUpdate == 0)
m_wcMoveDistPerUpdate = m_pmuntc->GetMoveDistPerUpdate();
// If the target is in blocked area, walk back toward this unit to find the first free tile,
// in a straight line.
TerrainMap *ptrmap = gsim.GetLevel()->GetTerrainMap();
TCoord txTo = TcFromWc(m_wptTarget.wx);
TCoord tyTo = TcFromWc(m_wptTarget.wy);
TCoord txFree, tyFree;
if (ptrmap->IsBlocked(txTo, tyTo, 0)) {
if (ptrmap->FindFirstUnoccupied(txTo, tyTo, TcFromWc(m_wx), TcFromWc(m_wy), &txFree, &tyFree)) {
m_wptTarget.wx = WcFromTc(txFree) + kwcTileHalf;
m_wptTarget.wy = WcFromTc(tyFree) + kwcTileHalf;
}
}
// Let movement code know that a path is pending for this gob so proper waiting semantics can be
// performed.
m_wfMunt |= kfMuntPathPending;
m_wfMunt &= ~kfMuntStuck;
}
void MobileUnitGob::MoveExit()
{
m_wfMunt &= ~(kfMuntMoveWait | kfMuntPathPending);
delete m_ppathUnit;
m_ppathUnit = NULL;
delete m_ppathAvoid;
m_ppathAvoid = NULL;
// Send notifications to any gobs waiting on this gob
if (m_wfMunt & kfMuntMoveWaitingNearby)
NotifyMoveWaitingNearby(m_wx, m_wy);
}
Path *MobileUnitGob::FindPath(TCoord txFrom, TCoord tyFrom, TCoord txTo, TCoord tyTo)
{
TerrainMap *ptrmap = gsim.GetLevel()->GetTerrainMap();
#ifdef __CPU_68K
// See if we have a cached path that we can reuse - 68K only since this
// sucks
#define ktcPathLengthReuse 35
#define ktcPathEndPointReuse 7
word nDistT = ganSquared[abs(txFrom - txTo)] + ganSquared[abs(tyFrom - tyTo)];
if (GetType() != kgtGalaxMiner && nDistT >= ganSquared[ktcPathLengthReuse]) {
for (int nPath = 0; nPath < s_cpathCached; nPath++) {
// Long enough to be worth it?
Path *ppathT = s_apathCached[nPath];
if (ppathT->GetCount() < ktcPathLengthReuse)
continue;
// In the general vicinity of our start / end points
TPoint tptStart;
ppathT->GetStartPoint(&tptStart);
TPoint tptEnd;
ppathT->GetPointRaw(ppathT->GetCount() - 1, &tptEnd);
word nDistStart = ganSquared[abs(tptStart.tx - txFrom)] + ganSquared[abs(tptStart.ty - tyFrom)];
word nDistEnd = ganSquared[abs(tptEnd.tx - txTo)] + ganSquared[abs(tptEnd.ty - tyTo)];
word nDistReuse = ganSquared[ktcPathEndPointReuse] * 2;
if (nDistStart > nDistReuse || nDistEnd > nDistReuse)
continue;
// See if our closest approach is free of obstacles
TPoint tptClosest;
if (ppathT->FindClosestPoint(txFrom, tyFrom, 0, 5, &tptClosest) == -1)
continue;
if (ptrmap->IsLineOccupied(txFrom, tyFrom, tptClosest.tx, tptClosest.ty, 0))
continue;
// Looking good so far. See if our closest end point is free of obstacles
TPoint tptDestClosest;
int itptDestClosest = ppathT->FindClosestPoint(txTo, tyTo, 0, ppathT->GetCount(), &tptDestClosest);
if (itptDestClosest == -1)
continue;
if (ptrmap->IsLineOccupied(tptDestClosest.tx, tptDestClosest.ty, txTo, tyTo, 0))
continue;
// We're ready to use this path. First create a copy
ppathT = ppathT->Clone();
if (ppathT == NULL)
continue;
// If the ending is the same, use it
if (tptDestClosest.tx == txTo && tptDestClosest.ty == tyTo)
return ppathT;
// Now lop off the end and amend it with our new dest
// If this doesn't work, use it as is
if (!ppathT->TrimEnd(itptDestClosest))
return ppathT;
// Add our new ending. If we run into something, use the path as is
Path *ppathAppend = ptrmap->FindLinePath(tptDestClosest.tx, tptDestClosest.ty, txTo, tyTo, kbfStructure);
if (ppathAppend == NULL)
return ppathT;
// Append this to our ppath
ppathT->Append(ppathAppend);
delete ppathAppend;
// Done
return ppathT;
}
}
#endif
// No cached paths that satisfy our needs. Make a new one.
// If it's close first try an easy path
#define ktcFastPathRangeMax 20
Path *ppathT = NULL;
if (ganSquared[abs(txFrom - txTo)] + ganSquared[abs(tyFrom - tyTo)] < ganSquared[ktcFastPathRangeMax] * 2)
ppathT = ptrmap->FindLinePath(txFrom, tyFrom, txTo, tyTo, kbfStructure);
if (ppathT == NULL)
ppathT = ptrmap->FindPath(txFrom, tyFrom, txTo, tyTo, kbfStructure);
#ifdef __CPU_68K
// Cache this path if it is long enough to be interesting
if (ppathT != NULL) {
if (ppathT->GetCount() >= ktcPathLengthReuse) {
// Move the existing paths that we have down one
Path *ppathCache = ppathT->Clone();
if (ppathCache != NULL) {
if (s_cpathCached == ARRAYSIZE(s_apathCached)) {
delete s_apathCached[ARRAYSIZE(s_apathCached) - 1];
} else {
s_cpathCached++;
}
memmove(&s_apathCached[1], &s_apathCached[0], (ARRAYSIZE(s_apathCached) - 1) * ELEMENTSIZE(s_apathCached));
s_apathCached[0] = ppathCache;
}
}
}
#endif
return ppathT;
}
void MobileUnitGob::FreeCachedPaths()
{
for (int nPath = 0; nPath < s_cpathCached; nPath++)
delete s_apathCached[nPath];
s_cpathCached = NULL;
}
bool MobileUnitGob::PrepPath(WCoord wxDst, WCoord wyDst)
{
// Get a path.
TCoord txFrom = TcFromWc(m_wx);
TCoord tyFrom = TcFromWc(m_wy);
TCoord txTo = TcFromWc(wxDst);
TCoord tyTo = TcFromWc(wyDst);
Path *ppathT = FindPath(txFrom, tyFrom, txTo, tyTo);
// Prep unit path
delete m_ppathAvoid;
m_ppathAvoid = NULL;
delete m_ppathUnit;
m_ppathUnit = ppathT;
m_itptPath = 0;
m_wfMunt &= ~kfMuntPathPending;
#ifdef DRAW_PATHS
m_wxDst = m_wx;
m_wyDst = m_wy;
#endif
m_cMoveStepsRemaining = 0;
return m_ppathUnit != NULL;
}
int MobileUnitGob::MoveUpdate()
{
// If a command is pending and now is a good time to handle it, do so.
if ((m_wfMunt & kfMuntCommandPending) && IsReadyForCommand()) {
m_wfMunt &= ~kfMuntCommandPending;
gsmm.SendMsg(&m_msgPending);
return knMoveMoving;
}
// Initialize a path if we haven't yet. Don't start moving on it yet so
// that all mobile units in a group move have paths. This establishes
// proper waiting behavior
if (m_ppathUnit == NULL) {
// Check if already there
if (CheckDestinationReached())
return knMoveTargetReached;
// No, so calc a path
if (!PrepPath(m_wptTarget.wx, m_wptTarget.wy)) {
// Trace("0x%08lx: 0x%08lx @ %d,%d calc path failed", HostGetTickCount(), this, TcFromWc(m_wx), TcFromWc(m_wy));
m_wfMunt |= kfMuntStuck;
return knMoveStuck;
}
// Trace("0x%08lx: 0x%08lx @ %d,%d calc path succeeded", HostGetTickCount(), this, TcFromWc(m_wx), TcFromWc(m_wy));
}
// We have a path. If we're not in transition, prepare the next transition
if (!InTransition()) {
// Trace("0x%08lx: 0x%08lx @ %d,%d not in transition.", HostGetTickCount(), this, TcFromWc(m_wx), TcFromWc(m_wy));
// If the next transition could not be prepared, error out
TPoint tptNext;
int nMoveResult;
if (!PrepareNextTransition(&tptNext, &nMoveResult)) {
#if 0 // def DEBUG
static char *s_aszMoveResult[] = { "knMoveMoving", "knMoveTargetReached", "knMoveStuck", "knMoveWaiting" };
Trace("0x%08lx @ %d,%d returning %s", this, TcFromWc(m_wx), TcFromWc(m_wy), s_aszMoveResult[nMoveResult]);
if (nMoveResult == knMoveWaiting)
Trace(" (waiting on %d, %d, reserving munt: 0x%08lx)", m_txDst, m_tyDst, GetReservingUnit(m_txDst, m_tyDst));
#endif
return nMoveResult;
}
// Looks good, enter transition
// Trace("0x%08lx @ %d,%d -> %d, %d entering transition.", this, TcFromWc(m_wx), TcFromWc(m_wy), tptNext.tx, tptNext.ty);
EnterTransition(&tptNext);
}
// We're in transition; move through the transition
ContinueTransition();
return knMoveMoving;
}
void MobileUnitGob::EnterTransition(TPoint *ptptNext)
{
// Attempt to enter the next transition
Assert(!InTransition());
Assert(ptptNext->tx != TcFromWc(m_wx) || ptptNext->ty != TcFromWc(m_wy));
// Clear the replicator input spot. This gob isn't a viable replication candidate in transition.
m_wfMunt &= ~kfMuntAtReplicatorInput;
// Remember where this unit is going
m_txDst = ptptNext->tx;
m_tyDst = ptptNext->ty;
// Remember this for "draw paths" feature
#ifdef DRAW_PATHS
m_wxDst = WcFromTc(m_txDst) + kwcTileHalf;
m_wyDst = WcFromTc(m_tyDst) + kwcTileHalf;
#endif
// Unreserve current tile, reserve new tile
TCoord txOld = TcFromWc(m_wx);
TCoord tyOld = TcFromWc(m_wy);
Assert(IsTileReserved(txOld, tyOld));
ReserveTile(txOld, tyOld, false);
m_dirNext = DirectionFromLocations(txOld, tyOld, ptptNext->tx, ptptNext->ty);
Assert(m_dirNext != kdirInvalid);
// Tile is assumed to be available; reserve it and start a transition
Assert(!IsTileReserved(ptptNext->tx, ptptNext->ty));
ReserveTile(ptptNext->tx, ptptNext->ty, true);
// Mark that we've reserved the dest tile so it can be unreserved if
// the unit becomes deactivated.
m_wfMunt |= kfMuntDestinationReserved;
// Send notifications to any gobs waiting on this gob's tile to clear, which it
// just did.
if (m_wfMunt & kfMuntMoveWaitingNearby)
NotifyMoveWaitingNearby(m_wx, m_wy);
// Prepare the appropriate animation
StartAnimation(&m_ani, m_pmuntc->anMovingStripIndices[m_dir], 0, kfAniLoop | kfAniIgnoreFirstAdvance);
// Calc move steps to next tile. Adjust for diagonal.
// This unit is now "in transition".
WCoord wcMoveDist = m_wcMoveDistPerUpdate;
if (m_dirNext & 1)
wcMoveDist = gawcDiagonalDist[wcMoveDist];
m_cMoveStepsRemaining = gaDiv256byNWithRounding[wcMoveDist];
}
void MobileUnitGob::ContinueTransition()
{
// We're in transition and simply moving to the next tile
Assert(InTransition());
// Transition movement doesn't skip updates
m_unvl.MinSkip();
// Make sure we're facing the way we want to go before we go
if (m_dirNext != m_dir) {
m_dir = TurnToward(m_dirNext, m_dir);
StartAnimation(&m_ani, m_pmuntc->anMovingStripIndices[m_dir], 0, kfAniLoop | kfAniIgnoreFirstAdvance);
return;
}
WCoord wxNew = m_wx;
WCoord wyNew = m_wy;
m_cMoveStepsRemaining--;
if (m_cMoveStepsRemaining == 0) {
// Hop to the exact center of the destination tile
wxNew = WcTrunc(m_wx) + kwcTileHalf;
wyNew = WcTrunc(m_wy) + kwcTileHalf;
// No longer in transition; we're at the destination. Clear this bit
// so we know which tile is reserved
m_wfMunt &= ~kfMuntDestinationReserved;
} else {
WCoord wcMoveDist = m_wcMoveDistPerUpdate;
// Are we moving diagonally?
if (m_dir & 1)
wcMoveDist = gawcDiagonalDist[wcMoveDist];
int dx = g_mpDirToDx[m_dir];
if (dx == -1) {
wxNew -= wcMoveDist;
} else if (dx == 1) {
wxNew += wcMoveDist;
}
int dy = g_mpDirToDy[m_dir];
if (dy == -1) {
wyNew -= wcMoveDist;
} else if (dy == 1) {
wyNew += wcMoveDist;
}
}
// Move here
SetPosition(wxNew, wyNew);
// Animate the little fella running there.
AdvanceAnimation(&m_ani);
// Handle flashing, etc
DefUpdate();
}
bool MobileUnitGob::IsMoveWaitCycle(MobileUnitGob *pmunt)
{
// This routine detects move wait loops (units waiting on units waiting on
// units, etc). If there is a loop, this move wait isn't valid.
// Quick outs. If nobody is waiting on this unit, then by implication this won't
// be a cycle. Likewise if the unit we want to wait on isn't waiting, then by
// implication this won't be a cycle.
if (!(m_wfMunt & kfMuntMoveWaitingNearby))
return false;
// Special case: if pmunt is attacking then not a wait cycle since it
// isn't mobile.
if (pmunt->m_st == kstAttack)
return false;
// Start at pmunt and see if we loop back to the current unit
int cCycle = 0;
MobileUnitGob *pmuntT = pmunt;
while (pmuntT != NULL) {
// If we've looped back to ourselves, this is a cycle
if (pmuntT == this)
return true;
// If this unit isn't move waiting, not a cycle
if (!(pmuntT->m_wfMunt & kfMuntMoveWait))
return false;
// Cycle to who pmuntT is waiting on
// Because of evaulation order it is possible that a pmuntT is waiting on a
// tile that is not reserved because the reserving munt started a transition
// during this update cycle before pmuntT gob executed. This is a break in the cycle;
// whatever gob takes this tile eventually will recheck for cycles, so it is
// ok to assume this condition is not a cycle.
pmuntT = GetReservingUnit(pmuntT->m_txDst, pmuntT->m_tyDst);
if (pmuntT == NULL)
return false;
// Break out of we've looped too much.
if (cCycle++ > 15)
return false;
}
return false;
}
bool MobileUnitGob::MoveWaitForUnit(MobileUnitGob *pmunt, TCoord tx, TCoord ty)
{
// Invalid if this creates a cycle
if (IsMoveWaitCycle(pmunt))
return false;
// This unit will "move wait".
m_wfMunt |= kfMuntMoveWait;
m_txDst = tx;
m_tyDst = ty;
pmunt->m_wfMunt |= kfMuntMoveWaitingNearby;
return true;
}
bool MobileUnitGob::CheckDestinationReached()
{
// If we're where we want to be, we're done
TCoord tx = TcFromWc(m_wx);
TCoord ty = TcFromWc(m_wy);
if (tx == TcFromWc(m_wptTarget.wx) && ty == TcFromWc(m_wptTarget.wy))
return true;
// Not at dest if we have no path
if (m_ppathUnit == NULL)
return false;
// See if we're "close enough"
TCoord txTargetCenter = TcFromWc(m_wptTargetCenter.wx);
TCoord tyTargetCenter = TcFromWc(m_wptTargetCenter.wy);
int dtx = abs(tx - txTargetCenter);
if (dtx >= ARRAYSIZE(gmpDistFromDxy))
return false;
int dty = abs(ty - tyTargetCenter);
if (dty >= ARRAYSIZE(gmpDistFromDxy))
return false;
if (gmpDistFromDxy[dtx][dty] > m_tcTargetRadius)
return false;
// We're close enough; need to check if there is terrain between this unit
// and the center. If so we're not at the dest.
TerrainMap *ptrmap = gsim.GetLevel()->GetTerrainMap();
if (ptrmap->IsLineOccupied(tx, ty, txTargetCenter, tyTargetCenter, 0))
return false;
return true;
}
bool MobileUnitGob::PrepareNextTransition(TPoint *ptptNext, int *pnMoveResult)
{
Assert(!InTransition());
// First clear move wait since we'll be making a fresh decision
m_wfMunt &= ~kfMuntMoveWait;
// Get the viable transition locations
TPoint atpt[8];
int ctpt = GetNextLocations(atpt);
if (ctpt == 0) {
// Check to see if we're at a replicator input point. If so move wait until the replicator
// takes over
if (CheckReplicatorPoint()) {
m_wfMunt |= kfMuntAtReplicatorInput | kfMuntMoveWait;
*pnMoveResult = knMoveTargetReached;
return false;
}
// No move candidates. Are we close enough to the target?
if (CheckDestinationReached()) {
*pnMoveResult = knMoveTargetReached;
return false;
}
// No such luck. We're stuck.
m_wfMunt |= kfMuntStuck;
*pnMoveResult = knMoveStuck;
return false;
}
// See if our first choice is clear; if so use it (common case)
byte bf = GetTileFlags(atpt[0].tx, atpt[0].ty);
if (!(bf & (kbfStructure | kbfMobileUnit))) {
*ptptNext = atpt[0];
m_unvl.MinSkip();
*pnMoveResult = knMoveMoving;
return true;
}
// Find reserving munts once
MobileUnitGob *apmunt[8];
byte abf[8];
for (int itpt = 0; itpt < ctpt; itpt++) {
// Structure or mobile unit?
abf[itpt] = GetTileFlags(atpt[itpt].tx, atpt[itpt].ty);
if (abf[itpt] & kbfMobileUnit) {
apmunt[itpt] = GetReservingUnit(atpt[itpt].tx, atpt[itpt].ty);
#ifdef DEBUG
if (apmunt[itpt] == NULL) {
Assert();
}
if (apmunt[itpt] == this) {
Assert();
}
#endif
//Assert(apmunt[itpt] != NULL && apmunt[itpt] != this);
} else {
apmunt[itpt] = NULL;
}
}
// Check open tiles or move wait opportunities
if (ContinueMoveWaiting(apmunt, abf, atpt, ctpt, ptptNext)) {
if (IsMoveWaiting()) {
*pnMoveResult = knMoveWaiting;
return false;
}
return true;
}
// All the move locations are currently occupied. Let's see if we're close
// enough to the destination already.
if (CheckDestinationReached()) {
*pnMoveResult = knMoveTargetReached;
return false;
}
// Need to deal with blockers.
if (HandleCollision(apmunt, abf, atpt, ctpt)) {
// If move waiting, go into wait mode
if (IsMoveWaiting()) {
*pnMoveResult = knMoveWaiting;
return false;
}
// Otherwise wait an update so that the new path options get
// re-evaluated
m_unvl.MinSkip();
*pnMoveResult = knMoveMoving;
return false;
}
// Nothing!
m_wfMunt |= kfMuntStuck;
*pnMoveResult = knMoveStuck;
return false;
}
bool MobileUnitGob::HandleCollision(MobileUnitGob **apmunt, byte *abf, TPoint *atpt, int ctpt)
{
// All locations are blocked. We need to know who we're colliding with
// Ask the standing blocker to move out of the way. This usually works
// if the unit isn't an enemy and isn't attacking
int itpt;
for (itpt = 0; itpt < ctpt; itpt++) {
MobileUnitGob *pmunt = apmunt[itpt];
if (pmunt == NULL)
continue;
if (!pmunt->IsMobile()) {
if (PlanMoveAsidePath(pmunt, atpt[itpt].tx, atpt[itpt].ty))
return true;
}
}
// Try to locally path around. Not important to check all
// units since the first path will work around the other choices.
if (FindLocalAvoidPath(atpt[0].tx, atpt[0].ty))
return true;
// Find a friendly attacking unit to wait on
for (itpt = 0; itpt < ctpt; itpt++) {
if (apmunt[itpt] == NULL)
continue;
if (apmunt[itpt]->m_st == kstAttack && apmunt[itpt]->IsAlly(GetSide()))
if (MoveWaitForUnit(apmunt[itpt], atpt[itpt].tx, atpt[itpt].ty))
return true;
}
return false;
}
bool MobileUnitGob::ContinueMoveWaiting(MobileUnitGob **apmunt, byte *abf, TPoint *atpt, int ctpt, TPoint *ptptNext)
{
int itpt;
#if 0
for (itpt = 0; itpt < ctpt; itpt++) {
// If the dest is not reserved, take it
byte bf = abf[itpt];
if (!(bf & (kbfStructure | kbfMobileUnit))) {
*ptptNext = atpt[itpt];
return true;
}
// If not a mobile unit, can't move wait on it
if (!(bf & kbfMobileUnit))
continue;
// Deprioritize moving waiting on a munt that is still in transition
MobileUnitGob *pmunt = apmunt[itpt];
if (!pmunt->InTransition() && pmunt->IsMobile()) {
if (MoveWaitForUnit(pmunt, atpt[itpt].tx, atpt[itpt].ty))
return true;
}
}
#endif
// Didn't find anything; try move waiting on the first possible unit
for (itpt = 0; itpt < ctpt; itpt++) {
// If the dest is not reserved, take it
byte bf = abf[itpt];
if (!(bf & (kbfStructure | kbfMobileUnit))) {
*ptptNext = atpt[itpt];
return true;
}
// Deprioritize moving waiting on a munt that is still in transition
MobileUnitGob *pmunt = apmunt[itpt];
if (pmunt != NULL && pmunt->IsMobile()) {
if (MoveWaitForUnit(pmunt, atpt[itpt].tx, atpt[itpt].ty))
return true;
}
}
return false;
}
bool MobileUnitGob::FindLocalAvoidPath(TCoord tx, TCoord ty)
{
// Get the trackpoint for the current location
TCoord txFrom = TcFromWc(m_wx);
TCoord tyFrom = TcFromWc(m_wy);
TrackPoint trkpStart;
if (!trkpStart.Init(m_ppathUnit, txFrom, tyFrom, m_itptPath, 3))
return false;
Direction dirBlocked = DirectionFromLocations(txFrom, tyFrom, tx, ty);
// Find a closer track point.
#define kcStepsAvoid 15
// Try clockwise first
TPoint atptCW[kcStepsAvoid];
TrackPoint trkpCW;
int ctptCW = FindCloserTrackPoint(&trkpStart, dirBlocked, true, kcStepsAvoid, atptCW, &trkpCW);
// Counter-clockwise
TPoint atptCCW[kcStepsAvoid];
TrackPoint trkpCCW;
int ctptCCW = FindCloserTrackPoint(&trkpStart, dirBlocked, false, kcStepsAvoid, atptCCW, &trkpCCW);
// Which one to use?
int ctptUse;
TPoint *atptUse;
bool fSuccess = true;
if (ctptCCW > 0) {
// CCW is valid. Is CW valid? If so compare length
if (ctptCW > 0) {
// Both valid. Which is shorter?
if (ctptCW < ctptCCW) {
ctptUse = ctptCW;
atptUse = atptCW;
} else {
ctptUse = ctptCCW;
atptUse = atptCCW;
}
} else {
// CW is invalid, CCW is valid
ctptUse = ctptCCW;
atptUse = atptCCW;
}
} else {
// CCW is invalid. If CW is valid; use it
if (ctptCW > 0) {
ctptUse = ctptCW;
atptUse = atptCW;
} else {
// Both invalid
return false;
}
}
// Create a path from this and use it
// If our edge finder approach worked, use it
TerrainMap *ptrmap = gsim.GetLevel()->GetTerrainMap();
Path *ppath = NULL;
if (fSuccess) {
Assert(ctptUse != 0);
ppath = CreatePath(ptrmap, txFrom, tyFrom, atptUse, ctptUse);
if (ppath != NULL) {
// Success. This "avoid" path gets us further along the unit path.
delete m_ppathAvoid;
m_ppathAvoid = ppath;
return true;
}
}
return false;
}
// Map the last direction travelled to next directions to scan
Direction gmpDirLastDirScanStartCW[] = { kdirNE, kdirSE, kdirSE, kdirSW, kdirSW, kdirNW, kdirNW, kdirNE };
Direction gmpDirLastDirScanEndCW[] = { (kdirNE - 5) & 7, (kdirSE - 5) & 7, (kdirSE - 5) & 7, (kdirSW - 5) & 7, (kdirSW - 5) & 7, (kdirNW - 5) & 7, (kdirNW - 5) & 7, (kdirNE - 5) & 7};
Direction gmpDirLastDirScanStartCCW[] = { kdirNW, kdirNW, kdirNE, kdirNE, kdirSE, kdirSE, kdirSW, kdirSW };
Direction gmpDirLastDirScanEndCCW[] = { (kdirNW + 5) & 7, (kdirNW + 5) & 7, (kdirNE + 5) & 7, (kdirNE + 5) & 7, (kdirSE + 5) & 7, (kdirSE + 5) & 7, (kdirSW + 5) & 7, (kdirSW + 5) & 7 };
int MobileUnitGob::FindCloserTrackPoint(TrackPoint *ptrkpStart, Direction dirBlocked, bool fClockwise, int ctpt, TPoint *atpt, TrackPoint *ptrkpNew)
{
Direction *pmpDirLastDirScanStart;
Direction *pmpDirLastDirScanEnd;
Direction dirStart, dirEnd;
int ddir;
if (fClockwise) {
pmpDirLastDirScanStart = gmpDirLastDirScanStartCW;
pmpDirLastDirScanEnd = gmpDirLastDirScanEndCW;
dirStart = (dirBlocked - 1) & 7;
dirEnd = (dirStart - 7) & 7;
ddir = -1;
} else {
pmpDirLastDirScanStart = gmpDirLastDirScanStartCCW;
pmpDirLastDirScanEnd = gmpDirLastDirScanEndCCW;
dirStart = (dirBlocked + 1) & 7;
dirEnd = (dirStart + 7) & 7;
ddir = 1;
}
TCoord ctx, cty;
ggobm.GetMapSize(&ctx, &cty);
TerrainMap *ptrmap = gsim.GetLevel()->GetTerrainMap();
int itptClosestSearch = m_itptPath > 0 ? m_itptPath - 1 : 0;
ptrkpNew->InitFrom(ptrkpStart);
int ctptLastGood = 0;
TCoord txFrom = TcFromWc(m_wx);
TCoord tyFrom = TcFromWc(m_wy);
TCoord txLast = txFrom;
TCoord tyLast = tyFrom;
for (int itpt = 0; itpt < ctpt; itpt++) {
Direction dirBest = kdirInvalid;
for (Direction dirNew = dirStart; dirNew != dirEnd; dirNew = (dirNew + ddir) & 7) {
// Valid TCoord?
TCoord txNew = txLast + g_mpDirToDx[dirNew];
TCoord tyNew = tyLast + g_mpDirToDy[dirNew];
if (txNew < 0 || txNew >= ctx)
continue;
if (tyNew < 0 || tyNew >= cty)
continue;
// Is tile blocked? Can't rely on moving or standing units getting
// out of the way because they may not be able to
if (ptrmap->IsBlocked(txNew, tyNew, kbfStructure | kbfMobileUnit))
continue;
// Open tile, we're there
dirBest = dirNew;
break;
}
// Anything?
if (dirBest == kdirInvalid)
return ctptLastGood;
// If this point is further along the path than our last track point
// then remember this position
atpt[itpt].tx = txLast + g_mpDirToDx[dirBest];
atpt[itpt].ty = tyLast + g_mpDirToDy[dirBest];
TrackPoint trkpT;
if (trkpT.Init(m_ppathUnit, atpt[itpt].tx, atpt[itpt].ty, itptClosestSearch, ctpt)) {
// Remember this as the last best progress point
if (ptrkpNew->IsProgress(&trkpT)) {
#if 0
// Remembers a "last best" progress point without knowing access to the unit
// path is possible from there
ptrkpNew->InitFrom(&trkpT);
ctptLastGood = itpt + 1;
#else
// If the unit path is accessible from here, return this path
TPoint tptClosest;
trkpT.GetClosestPoint(&tptClosest);
if (!ptrmap->IsLineOccupied(atpt[itpt].tx, atpt[itpt].ty, tptClosest.tx, tptClosest.ty, kbfStructure)) {
ptrkpNew->InitFrom(&trkpT);
return itpt + 1;
}
#endif
}
}
// Go to next point in path
txLast = atpt[itpt].tx;
tyLast = atpt[itpt].ty;
dirStart = pmpDirLastDirScanStart[dirBest];
dirEnd = pmpDirLastDirScanEnd[dirBest];
}
return ctptLastGood;
}
MoveDirections s_movd[50];
word s_nSeqMoveAside;
bool MobileUnitGob::PlanMoveAsidePath(MobileUnitGob *pmuntBlocking, TCoord txTo, TCoord tyTo)
{
// Enemies won't move aside
if (!IsAlly(pmuntBlocking->GetSide()))
return false;
// If attacking, won't move aside
if (pmuntBlocking->m_st == kstAttack)
return false;
// Calc the direction this unit is trying to move. In general we prioritize
// moving orthogonal to this direction, then backwards, then forwards.
TCoord txFrom = TcFromWc(m_wx);
TCoord tyFrom = TcFromWc(m_wy);
Direction dirMover = DirectionFromLocations(txFrom, tyFrom, txTo, tyTo);
// Before starting inc the sequence number; this'll make sure we don't
// visit gobs that have already been enumerated.
s_nSeqMoveAside++;
// Mark this unit so that it isn't asked to move aside (no backwards
// support at the moment)
m_nSeqMoveAside = s_nSeqMoveAside;
// Add the first one to the list
MoveDirections *pmovd = s_movd;
pmuntBlocking->m_nSeqMoveAside = s_nSeqMoveAside;
if (!pmuntBlocking->GetMoveAsideDirections(dirMover, pmovd))
return false;
// Enumerate mobile units in a chain until an open space to move to is
// found.
TCoord ctx, cty;
ggobm.GetMapSize(&ctx, &cty);
bool fFound = false;
while (true) {
// Enumerate the possible moving directions for this gob.
TCoord txUnit = TcFromWc(pmovd->pmunt->m_wx);
TCoord tyUnit = TcFromWc(pmovd->pmunt->m_wy);
MoveDirections *pmovdT = pmovd;
for (; pmovd->idir < pmovd->cdir; pmovd->idir++) {
// Enumerate the directions in order provided, get that munt
Direction dir = pmovd->adir[pmovd->idir];
TCoord txNext = txUnit + g_mpDirToDx[dir];
TCoord tyNext = tyUnit + g_mpDirToDy[dir];
if (txNext < 0 || txNext >= ctx || tyNext < 0 || tyNext >= cty)
continue;
MobileUnitGob *pmuntNext = GetMobileUnitAt(txNext, tyNext);
// If this space is open we're done and we have a path we
// can create
if (pmuntNext == NULL) {
fFound = true;
break;
}
// Is there room to add another? If not then continue.
if ((pmovd + 1) - s_movd >= ARRAYSIZE(s_movd))
continue;
// Is it an ally? Enemies won't move aside
if (!IsAlly(pmuntNext->GetSide()))
continue;
// Attacking? If attacking, won't move aside
if (pmuntNext->m_st == kstAttack)
continue;
// Try to add it to the list. It'll get added if it can move
// in this direction.
if (!pmuntNext->GetMoveAsideDirections(dirMover, pmovd + 1))
continue;
// Added ok; advance pmovd to this new entry, break out of
// loop to re-enter
pmovd++;
break;
}
// If another was added to the list, enumerate it
if (pmovd != pmovdT)
continue;
// If we found a path, we're done; break out
if (fFound)
break;
// Couldn't find a munt from this munt that would move aside. Go back
// to the previous munt and continue enumerating if there are any left
if (pmovd == s_movd)
break;
pmovd--;
pmovd->idir++;
}
if (!fFound)
return false;
// Walk backwards through the move directions asking units to move aside
for (MoveDirections *pmovdT = pmovd; pmovdT >= s_movd; pmovdT--) {
if (!pmovdT->pmunt->AcceptMoveAsideRequest(pmovdT->adir[pmovdT->idir]))
return false;
}
// This unit needs to move wait on the column that is moving aside
MoveWaitForUnit(pmuntBlocking, txTo, tyTo);
return true;
}
MobileUnitGob *MobileUnitGob::GetMobileUnitAt(TCoord tx, TCoord ty)
{
// Enum gids here
for (Gid gid = ggobm.GetFirstGid(tx, ty); gid != kgidNull; gid = ggobm.GetNextGid(gid)) {
// Any mobile units in this tile?
MobileUnitGob *pmunt = (MobileUnitGob *)ggobm.GetGob(gid);
if (pmunt == NULL)
continue;
if (!(pmunt->m_ff & kfGobMobileUnit))
continue;
if (pmunt->IsMobile())
continue;
if (pmunt->IsMoveWaiting())
continue;
return pmunt;
}
return NULL;
}
// Move asides look directional independently in this order: sides first, backwards, then forwards
Direction s_mpDirToDirsSorted[8][8] = {
{ kdirW, kdirE, kdirSW, kdirSE, kdirNW, kdirNE, kdirS, kdirN, },
{ kdirNW, kdirSE, kdirW, kdirS, kdirN, kdirE, kdirSW, kdirNE, },
{ kdirN, kdirS, kdirNW, kdirSW, kdirNE, kdirSE, kdirW, kdirE, },
{ kdirNE, kdirSW, kdirN, kdirW, kdirE, kdirS, kdirNW, kdirSE, },
{ kdirE, kdirW, kdirNE, kdirNW, kdirSE, kdirSW, kdirN, kdirS, },
{ kdirSE, kdirNW, kdirE, kdirN, kdirS, kdirW, kdirNE, kdirSW, },
{ kdirS, kdirN, kdirSE, kdirNE, kdirSW, kdirNW, kdirE, kdirW, },
{ kdirSW, kdirNE, kdirS, kdirE, kdirW, kdirN, kdirSE, kdirNW, },
};
// NOTE: Should this be overridable (virtual or a message)?
bool MobileUnitGob::GetMoveAsideDirections(Direction dirMover, MoveDirections *pmovd)
{
// Special hack for miners
if (m_st == kstMinerRotateForEntry)
return false;
// Initialize
pmovd->pmunt = this;
pmovd->cdir = 0;
pmovd->idir = 0;
// If this unit is being asked for move aside directions it has already been
// marked, assert if not true.
Assert(m_nSeqMoveAside == s_nSeqMoveAside);
// Fill in reasonable move directons into pmovd.
TCoord ctx, cty;
ggobm.GetMapSize(&ctx, &cty);
Direction *adir = s_mpDirToDirsSorted[dirMover];
TCoord txFrom = TcFromWc(m_wx);
TCoord tyFrom = TcFromWc(m_wy);
// Enumerate directions
TerrainMap *ptrmap = gsim.GetLevel()->GetTerrainMap();
for (int idir = 0; idir < 8; idir++) {
// Get mobile unit at this location
Direction dir = adir[idir];
TCoord tx = txFrom + g_mpDirToDx[dir];
TCoord ty = tyFrom + g_mpDirToDy[dir];
if (tx < 0 || tx >= ctx || ty < 0 || ty >= cty)
continue;
// If in attack state this new move aside position is only
// plausible if it is still in range
// Is this location blocked by terrain?
if (ptrmap->IsBlocked(tx, ty, kbfStructure))
continue;
// See what munt is there if any
MobileUnitGob *pmunt = GetMobileUnitAt(tx, ty);
// Prioritize empty
if (pmunt == NULL) {
// Even if there is no munt there, the tile may be reserved by
// another munt that is in transition to this tile. If the tile is
// reserved, it is not a suitable destination
if (IsTileReserved(tx, ty))
continue;
pmovd->adir[0] = dir;
pmovd->cdir = 1;
return true;
}
// Already been asked? If not it is fresh meat
if (pmunt->m_nSeqMoveAside == s_nSeqMoveAside)
continue;
pmunt->m_nSeqMoveAside = s_nSeqMoveAside;
// Mobile? Only non-mobile munts are candidates
if (pmunt->IsMobile())
continue;
// Either a viable munt is there or nothing is there, in any case add
// it to the list
pmovd->adir[pmovd->cdir++] = dir;
}
return pmovd->cdir != 0;
}
bool MobileUnitGob::AcceptMoveAsideRequest(Direction dir)
{
// This unit has agreed to move aside in this direction and is now being
// asked to do so.
// Two cases: either the unit is standing or move waiting. If it is
// standing then it can just move out of the way.
TCoord txFrom = TcFromWc(m_wx);
TCoord tyFrom = TcFromWc(m_wy);
TCoord txTo = txFrom + g_mpDirToDx[dir];
TCoord tyTo = tyFrom + g_mpDirToDy[dir];
// Unit not move waiting.
// Move in this direction. This has already been evaluated to be safe and
// sane.
Assert(m_ppathUnit == NULL);
Assert(!IsMoveWaiting());
WCoord wxDst = WcFromTc(txTo) + kwcTileHalf;
WCoord wyDst = WcFromTc(tyTo) + kwcTileHalf;
// Issue move command. These occur in order so earlier units can move wait
// on later units. We don't want overridden behavior (such as the miner
// mining galaxite) to interfere with move aside, so call
// MobileUnitGob::SetTarget directly.
MobileUnitGob::SetTarget(kgidNull, WcFromTc(txTo) + kwcTileHalf, WcFromTc(tyTo) + kwcTileHalf);
return true;
}
int MobileUnitGob::GetNextLocations(TPoint *atpt)
{
// Use avoid path if it exists
int cStepsFurtherStop = 3;
if (m_ppathAvoid != NULL) {
// Are we at the destination yet?
TCoord txFrom = TcFromWc(m_wx);
TCoord tyFrom = TcFromWc(m_wy);
TPoint tptT;
bool fEnd = false;
if (m_ppathAvoid->GetPoint(m_ppathAvoid->GetCount() - 1, &tptT, 0)) {
if (tptT.tx == txFrom && tptT.ty == tyFrom)
fEnd = true;
} else {
fEnd = true;
}
// If we still have more to avoid, do it
if (!fEnd) {
int itptDummy = 0;
int ctpt = GetNextLocations2(m_ppathAvoid, &itptDummy, m_ppathAvoid->GetCount(), atpt);
if (ctpt != 0)
return ctpt;
}
// Avoid path is done; delete it
cStepsFurtherStop = _min(cStepsFurtherStop, m_ppathAvoid->GetCount());
delete m_ppathAvoid;
m_ppathAvoid = NULL;
}
// Use unit path
int ctpt = GetNextLocations2(m_ppathUnit, &m_itptPath, cStepsFurtherStop, atpt);
if (ctpt == 0)
return 0;
if (m_itptPath > 1)
m_ppathUnit->SetCacheIndex(m_itptPath - 2);
return ctpt;
}
int MobileUnitGob::GetNextLocations2(Path *ppath, int *pitptStart, int cStepsFurtherStop, TPoint *atpt)
{
// The current progress is measured relative to the unit path. Then
// surrounding locations are profiled for making better progress. Positions
// with better progress are returned as move destinations.
// Initialize a trackpoint
TCoord txFrom = TcFromWc(m_wx);
TCoord tyFrom = TcFromWc(m_wy);
Assert(GetReservingUnit(txFrom, tyFrom) == this);
Assert(IsTileReserved(txFrom, tyFrom));
TrackPoint trkpStart;
if (!trkpStart.Init(ppath, txFrom, tyFrom, *pitptStart, cStepsFurtherStop))
return false;
TPoint tptClosest;
trkpStart.GetClosestPoint(&tptClosest);
int itptClosest = trkpStart.GetClosestPointIndex();
*pitptStart = itptClosest;
// If we're at the end we're done
if (itptClosest == ppath->GetCount() - 1) {
if (tptClosest.tx == txFrom && tptClosest.ty == tyFrom)
return 0;
}
// See if we're close to the next step already
TPoint tptNext;
if (ppath->GetPoint(itptClosest + 1, &tptNext, kbfStructure | kbfMobileUnit)) {
// Have a next step and it is free of obstacles. If we're on the path
// then we're close to it, return it
if (abs(tptNext.tx - txFrom) <= 1 && abs(tptNext.ty - tyFrom) <= 1) {
Assert(tptNext.tx != txFrom || tptNext.ty != tyFrom);
atpt[0] = tptNext;
return 1;
}
}
// Maybe not actually on "next" but closer to closest. If so take it
byte bf = GetTileFlags(tptClosest.tx, tptClosest.ty);
if (!(bf & (kbfStructure | kbfMobileUnit))) {
if (abs(tptClosest.tx - txFrom) <= 1 && abs(tptClosest.ty - tyFrom) <= 1) {
Assert(tptClosest.tx != txFrom || tptClosest.ty != tyFrom);
atpt[0] = tptClosest;
return 1;
}
}
// Get after point of trackpoint
TPoint tptAfter;
trkpStart.GetAfterPoint(&tptAfter);
// Enumerate the surrounding tiles and measure each for progress along the
// unit path.
int itptClosestMeasure = (itptClosest == 0 ? 0 : itptClosest - 1);
TrackPoint atrkp[8];
int ctpt = 0;
TCoord ctx, cty;
ggobm.GetMapSize(&ctx, &cty);
TerrainMap *ptrmap = gsim.GetLevel()->GetTerrainMap();
for (Direction dir = 0; dir < 8; dir++) {
TCoord tx = txFrom + g_mpDirToDx[dir];
TCoord ty = tyFrom + g_mpDirToDy[dir];
if (tx < 0 || tx >= ctx || ty < 0 || ty >= cty)
continue;
if (ptrmap->IsBlocked(tx, ty, 0))
continue;
atrkp[ctpt].Init(ppath, tx, ty, itptClosestMeasure, 1);
if (trkpStart.IsProgress(&atrkp[ctpt])) {
// If blocked by terrain, not valid progress
if (ptrmap->IsLineOccupied(tx, ty, tptAfter.tx, tptAfter.ty, 0))
continue;
// Looks like a good candidate
ctpt++;
}
}
// If there are no unblocked positions, take the next one even if it is
// blocked and callers will try and move around it.
if (ctpt == 0) {
for (Direction dir = 0; dir < 8; dir++) {
TCoord tx = txFrom + g_mpDirToDx[dir];
TCoord ty = tyFrom + g_mpDirToDy[dir];
if (tx < 0 || tx >= ctx || ty < 0 || ty >= cty)
continue;
atrkp[0].Init(ppath, tx, ty, itptClosestMeasure, 1);
if (trkpStart.IsProgress(&atrkp[0])) {
atpt[0].tx = tx;
atpt[0].ty = ty;
return 1;
}
}
}
// Sort the candidates.
int an[8];
int i;
for (i = 0; i < ctpt; i++)
an[i] = i;
for (i = ctpt - 1; i >= 0; i--) {
for (int j = 1; j <= i; j++) {
if (atrkp[an[j - 1]].IsBetterSort(&atrkp[an[j]])) {
int n = an[j - 1];
an[j - 1] = an[j];
an[j] = n;
}
}
}
for (i = 0; i < ctpt; i++)
atrkp[an[i]].GetInitialPoint(&atpt[i]);
// Done
return ctpt;
}
bool MobileUnitGob::IsMobile()
{
if (m_ppathUnit == NULL && !(m_wfMunt & (kfMuntPathPending | kfMuntAtReplicatorInput)))
return false;
return true;
}
bool MobileUnitGob::CheckReplicatorPoint()
{
TCoord tx = TcFromWc(m_wx);
TCoord ty = TcFromWc(m_wy);
int cReplicators = ReplicatorGob::GetReplicatorCount();
for (int n = 0; n < cReplicators; n++) {
TPoint tptReplicator;
ReplicatorGob::GetReplicatorInputPoint(n, &tptReplicator);
if (tptReplicator.tx == tx && tptReplicator.ty == ty)
return true;
}
return false;
}
void MobileUnitGob::SetPosition(WCoord wx, WCoord wy)
{
// Check if nothing to do
if (m_wx == wx && m_wy == wy)
return;
// Notify waiters
if (!InTransition()) {
if (m_wfMunt & kfMuntMoveWaitingNearby)
NotifyMoveWaitingNearby(m_wx, m_wy);
}
// Mark for redraw if this gob has changed pixel locations
if (PcFromWc(wx) != PcFromWc(m_wx) || PcFromWc(wy) != PcFromWc(m_wy))
MarkRedraw();
// Be sure not to sleep on this important new development
m_unvl.MinSkip();
// Keep GobMgr in the loop so it can maintain proper depth sorting
bool fTileChange = ggobm.MoveGob(this, m_wx, m_wy, wx, wy);
WCoord wxOld = m_wx;
WCoord wyOld = m_wy;
m_wx = wx;
m_wy = wy;
// Special tasks if we've moved between tiles
if (fTileChange) {
// Reveal fog
if (m_pplr == gpplrLocal) {
WCoord wxView, wyView;
gsim.GetViewPos(&wxView, &wyView);
FogMap *pfogm = gsim.GetLevel()->GetFogMap();
TPoint tpt;
GetTilePosition(&tpt);
RevealPattern *prvlp = (RevealPattern *)(m_puntc->wf & kfUntcLargeDefog ? grvlpLarge : grvlp);
pfogm->Reveal(tpt.tx, tpt.ty, prvlp, gpupdSim, wxView, wyView);
}
// Update minimap
if (gpmm != NULL) {
TRect trc;
trc.left = wxOld < wx ? TcFromWc(wxOld) : TcFromWc(wx);
trc.top = wyOld < wy ? TcFromWc(wyOld) : TcFromWc(wy);
trc.right = (wxOld > wx ? TcFromWc(wxOld) : TcFromWc(wx)) + 1;
trc.bottom = (wyOld > wy ? TcFromWc(wyOld) : TcFromWc(wy)) + 1;
gpmm->RedrawTRect(&trc);
}
// Notify enemy of this gob nearby
NotifyEnemyNearby();
// Move between areas
AreaMask amOld = ggobm.CalcAreaMask(TcFromWc(wxOld), TcFromWc(wyOld));
AreaMask amNew = ggobm.CalcAreaMask(TcFromWc(m_wx), TcFromWc(m_wy));
ggobm.MoveGobBetweenAreas(m_gid, amOld, amNew);
}
}
UnitGob *MobileUnitGob::FindValidTargetInArea(int nArea)
{
Enum enm;
while (true) {
Gob *puntTarget = ggobm.EnumGobsInArea(&enm, m_msgAction.GuardAreaCommand.nArea, ~m_pplr->GetAllies(), kumMobileUnits);
if (puntTarget == NULL)
return NULL;
if (IsValidTarget(puntTarget)) {
Assert(puntTarget->GetFlags() & kfGobUnit);
return (UnitGob *)puntTarget;
}
}
}
#ifdef MP_DEBUG_SHAREDMEM
void MobileUnitGob::MPValidate()
{
MPValidateGobMember(MobileUnitGob, m_dir);
MPValidateGobMember(MobileUnitGob, m_dirNext);
MPValidateGobMember(MobileUnitGob, m_gidTarget);
MPValidateGobMember(MobileUnitGob, m_tLastFire);
MPValidateGobMember(MobileUnitGob, m_msgPending);
MPValidateGobMember(MobileUnitGob, m_msgAction);
MPValidateGobMember(MobileUnitGob, m_cCountdown);
MPValidateGobMember(MobileUnitGob, m_cMoveStepsRemaining);
MPValidateGobMember(MobileUnitGob, m_tptChaseInitial);
MPValidateGobMember(MobileUnitGob, m_mua);
MPValidateGobMember(MobileUnitGob, m_muaPending);
MPValidateGobMember(MobileUnitGob, m_wfMunt);
MPValidateGobMember(MobileUnitGob, m_stPending);
MPValidateGobMember(MobileUnitGob, m_cupdLastHitOrNearbyAllyHit);
MPValidateGobMember(MobileUnitGob, m_wxDst);
MPValidateGobMember(MobileUnitGob, m_wyDst);
MPValidateGobMember(MobileUnitGob, m_txDst);
MPValidateGobMember(MobileUnitGob, m_tyDst);
MPValidateGobMember(MobileUnitGob, m_wptTarget);
MPValidateGobMember(MobileUnitGob, m_nSeqMoveAside);
MPValidateGobMember(MobileUnitGob, m_itptPath);
MPValidateGobMember(MobileUnitGob, m_wptTargetCenter);
MPValidateGobMember(MobileUnitGob, m_tcTargetRadius);
MPValidateGobMember(MobileUnitGob, m_wcMoveDistPerUpdate);
UnitGob::MPValidate();
}
#endif
} // namespace wi