hostile-takeover/game/Struct.cpp
Nathan Fulton 4aadbf87cb strings.h -> wistrings.h
Change strings.h to wistrings.h to prevent SDL.h from including it
while trying to include the CRT strings.h
2016-08-24 21:40:38 -04:00

1189 lines
31 KiB
C++

#include "ht.h"
#include "wistrings.h"
namespace wi {
static AnimationData *s_panidStructureExplosion = NULL;
static AnimationData *s_panidBigSmoke = NULL;
static DialogForm *s_pfrmConfirmation = NULL;
TBitmap *StructGob::s_ptbmRepairing = NULL;
TBitmap *StructGob::s_ptbmNeedsPower = NULL;
TBitmap *StructGob::s_ptbmNeedCredits = NULL;
//===========================================================================
// StructGob implementation
bool StructGob::InitClass(StructConsts *pstruc, IniReader *pini)
{
if (!UnitGob::InitClass(pstruc, pini))
return false;
char szTemplate[10];
itoa(pstruc->gt, szTemplate, 10);
// Required properties
if (pini->GetPropertyValue(szTemplate, "TileDimensions", "%d,%d",
&pstruc->ctx, &pstruc->cty) != 2)
return false;
int nArmorStrength;
if (pini->GetPropertyValue(szTemplate, "ArmorStrength", "%d", &nArmorStrength) != 1)
return false;
Assert(nArmorStrength < 1 << 10);
pstruc->fxArmorStrength = itofx(nArmorStrength);
// Don't include Headquarters in min/max since they aren't player-buildable
if (pstruc->gt != kgtHeadquarters) {
gfxStructureArmorStrengthMin = _min(gfxStructureArmorStrengthMin, pstruc->fxArmorStrength);
gfxStructureArmorStrengthMax = _max(gfxStructureArmorStrengthMax, pstruc->fxArmorStrength);
}
// Optional properties
if (pini->GetPropertyValue(szTemplate, "ArmorStrengthMP", "%d", &nArmorStrength) != 1) {
pstruc->fxArmorStrengthMP = pstruc->fxArmorStrength;
} else {
Assert(nArmorStrength < 1 << 10);
pstruc->fxArmorStrengthMP = itofx(nArmorStrength);
}
if (pini->GetPropertyValue(szTemplate, "ReserveDimensions", "%d,%d",
&pstruc->ctxReserve, &pstruc->ctyReserve) != 2) {
pstruc->ctxReserve = pstruc->ctx;
pstruc->ctyReserve = pstruc->cty;
}
if (pini->GetPropertyValue(szTemplate, "PowerSupply", "%d", &pstruc->nPowerSupply) != 1)
pstruc->nPowerSupply = 0;
gnPowerSupplyMin = _min(gnPowerSupplyMin, pstruc->nPowerSupply);
gnPowerSupplyMax = _max(gnPowerSupplyMax, pstruc->nPowerSupply);
if (pini->GetPropertyValue(szTemplate, "PowerDemand", "%d", &pstruc->nPowerDemand) != 1)
pstruc->nPowerDemand = 0;
gnPowerDemandMin = _min(gnPowerDemandMin, pstruc->nPowerDemand);
gnPowerDemandMax = _max(gnPowerDemandMax, pstruc->nPowerDemand);
if (pini->GetPropertyValue(szTemplate, "UpgradeCost", "%d", &pstruc->nUpgradeCost) != 1)
pstruc->nUpgradeCost = 0;
// Preload the structure's menu form
if (!LoadMenu(pstruc, pini, szTemplate, kidfStructMenu))
return false;
// Preload the confirmation "Are you sure?" dialog
if (!LoadConfirmation()) {
return false;
}
// Preload the smoke animation data
if (s_panidBigSmoke == NULL) {
s_panidBigSmoke = LoadAnimationData("smoke.anir");
if (s_panidBigSmoke == NULL)
return false;
}
// Preload the structure explosion animation data
if (s_panidStructureExplosion == NULL) {
s_panidStructureExplosion = LoadAnimationData("sexplosion.anir");
if (s_panidStructureExplosion == NULL)
return false;
}
// Preload the repair symbol bitmap
if (s_ptbmRepairing == NULL) {
s_ptbmRepairing = LoadTBitmap("repairing_symbol.tbm");
if (s_ptbmRepairing == NULL) {
Assert("Failed to load repairing_symbol.tbm");
return false;
}
}
// Preload the needs power symbol bitmap
if (s_ptbmNeedsPower == NULL) {
s_ptbmNeedsPower = LoadTBitmap("needs_power_symbol.tbm");
if (s_ptbmNeedsPower == NULL) {
Assert("Failed to load needs_power_symbol.tbm");
return false;
}
}
// Preload the need credits symbol bitmap
if (s_ptbmNeedCredits == NULL) {
s_ptbmNeedCredits = LoadTBitmap("needs_credits_symbol.tbm");
if (s_ptbmNeedCredits == NULL) {
Assert("Failed to load need_credits_symbol.tbm");
return false;
}
}
// All structures defog a larger area
pstruc->wf |= kfUntcLargeDefog;
return true;
}
void StructGob::ExitClass(StructConsts *pstruc)
{
delete s_ptbmNeedCredits;
s_ptbmNeedCredits = NULL;
delete s_ptbmNeedsPower;
s_ptbmNeedsPower = NULL;
delete s_ptbmRepairing;
s_ptbmRepairing = NULL;
delete s_panidStructureExplosion;
s_panidStructureExplosion = NULL;
delete s_panidBigSmoke;
s_panidBigSmoke = NULL;
delete s_pfrmConfirmation;
s_pfrmConfirmation = NULL;
UnitGob::ExitClass(pstruc);
}
StructGob::StructGob(StructConsts *pstruc) : UnitGob(pstruc)
{
m_ff |= kfGobStructure;
m_tLastSmoke = 0;
m_nSeqLastVisible = 0;
}
StructGob::~StructGob()
{
}
bool StructGob::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;
// Mark this structure's position as occupied on the terrain map
TerrainMap *ptrmap = gsim.GetLevel()->GetTerrainMap();
TCoord tx = TcFromWc(m_wx);
TCoord ty = TcFromWc(m_wy);
Assert(tx >= 0 && ty >= 0 && tx + m_pstruc->ctx <= ptrmap->GetWidth() && ty + m_pstruc->cty <= ptrmap->GetHeight(),
"%s out of map bounds", m_pstruc->szName);
ptrmap->SetFlags(tx, ty, m_pstruc->ctx, m_pstruc->cty, kbfStructure);
Assert(tx + m_pstruc->ctxReserve <= ptrmap->GetWidth() && ty + m_pstruc->ctyReserve <= ptrmap->GetHeight(),
"%s reserve out of map bounds", m_pstruc->szName);
ptrmap->SetFlags(tx, ty, m_pstruc->ctxReserve, m_pstruc->ctyReserve, kbfReserved);
// Shadow this structure's occupation in the gid map
ggobm.ShadowGob(this, TcFromWc(m_wx), TcFromWc(m_wy), m_pstruc->ctx, m_pstruc->cty);
// Redraw this part of the minimap
TRect trc;
GetTileRect(&trc);
gpmm->RedrawTRect(&trc);
if (ff & kfGobBeingBuilt) {
SetState(kstBeingBuilt);
} else {
Activate();
SetState(kstIdle);
}
return true;
}
void StructGob::Delete()
{
// Mark this structure's position as unoccupied on the terrain map
gsim.GetLevel()->GetTerrainMap()->ClearFlags(TcFromWc(m_wx), TcFromWc(m_wy),
m_pstruc->ctx, m_pstruc->cty, kbfStructure);
gsim.GetLevel()->GetTerrainMap()->ClearFlags(TcFromWc(m_wx), TcFromWc(m_wy),
m_pstruc->ctxReserve, m_pstruc->ctyReserve, kbfReserved);
// Unshadow this structure's occupation in the gid map
ggobm.UnshadowGob(this, TcFromWc(m_wx), TcFromWc(m_wy), m_pstruc->ctx, m_pstruc->cty);
// Remove from Gid map and force minimap redraw
UnitGob::Delete();
}
#define knVerStructGobState 3
bool StructGob::LoadState(Stream *pstm)
{
byte nVer = pstm->ReadByte();
if (nVer != knVerStructGobState)
return false;
dword ctLastSmoke = pstm->ReadDword();
m_tLastSmoke = gsim.GetTickCount() - ctLastSmoke;
if (UnitGob::LoadState(pstm)) {
ggobm.ShadowGob(this, TcFromWc(m_wx), TcFromWc(m_wy), m_pstruc->ctx, m_pstruc->cty);
if (m_wfUnit & kfUnitNeedCredits)
WaitingForCredits(true, true);
return true;
}
return false;
}
bool StructGob::SaveState(Stream *pstm)
{
pstm->WriteByte(knVerStructGobState);
pstm->WriteDword((dword)(gsim.GetTickCount() - m_tLastSmoke));
return UnitGob::SaveState(pstm);
}
// are you sure dialog used to confirm structure recycling
bool StructGob::LoadConfirmation()
{
if (s_pfrmConfirmation != NULL)
return true;
s_pfrmConfirmation = new DialogForm();
if (s_pfrmConfirmation == NULL)
return false;
if (!s_pfrmConfirmation->Init(gpmfrmm, gpiniForms, kidfAreYouSure))
return false;
s_pfrmConfirmation->SetFlags(s_pfrmConfirmation->GetFlags() | kfFrmAutoTakedown);
gpmfrmm->RemoveForm(s_pfrmConfirmation);
return true;
}
bool StructGob::PopupConfirmation(char *pszTitle)
{
Assert(s_pfrmConfirmation != NULL);
// set the title color here so it will be correct for the gob we're
// popping off of
s_pfrmConfirmation->SetTitleColor(GetSideColor(GetSide()));
// set the position to be near the gob like the menu is
Rect rc;
s_pfrmConfirmation->GetRect(&rc);
int cx = rc.Width();
int cy = rc.Height();
Size sizPlayfield;
ggame.GetPlayfieldSize(&sizPlayfield);
WPoint wpt;
GetCenter(&wpt);
WCoord wxViewOrigin, wyViewOrigin;
gsim.GetViewPos(&wxViewOrigin, &wyViewOrigin);
int xDialog = PcFromWc(wpt.wx - wxViewOrigin) - (cx / 2);
int yDialog = PcFromWc(wpt.wy - wyViewOrigin) - (cy / 2);
if (xDialog < 0)
xDialog = 0;
else if (xDialog >= sizPlayfield.cx - cx)
xDialog = sizPlayfield.cx - cx;
if (yDialog < 0)
yDialog = 0;
else if (yDialog >= sizPlayfield.cy - cy)
yDialog = sizPlayfield.cy - cy;
rc.Offset(xDialog - rc.left, yDialog - rc.top);
s_pfrmConfirmation->SetRect(&rc);
// set the title
LabelControl *pctl = (LabelControl *)s_pfrmConfirmation->GetControlPtr(kidcTitle);
pctl->SetText(pszTitle);
// Show the dialog and get the user's selection
s_pfrmConfirmation->SetUserDataPtr((void*) this);
gpmfrmm->AddForm(s_pfrmConfirmation);
int idc;
s_pfrmConfirmation->DoModal(&idc);
gpmfrmm->RemoveForm(s_pfrmConfirmation);
return idc == kidcOk;
}
void StructGob::TakedownConfirmation()
{
Assert(s_pfrmConfirmation != NULL);
if ((StructGob *)s_pfrmConfirmation->GetUserDataPtr() == this)
s_pfrmConfirmation->EndForm(kidcCancel);
}
void StructGob::Activate()
{
// Now this structure can influence global power
m_pplr->AddPowerSupplyAndDemand(m_pstruc->nPowerSupply, m_pstruc->nPowerDemand);
// Add to area lists
AreaMask amNew = ggobm.CalcAreaMask(TcFromWc(m_wx), TcFromWc(m_wy), m_pstruc->ctx, m_pstruc->cty);
ggobm.MoveGobBetweenAreas(m_gid, 0, amNew);
// Call base
UnitGob::Activate();
}
void StructGob::Deactivate()
{
WaitingForCredits(false);
m_wfUnit &= ~(kfUnitRepairing | kfUnitDrawRepairingSymbol | kfUnitDrawNeedsPowerSymbol);
EnableOrDisableSymbolLayer();
// Remove this structure's global power influence
m_pplr->AddPowerSupplyAndDemand(-m_pstruc->nPowerSupply, -m_pstruc->nPowerDemand);
Assert(s_pfrmConfirmation != NULL);
if ((StructGob *)s_pfrmConfirmation->GetUserDataPtr() == this)
s_pfrmConfirmation->EndForm(kidcCancel);
// Remove from area lists
AreaMask amOld = ggobm.CalcAreaMask(TcFromWc(m_wx), TcFromWc(m_wy), m_pstruc->ctx, m_pstruc->cty);
ggobm.MoveGobBetweenAreas(m_gid, amOld, 0);
// Call base
UnitGob::Deactivate();
}
void StructGob::GetTileRect(TRect *ptrc)
{
ptrc->left = TcFromWc(m_wx);
ptrc->top = TcFromWc(m_wy);
ptrc->right = ptrc->left + m_pstruc->ctx;
ptrc->bottom = ptrc->top + m_pstruc->cty;
}
void StructGob::GetTilePaddedWRect(WRect *pwrc)
{
pwrc->left = WcTrunc(m_wx);
pwrc->top = WcTrunc(m_wy);
pwrc->right = pwrc->left + WcFromTc(m_pstruc->ctx);
pwrc->bottom = pwrc->top + WcFromTc(m_pstruc->cty);
}
bool StructGob::IsAccessible(TCoord tx, TCoord ty)
{
// tx, ty is in the structure "somewhere". See if it is accessible (a tile somewhere around it that is free
// of terrain and free of structures
TerrainMap *ptrmap = gsim.GetLevel()->GetTerrainMap();
TCoord ctx, cty;
ggobm.GetMapSize(&ctx, &cty);
for (Direction dir = 0; dir < 8; dir++) {
TCoord txT = tx + g_mpDirToDx[dir];
TCoord tyT = ty + g_mpDirToDy[dir];
if (txT < 0 || txT >= ctx || tyT < 0 || tyT >= cty)
continue;
if (!ptrmap->IsBlocked(txT, tyT, kbfStructure))
return true;
}
return false;
}
void StructGob::GetAttackPoint(WPoint *pwpt)
{
// Need to get a terrain accessible point on this structure that can be attacked.
// This tile needs to be free of blocking terrain and blocking structures.
// If there is none at least get the closest to the enemy.
// Don't use GetCenter() as that uses the UI rect. We want the structure rect which gaurantees
// a coordinate whose tile is occupied by this structure
WPoint wptExtent;
wptExtent.wx = WcFromTc(TcFromWc(m_wx) + m_pstruc->ctx);
wptExtent.wy = WcFromTc(TcFromWc(m_wy) + m_pstruc->cty);
pwpt->wx = WcTrunc(m_wx + (wptExtent.wx - m_wx) / 2) + kwcTileHalf;
pwpt->wy = WcTrunc(m_wy + (wptExtent.wy - m_wy) / 2) + kwcTileHalf;
TCoord txTest = TcFromWc(pwpt->wx);
TCoord tyTest = TcFromWc(pwpt->wy);
if (IsAccessible(txTest, tyTest))
return;
// Otherwise find a spot that is accessible
TCoord tx = TcFromWc(m_wx);
TCoord ty = TcFromWc(m_wy);
for (TCoord tyT = ty; tyT < ty + m_pstruc->cty; tyT++) {
for (TCoord txT = tx; txT < tx + m_pstruc->ctx; txT++) {
if (txT != txTest && tyT != tyTest) {
if (IsAccessible(txT, tyT)) {
pwpt->wx = WcFromTc(txT) + kwcTileHalf;
pwpt->wy = WcFromTc(tyT) + kwcTileHalf;
return;
}
}
}
}
// Couldn't find anything that is "accessible" unfortunately. Try to find something close by
// that is accessible
FindNearestFreeTile(TcFromWc(pwpt->wx), TcFromWc(pwpt->wy), pwpt, kbfStructure);
}
void StructGob::DrawSymbol(TBitmap *ptbm, DibBitmap *pbm, int xViewOrigin, int yViewOrigin)
{
int x = -xViewOrigin + PcFromWc(m_wx);
int y = -yViewOrigin + PcFromWc(m_wy);
Rect rcT;
rcT.FromWorldRect(&m_puntc->wrcUIBounds);
Size sizT;
ptbm->GetSize(&sizT);
ptbm->BltTo(pbm, x + (rcT.Width() - sizT.cx) / 2, y + (rcT.Height() - sizT.cy) / 2);
}
void StructGob::Draw(DibBitmap *pbm, int xViewOrigin, int yViewOrigin, int nLayer)
{
switch (nLayer) {
case knLayerMiniMap:
{
int iclr;
if (m_ff & kfGobSelected)
iclr = kiclrWhite;
else
iclr = GetSideColor(m_pplr->GetSide());
int cxy = gsim.GetMiniMapScale();
pbm->Fill(xViewOrigin + MmcFromWc(m_wx), yViewOrigin + MmcFromWc(m_wy), m_pstruc->ctx * cxy, m_pstruc->cty * cxy,
GetColor(iclr));
}
return;
case knLayerSymbols:
// only one symbol shows at a time. Low Power can alternate with repairing
// or with need credits, but repairing and need credits are mutually exclusive
UnitGob::Draw(pbm, xViewOrigin, yViewOrigin, nLayer);
if (m_wfUnit & kfUnitDrawNeedsPowerSymbol) {
DrawSymbol(s_ptbmNeedsPower, pbm, xViewOrigin, yViewOrigin);
} else {
if (m_wfUnit & kfUnitDrawRepairingSymbol) {
DrawSymbol(s_ptbmRepairing, pbm, xViewOrigin, yViewOrigin);
} else if (m_wfUnit & kfUnitDrawNeedCreditsSymbol) {
DrawSymbol(s_ptbmNeedCredits, pbm, xViewOrigin, yViewOrigin);
}
}
break;
case knLayerSelection:
if ((m_ff & (kfGobSelected | kfGobBeingBuilt)) == kfGobBeingBuilt) {
Rect rcT;
rcT.FromWorldRect(&m_puntc->wrcUIBounds);
rcT.Offset(-xViewOrigin + PcFromWc(m_wx), -yViewOrigin + PcFromWc(m_wy));
DrawHealthIndicator(pbm, &rcT, m_fxHealth, m_puntc->GetArmorStrength());
return;
}
// fall through
default:
UnitGob::Draw(pbm, xViewOrigin, yViewOrigin, nLayer);
break;
}
}
void StructGob::DefUpdate()
{
// If we're repairing we'll skip no updates, even if blocked on credits. If we're blocked
// on low power then we'll skip till we get to the next symbol flash. However, most of the time
// we will be none of these, so GetUpdateCount and do the math for cupdUntilFlash inside the tests
// for symbol situations rather than on every single update.
int cUpdates;
int cupdUntilFlash;
if ((m_puntc->wf & kfUntcNotifyPowerLowHigh) && m_pplr->IsPowerLow() && (m_ff & kfGobActive)) {
cUpdates = gsim.GetUpdateCount();
cupdUntilFlash = kcupdSymbolFlashRate - (cUpdates % kcupdSymbolFlashRate);
if (cupdUntilFlash == kcupdSymbolFlashRate) {
// Low Power will flash ON for odd intervals
if ((short)(cUpdates / kcupdSymbolFlashRate) & 1)
m_wfUnit |= kfUnitDrawNeedsPowerSymbol;
else
m_wfUnit &= ~kfUnitDrawNeedsPowerSymbol;
EnableOrDisableSymbolLayer();
if (gwfPerfOptions & kfPerfSymbolFlashing)
MarkRedraw();
}
m_unvl.MinSkip(cupdUntilFlash - 1);
}
// If repairing and sufficient credits are available
if (m_wfUnit & kfUnitRepairing) {
cUpdates = gsim.GetUpdateCount();
cupdUntilFlash = kcupdSymbolFlashRate - (cUpdates % kcupdSymbolFlashRate);
int nCreditsHave = m_pplr->GetCredits();
if (cupdUntilFlash == kcupdSymbolFlashRate) {
if (nCreditsHave > 0) {
// Repair will flash OFF for odd intervals
if ((short)(cUpdates / kcupdSymbolFlashRate) & 1)
m_wfUnit &= ~kfUnitDrawRepairingSymbol;
else
m_wfUnit |= kfUnitDrawRepairingSymbol;
} else {
m_wfUnit &= ~kfUnitDrawRepairingSymbol;
}
EnableOrDisableSymbolLayer();
if (gwfPerfOptions & kfPerfSymbolFlashing)
MarkRedraw();
}
fix fxArmorStrength = m_pstruc->GetArmorStrength();
if (m_fxHealth < fxArmorStrength) {
// n health unit costs m credits to repair
int nCreditsNeeded = knCreditsPerRepairUpdate;
// Take whatever is left (repair on a discount!)
if (nCreditsHave != 0 && nCreditsHave < nCreditsNeeded)
nCreditsNeeded = nCreditsHave;
if (nCreditsHave >= nCreditsNeeded) {
fix fxHealth = addfx(m_fxHealth, kfxHealthUnitsRepairedPerUpdate);
m_pplr->SetCredits(nCreditsHave - nCreditsNeeded, true, knConsumerRepair);
// make sure we're not in need credits mode
WaitingForCredits(false);
// At full strength? If so, stop repairing
if (fxHealth >= fxArmorStrength) {
fxHealth = fxArmorStrength;
m_wfUnit &= ~(kfUnitRepairing | kfUnitDrawRepairingSymbol);
Assert(!(m_wfUnit & kfUnitNeedCredits), "finished a repair without credits?");
EnableOrDisableSymbolLayer();
MarkRedraw();
}
SetHealth(fxHealth);
} else {
WaitingForCredits(true);
}
} else {
m_wfUnit &= ~(kfUnitRepairing | kfUnitDrawRepairingSymbol);
Assert(!(m_wfUnit & kfUnitNeedCredits), "finished a repair without credits?");
EnableOrDisableSymbolLayer();
MarkRedraw();
}
// when repairing we need every update to do our repair increments or
// to check for a fresh cash infusion (there is no credit change
// notification and because building can block w/o credits at zero it's
// too complex for now)
m_unvl.MinSkip();
}
if (m_wfUnit & kfUnitNeedCredits) {
cUpdates = gsim.GetUpdateCount();
cupdUntilFlash = kcupdSymbolFlashRate - (cUpdates % kcupdSymbolFlashRate);
if (cupdUntilFlash == kcupdSymbolFlashRate) {
// NeedCredits will flash OFF for odd intervals
if ((short)(cUpdates / kcupdSymbolFlashRate) & 1)
m_wfUnit &= ~kfUnitDrawNeedCreditsSymbol;
else
m_wfUnit |= kfUnitDrawNeedCreditsSymbol;
EnableOrDisableSymbolLayer();
if (gwfPerfOptions & kfPerfSymbolFlashing)
MarkRedraw();
}
// again, we have no credit infusion update, and so far all things that
// can block on credits get every update while credit consuming
m_unvl.MinSkip();
}
UnitGob::DefUpdate();
}
void StructGob::EnableOrDisableSymbolLayer()
{
dword ffOld = m_ff;
if (!IsAlly(gpplrLocal->GetSide()) && ((gpplrLocal->GetHandicap() & kfHcapShowEnemyResourceStatus) == 0))
m_wfUnit &= ~(kfUnitDrawNeedsPowerSymbol | kfUnitDrawNeedCreditsSymbol);
if (m_wfUnit & (kfUnitDrawRepairingSymbol | kfUnitDrawNeedsPowerSymbol | kfUnitDrawNeedCreditsSymbol)) {
m_ff |= kfGobLayerSymbols;
} else {
m_ff &= ~kfGobLayerSymbols;
}
// Changed?
if ((ffOld ^ m_ff) & kfGobLayerSymbols) {
if (gwfPerfOptions & kfPerfSymbolFlashing)
MarkRedraw();
}
}
bool StructGob::NeedsRepair()
{
return m_fxHealth < m_pstruc->GetArmorStrength();
}
bool StructGob::IsTakeoverable(Player *pplr)
{
// Can only takeover if the player has limit space
if (!ggobm.IsBelowLimit(knLimitStruct, pplr)) {
if (pplr == gpplrLocal)
ShowAlert(kidsBuildingLimitReached);
return false;
}
if (m_ff & kfGobActive)
return true;
return false;
}
void StructGob::Takeover(Player *pplr)
{
// Removes any player-specific influence this Structure has
Deactivate();
// Change owners
Assert(ggobm.IsBelowLimit(knLimitStruct, pplr));
ggobm.TrackGobCounts(this, false);
m_pplr->IncStructuresLost();
SetOwner(pplr);
m_pplr->IncEnemyStructuresKilled();
ggobm.TrackGobCounts(this, true);
// Applies any player-specific influence this Structure has
Activate();
// Reveal fog around this newly owned structure
if (pplr == gpplrLocal) {
WCoord wxView, wyView;
gsim.GetViewPos(&wxView, &wyView);
FogMap *pfogm = gsim.GetLevel()->GetFogMap();
WPoint wpt;
GetCenter(&wpt);
RevealPattern *prvlp = (RevealPattern *)(m_pstruc->wf & kfUntcLargeDefog ? grvlpLarge : grvlp);
pfogm->Reveal(TcFromWc(wpt.wx), TcFromWc(wpt.wy), prvlp, gpupdSim, wxView, wyView);
}
// force an update so it gets in sync on lowpower/credits etc
m_unvl.MinSkip();
// Redraw this part of the minimap
TRect trc;
GetTileRect(&trc);
gpmm->RedrawTRect(&trc);
}
// pplr is used for the replicator do do a custom override
void StructGob::WaitingForCredits(bool fNeed, bool fOverride, Player *pplr)
{
// override is for LoadState
if ((fNeed == ((m_wfUnit & kfUnitNeedCredits) == kfUnitNeedCredits)) && !fOverride)
return;
int cDelta;
if (fNeed) {
m_wfUnit |= kfUnitNeedCredits;
cDelta = 1;
} else {
m_wfUnit &= ~( kfUnitDrawNeedCreditsSymbol | kfUnitNeedCredits );
EnableOrDisableSymbolLayer();
if (gwfPerfOptions & kfPerfSymbolFlashing)
MarkRedraw();
cDelta = -1;
}
// track how many buildings need credits in the player so the local player can reflect
// that need in the UI
if (pplr == NULL)
pplr = m_pplr;
pplr->ModifyNeedCreditsCount(cDelta);
return;
}
void StructGob::OnPowerLowHigh()
{
if (!GetOwner()->IsPowerLow()) {
m_wfUnit &= ~kfUnitDrawNeedsPowerSymbol;
EnableOrDisableSymbolLayer();
MarkRedraw();
} else {
// force an update to start symbol flashing
m_unvl.MinSkip();
}
}
void StructGob::Repair(bool fOn)
{
if (fOn) {
// If already at max health, don't waste any more time
if (m_fxHealth == m_pstruc->GetArmorStrength())
return;
// toggle on repair state. Icon will be shown in update
m_wfUnit |= kfUnitRepairing;
if (m_pplr == gpplrLocal) {
if (m_pplr->GetCredits() > 0)
gsndm.PlaySfx(m_pstruc->sfxRepair);
}
} else {
m_wfUnit &= ~(kfUnitRepairing | kfUnitDrawRepairingSymbol | kfUnitDrawNeedCreditsSymbol);
WaitingForCredits(false);
if (m_pplr == gpplrLocal)
gsndm.PlaySfx(m_pstruc->sfxAbortRepair);
}
m_unvl.MinSkip();
}
bool StructGob::IsValidTarget(Gob *pgobTarget)
{
// most structures cannot attack. Towers can so they override this.
return false;
}
void StructGob::InitMenu(Form *pfrm)
{
ButtonControl *pbtn = (ButtonControl *)pfrm->GetControlPtr(kidcRepair);
pbtn->Enable(NeedsRepair());
pbtn->Show((m_wfUnit & kfUnitRepairing) == 0);
pbtn = (ButtonControl *)pfrm->GetControlPtr(kidcAbortRepair);
pbtn->Show((m_wfUnit & kfUnitRepairing) != 0);
}
void StructGob::OnMenuItemSelected(int idc)
{
switch (idc) {
// RepairCommand acts as a toggle
case kidcRepair:
case kidcAbortRepair:
gcmdq.Enqueue(kmidRepairCommand, m_gid);
break;
case kidcSelfDestruct:
if (PopupConfirmation("SELL BUILDING"))
gcmdq.Enqueue(kmidSelfDestructCommand, m_gid);
break;
}
}
void StructGob::SelfDestruct(bool fRecycleValue)
{
// Refund half the value of the structure if recycling
if (fRecycleValue) {
m_pplr->SetCredits(m_pplr->GetCredits() + (m_pstruc->GetCost() / 2), true);
// Only do this for 'sold' structures. Structures lost while being built don't count as 'lost'
m_pplr->IncStructuresLost();
}
SetState(kstDying);
}
void StructGob::SetHealth(fix fxHealth)
{
UnitGob::SetHealth(fxHealth);
if (m_ff & kfGobBeingBuilt)
return;
if (m_fxHealth * 2 < m_pstruc->GetArmorStrength()) {
if (m_ani.GetStrip() != 1)
StartAnimation(&m_ani, 1, 0, m_ani.GetFlags());
} else {
if (m_ani.GetStrip() != 0)
StartAnimation(&m_ani, 0, 0, m_ani.GetFlags());
}
}
ScorchGob *StructGob::GetScorchGob()
{
if (ggobm.IsBelowLimit(knLimitScorch))
return new ScorchGob;
// Find the oldest scorch and delete it
int nSequenceOldest = 0x7fff;
ScorchGob *pgobScorchOldest = NULL;
for (Gob *pgobT = ggobm.GetFirstGob(); pgobT != NULL; pgobT = ggobm.GetNextGob(pgobT)) {
if (pgobT->GetType() != kgtScorch)
continue;
ScorchGob *pgobScorch = (ScorchGob *)pgobT;
int nSequence = pgobScorch->GetSequence();
if (nSequence < nSequenceOldest) {
nSequenceOldest = nSequence;
pgobScorchOldest = pgobScorch;
}
}
Assert(pgobScorchOldest != NULL);
if (pgobScorchOldest != NULL) {
ggobm.RemoveGob(pgobScorchOldest);
delete pgobScorchOldest;
}
return new ScorchGob;
}
int StructGob::ProcessStateMachineMessage(State st, Message *pmsg)
{
BeginStateMachine
OnMsg(kmidPowerLowHigh)
OnPowerLowHigh();
OnMsg(kmidHit)
int fxHealthPrev = m_fxHealth;
// apply damage
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->IncEnemyStructuresKilled();
m_pplr->IncStructuresLost();
SetState(kstDying);
} else {
ShowDamageIndicator();
// If health is dipping into the severely damaged zone, fire off
// a couple smoke animations.
if (fxHealthPrev * 2 >= m_pstruc->GetArmorStrength() && m_fxHealth * 2 < m_pstruc->GetArmorStrength()) {
// Play damaged sound
gsndm.PlaySfx(m_pstruc->sfxDamaged);
if (gsim.GetTickCount() - m_tLastSmoke > 200) {
int cSmokes = (GetRandom() % 3) + 2;
if (m_pstruc->ctx == 1 || m_pstruc->cty == 1)
cSmokes /= 2;
for (int i = 0; i < cSmokes; i++)
gsmm.SendDelayedMsg(kmidSpawnSmoke, GetRandom() & 63, m_gid, m_gid);
m_tLastSmoke = gsim.GetTickCount();
}
}
}
// Remember that a structure has been attacked
m_pplr->SetFlags(m_pplr->GetFlags() | kfPlrStructureAttacked);
// Notify nearby allies that we've been hit! CONSIDER: pass in an expanded range?
NotifyNearbyAlliesOfHit(pmsg->Hit.gidAssailant);
OnMsg(kmidSpawnSmoke)
if (gwfPerfOptions & kfPerfSmoke) {
if (ggobm.IsBelowLimit(knLimitSupport)) {
SmokeGob *pgob = new SmokeGob((GetRandom() & 3) + 4);
Assert(pgob != NULL, "out of memory!");
if (pgob != NULL) {
WCoord wcx = WcFromTc(m_pstruc->ctx);
WCoord wcy = WcFromTc(m_pstruc->cty) - kwcTileHalf;
WCoord wdx = GetRandom() % wcx;
WCoord wdy = (GetRandom() % wcy) + WcFromTile16ths(12);
WCoord wxSmoke = m_wx + wdx;
WCoord wySmoke = m_wy + wdy;
Size wsizMap;
gsim.GetLevel()->GetTileMap()->GetTCoordMapSize(&wsizMap);
wsizMap.cx = WcFromTc(wsizMap.cx);
wsizMap.cy = WcFromTc(wsizMap.cy);
if (wxSmoke < 0)
wxSmoke = 0;
if (wxSmoke >= wsizMap.cx)
wxSmoke = wsizMap.cx - 1;
if (wySmoke >= wsizMap.cy)
wySmoke = wsizMap.cy - 1;
if (!pgob->Init(wxSmoke, wySmoke, m_gid))
delete pgob;
}
}
}
OnMsg(kmidSelfDestructCommand)
// assume we only get this message when user has
// initiated a recycle. Game can call the method.
SelfDestruct();
OnMsg(kmidRepairCommand)
// Toggle repairing
Repair(!(m_wfUnit & kfUnitRepairing));
OnMsg(kmidDelete)
Assert("Shouldn't receive kmidDelete when not in kstDying state");
//-----------------------------------------------------------------------
State(kstBeingBuilt)
OnEnter
m_ff |= kfGobLayerSelection;
OnExit
m_ff &= ~kfGobLayerSelection;
OnMsg(kmidBuildComplete)
Activate();
SetState(kstIdle);
OnUpdate
// Don't advance animation
DefUpdate();
//-----------------------------------------------------------------------
State(kstIdle)
OnEnter
// Play idle animation, chosen based on the health of the Structure
int nStrip;
if (m_fxHealth == 0)
nStrip = 2; // destroyed
else if (m_fxHealth * 2 < m_pstruc->GetArmorStrength())
nStrip = 1; // damaged
else
nStrip = 0; // healthy
StartAnimation(&m_ani, nStrip, 0, kfAniIgnoreFirstAdvance | kfAniLoop);
OnUpdate
AdvanceAnimation(&m_ani);
DefUpdate();
//-----------------------------------------------------------------------
State(kstDying)
OnEnter
// if you're destroyed during building it's possible to get here unactivated
if (m_ff & kfGobActive)
Deactivate();
m_ff ^= kfGobLayerDepthSorted | kfGobLayerSurfaceDecal;
gsndm.PlaySfx(m_pstruc->sfxDestroyed);
StartAnimation(&m_ani, 2, 0, kfAniIgnoreFirstAdvance); // UNDONE: hardcoded
// dont remove ourselves from the "occupied" map until it's all over
// so troops can't run across this freshly destroyed area while it's hot (they have cheap boots)
gsmm.SendDelayedMsg(kmidDelete, 800, m_gid, m_gid);
// Show a huge explosion!
WCoord wcx = WcFromTc(m_pstruc->ctx);
WCoord wcy = WcFromTc(m_pstruc->cty) + kwcTileHalf;
Gob *pgobExpl = CreateAnimGob(m_wx + (wcx / 2), m_wy + (wcy / 2), kfAnmDeleteWhenDone | kfAnmSmokeFireLayer, NULL,
s_panidStructureExplosion);
if (pgobExpl != NULL)
pgobExpl->SetOwner(m_pplr);
// Show some smoke (in the near future)
int cSmokes = (GetRandom() % 3) + 1;
int i;
for (i = 0; i < cSmokes; i++)
gsmm.SendDelayedMsg(kmidSpawnSmoke, GetRandom() & 63, m_gid, m_gid);
// Lay down a random smattering of scorch marks
if (gwfPerfOptions & kfPerfScorchMarks) {
int cScorches = (GetRandom() & 3) + 1;
WCoord wcxScorchable = WcFromTc(m_pstruc->ctx);
WCoord wcyScorchable = WcFromTc(m_pstruc->cty);
Size wsizMap;
gsim.GetLevel()->GetTileMap()->GetTCoordMapSize(&wsizMap);
wsizMap.cx = WcFromTc(wsizMap.cx);
wsizMap.cy = WcFromTc(wsizMap.cy);
for (i = 0; i < cScorches; i++) {
// Pick a random scorch type to lay down but make sure it is
// one that is smaller than the structure leaving it.
Size sizScorch;
WCoord wcxScorch, wcyScorch;
int nScorch;
do {
nScorch = GetRandom() % (sizeof(gaptbmScorches) / sizeof(TBitmap *));
gaptbmScorches[nScorch]->GetSize(&sizScorch);
wcxScorch = WcFromUpc(sizScorch.cx);
wcyScorch = WcFromUpc(sizScorch.cy);
} while (wcxScorch > wcxScorchable || wcyScorch > wcyScorchable);
WCoord wxoff = GetRandom() % (wcxScorchable - wcxScorch + 1);
WCoord wyoff = GetRandom() % (wcyScorchable - wcyScorch + 1);
ScorchGob *pgobScorch = GetScorchGob();
if (pgobScorch != NULL) {
// Make sure the scorch stays within the map bounds
WCoord wxScorch = m_wx + wxoff + (wcxScorch / 2);
WCoord wyScorch = m_wy + wyoff + wcyScorch;
if (wxScorch < 0)
wxScorch = 0;
if (wxScorch >= wsizMap.cx)
wxScorch = wsizMap.cx - 1;
if (wyScorch >= wsizMap.cy)
wyScorch = wsizMap.cy - 1;
pgobScorch->Init(wxScorch, wyScorch, nScorch);
}
}
}
OnUpdate
AdvanceAnimation(&m_ani);
OnMsg(kmidDelete)
Delete();
return knDeleted;
// Do this so the kmidSpawnSmoke message will bubble up to the 'global' handler above
OnMsg(kmidSpawnSmoke)
return knNotHandled;
// Eat all other messages (e.g., kmidHit, kmidNearbyAllyHit)
DiscardMsgs
//-----------------------------------------------------------------------
EndStateMachine
}
//
// SmokeGob implementation
//
SmokeGob::SmokeGob(int cLoops)
{
m_cLoops = cLoops;
}
bool SmokeGob::Init(WCoord wx, WCoord wy, StateMachineId smidNotify)
{
return AnimGob::Init(wx, wy, kfAnmLoop | kfAnmSmokeFireLayer, NULL, s_panidBigSmoke, 0, smidNotify, NULL);
}
#define knVerSmokeGobState 1
bool SmokeGob::LoadState(Stream *pstm)
{
byte nVer = pstm->ReadByte();
if (nVer != knVerSmokeGobState)
return false;
m_cLoops = pstm->ReadWord();
m_ani.Init(s_panidBigSmoke);
m_ani.LoadState(pstm);
m_smidNotify = pstm->ReadWord();
m_wfAnm = kfAnmLoop | kfAnmSmokeFireLayer;
return AnimGob::LoadState(pstm);
}
bool SmokeGob::SaveState(Stream *pstm)
{
pstm->WriteByte(knVerSmokeGobState);
pstm->WriteWord(m_cLoops);
m_ani.SaveState(pstm);
pstm->WriteWord(m_smidNotify);
return AnimGob::SaveState(pstm);
}
bool SmokeGob::IsSavable()
{
// Because AnimGob is not savable, we need to override
return true;
}
GobType SmokeGob::GetType()
{
return kgtSmoke;
}
// After the initial set (smoke growing) completes, draw the
// cycle a few times then destroy self.
bool SmokeGob::OnStripDone()
{
if (m_ani.GetStrip() == 0) {
SetAnimationStrip(&m_ani, 1);
} else {
m_cLoops--;
if (m_cLoops < 0)
return true;
}
return false;
}
} // namespace wi