mirror of
https://github.com/spiffcode/hostile-takeover.git
synced 2025-12-16 12:08:36 +00:00
615 lines
16 KiB
C++
615 lines
16 KiB
C++
#include "ht.h"
|
|
|
|
namespace wi {
|
|
|
|
static int s_anGunTowerTurretStripIndices[16] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
|
|
|
|
//
|
|
// Abstract base class for Towers
|
|
//
|
|
|
|
bool TowerGob::InitClass(TowerConsts *ptwrc, IniReader *pini)
|
|
{
|
|
if (!StructGob::InitClass(ptwrc, pini))
|
|
return false;
|
|
|
|
ptwrc->anFiringStripIndices = s_anGunTowerTurretStripIndices;
|
|
ptwrc->wf |= kfUntcNotifyEnemyNearby | kfUntcNotifyPowerLowHigh;
|
|
|
|
char szTemplate[10];
|
|
itoa(ptwrc->gt, szTemplate, 10);
|
|
return true;
|
|
}
|
|
|
|
void TowerGob::ExitClass(TowerConsts *ptwrc)
|
|
{
|
|
StructGob::ExitClass(ptwrc);
|
|
}
|
|
|
|
TowerGob::TowerGob(TowerConsts *ptwrc) : StructGob(ptwrc)
|
|
{
|
|
m_tLastFire = 0;
|
|
m_gidTargetPrimary = kgidNull;
|
|
m_gidTargetSecondary = kgidNull;
|
|
m_wptTarget.wx = kwxInvalid; // kxInvalid = no target location
|
|
|
|
// Turret stuff
|
|
|
|
m_dir16Turret = kdirS * 2;
|
|
m_aniTurret.Init(m_ptwrc->panid);
|
|
SetAnimationStrip(&m_aniTurret, m_ptwrc->anFiringStripIndices[m_dir16Turret]);
|
|
}
|
|
|
|
#define knVerTowerGobState 1
|
|
bool TowerGob::LoadState(Stream *pstm)
|
|
{
|
|
byte nVer = pstm->ReadByte();
|
|
if (nVer != knVerTowerGobState)
|
|
return false;
|
|
m_dir16Turret = pstm->ReadByte();
|
|
m_aniTurret.Init(m_ptwrc->panid);
|
|
SetAnimationStrip(&m_aniTurret, m_ptwrc->anFiringStripIndices[m_dir16Turret]);
|
|
m_gidTargetPrimary = pstm->ReadWord();
|
|
m_gidTargetSecondary = pstm->ReadWord();
|
|
m_tLastFire = gsim.GetTickCount() - pstm->ReadDword();
|
|
m_wptTarget.wx = pstm->ReadWord();
|
|
m_wptTarget.wy = pstm->ReadWord();
|
|
return StructGob::LoadState(pstm);
|
|
}
|
|
|
|
bool TowerGob::SaveState(Stream *pstm)
|
|
{
|
|
pstm->WriteByte(knVerTowerGobState);
|
|
pstm->WriteByte(m_dir16Turret);
|
|
pstm->WriteWord(m_gidTargetPrimary);
|
|
pstm->WriteWord(m_gidTargetSecondary);
|
|
pstm->WriteDword((dword)(gsim.GetTickCount() - m_tLastFire));
|
|
pstm->WriteWord(m_wptTarget.wx);
|
|
pstm->WriteWord(m_wptTarget.wy);
|
|
return StructGob::SaveState(pstm);
|
|
}
|
|
|
|
void TowerGob::Activate()
|
|
{
|
|
NotifyEnemyNearby();
|
|
StructGob::Activate();
|
|
}
|
|
|
|
#ifdef DRAW_LINES
|
|
void TowerGob::DrawTargetLine(DibBitmap *pbm, int xViewOrigin, int yViewOrigin)
|
|
{
|
|
if (m_wptTarget.wx == kwxInvalid)
|
|
return;
|
|
|
|
WPoint wptCenter;
|
|
GetCenter(&wptCenter);
|
|
pbm->DrawLine(PcFromUwc(wptCenter.wx) - xViewOrigin, PcFromUwc(wptCenter.wy) - yViewOrigin,
|
|
PcFromUwc(m_wptTarget.wx) - xViewOrigin, PcFromUwc(m_wptTarget.wy) - yViewOrigin,
|
|
GetSideColor(m_pplr->GetSide()));
|
|
}
|
|
#endif
|
|
|
|
void TowerGob::Draw(DibBitmap *pbm, int xViewOrigin, int yViewOrigin, int nLayer)
|
|
{
|
|
// Draw base
|
|
|
|
StructGob::Draw(pbm, xViewOrigin, yViewOrigin, nLayer);
|
|
|
|
// Draw turret
|
|
|
|
if (nLayer == knLayerDepthSorted) {
|
|
|
|
// Don't draw the turret on top of the destroyed base
|
|
|
|
if (m_ani.GetStrip() != 2) {
|
|
Side side = m_pplr->GetSide();
|
|
if (m_ff & kfGobDrawFlashed)
|
|
side = (Side)-1;
|
|
else if (m_ff & kfGobBeingBuilt)
|
|
side = ksideNeutral;
|
|
|
|
// The turret is aligned with the base's special point
|
|
|
|
Point ptBaseSpecial;
|
|
m_ani.GetSpecialPoint(&ptBaseSpecial);
|
|
m_aniTurret.Draw(pbm, PcFromUwc(m_wx) - xViewOrigin + ptBaseSpecial.x,
|
|
PcFromUwc(m_wy) - yViewOrigin + ptBaseSpecial.y, side);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool TowerGob::IsTakeoverable(Player *pplr)
|
|
{
|
|
// towers are not Takeoverable
|
|
return false;
|
|
}
|
|
|
|
bool TowerGob::IsValidTarget(Gob *pgobTarget)
|
|
{
|
|
// most structures cannot attack, so bypass the struct class on this one
|
|
// TUNE:
|
|
// however, since we can't move, let's make sure the target is in range ?
|
|
//if IsGobWithinRange(pgobTarget, m_ptwrc->tcFiringRange)
|
|
|
|
return UnitGob::IsValidTarget(pgobTarget);
|
|
}
|
|
|
|
void TowerGob::SetTarget(Gid gid, WCoord wx, WCoord wy, WCoord wxCenter, WCoord wyCenter, TCoord tcRadius, WCoord wcMoveDistPerUpdate)
|
|
{
|
|
// If target doesn't exist anymore, ignore
|
|
|
|
Gob *pgobTarget = ggobm.GetGob(gid);
|
|
if (pgobTarget == NULL)
|
|
return;
|
|
|
|
// If target is an ally, ignore
|
|
|
|
if (IsAlly(pgobTarget->GetSide()))
|
|
return;
|
|
|
|
// Attack it!
|
|
// Flash the target Gob
|
|
|
|
if (!((UnitGob *)pgobTarget)->IsAlly(gpplrLocal->GetSide()))
|
|
pgobTarget->Flash();
|
|
|
|
// Queue message
|
|
|
|
Message msgT;
|
|
msgT.mid = kmidAttackCommand;
|
|
msgT.smidSender = m_gid;
|
|
msgT.smidReceiver = m_gid;
|
|
msgT.AttackCommand.wptTarget.wx = 0;
|
|
msgT.AttackCommand.wptTarget.wy = 0;
|
|
msgT.AttackCommand.gidTarget = gid;
|
|
gcmdq.Enqueue(&msgT);
|
|
}
|
|
|
|
void TowerGob::DefUpdate()
|
|
{
|
|
// Try to make the turret point toward the target
|
|
// UNDONE: unless we're already firing
|
|
|
|
// BUGBUG: this usage of the special point effectively makes the turret's direction
|
|
// resolution dependent which will break multiplayer games between devices of
|
|
// differing resolution.
|
|
|
|
Point ptSpecial;
|
|
m_ani.GetSpecialPoint(&ptSpecial);
|
|
WCoord wx = m_wx + WcFromPc(ptSpecial.x);
|
|
WCoord wy = m_wy + WcFromPc(ptSpecial.y);
|
|
|
|
if (m_wptTarget.wx != kwxInvalid) {
|
|
|
|
// If the target is in the tile we're in stop trying to look exactly at it
|
|
|
|
if (WcTrunc(wx) != WcTrunc(m_wptTarget.wx) || WcTrunc(wy) != WcTrunc(m_wptTarget.wy)) {
|
|
|
|
Direction16 dir16 = CalcDir16(m_wptTarget.wx - wx, m_wptTarget.wy - wy);
|
|
|
|
// Make sure we're facing the way we want to go
|
|
|
|
int d = dir16 - m_dir16Turret;
|
|
if (d != 0) {
|
|
if (d < -8)
|
|
d = 1;
|
|
else if (d > 8)
|
|
d = -1;
|
|
if (d < 0)
|
|
m_dir16Turret--;
|
|
else
|
|
m_dir16Turret++;
|
|
m_dir16Turret = ((unsigned int)m_dir16Turret) % 16;
|
|
SetAnimationStrip(&m_aniTurret, m_ptwrc->anFiringStripIndices[m_dir16Turret]);
|
|
m_unvl.MinSkip();
|
|
}
|
|
}
|
|
}
|
|
|
|
AdvanceAnimation(&m_aniTurret);
|
|
|
|
StructGob::DefUpdate();
|
|
}
|
|
|
|
UnitGob *TowerGob::FindEnemyNearby(TCoord tcRange)
|
|
{
|
|
// smart targeting requires a surveillance center and power
|
|
|
|
if (!m_pplr->IsPowerLow() && (m_pplr->GetUnitCount(kutRadar) > 0)) {
|
|
return UnitGob::FindEnemyNearby(tcRange);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
int TowerGob::ProcessStateMachineMessage(State st, Message *pmsg)
|
|
{
|
|
BeginStateMachine
|
|
OnMsg(kmidEnemyNearby)
|
|
// Notification that an enemy is nearby
|
|
|
|
RememberEnemyNearby(pmsg->EnemyNearby.gidEnemy);
|
|
m_unvl.MinSkip();
|
|
|
|
OnMsg(kmidAttackCommand)
|
|
// stash attack parameters to be picked up by the attack state
|
|
|
|
m_gidTargetPrimary = pmsg->AttackCommand.gidTarget;
|
|
SetState(kstAttack);
|
|
|
|
OnMsg(kmidFire)
|
|
UnitGob *puntTarget = (UnitGob *)ggobm.GetGob(m_gidTargetPrimary);
|
|
if (puntTarget == NULL || !IsGobWithinRange(puntTarget, m_ptwrc->tcFiringRange))
|
|
puntTarget = (UnitGob *)ggobm.GetGob(m_gidTargetSecondary);
|
|
if (puntTarget != NULL) {
|
|
// Fire off the shot!
|
|
|
|
WCoord wdxRnd = ((GetRandom() & 7) - 3) * kwcTile16th;
|
|
WCoord wdyRnd = ((GetRandom() & 7) - 3) * kwcTile16th;
|
|
|
|
Point ptTurretSpecial;
|
|
m_aniTurret.GetSpecialPoint(&ptTurretSpecial, 1);
|
|
Point ptBaseSpecial;
|
|
m_ani.GetSpecialPoint(&ptBaseSpecial);
|
|
|
|
WPoint wpt;
|
|
puntTarget->GetCenter(&wpt);
|
|
CreateBulletGob(m_wx + WcFromPc(ptBaseSpecial.x + ptTurretSpecial.x),
|
|
m_wy + WcFromPc(ptBaseSpecial.y + ptTurretSpecial.y),
|
|
wpt.wx + wdxRnd, wpt.wy + wdyRnd,
|
|
GetDamageTo(puntTarget) / 3, m_gid, puntTarget->GetId());
|
|
}
|
|
|
|
OnMsg(kmidHit)
|
|
// It is possible to be hit by someone without having a chance to target
|
|
// it first. The worse case of this is when an enemy is lost track of
|
|
// due to the short m_agidEnemyNearby. In any case, if we aren't already
|
|
// busy with something else attack the assailant.
|
|
|
|
if (m_gidTargetSecondary == kgidNull && !m_pplr->IsPowerLow() && m_pplr->GetUnitCount(kutRadar) > 0) {
|
|
m_gidTargetSecondary = pmsg->Hit.gidAssailant;
|
|
SetState(kstAttack);
|
|
}
|
|
|
|
return StructGob::ProcessStateMachineMessage(st, pmsg);
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
State(kstIdle)
|
|
OnEnter
|
|
m_wptTarget.wx = kwxInvalid; // no target
|
|
|
|
//OnMsg(kmidHit) - if power is low then we'll end up taking hits that
|
|
// we don't respond to. Sometimes we'll take a first hit before we respond too. C'est le guerre
|
|
|
|
OnUpdate
|
|
// any enemy in range?
|
|
|
|
UnitGob *puntTarget = FindEnemyNearby(m_ptwrc->tcFiringRange);
|
|
if (puntTarget != NULL) {
|
|
|
|
// let's have a little focus
|
|
|
|
// DWM: this can occur if the tower loses power while it has a secondary target
|
|
// It's OK because it just means the tower picks a new secondary when it wakes up
|
|
// Assert(m_gidTargetSecondary == kgidNull);
|
|
m_gidTargetSecondary = puntTarget->GetId();
|
|
SetState(kstAttack);
|
|
}
|
|
|
|
// Handle flashing, turret pointing, firing animation
|
|
|
|
DefUpdate();
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
State(kstAttack)
|
|
OnUpdate
|
|
|
|
// if power is low then we're just not going to do anything
|
|
|
|
UnitGob* puntTarget = NULL;
|
|
if (!GetOwner()->IsPowerLow()) {
|
|
|
|
// look for our primary (player-set) enemy first
|
|
|
|
if (m_gidTargetPrimary != kgidNull) {
|
|
puntTarget = (UnitGob *)ggobm.GetGob(m_gidTargetPrimary);
|
|
if (puntTarget == NULL || !IsValidTarget(puntTarget)) {
|
|
|
|
// primary enemy is dead/gone or taken over, we can forget it.
|
|
|
|
m_gidTargetPrimary = kgidNull;
|
|
puntTarget = NULL;
|
|
} else if (!IsGobWithinRange(puntTarget, m_ptwrc->tcFiringRange)) {
|
|
|
|
// primary target not in range
|
|
|
|
puntTarget = NULL;
|
|
}
|
|
}
|
|
|
|
if (puntTarget == NULL) {
|
|
|
|
// primary is not available, who can we shoot at in the meantime?
|
|
|
|
puntTarget = (UnitGob *)ggobm.GetGob(m_gidTargetSecondary);
|
|
if (puntTarget == NULL || !IsValidTarget(puntTarget) ||
|
|
!IsGobWithinRange(puntTarget, m_ptwrc->tcFiringRange)) {
|
|
|
|
// secondary target not available, new secondary?
|
|
|
|
puntTarget = FindEnemyNearby(m_ptwrc->tcFiringRange);
|
|
if (puntTarget != NULL)
|
|
m_gidTargetSecondary = puntTarget->GetId();
|
|
else
|
|
m_gidTargetSecondary = kgidNull;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (puntTarget != NULL) {
|
|
|
|
// we've got one, fire at the enemy
|
|
|
|
Point ptSpecial;
|
|
m_ani.GetSpecialPoint(&ptSpecial);
|
|
puntTarget->GetAttackPoint(&m_wptTarget);
|
|
Fire(puntTarget, m_wptTarget.wx, m_wptTarget.wy, m_wptTarget.wx - (m_wx + WcFromPc(ptSpecial.x)),
|
|
m_wptTarget.wy - (m_wy + WcFromPc(ptSpecial.y)));
|
|
} else {
|
|
|
|
// no valid targets in range
|
|
|
|
SetState(kstIdle);
|
|
}
|
|
|
|
// Handle flashing
|
|
|
|
DefUpdate();
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
State(kstDying)
|
|
// Override to avoid animating through the Galaxite level frames
|
|
OnUpdate
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
#if 0
|
|
EndStateMachineInherit(StructGob)
|
|
#else
|
|
return knHandled;
|
|
}
|
|
} else {
|
|
return (int)StructGob::ProcessStateMachineMessage(st, pmsg);
|
|
}
|
|
return (int)StructGob::ProcessStateMachineMessage(st, pmsg);
|
|
#endif
|
|
}
|
|
|
|
dword TowerGob::GetAnimationHash() {
|
|
dword dw = StructGob::GetAnimationHash();
|
|
return dw ^ (m_dir16Turret << 8);
|
|
}
|
|
|
|
void TowerGob::GetAnimationBounds(Rect *prc, bool fBase) {
|
|
StructGob::GetAnimationBounds(prc, fBase);
|
|
|
|
if (!fBase) {
|
|
Rect rcTurret;
|
|
m_aniTurret.GetBounds(&rcTurret);
|
|
Point ptBaseSpecial;
|
|
m_ani.GetSpecialPoint(&ptBaseSpecial);
|
|
rcTurret.Offset(ptBaseSpecial.x, ptBaseSpecial.y);
|
|
prc->Union(&rcTurret);
|
|
}
|
|
}
|
|
|
|
void TowerGob::DrawAnimation(DibBitmap *pbm, int x, int y)
|
|
{
|
|
Side side = m_pplr->GetSide();
|
|
StructGob::DrawAnimation(pbm, x, y);
|
|
Point ptBaseSpecial;
|
|
m_ani.GetSpecialPoint(&ptBaseSpecial);
|
|
m_aniTurret.Draw(pbm, x + ptBaseSpecial.x, y + ptBaseSpecial.y, side);
|
|
}
|
|
|
|
void TowerGob::GetClippingBounds(Rect *prc)
|
|
{
|
|
StructGob::GetClippingBounds(prc);
|
|
Rect rcTurret;
|
|
m_aniTurret.GetBounds(&rcTurret);
|
|
Point ptBaseSpecial;
|
|
m_ani.GetSpecialPoint(&ptBaseSpecial);
|
|
rcTurret.Offset(ptBaseSpecial.x + PcFromUwc(m_wx), ptBaseSpecial.y + PcFromUwc(m_wy));
|
|
prc->Union(&rcTurret);
|
|
}
|
|
|
|
//
|
|
// GunTowerGob implementation
|
|
//
|
|
|
|
static TowerConsts gtwrcGun;
|
|
|
|
bool GunTowerGob::InitClass(IniReader *pini)
|
|
{
|
|
gtwrcGun.gt = kgtMachineGunTower;
|
|
gtwrcGun.ut = kutMachineGunTower;
|
|
gtwrcGun.umPrerequisites = kumReactor | kumRadar;
|
|
|
|
// Sound effects
|
|
|
|
gtwrcGun.sfxAbortRepair = ksfxMachineGunTowerAbortRepair;
|
|
gtwrcGun.sfxAttack = ksfxMachineGunTowerAttack;
|
|
gtwrcGun.sfxDamaged = ksfxMachineGunTowerDamaged;
|
|
gtwrcGun.sfxDestroyed = ksfxMachineGunTowerDestroyed;
|
|
gtwrcGun.sfxFire = ksfxMachineGunTowerFire;
|
|
gtwrcGun.sfxImpact = ksfxNothing;
|
|
gtwrcGun.sfxRepair = ksfxMachineGunTowerRepair;
|
|
gtwrcGun.sfxSelect = ksfxMachineGunTowerSelect;
|
|
|
|
return TowerGob::InitClass(>wrcGun, pini);
|
|
}
|
|
|
|
void GunTowerGob::ExitClass()
|
|
{
|
|
TowerGob::ExitClass(>wrcGun);
|
|
}
|
|
|
|
GunTowerGob::GunTowerGob() : TowerGob(>wrcGun)
|
|
{
|
|
}
|
|
|
|
// UNDONE: UnitGob can handle some of this (e.g., timing)
|
|
|
|
bool GunTowerGob::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
|
|
|
|
Direction16 dir16Fire = CalcDir16(wdx, wdy);
|
|
if (m_dir16Turret != dir16Fire) {
|
|
m_unvl.MinSkip();
|
|
return false;
|
|
}
|
|
|
|
// Firing rate is limited by ctFiringRate
|
|
|
|
long t = gsim.GetTickCount();
|
|
int ctWait = m_ptwrc->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
|
|
|
|
StartAnimation(&m_aniTurret, m_ptwrc->anFiringStripIndices[dir16Fire], 0, kfAniResetWhenDone);
|
|
gsmm.SendDelayedMsg(kmidFireComplete, m_aniTurret.GetRemainingStripTime(), m_gid, m_gid);
|
|
|
|
WCoord wdxRnd = ((GetRandom() & 7) - 3) * kwcTile16th;
|
|
WCoord wdyRnd = ((GetRandom() & 7) - 3) * kwcTile16th;
|
|
|
|
Point ptTurretSpecial;
|
|
m_aniTurret.GetSpecialPoint(&ptTurretSpecial, 1);
|
|
Point ptBaseSpecial;
|
|
m_ani.GetSpecialPoint(&ptBaseSpecial);
|
|
|
|
CreateBulletGob(m_wx + WcFromPc(ptBaseSpecial.x + ptTurretSpecial.x),
|
|
m_wy + WcFromPc(ptBaseSpecial.y + ptTurretSpecial.y),
|
|
wx + wdxRnd, wy + wdyRnd,
|
|
GetDamageTo(puntTarget) / 3, m_gid, puntTarget->GetId());
|
|
|
|
// Two more shots at slight delays
|
|
|
|
gsmm.SendDelayedMsg(kmidFire, kctUpdate * 2, m_gid, m_gid);
|
|
gsmm.SendDelayedMsg(kmidFire, kctUpdate * 4, m_gid, m_gid);
|
|
|
|
// Play sound
|
|
|
|
gsndm.PlaySfx(m_ptwrc->sfxFire);
|
|
|
|
return true;
|
|
}
|
|
|
|
#if defined(DEBUG_HELPERS)
|
|
char *GunTowerGob::GetName()
|
|
{
|
|
return "GunTower";
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// RocketTowerGob implementation
|
|
//
|
|
|
|
static TowerConsts gtwrcRocket;
|
|
|
|
bool RocketTowerGob::InitClass(IniReader *pini)
|
|
{
|
|
gtwrcRocket.gt = kgtRocketTower;
|
|
gtwrcRocket.ut = kutRocketTower;
|
|
gtwrcRocket.umPrerequisites = kumReactor | kumRadar;
|
|
|
|
// Sound effects
|
|
|
|
gtwrcRocket.sfxAbortRepair = ksfxRocketTowerAbortRepair;
|
|
gtwrcRocket.sfxAttack = ksfxRocketTowerAttack;
|
|
gtwrcRocket.sfxDamaged = ksfxRocketTowerDamaged;
|
|
gtwrcRocket.sfxDestroyed = ksfxRocketTowerDestroyed;
|
|
gtwrcRocket.sfxFire = ksfxRocketTowerFire;
|
|
gtwrcRocket.sfxImpact = ksfxRocketTowerImpact;
|
|
gtwrcRocket.sfxRepair = ksfxRocketTowerRepair;
|
|
gtwrcRocket.sfxSelect = ksfxRocketTowerSelect;
|
|
return TowerGob::InitClass(>wrcRocket, pini);
|
|
}
|
|
|
|
void RocketTowerGob::ExitClass()
|
|
{
|
|
TowerGob::ExitClass(>wrcRocket);
|
|
}
|
|
|
|
RocketTowerGob::RocketTowerGob() : TowerGob(>wrcRocket)
|
|
{
|
|
}
|
|
|
|
#if defined(DEBUG_HELPERS)
|
|
char *RocketTowerGob::GetName()
|
|
{
|
|
return "RocketTower";
|
|
}
|
|
#endif
|
|
|
|
// UNDONE: UnitGob can handle some of this (e.g., timing)
|
|
|
|
bool RocketTowerGob::Fire(UnitGob *puntTarget, WCoord wx, WCoord wy, WCoord wdx, WCoord wdy)
|
|
{
|
|
// Firing rate is limited by ctFiringRate
|
|
|
|
long t = gsim.GetTickCount();
|
|
int ctWait = m_ptwrc->ctFiringRate;
|
|
int ctRemaining = ctWait - (int)(t - m_tLastFire);
|
|
if (ctRemaining > 0) {
|
|
m_unvl.MinSkip((ctRemaining + (kctUpdate / 2)) / kctUpdate - 1);
|
|
return false;
|
|
}
|
|
|
|
Direction16 dir16Fire = CalcDir16(wdx, wdy);
|
|
if (m_dir16Turret != dir16Fire)
|
|
return false;
|
|
|
|
m_tLastFire = t;
|
|
|
|
// Play firing animation (start on frame 1 where the action is)
|
|
|
|
StartAnimation(&m_aniTurret, m_ptwrc->anFiringStripIndices[dir16Fire], 1, kfAniIgnoreFirstAdvance | kfAniResetWhenDone);
|
|
gsmm.SendDelayedMsg(kmidFireComplete, m_aniTurret.GetRemainingStripTime(), m_gid, m_gid);
|
|
|
|
// Fire off the shot!
|
|
|
|
WCoord wdxRnd = ((GetRandom() & 7) - 3) * kwcTile16th;
|
|
WCoord wdyRnd = ((GetRandom() & 7) - 3) * kwcTile16th;
|
|
Point ptTurretSpecial;
|
|
m_aniTurret.GetSpecialPoint(&ptTurretSpecial);
|
|
Point ptBaseSpecial;
|
|
m_ani.GetSpecialPoint(&ptBaseSpecial);
|
|
|
|
// This little hack to handle rocket firing is easier/cheaper than having
|
|
// the RocketTankGob override TankGob::Fire
|
|
|
|
CreateRocketGob(m_wx + WcFromPc(ptBaseSpecial.x + ptTurretSpecial.x),
|
|
m_wy + WcFromPc(ptBaseSpecial.y + ptTurretSpecial.y),
|
|
wx + wdxRnd, wy + wdyRnd, GetDamageTo(puntTarget), m_gid, puntTarget->GetId());
|
|
|
|
// Play sound
|
|
|
|
gsndm.PlaySfx(m_ptwrc->sfxFire);
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace wi
|