mirror of
https://github.com/spiffcode/hostile-takeover.git
synced 2025-12-16 12:08:36 +00:00
844 lines
21 KiB
C++
844 lines
21 KiB
C++
#include "ht.h"
|
|
|
|
namespace wi {
|
|
|
|
const int kcMTankSecondShot = 2; // Frame count of first shot (ignoring 1st frame) - MTank Anims need to match!
|
|
const int kifrmTankAction = 1; // frame where the shot really starts
|
|
|
|
//
|
|
// Abstract Tank
|
|
//
|
|
|
|
bool TankGob::InitClass(MobileUnitConsts *pmuntc, IniReader *pini)
|
|
{
|
|
pmuntc->wf |= kfUntcNotifyEnemyNearby;
|
|
return MobileUnitGob::InitClass(pmuntc, pini);
|
|
}
|
|
|
|
void TankGob::ExitClass(MobileUnitConsts *pmuntc)
|
|
{
|
|
MobileUnitGob::ExitClass(pmuntc);
|
|
}
|
|
|
|
TankGob::TankGob(MobileUnitConsts *pmuntc) : MobileUnitGob(pmuntc)
|
|
{
|
|
m_dir = kdirS;
|
|
|
|
// Turret stuff
|
|
|
|
m_dir16Turret = m_dir * 2;
|
|
}
|
|
|
|
bool TankGob::Init(WCoord wx, WCoord wy, Player *pplr, fix fxHealth, dword ff, const char *pszName)
|
|
{
|
|
if (!MobileUnitGob::Init(wx, wy, pplr, fxHealth, ff, pszName))
|
|
return false;
|
|
|
|
StartAnimation(&m_ani, m_dir, 0, 0);
|
|
m_aniTurret.Init(m_pmuntc->panid);
|
|
SetAnimationStrip(&m_aniTurret, m_pmuntc->anFiringStripIndices[m_dir16Turret]);
|
|
return true;
|
|
}
|
|
|
|
#define knVerTankGobState 1
|
|
bool TankGob::LoadState(Stream *pstm)
|
|
{
|
|
byte nVer = pstm->ReadByte();
|
|
if (nVer != knVerTankGobState)
|
|
return false;
|
|
m_dir16Turret = pstm->ReadByte();
|
|
m_aniTurret.Init(m_pmuntc->panid);
|
|
SetAnimationStrip(&m_aniTurret, m_pmuntc->anFiringStripIndices[m_dir16Turret]);
|
|
return MobileUnitGob::LoadState(pstm);
|
|
}
|
|
|
|
bool TankGob::SaveState(Stream *pstm)
|
|
{
|
|
pstm->WriteByte(knVerTankGobState);
|
|
pstm->WriteByte(m_dir16Turret);
|
|
return MobileUnitGob::SaveState(pstm);
|
|
}
|
|
|
|
void TankGob::Draw(DibBitmap *pbm, int xViewOrigin, int yViewOrigin, int nLayer)
|
|
{
|
|
if (nLayer == knLayerDepthSorted) {
|
|
|
|
#ifdef DRAW_OCCUPIED_TILE_INDICATOR
|
|
{
|
|
WRect wrcT;
|
|
GetTilePaddedWRect(&wrcT);
|
|
Rect rcT;
|
|
rcT.FromWorldRect(&wrcT);
|
|
rcT.Offset(-xViewOrigin, -yViewOrigin);
|
|
DrawBorder(pbm, &rcT, 1, GetColor(kiclrWhite));
|
|
}
|
|
#endif
|
|
Side side = m_pplr->GetSide();
|
|
if (m_ff & kfGobDrawFlashed)
|
|
side = (Side)-1;
|
|
|
|
// Draw base
|
|
|
|
int x = PcFromUwc(m_wx) - xViewOrigin;
|
|
int y = PcFromUwc(m_wy) - yViewOrigin;
|
|
m_ani.Draw(pbm, x, y, side);
|
|
|
|
// Draw turret
|
|
// The turret is aligned with the base's special point
|
|
|
|
Point ptBaseSpecial;
|
|
m_ani.GetSpecialPoint(&ptBaseSpecial);
|
|
m_aniTurret.Draw(pbm, x + ptBaseSpecial.x, y + ptBaseSpecial.y, side);
|
|
} else {
|
|
MobileUnitGob::Draw(pbm, xViewOrigin, yViewOrigin, nLayer);
|
|
}
|
|
}
|
|
|
|
// UNDONE: MobileUnitGob can handle some of this (e.g., timing)
|
|
|
|
bool TankGob::Fire(UnitGob *puntTarget, WCoord wx, WCoord wy, WCoord wdx, WCoord wdy)
|
|
{
|
|
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_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_aniTurret, m_pmuntc->anFiringStripIndices[dir16Fire], kifrmTankAction, kfAniIgnoreFirstAdvance | kfAniResetWhenDone);
|
|
m_wfMunt |= kfMuntFiring;
|
|
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 ptSpecial;
|
|
m_aniTurret.GetSpecialPoint(&ptSpecial, kifrmTankAction);
|
|
|
|
LaunchProjectile(m_wx + WcFromPc(ptSpecial.x), m_wy + WcFromPc(ptSpecial.y), wx + wdxRnd, wy + wdyRnd,
|
|
GetDamageTo(puntTarget), m_gid, puntTarget->GetId());
|
|
|
|
// Play sound
|
|
|
|
gsndm.PlaySfx(m_pmuntc->sfxFire);
|
|
|
|
return true;
|
|
}
|
|
|
|
// default. Overridden by several tanks
|
|
void TankGob::LaunchProjectile(WCoord wx, WCoord wy, WCoord wxTarget, WCoord wyTarget, int nDamage, Gid gidOwner, Gid gidTarget)
|
|
{
|
|
CreateTankShotGob(wx, wy, wxTarget, wyTarget, nDamage, gidOwner, gidTarget);
|
|
}
|
|
|
|
void TankGob::Idle()
|
|
{
|
|
// 1/2 of the time we pivot the turret left, 1/2 we pivot it right
|
|
|
|
if (GetRandom() & 1) {
|
|
m_dir16Turret--;
|
|
if (m_dir16Turret < 0)
|
|
m_dir16Turret = 15;
|
|
} else {
|
|
m_dir16Turret++;
|
|
if (m_dir16Turret > 15)
|
|
m_dir16Turret = 0;
|
|
}
|
|
SetAnimationStrip(&m_aniTurret, m_pmuntc->anFiringStripIndices[m_dir16Turret]);
|
|
}
|
|
|
|
void TankGob::DefUpdate()
|
|
{
|
|
// Try to make the turrent point toward the target (unless we're already firing)
|
|
|
|
if (m_wptTarget.wx != kwxInvalid && !(m_wfMunt & kfMuntFiring)) {
|
|
// Get the aim point. If we have a unit target, it is the unit's center
|
|
// If we don't have a unit target, it is whatever is in m_wptTarget
|
|
|
|
WPoint wptAim = m_wptTarget;
|
|
Gob *pgobTarget = ggobm.GetGob(m_gidTarget);
|
|
if (pgobTarget != NULL)
|
|
pgobTarget->GetCenter(&wptAim);
|
|
|
|
// If the target is in the tile we're in stop trying to look exactly at it
|
|
|
|
if (WcTrunc(m_wx) != WcTrunc(wptAim.wx) || WcTrunc(m_wy) != WcTrunc(wptAim.wy)) {
|
|
|
|
Direction16 dir16 = CalcDir16(wptAim.wx - m_wx, wptAim.wy - m_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_pmuntc->anFiringStripIndices[m_dir16Turret]);
|
|
m_unvl.MinSkip();
|
|
}
|
|
}
|
|
}
|
|
|
|
AdvanceAnimation(&m_aniTurret);
|
|
|
|
MobileUnitGob::DefUpdate();
|
|
}
|
|
|
|
dword TankGob::GetAnimationHash() {
|
|
dword dw = MobileUnitGob::GetAnimationHash();
|
|
return dw ^ (m_dir16Turret << 8);
|
|
}
|
|
|
|
void TankGob::GetAnimationBounds(Rect *prc, bool fBase) {
|
|
MobileUnitGob::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 TankGob::DrawAnimation(DibBitmap *pbm, int x, int y)
|
|
{
|
|
Side side = m_pplr->GetSide();
|
|
MobileUnitGob::DrawAnimation(pbm, x, y);
|
|
Point ptBaseSpecial;
|
|
m_ani.GetSpecialPoint(&ptBaseSpecial);
|
|
m_aniTurret.Draw(pbm, x + ptBaseSpecial.x, y + ptBaseSpecial.y, side);
|
|
}
|
|
|
|
void TankGob::GetClippingBounds(Rect *prc)
|
|
{
|
|
MobileUnitGob::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);
|
|
}
|
|
|
|
//
|
|
// Light Tank
|
|
//
|
|
|
|
static MobileUnitConsts gLTankConsts;
|
|
|
|
#if defined(DEBUG_HELPERS)
|
|
char *LTankGob::GetName()
|
|
{
|
|
return "LTank";
|
|
}
|
|
#endif
|
|
|
|
static int s_anTurretStripIndices[16] = { 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 };
|
|
static int s_anBaseStripIndices[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };
|
|
static int s_anIdleStripIndices[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };
|
|
|
|
bool LTankGob::InitClass(IniReader *pini)
|
|
{
|
|
gLTankConsts.gt = kgtLightTank;
|
|
gLTankConsts.ut = kutLightTank;
|
|
|
|
// Initialize the frame indices arrays
|
|
|
|
gLTankConsts.anFiringStripIndices = s_anTurretStripIndices;
|
|
gLTankConsts.anMovingStripIndices = s_anBaseStripIndices;
|
|
gLTankConsts.anIdleStripIndices = s_anIdleStripIndices;
|
|
|
|
// Sound effects
|
|
|
|
gLTankConsts.sfxFire = ksfxLightTankFire;
|
|
gLTankConsts.sfxImpact = ksfxLightTankImpact;
|
|
|
|
gLTankConsts.sfxcDestroyed = ksfxcVehicleDestroyed;
|
|
gLTankConsts.sfxcSelect = ksfxcMajor02Select;
|
|
gLTankConsts.sfxcMove = ksfxcMajor02Move;
|
|
gLTankConsts.sfxcAttack = ksfxcMajor02Attack;
|
|
|
|
return TankGob::InitClass(&gLTankConsts, pini);
|
|
}
|
|
|
|
void LTankGob::ExitClass()
|
|
{
|
|
TankGob::ExitClass(&gLTankConsts);
|
|
}
|
|
|
|
LTankGob::LTankGob() : TankGob(&gLTankConsts)
|
|
{
|
|
}
|
|
|
|
//
|
|
// Medium Tank
|
|
//
|
|
|
|
static MobileUnitConsts gMTankConsts;
|
|
|
|
#if defined(DEBUG_HELPERS)
|
|
char *MTankGob::GetName()
|
|
{
|
|
return "MTank";
|
|
}
|
|
#endif
|
|
|
|
bool MTankGob::InitClass(IniReader *pini)
|
|
{
|
|
gMTankConsts.gt = kgtMediumTank;
|
|
gMTankConsts.ut = kutMediumTank;
|
|
gMTankConsts.upgmPrerequisites = kupgmAdvancedVTS;
|
|
|
|
// Initialize the frame indices arrays
|
|
|
|
gMTankConsts.anFiringStripIndices = s_anTurretStripIndices;
|
|
gMTankConsts.anMovingStripIndices = s_anBaseStripIndices;
|
|
gMTankConsts.anIdleStripIndices = s_anIdleStripIndices;
|
|
|
|
// Sound effects
|
|
|
|
gMTankConsts.sfxFire = ksfxMediumTankFire;
|
|
gMTankConsts.sfxImpact = ksfxMediumTankImpact;
|
|
|
|
gMTankConsts.sfxcDestroyed = ksfxcVehicleDestroyed;
|
|
gMTankConsts.sfxcSelect = ksfxcMale06Select;
|
|
gMTankConsts.sfxcMove = ksfxcMale06Move;
|
|
gMTankConsts.sfxcAttack = ksfxcMale06Attack;
|
|
|
|
return TankGob::InitClass(&gMTankConsts, pini);
|
|
}
|
|
|
|
void MTankGob::ExitClass()
|
|
{
|
|
TankGob::ExitClass(&gMTankConsts);
|
|
}
|
|
|
|
// need to load state & save state
|
|
|
|
MTankGob::MTankGob() : TankGob(&gMTankConsts)
|
|
{
|
|
m_pgobSecondShot = NULL;
|
|
}
|
|
|
|
MTankGob::~MTankGob()
|
|
{
|
|
// don't be left holding a shot. This does mean
|
|
// that exiting and restarting will lose this shot.
|
|
|
|
delete m_pgobSecondShot;
|
|
}
|
|
|
|
// Make sure the second shot doesn't fire after this tank has been
|
|
// destroyed.
|
|
|
|
void MTankGob::Deactivate()
|
|
{
|
|
delete m_pgobSecondShot;
|
|
m_pgobSecondShot = NULL;
|
|
TankGob::Deactivate();
|
|
}
|
|
|
|
void MTankGob::LaunchProjectile(WCoord wx, WCoord wy, WCoord wxTarget, WCoord wyTarget, int nDamage, Gid gidOwner, Gid gidTarget)
|
|
{
|
|
CreateTankShotGob(wx, wy, wxTarget, wyTarget, nDamage / 2, gidOwner, gidTarget);
|
|
|
|
// set up for the 2nd shot, kcMTankSecondShot updates (frames) later
|
|
//kcMTankSecondShot is defined in code, anims need to match!
|
|
|
|
m_cShotcountdown = kcMTankSecondShot;
|
|
|
|
// Add the current update interval count since m_cShotcountdown gets decremented in DefUpdate
|
|
// right after this call
|
|
|
|
m_cShotcountdown += m_unvl.GetUpdateCount();
|
|
|
|
m_unvl.MinSkip(m_cShotcountdown);
|
|
|
|
if (ggobm.IsBelowLimit(knLimitSupport)) {
|
|
m_pgobSecondShot = new TankShotGob();
|
|
Assert(m_pgobSecondShot != NULL, "out of memory!");
|
|
if (m_pgobSecondShot != NULL) {
|
|
// we need to find the 2nd special point for the 2nd shot, modify the endpoint as well
|
|
|
|
Point ptSpecial1, ptSpecial2;
|
|
m_aniTurret.GetSpecialPoint(&ptSpecial1, kifrmTankAction);
|
|
m_aniTurret.GetSpecialPoint(&ptSpecial2, kifrmTankAction + kcMTankSecondShot);
|
|
|
|
if (!m_pgobSecondShot->Init(TankShotGob::s_panidShot, wx + WcFromPc(ptSpecial2.x - ptSpecial1.x), wy + WcFromPc(ptSpecial2.y-ptSpecial1.y),
|
|
wxTarget + WcFromPc(ptSpecial2.x - ptSpecial1.x), wyTarget + WcFromPc(ptSpecial2.y-ptSpecial1.y),
|
|
nDamage / 2, gidOwner, gidTarget, kwcTankShotRate)) {
|
|
delete m_pgobSecondShot;
|
|
m_pgobSecondShot = NULL;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MTankGob::DefUpdate()
|
|
{
|
|
// if we have a 2nd shot pending, countdown to fire and make it so!
|
|
|
|
if (m_pgobSecondShot != NULL) {
|
|
m_cShotcountdown -= m_unvl.GetUpdateCount();
|
|
if (m_cShotcountdown < 0) {
|
|
// The unit limit check was made when this gob was created, however now that it is time to fire,
|
|
// the limit may have been reached (since it is Launch that adds the gob). Check again.
|
|
|
|
if (ggobm.IsBelowLimit(knLimitSupport)) {
|
|
m_pgobSecondShot->Launch();
|
|
} else {
|
|
delete m_pgobSecondShot;
|
|
}
|
|
m_pgobSecondShot = NULL;
|
|
gsndm.PlaySfx(m_pmuntc->sfxFire);
|
|
}
|
|
}
|
|
TankGob::DefUpdate();
|
|
}
|
|
|
|
//
|
|
// Rocket Tank
|
|
//
|
|
|
|
static MobileUnitConsts gRTankConsts;
|
|
|
|
#if defined(DEBUG_HELPERS)
|
|
char *RTankGob::GetName()
|
|
{
|
|
return "RTank";
|
|
}
|
|
#endif
|
|
|
|
bool RTankGob::InitClass(IniReader *pini)
|
|
{
|
|
gRTankConsts.gt = kgtRocketVehicle;
|
|
gRTankConsts.ut = kutRocketVehicle;
|
|
gRTankConsts.upgmPrerequisites = kupgmAdvancedVTS;
|
|
|
|
// Initialize the frame indices arrays
|
|
|
|
gRTankConsts.anFiringStripIndices = s_anTurretStripIndices;
|
|
gRTankConsts.anMovingStripIndices = s_anBaseStripIndices;
|
|
gRTankConsts.anIdleStripIndices = s_anIdleStripIndices;
|
|
|
|
// Sound effects
|
|
|
|
gRTankConsts.sfxFire = ksfxRocketVehicleFire;
|
|
gRTankConsts.sfxImpact = ksfxRocketVehicleImpact;
|
|
|
|
gRTankConsts.sfxcDestroyed = ksfxcVehicleDestroyed;
|
|
gRTankConsts.sfxcSelect = ksfxcMajor01Select;
|
|
gRTankConsts.sfxcMove = ksfxcMajor01Move;
|
|
gRTankConsts.sfxcAttack = ksfxcMajor01Attack;
|
|
|
|
return TankGob::InitClass(&gRTankConsts, pini);
|
|
}
|
|
|
|
void RTankGob::ExitClass()
|
|
{
|
|
TankGob::ExitClass(&gRTankConsts);
|
|
}
|
|
|
|
RTankGob::RTankGob() : TankGob(&gRTankConsts)
|
|
{
|
|
}
|
|
|
|
void RTankGob::LaunchProjectile(WCoord wx, WCoord wy, WCoord wxTarget, WCoord wyTarget, int nDamage, Gid gidOwner, Gid gidTarget)
|
|
{
|
|
CreateRocketGob(wx, wy, wxTarget, wyTarget, nDamage, gidOwner, gidTarget);
|
|
}
|
|
|
|
|
|
//
|
|
// Machine Gun Tank
|
|
//
|
|
|
|
static MobileUnitConsts gGTankConsts;
|
|
|
|
#if defined(DEBUG_HELPERS)
|
|
char *GTankGob::GetName()
|
|
{
|
|
return "GTank";
|
|
}
|
|
#endif
|
|
|
|
bool GTankGob::InitClass(IniReader *pini)
|
|
{
|
|
gGTankConsts.gt = kgtMachineGunVehicle;
|
|
gGTankConsts.ut = kutMachineGunVehicle;
|
|
|
|
// Initialize the frame indices arrays
|
|
|
|
gGTankConsts.anFiringStripIndices = s_anTurretStripIndices;
|
|
gGTankConsts.anMovingStripIndices = s_anBaseStripIndices;
|
|
gGTankConsts.anIdleStripIndices = s_anIdleStripIndices;
|
|
|
|
// Sound effects
|
|
|
|
gGTankConsts.sfxFire = ksfxMachineGunVehicleFire;
|
|
gGTankConsts.sfxImpact = ksfxNothing;
|
|
|
|
gGTankConsts.sfxcDestroyed = ksfxcVehicleDestroyed;
|
|
gGTankConsts.sfxcSelect = ksfxcMale01Select;
|
|
gGTankConsts.sfxcMove = ksfxcMale01Move;
|
|
gGTankConsts.sfxcAttack = ksfxcMale01Attack;
|
|
|
|
return TankGob::InitClass(&gGTankConsts, pini);
|
|
}
|
|
|
|
void GTankGob::ExitClass()
|
|
{
|
|
TankGob::ExitClass(&gGTankConsts);
|
|
}
|
|
|
|
GTankGob::GTankGob() : TankGob(&gGTankConsts)
|
|
{
|
|
}
|
|
|
|
// UNDONE: MobileUnitGob can handle some of this (e.g., timing)
|
|
|
|
bool GTankGob::Fire(UnitGob *puntTarget, WCoord wx, WCoord wy, WCoord wdx, WCoord wdy)
|
|
{
|
|
// 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;
|
|
}
|
|
|
|
Direction16 dir16Fire = CalcDir16(wdx, wdy);
|
|
if (m_dir16Turret != dir16Fire)
|
|
return false;
|
|
|
|
m_tLastFire = t;
|
|
|
|
// Play firing animation
|
|
|
|
StartAnimation(&m_aniTurret, m_pmuntc->anFiringStripIndices[dir16Fire], 0, kfAniResetWhenDone);
|
|
m_wfMunt |= kfMuntFiring;
|
|
gsmm.SendDelayedMsg(kmidFireComplete, m_aniTurret.GetRemainingStripTime(), m_gid, m_gid);
|
|
|
|
// Fire off those shots!
|
|
|
|
WCoord wdxRnd = ((GetRandom() & 7) - 3) * kwcTile16th;
|
|
WCoord wdyRnd = ((GetRandom() & 7) - 3) * kwcTile16th;
|
|
Point ptSpecial;
|
|
m_aniTurret.GetSpecialPoint(&ptSpecial, kifrmTankAction);
|
|
|
|
CreateBulletGob(m_wx + WcFromPc(ptSpecial.x), m_wy + WcFromPc(ptSpecial.y), wx + wdxRnd, wy + wdyRnd,
|
|
GetDamageTo(puntTarget) / 3, m_gid, puntTarget->GetId());
|
|
|
|
// Two more shots at slight delays
|
|
|
|
gsmm.SendDelayedMsg(kmidFire, kctUpdate * 3, m_gid, m_gid);
|
|
gsmm.SendDelayedMsg(kmidFire, kctUpdate * 6, m_gid, m_gid);
|
|
|
|
// Play sound
|
|
|
|
gsndm.PlaySfx(m_pmuntc->sfxFire);
|
|
|
|
return true;
|
|
}
|
|
|
|
int GTankGob::ProcessStateMachineMessage(State st, Message *pmsg)
|
|
{
|
|
BeginStateMachine
|
|
OnMsg(kmidFire)
|
|
UnitGob *puntTarget = (UnitGob *)ggobm.GetGob(m_gidTarget);
|
|
if (puntTarget != NULL) {
|
|
// Fire off the shot!
|
|
|
|
WCoord wdxRnd = ((GetRandom() & 7) - 3) * kwcTile16th;
|
|
WCoord wdyRnd = ((GetRandom() & 7) - 3) * kwcTile16th;
|
|
|
|
WPoint wpt;
|
|
puntTarget->GetCenter(&wpt);
|
|
Point ptSpecial;
|
|
m_aniTurret.GetSpecialPoint(&ptSpecial, kifrmTankAction);
|
|
CreateBulletGob(m_wx + WcFromPc(ptSpecial.x), m_wy + WcFromPc(ptSpecial.y), wpt.wx + wdxRnd, wpt.wy + wdyRnd,
|
|
GetDamageTo(puntTarget) / 3, m_gid, puntTarget->GetId());
|
|
}
|
|
|
|
#if 0
|
|
EndStateMachineInherit(MobileUnitGob)
|
|
#else
|
|
return knHandled;
|
|
}
|
|
} else {
|
|
return (int)MobileUnitGob::ProcessStateMachineMessage(st, pmsg);
|
|
}
|
|
return (int)MobileUnitGob::ProcessStateMachineMessage(st, pmsg);
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// TankShotGob implementation
|
|
//
|
|
|
|
AnimationData *TankShotGob::s_panidShot = NULL;
|
|
|
|
// this will actually create & fire
|
|
TankShotGob *CreateTankShotGob(WCoord wx, WCoord wy, WCoord wxTarget, WCoord wyTarget, int nDamage, Gid gidOwner, Gid gidTarget)
|
|
{
|
|
if (!ggobm.IsBelowLimit(knLimitSupport))
|
|
return NULL;
|
|
|
|
TankShotGob *pgob = new TankShotGob();
|
|
Assert(pgob != NULL, "out of memory!");
|
|
if (pgob == NULL)
|
|
return NULL;
|
|
|
|
if (!pgob->Init(TankShotGob::s_panidShot, wx, wy, wxTarget, wyTarget, nDamage, gidOwner, gidTarget, kwcTankShotRate)) {
|
|
delete pgob;
|
|
return NULL;
|
|
}
|
|
|
|
pgob->Launch();
|
|
return pgob;
|
|
}
|
|
|
|
bool TankShotGob::InitClass(IniReader *pini)
|
|
{
|
|
s_panidShot = LoadAnimationData("tankshot.anir");
|
|
if (s_panidShot == NULL)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
void TankShotGob::ExitClass()
|
|
{
|
|
delete s_panidShot;
|
|
s_panidShot = NULL;
|
|
}
|
|
|
|
TankShotGob::TankShotGob()
|
|
{
|
|
m_ff |= kfGobStateMachine | kfGobLayerSmokeFire;
|
|
}
|
|
|
|
bool TankShotGob::Init(AnimationData *panid, WCoord wx, WCoord wy, WCoord wxTarget, WCoord wyTarget, int nDamage, Gid gidOwner, Gid gidTarget, WCoord wcMoveRate)
|
|
{
|
|
// Units fire from their special point which may be off the edge of the map.
|
|
// We cannot allow Gobs to be off the map so here we bring it on.
|
|
|
|
BringInBounds(&wx, &wy);
|
|
BringInBounds(&wxTarget, &wyTarget);
|
|
|
|
#ifdef DEBUG
|
|
TileMap *ptmap = gsim.GetLevel()->GetTileMap();
|
|
Size sizT;
|
|
ptmap->GetTCoordMapSize(&sizT);
|
|
Assert(wx < WcFromTc(sizT.cx) && wy < WcFromTc(sizT.cy));
|
|
Assert(wxTarget < WcFromTc(sizT.cx) && wyTarget < WcFromTc(sizT.cy));
|
|
#endif
|
|
|
|
// initialize our state but don't actually fire, save that for Launch
|
|
|
|
m_gidOwner = gidOwner;
|
|
m_gidTarget = gidTarget;
|
|
m_nDamage = nDamage;
|
|
|
|
m_li.Init(wx, wy, wxTarget, wyTarget, wcMoveRate);
|
|
|
|
// LineIterator initializes x,y to the first step-integral point on the line,
|
|
// presuming that the final step should be at the target x,y
|
|
|
|
m_wx = m_li.GetWX();
|
|
m_wy = m_li.GetWY();
|
|
|
|
m_ani.Init(panid);
|
|
if (m_ani.GetAnimationData()->GetFrameCount(0) == 8)
|
|
StartAnimation(&m_ani, 0, CalcDir(wxTarget - wx, wyTarget - wy), kfAniDone);
|
|
|
|
return true;
|
|
}
|
|
|
|
void TankShotGob::Launch()
|
|
{
|
|
// HACK: If this animation has 8 frames we assume that's one for each direction
|
|
|
|
if (m_ani.GetAnimationData()->GetFrameCount(0) != 8)
|
|
StartAnimation(&m_ani, 0, 0, kfAniLoop);
|
|
|
|
// Add the fresh Gob to the GobMgr. GobMgr::AddGob assigns this Gob a gid
|
|
|
|
ggobm.AddGob(this);
|
|
|
|
// Let the target know when it will be hit. Doing it this way
|
|
// means the hit will arrive in the same number of updates for all
|
|
// players (the number of updates calc'd by the shooter). Depending
|
|
// on screen resolution, the animated travel time may be off by an update
|
|
|
|
Message msgT;
|
|
msgT.mid = kmidHit;
|
|
msgT.smidSender = m_gidOwner;
|
|
msgT.smidReceiver = m_gidTarget;
|
|
msgT.Hit.gidAssailant = m_gidOwner;
|
|
msgT.Hit.sideAssailant = ggobm.GetGob(m_gidOwner)->GetSide();
|
|
msgT.Hit.nDamage = m_nDamage;
|
|
|
|
// BUGBUG: m_li.GetStepsRemaining is influenced by the firing point which is resolution dependent.
|
|
// Instead, this message's delay should be derived using the distance from the world coordinate
|
|
// centers of the source and target
|
|
|
|
gsmm.SendDelayedMsg(&msgT, (m_li.GetStepsRemaining() + 1) * (kcmsUpdate / 10));
|
|
}
|
|
|
|
// TankShotGobs don't get loaded
|
|
|
|
bool TankShotGob::Init(IniReader *pini, FindProp *pfind, const char *pszName)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool TankShotGob::IsSavable()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
GobType TankShotGob::GetType()
|
|
{
|
|
return kgtTankShot;
|
|
}
|
|
|
|
void TankShotGob::GetClippingBounds(Rect *prc)
|
|
{
|
|
// hardcoded that the travel is strip 0 and the impact is strip 1
|
|
|
|
if (m_ani.GetStrip() == 0) {
|
|
if (!(gwfPerfOptions & kfPerfShots)) {
|
|
prc->SetEmpty();
|
|
return;
|
|
}
|
|
} else {
|
|
if (!(gwfPerfOptions & kfPerfShotImpacts)) {
|
|
prc->SetEmpty();
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_ani.GetBounds(prc);
|
|
prc->Offset(PcFromUwc(m_wx), PcFromUwc(m_wy));
|
|
}
|
|
|
|
void TankShotGob::Draw(DibBitmap *pbm, int xViewOrigin, int yViewOrigin, int nLayer)
|
|
{
|
|
if (nLayer == knLayerSmokeFire) {
|
|
// hardcoded that the travel is strip 0 and the impact is strip 1
|
|
|
|
if (m_ani.GetStrip() == 0) {
|
|
if (!(gwfPerfOptions & kfPerfShots))
|
|
return;
|
|
} else {
|
|
if (!(gwfPerfOptions & kfPerfShotImpacts))
|
|
return;
|
|
}
|
|
m_ani.Draw(pbm, PcFromUwc(m_wx) - xViewOrigin, PcFromUwc(m_wy) - yViewOrigin, m_pplr->GetSide());
|
|
}
|
|
}
|
|
|
|
#if defined(DEBUG_HELPERS)
|
|
char *TankShotGob::GetName()
|
|
{
|
|
return "TankShot";
|
|
}
|
|
#endif
|
|
|
|
int TankShotGob::ProcessStateMachineMessage(State st, Message *pmsg)
|
|
{
|
|
BeginStateMachine
|
|
OnUpdate
|
|
// Advance the shot animation
|
|
// hardcoded that the travel is strip 0 and the impact is strip 1
|
|
|
|
if (m_ani.GetStrip() == 0) {
|
|
AdvanceAnimation(&m_ani);
|
|
MarkRedraw();
|
|
|
|
// Advance the shot position
|
|
|
|
if (m_li.Step()) {
|
|
WCoord wxOld = m_wx;
|
|
WCoord wyOld = m_wy;
|
|
m_wx = m_li.GetWX();
|
|
m_wy = m_li.GetWY();
|
|
|
|
// Keep GobMgr in the loop so it can maintain proper depth sorting
|
|
|
|
if (m_wx != wxOld || m_wy != wyOld)
|
|
ggobm.MoveGob(this, wxOld, wyOld, m_wx, m_wy);
|
|
|
|
// Assumes getting called each update
|
|
|
|
m_unvl.MinSkip();
|
|
|
|
} else {
|
|
|
|
// Play impact sound
|
|
|
|
UnitGob *punt = (UnitGob *)ggobm.GetGob(m_gidOwner);
|
|
if (punt != NULL) {
|
|
UnitConsts *puntc = (UnitConsts *)punt->GetConsts();
|
|
gsndm.PlaySfx(puntc->sfxImpact);
|
|
}
|
|
|
|
// Start the impact animation
|
|
|
|
StartAnimation(&m_ani, 1, 0, 0);
|
|
}
|
|
|
|
// Assume valid if we're not drawing shots
|
|
|
|
if (!(gwfPerfOptions & kfPerfShots))
|
|
m_ff &= ~kfGobRedraw;
|
|
|
|
} else {
|
|
|
|
// Advance the impact animation
|
|
|
|
if (!AdvanceAnimation(&m_ani)) {
|
|
|
|
// Kill this shot
|
|
|
|
ggobm.RemoveGob(this);
|
|
delete this;
|
|
return knDeleted;
|
|
}
|
|
|
|
// Assume valid if we're not drawing shot impacts
|
|
|
|
if (!(gwfPerfOptions & kfPerfShotImpacts))
|
|
m_ff &= ~kfGobRedraw;
|
|
}
|
|
|
|
EndStateMachine
|
|
}
|
|
|
|
} // namespace wi
|