hostile-takeover/game/Processor.cpp
2016-08-31 23:54:48 -04:00

634 lines
17 KiB
C++

#include "ht.h"
#include "wistrings.h"
namespace wi {
//
// ProcessorGob implementation
//
static StructConsts gConsts;
//
// Gob methods
//
bool ProcessorGob::InitClass(IniReader *pini)
{
gConsts.gt = kgtProcessor;
gConsts.ut = kutProcessor;
gConsts.umPrerequisites = kumReactor;
gConsts.wf |= kfUntcHasFullnessIndicator;
// Sound effects
gConsts.sfxAbortRepair = ksfxGalaxiteProcessorAbortRepair;
gConsts.sfxRepair = ksfxGalaxiteProcessorRepair;
gConsts.sfxDamaged = ksfxGalaxiteProcessorDamaged;
gConsts.sfxSelect = ksfxGalaxiteProcessorSelect;
gConsts.sfxDestroyed = ksfxGalaxiteProcessorDestroyed;
gConsts.sfxImpact = ksfxNothing;
return StructGob::InitClass(&gConsts, pini);
}
void ProcessorGob::ExitClass()
{
StructGob::ExitClass(&gConsts);
}
ProcessorGob::ProcessorGob() : StructGob(&gConsts)
{
m_aniOverlay.Init(m_pstruc->panid);
StartAnimation(&m_aniOverlay, 3, 0, 0);
m_gidMiner = kgidNull;
m_wptFakeMiner.wx = kwxInvalid;
m_fProcessingAnimationInProgress = false;
}
#define knVerProcessorGobState 2
bool ProcessorGob::LoadState(Stream *pstm)
{
byte nVer = pstm->ReadByte();
if (nVer != knVerProcessorGobState)
return false;
m_gidMiner = pstm->ReadWord();
m_wptFakeMiner.wx = pstm->ReadWord();
m_wptFakeMiner.wy = pstm->ReadWord();
m_fProcessingAnimationInProgress = pstm->ReadByte() != 0 ? true : false;
m_fDoorMoving = pstm->ReadByte() != 0 ? true : false;
m_aniOverlay.LoadState(pstm);
return StructGob::LoadState(pstm);
}
bool ProcessorGob::SaveState(Stream *pstm)
{
pstm->WriteByte(knVerProcessorGobState);
pstm->WriteWord(m_gidMiner);
pstm->WriteWord(m_wptFakeMiner.wx);
pstm->WriteWord(m_wptFakeMiner.wy);
pstm->WriteByte(m_fProcessingAnimationInProgress);
pstm->WriteByte(m_fDoorMoving);
m_aniOverlay.SaveState(pstm);
return StructGob::SaveState(pstm);
}
int CalcCreditsShare(Player *pplr)
{
int cWarehouses = pplr->GetUnitCount(kutWarehouse);
int cProcessors = pplr->GetUnitCount(kutProcessor);
// Takeover the credits this Processor 'owns'
return pplr->GetCredits() / (cWarehouses + cProcessors);
}
void ProcessorGob::GetClippingBounds(Rect *prc)
{
UnitGob::GetClippingBounds(prc);
if (m_wptFakeMiner.wx != kwxInvalid) {
int ifrm = m_wptFakeMiner.wy < m_wy + kwcTile ? 1 : 0;
int xMiner = PcFromUwc(m_wptFakeMiner.wx);
int yMiner = PcFromWc(m_wptFakeMiner.wy);
Rect rcFakeMiner;
m_pstruc->panid->GetBounds(4, ifrm, &rcFakeMiner);
rcFakeMiner.Offset(xMiner, yMiner);
prc->Union(&rcFakeMiner);
}
}
bool ProcessorGob::IsTakeoverable(Player *pplr)
{
// Have to make sure there is limit space for the miner too
if (m_gidMiner != kgidNull) {
MinerGob *pgobMiner = (MinerGob *)ggobm.GetGob(m_gidMiner, false);
if (pgobMiner != NULL) {
if (!ggobm.IsBelowLimit(knLimitMobileUnit, pplr)) {
if (pplr == gpplrLocal)
ShowAlert(kidsUnitLimitReached);
return false;
}
}
}
return StructGob::IsTakeoverable(pplr);
}
// Override Takeover to funds to the new owner
void ProcessorGob::Takeover(Player *pplr)
{
// Takeover the credits this Processor 'owns'
int cCreditsTaken = CalcCreditsShare(m_pplr);
m_pplr->SetCredits(m_pplr->GetCredits() - cCreditsTaken, true);
pplr->SetCredits(pplr->GetCredits() + cCreditsTaken, true);
// if there's a miner parked inside, winner!
if (m_gidMiner != kgidNull) {
// take it over
MinerGob *pgobMiner = (MinerGob *)ggobm.GetGob(m_gidMiner, false);
if (pgobMiner != NULL) {
pgobMiner->Deactivate();
Assert(ggobm.IsBelowLimit(knLimitMobileUnit, pplr));
ggobm.TrackGobCounts(pgobMiner, false);
pgobMiner->SetOwner(pplr);
ggobm.TrackGobCounts(pgobMiner, true);
pgobMiner->Activate();
} else {
Assert(true); // there should be a miner!
}
}
// Takeover the Processor itself
StructGob::Takeover(pplr);
}
void ProcessorGob::Draw(DibBitmap *pbm, int xViewOrigin, int yViewOrigin, int nLayer)
{
// Draw base
StructGob::Draw(pbm, xViewOrigin, yViewOrigin, nLayer);
// Draw overlay
if (nLayer == knLayerDepthSorted) {
// Don't draw the overlay on top of the destroyed base
if (m_ani.GetStrip() != 2) {
Side side = m_pplr->GetSide();
// Draw fake miner if it is meant to be
if (m_wptFakeMiner.wx != kwxInvalid) {
int ifrm = m_wptFakeMiner.wy < m_wy + kwcTile ? 1 : 0;
int xMiner = PcFromUwc(m_wptFakeMiner.wx) - xViewOrigin;
int yMiner = PcFromWc(m_wptFakeMiner.wy) - yViewOrigin;
m_pstruc->panid->DrawFrame(4, ifrm, pbm, xMiner, yMiner, side);
}
if (m_ff & kfGobDrawFlashed)
side = (Side)-1;
else if (m_ff & kfGobBeingBuilt)
side = ksideNeutral;
int x = PcFromUwc(m_wx) - xViewOrigin;
int y = PcFromUwc(m_wy) - yViewOrigin;
m_aniOverlay.Draw(pbm, x, y, side);
}
} else if (nLayer == knLayerSelection && (m_ff & kfGobSelected)) {
WRect wrcT;
GetUIBounds(&wrcT);
Rect rcT;
rcT.FromWorldRect(&wrcT);
rcT.Offset(-xViewOrigin, -yViewOrigin);
int nCapacity = m_pplr->GetCapacity();
int nPips = 0;
if (nCapacity != 0)
nPips = ((m_pplr->GetCredits() * 10) + (nCapacity / 20)) / nCapacity;
DrawFullnessIndicator(pbm, &rcT, nPips, 10);
}
}
dword ProcessorGob::GetAnimationHash()
{
dword dw = StructGob::GetAnimationHash();
int nFrame = m_aniOverlay.GetFrame();
int nStrip = m_aniOverlay.GetStrip();
dw ^= (m_wptFakeMiner.wx << 16) | m_wptFakeMiner.wy;
return dw ^ ((nFrame << 16) | nStrip);
}
void ProcessorGob::GetAnimationBounds(Rect *prc, bool fBase)
{
if (fBase) {
m_ani.GetAnimationData()->GetBounds(0, 0, prc);
return;
}
StructGob::GetAnimationBounds(prc, fBase);
if (m_wptFakeMiner.wx != kwxInvalid) {
int ifrm = m_wptFakeMiner.wy < m_wy + kwcTile ? 1 : 0;
Rect rcBounds;
m_pstruc->panid->GetBounds(4, ifrm, &rcBounds);
int xOffsetMiner = PcFromUwc(m_wptFakeMiner.wx - m_wx);
int yOffsetMiner = PcFromWc(m_wptFakeMiner.wy - m_wy);
rcBounds.Offset(xOffsetMiner, yOffsetMiner);
prc->Union(&rcBounds);
}
}
void ProcessorGob::DrawAnimation(DibBitmap *pbm, int x, int y)
{
StructGob::DrawAnimation(pbm, x, y);
// Don't go further if destroyed
if (m_ani.GetStrip() == 2) {
return;
}
Side side = m_pplr->GetSide();
if (m_wptFakeMiner.wx != kwxInvalid) {
int xOffsetMiner = PcFromUwc(m_wptFakeMiner.wx - m_wx);
int yOffsetMiner = PcFromWc(m_wptFakeMiner.wy - m_wy);
int ifrm = m_wptFakeMiner.wy < m_wy + kwcTile ? 1 : 0;
m_pstruc->panid->DrawFrame(4, ifrm, pbm, x + xOffsetMiner,
y + yOffsetMiner, side);
}
if (m_ff & kfGobDrawFlashed) {
side = (Side)-1;
} else if (m_ff & kfGobBeingBuilt) {
side = ksideNeutral;
}
m_aniOverlay.Draw(pbm, x, y, side);
}
void ProcessorGob::NotifyMinersAttemptingDelivery(bool fDying)
{
// Notify miners that are stuck attempting delivery to this processor that the
// processor is available.
for (Gob *pgobT = ggobm.GetFirstGob(); pgobT != NULL; pgobT = ggobm.GetNextGob(pgobT)) {
if (pgobT->GetType() != kgtGalaxMiner)
continue;
if (pgobT->GetOwner() != m_pplr)
continue;
if (!(pgobT->GetFlags() & kfGobActive))
continue;
MinerGob *pmnr = (MinerGob *)pgobT;
if (pmnr->IsAttemptingToDeliver(m_gid)) {
if (fDying) {
pmnr->SendDeliverCommand(kgidNull);
} else {
pmnr->SendDeliverCommand(m_gid);
}
}
}
}
//
// StateMachine methods
//
#if defined(DEBUG_HELPERS)
char *ProcessorGob::GetName()
{
return "Processor";
}
#endif
int ProcessorGob::ProcessStateMachineMessage(State st, Message *pmsg)
{
BeginStateMachine
OnMsg(kmidGalaxiteDelivery)
// Remember the MinerGob and hide it (deselect it first)
// NOTE: It still occupies terrain and is counted in its owning
// Player's unit counts.
m_gidMiner = pmsg->smidSender;
MinerGob *pgobMiner = (MinerGob *)ggobm.GetGob(m_gidMiner, false);
pgobMiner->Hide(true);
// Set / clear reservation / occupation bits
TPoint tpt;
pgobMiner->GetTilePosition(&tpt);
gsim.GetLevel()->GetTerrainMap()->SetFlags(tpt.tx, tpt.ty, 1, 1, kbfStructure);
gsim.GetLevel()->GetTerrainMap()->ClearFlags(tpt.tx, tpt.ty, 1, 1, kbfMobileUnit);
SetState(kstProcessorGetMiner);
OnMsg(kmidAnimationComplete)
m_fProcessingAnimationInProgress = false;
m_unvl.MinSkip();
//-----------------------------------------------------------------------
State(kstBeingBuilt)
OnMsg(kmidBuildComplete)
// When a Processor is built at run time it brings a fresh Miner along with
// itself (included, free of charge!).
if (ggobm.IsBelowLimit(knLimitMobileUnit, m_pplr)) {
MinerGob *pmnr = new MinerGob();
Assert(pmnr != NULL, "out of memory!");
if (pmnr == NULL)
goto lbError;
if (!pmnr->Init(m_wx + kwcTile, m_wy + (kwcTile * 2), m_pplr, 0, 0, NULL)) {
delete pmnr;
goto lbError;
}
// Make sure the Miner prefers to return to this Processor
pmnr->SetFavoriteProcessor(m_gid);
// Hide the Miner so we can 'launch' it, same as it is launched
// after it completes a Galaxite delivery.
m_gidMiner = pmnr->GetId();
pmnr->GetCenter(&m_wptFakeMiner);
m_wptFakeMiner.wy = m_wy + WcFromTile16ths(15);
pmnr->Hide(true);
// Set / clear reservation / occupation bits
TPoint tpt;
pmnr->GetTilePosition(&tpt);
gsim.GetLevel()->GetTerrainMap()->SetFlags(tpt.tx, tpt.ty, 1, 1, kbfStructure);
gsim.GetLevel()->GetTerrainMap()->ClearFlags(tpt.tx, tpt.ty, 1, 1, kbfMobileUnit);
StartAnimation(&m_aniOverlay, 3, -1, 0);
SetState(kstProcessorPutMiner);
} else {
lbError:
// The miner spot was reserved while the gob was being built. Since the new
// miner wasn't created, clear the spot so other miners can use the processor.
Assert(m_pstruc->ctx == 3 && m_pstruc->cty == 2);
gsim.GetLevel()->GetTerrainMap()->ClearFlags(TcFromWc(m_wx) + 1, TcFromWc(m_wy) + m_pstruc->cty, 1, 1, kbfStructure);
SetState(kstIdle);
}
Activate();
OnEnter
// init will mark the building location as occupied.
// additionally here we'll mark the spot in front of our front door
// as occupied so the miner can exit when we're done building.
// We twiddle these flags on kmidGalaxiteDelivery and kstPutMiner
Assert(m_pstruc->ctx == 3 && m_pstruc->cty == 2);
gsim.GetLevel()->GetTerrainMap()->SetFlags(TcFromWc(m_wx) + 1,
TcFromWc(m_wy) + m_pstruc->cty, 1, 1, kbfStructure);
return StructGob::ProcessStateMachineMessage(st, pmsg);
//-----------------------------------------------------------------------
State(kstProcessorGetMiner)
OnEnter
MinerGob *pgobMiner = (MinerGob *)ggobm.GetGob(m_gidMiner, false);
if (pgobMiner != NULL) {
pgobMiner->GetCenter(&m_wptFakeMiner);
pgobMiner->Select(false);
pgobMiner->Hilight(false);
StartAnimation(&m_aniOverlay, 3, 0, 0);
m_fDoorMoving = false;
}
OnExit
// To keep StructGob from attempting to handle a state it doesn't understand
OnUpdate
m_unvl.MinSkip();
// Open the door when the miner gets close
if (m_wptFakeMiner.wy < m_wy + WcFromTile16ths(35)) {
AdvanceAnimation(&m_aniOverlay);
if (!m_fDoorMoving) {
m_fDoorMoving = true;
if (m_pplr == gpplrLocal)
gsndm.PlaySfx(ksfxGalaxiteProcessorDoorOpening);
}
}
if (m_wptFakeMiner.wy > m_wy + WcFromTile16ths(15)) {
m_wptFakeMiner.wy -= kwcTile16th;
MarkRedraw();
} else {
SetState(kstProcessorTakeGalaxite);
}
DefUpdate();
State(kstProcessorTakeGalaxite)
OnEnter
OnExit
// To keep StructGob from attempting to handle a state it doesn't understand
OnUpdate
m_unvl.MinSkip();
// Add credits while taking galaxite from miner
MinerGob *pgobMiner = (MinerGob *)ggobm.GetGob(m_gidMiner, false);
Assert(pgobMiner != NULL);
int nAmount = pgobMiner->GetGalaxiteAmount();
if (nAmount > 0) {
nAmount -= 2;
int nGalaxiteValue = m_pplr->GetHandicap() & kfHcapIncreasedMinerLoadValue ?
((knGalaxiteValue * (100 + knIncreasedMinerLoadValuePercent)) + 50) / 100 : knGalaxiteValue;
int nCreditsNew = m_pplr->GetCredits() + 2 * nGalaxiteValue;
if (nCreditsNew <= m_pplr->GetCapacity()) {
MarkRedraw();
m_pplr->SetCredits(nCreditsNew, true);
pgobMiner->SetGalaxiteAmount(nAmount);
// Kick off the processing animation if it isn't already in progress
if (!m_fProcessingAnimationInProgress) {
if (ggobm.IsBelowLimit(knLimitSupport)) {
PuffGob *pgob = new PuffGob();
Assert(pgob != NULL, "out of memory!");
if (pgob != NULL)
pgob->Init(m_wx, m_wy, m_gid);
}
m_fProcessingAnimationInProgress = true;
}
} else {
// Need more storage!
gsim.GetLevel()->GetTriggerMgr()->SetConditionTrue(knGalaxiteCapacityReachedCondition,
GetSideMask(GetSide()));
// TUNE:
#define kctIntervalStorageNotify (30 * 100)
if (m_pplr == gpplrLocal) {
static long s_tLastStorageNotify = 0;
long tCurrent = HostGetTickCount();
if (s_tLastStorageNotify == 0 || (tCurrent - s_tLastStorageNotify) >= kctIntervalStorageNotify) {
s_tLastStorageNotify = tCurrent;
gsndm.PlaySfx(ksfxGalaxiteWarehouseTooFull);
ShowAlert(kidsWarehouseTooFull);
}
}
}
} else {
SetState(kstProcessorPutMiner);
}
DefUpdate();
State(kstProcessorPutMiner)
OnEnter
m_fDoorMoving = false;
OnExit
// Restore the MinerGob.
MinerGob *pgobMiner = (MinerGob *)ggobm.GetGob(m_gidMiner, false);
Assert(pgobMiner != NULL);
pgobMiner->Hide(false);
// Set / clear reservation / occupation bits
TPoint tpt;
pgobMiner->GetTilePosition(&tpt);
gsim.GetLevel()->GetTerrainMap()->ClearFlags(tpt.tx, tpt.ty, 1, 1, kbfStructure);
gsim.GetLevel()->GetTerrainMap()->SetFlags(tpt.tx, tpt.ty, 1, 1, kbfMobileUnit);
// Have the Miner resume mining
// NOTE: We send a message rather than call MinerGob::Mine method directly
// so the state change will occur immediately.
SendMineCommand(pgobMiner->GetId(), kwxInvalid, 0);
m_gidMiner = kgidNull;
m_wptFakeMiner.wx = kwxInvalid;
// Notify any miners attempting to deliver
NotifyMinersAttemptingDelivery(false);
OnUpdate
m_unvl.MinSkip();
WPoint wptDst;
((MinerGob *)ggobm.GetGob(m_gidMiner, false))->GetCenter(&wptDst);
if (m_wptFakeMiner.wy > m_wy + WcFromTile16ths(27)) {
int ifrm = m_aniOverlay.GetFrame();
if (ifrm > 0) {
SetAnimationFrame(&m_aniOverlay, --ifrm);
if (!m_fDoorMoving) {
m_fDoorMoving = true;
if (m_pplr == gpplrLocal)
gsndm.PlaySfx(ksfxGalaxiteProcessorDoorClosing);
}
}
}
if (m_wptFakeMiner.wy < wptDst.wy) {
m_wptFakeMiner.wy += kwcTile16th;
MarkRedraw();
} else {
SetState(kstIdle);
}
DefUpdate();
State(kstDying)
OnEnter
#if 0 // DWM: crazy last minute change. Players don't lose credits when they sell Processors
// Sorry, player must lose some Credits along with this Processor
if (m_ff & kfGobActive)
m_pplr->SetCredits(m_pplr->GetCredits() - CalcCreditsShare(m_pplr), true);
#endif
// Remove the Miner too if it's inside the Processor
MinerGob *pgobMiner = (MinerGob *)ggobm.GetGob(m_gidMiner, false);
if (pgobMiner != NULL) {
// Clear the structure bit so the "miner tile" is free again
TPoint tpt;
pgobMiner->GetTilePosition(&tpt);
gsim.GetLevel()->GetTerrainMap()->ClearFlags(tpt.tx, tpt.ty, 1, 1, kbfStructure);
// Deactivate the miner first which will subtract
// it from its owning Player's unit counts.
pgobMiner->Deactivate();
ggobm.RemoveGob(pgobMiner);
delete pgobMiner;
m_gidMiner = kgidNull;
}
// The miner spot is reserved while this gob is being built. Clear the bit just in case.
// We don't need to check anything, just clearing it is safe.
Assert(m_pstruc->ctx == 3 && m_pstruc->cty == 2);
gsim.GetLevel()->GetTerrainMap()->ClearFlags(TcFromWc(m_wx) + 1,
TcFromWc(m_wy) + m_pstruc->cty, 1, 1, kbfStructure);
int nHandled = StructGob::ProcessStateMachineMessage(st, pmsg);
// Now that this gob is deactivated, notify any miners attempting
// deliver to find a different processor
NotifyMinersAttemptingDelivery(true);
return nHandled;
#if 0
EndStateMachineInherit(StructGob)
#else
return knHandled;
}
} else {
return (int)StructGob::ProcessStateMachineMessage(st, pmsg);
}
return (int)StructGob::ProcessStateMachineMessage(st, pmsg);
#endif
}
//
// PuffGob implementation
// UNDONE: Consider making AnimGob savable by passing in an index
// referring to a global array of AnimData's (then the index can be saved
// and we can get rid of PuffGob).
//
PuffGob::PuffGob()
{
}
bool PuffGob::Init(WCoord wx, WCoord wy, StateMachineId smidNotify)
{
UnitConsts *puntc = gapuntc[kutProcessor];
return AnimGob::Init(wx, wy, kfAnmDeleteWhenDone | kfAnmSmokeFireLayer, NULL, puntc->panid, puntc->panid->GetStripIndex("smoke"), smidNotify, NULL);
}
#define knVerPuffGobState 1
bool PuffGob::LoadState(Stream *pstm)
{
byte nVer = pstm->ReadByte();
if (nVer != knVerPuffGobState)
return false;
UnitConsts *puntc = gapuntc[kutProcessor];
m_ani.Init(puntc->panid);
m_ani.SetStrip(puntc->panid->GetStripIndex("smoke"));
m_ani.LoadState(pstm);
m_smidNotify = pstm->ReadWord();
m_wfAnm = kfAnmDeleteWhenDone | kfAnmSmokeFireLayer;
return AnimGob::LoadState(pstm);
}
bool PuffGob::SaveState(Stream *pstm)
{
pstm->WriteByte(knVerPuffGobState);
m_ani.SaveState(pstm);
pstm->WriteWord(m_smidNotify);
return AnimGob::SaveState(pstm);
}
bool PuffGob::IsSavable()
{
// Because AnimGob is not savable, we need to override
return true;
}
GobType PuffGob::GetType()
{
return kgtPuff;
}
} // namespace wi