hostile-takeover/game/Processor.cpp
Nathan Fulton 489399dfb4 Rotate miner south west to enter processor
art2432 processor faces more towards the south west compared to the art824 processor which faced basically south. The coordinates used for the fakeMiner animation have been modified to compensate for this and the miner now rotates to the south west before entering.
2017-10-21 20:42:32 -04:00

637 lines
18 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, 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);
m_wptFakeMiner.wx = m_wx + WcFromTile16ths(27);
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),
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(19)) {
m_wptFakeMiner.wy -= kwcTile16th;
m_wptFakeMiner.wx += kwcTile / 21;
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;
m_wptFakeMiner.wx -= kwcTile / 21;
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),
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