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

565 lines
16 KiB
C++

#include "ht.h"
#include "wistrings.h"
namespace wi {
//
// ReplicatorGob implementation
//
static AnimationData *s_panidActivator = NULL;
static BuilderConsts gConsts;
#if defined(DEBUG_HELPERS)
char *ReplicatorGob::GetName()
{
return "Replicator";
}
#endif
bool ReplicatorGob::InitClass(IniReader *pini)
{
gConsts.gt = kgtReplicator;
gConsts.ut = kutReplicator;
// Sound effects
gConsts.sfxUnitReady = ksfxReplicatorBuild;
#if 0
gConsts.sfxPowerOn = ksfxReplicatorOn;
gConsts.sfxPowerOff = ksfxReplicatorOff;
gConsts.sfxUnitBuildAbort = ksfxReplicatorAbortRecruiting;
gConsts.sfxUnitReady = ksfxReplicatorUnitReady;
gConsts.sfxAbortRepair = ksfxReplicatorAbortRepair;
gConsts.sfxDamaged = ksfxReplicatorDamaged;
gConsts.sfxDestroyed = ksfxReplicatorDestroyed;
gConsts.sfxRepair = ksfxReplicatorRepair;
gConsts.sfxSelect = ksfxReplicatorSelect;
#endif
// Preload the Activator animation data
if (s_panidActivator == NULL) {
s_panidActivator = LoadAnimationData("activator.anir");
if (s_panidActivator == NULL)
return false;
}
return StructGob::InitClass(&gConsts, pini);
}
void ReplicatorGob::ExitClass()
{
delete s_panidActivator;
s_panidActivator = NULL;
StructGob::ExitClass(&gConsts);
}
ReplicatorGob::ReplicatorGob() : StructGob(&gConsts)
{
m_fEnabled = false;
m_fReplicating = false;
m_pplrNeedCredits = NULL;
}
bool ReplicatorGob::Init(WCoord wx, WCoord wy, Player *pplr, fix fxHealth, dword ff, const char *pszName)
{
if (!StructGob::Init(wx, wy, pplr, fxHealth, ff, pszName))
return false;
// Clear out the Replicator's entrance and exits
TerrainMap *ptrmap = gsim.GetLevel()->GetTerrainMap();
ptrmap->ClearFlags(TcFromWc(m_wx) + kdtxReplicatorInput, TcFromWc(m_wy) + kdtyReplicatorInput, 1, 1, kbfStructure);
ptrmap->ClearFlags(TcFromWc(m_wx) + kdtxReplicatorOutput1 - 1, TcFromWc(m_wy) + kdtyReplicatorOutput1 + 1, 1, 2, kbfStructure);
ptrmap->ClearFlags(TcFromWc(m_wx) + kdtxReplicatorOutput2 + 1, TcFromWc(m_wy) + kdtyReplicatorOutput2 + 1, 1, 2, kbfStructure);
return true;
}
#define knVerReplicatorGobState 3
bool ReplicatorGob::LoadState(Stream *pstm)
{
byte nVer = pstm->ReadByte();
if (nVer != knVerReplicatorGobState)
return false;
m_fEnabled = pstm->ReadByte() != 0;
m_fReplicating = pstm->ReadByte() != 0;
m_ifrmLights = pstm->ReadByte();
s_cReplicators = pstm->ReadWord();
pstm->Read(s_atptInput, sizeof(s_atptInput));
Pid pid = pstm->ReadWord();
if (pid != kpidNeutral)
m_pplrNeedCredits = gplrm.GetPlayerFromPid(pid);
return StructGob::LoadState(pstm);
}
bool ReplicatorGob::SaveState(Stream *pstm)
{
pstm->WriteByte(knVerReplicatorGobState);
pstm->WriteByte(m_fEnabled);
pstm->WriteByte(m_fReplicating);
pstm->WriteByte(m_ifrmLights);
pstm->WriteWord(s_cReplicators);
pstm->Write(s_atptInput, sizeof(s_atptInput));
Pid pid = m_pplrNeedCredits == NULL ? kpidNeutral : m_pplrNeedCredits->GetId();
pstm->WriteWord(pid);
return StructGob::SaveState(pstm);
}
void ReplicatorGob::GetClippingBounds(Rect *prc)
{
// Union of the two strips that combine to form the Replicator image
m_pstruc->panid->GetBounds(0, 0, prc);
Rect rcT;
m_pstruc->panid->GetBounds(1, 0, &rcT);
prc->Union(&rcT);
// If selected, union with that rect
if (gwfPerfOptions & kfPerfSelectionBrackets) {
if (m_ff & kfGobSelected) {
Rect rcSel;
rcSel.FromWorldRect(&m_pstruc->wrcUIBounds);
// rcSel.top -= kcyHealthBar;
prc->Union(&rcSel);
}
}
prc->Offset(PcFromUwc(m_wx), PcFromUwc(m_wy));
}
void ReplicatorGob::Draw(DibBitmap *pbm, int xViewOrigin, int yViewOrigin, int nLayer)
{
if (nLayer == knLayerDepthSorted) {
int x = PcFromUwc(m_wx) - xViewOrigin;
int y = PcFromUwc(m_wy) - yViewOrigin;
Side side = m_pplr->GetSide();
if (m_ff & kfGobDrawFlashed)
side = (Side)-1;
m_ani.SetFrame(0);
m_ani.SetStrip(m_fEnabled ? 2 : 0);
m_ani.Draw(pbm, x, y, side);
m_ani.SetStrip(m_fEnabled ? 3 : 1);
#if defined(IPHONE) || defined(SDL)
// When this is scaled up by 1 1/3 (scaling 24 art to 32),
// the upper right quadrant piece is 55 high, which translates after
// rounding to 73, yet the lower right quad piece wants to go at
// location 56 (effectively) which scales to 75, so there is a gap.
m_ani.Draw(pbm, x, y - 1, side); // ugly hack
#else
m_ani.Draw(pbm, x, y, side);
#endif
if (m_fReplicating) {
m_ani.SetStrip(m_pmuntc->panid->GetStripIndex("l 0"));
m_ani.SetFrame(m_ifrmLights / 2);
m_ani.Draw(pbm, x, y, side);
}
} else if (nLayer == knLayerSelection) {
if (m_ff & kfGobSelected) {
Rect rcT;
rcT.FromWorldRect(&m_puntc->wrcUIBounds);
rcT.Offset(-xViewOrigin + PcFromWc(m_wx), -yViewOrigin + PcFromWc(m_wy));
DrawSelectionIndicator(pbm, &rcT, m_fxHealth, m_puntc->GetArmorStrength());
}
} else {
StructGob::Draw(pbm, xViewOrigin, yViewOrigin, nLayer);
}
}
void ReplicatorGob::GetInputTilePosition(TPoint *ptpt)
{
GetTilePosition(ptpt);
ptpt->tx += kdtxReplicatorInput;
ptpt->ty += kdtyReplicatorInput;
}
void ReplicatorGob::Enable(bool fEnable)
{
if (m_fEnabled != fEnable) {
gsndm.PlaySfx(fEnable ? ksfxReplicatorOn : ksfxReplicatorOff);
m_fEnabled = fEnable;
MarkRedraw();
}
}
void ReplicatorGob::Activate()
{
TPoint tpt;
GetTilePosition(&tpt);
AddReplicatorInputPoint(tpt.tx + kdtxReplicatorInput, tpt.ty + kdtyReplicatorInput);
StructGob::Activate();
}
void ReplicatorGob::Deactivate()
{
TPoint tpt;
GetTilePosition(&tpt);
RemoveReplicatorInputPoint(tpt.tx + kdtxReplicatorInput, tpt.ty + kdtyReplicatorInput);
StructGob::Deactivate();
}
bool ReplicatorGob::ClearOutputBay(TCoord txBay, TCoord tyBay, TCoord txOut, TCoord tyOut)
{
for (Gid gid = ggobm.GetFirstGid(txBay, tyBay); gid != kgidNull; gid = ggobm.GetNextGid(gid)) {
MobileUnitGob *pmunt = (MobileUnitGob *)ggobm.GetGob(gid);
if (pmunt == NULL)
continue;
if (!(pmunt->GetFlags() & kfGobMobileUnit))
continue;
// Don't ask it to move if it is already trying to
if (pmunt->IsReadyForCommand() && !pmunt->IsMobile()) {
Message msgT;
msgT.mid = kmidMoveCommand;
msgT.smidSender = m_gid;
msgT.smidReceiver = pmunt->GetId();
msgT.MoveCommand.wptTarget.wx = WcFromTc(txOut) + kwcTileHalf;
msgT.MoveCommand.wptTarget.wy = WcFromTc(tyOut) + kwcTileHalf;
msgT.MoveCommand.gidTarget = kgidNull;
msgT.MoveCommand.wptTargetCenter.wx = msgT.MoveCommand.wptTarget.wx;
msgT.MoveCommand.wptTargetCenter.wy = msgT.MoveCommand.wptTarget.wy;
msgT.MoveCommand.tcTargetRadius = 0;
msgT.MoveCommand.wcMoveDistPerUpdate = ((MobileUnitConsts *)pmunt->GetConsts())->GetMoveDistPerUpdate();
gsmm.SendMsg(&msgT);
}
// Jammed
return true;
}
// Maybe a unit is moving into the output bay (i.e., it is jammed)
if (!IsTileFree(txBay, tyBay, kbfMobileUnit))
return true; // jammed
// Open
return false;
}
int ReplicatorGob::ProcessStateMachineMessage(State st, Message *pmsg)
{
BeginStateMachine
OnMsg(kmidPlaySfx)
gsndm.PlaySfx((Sfx)pmsg->PlaySfx.sfx);
State(kstIdle)
OnUpdate
// If a mobile unit is at either output, ask it to move on
TPoint tpt;
GetTilePosition(&tpt);
Gid gid;
// Clear left and right output bays
Player *pplrNeedCredits = NULL;
bool fJammed = ClearOutputBay(tpt.tx + kdtxReplicatorOutput1, tpt.ty + kdtyReplicatorOutput1, tpt.tx + kdtxReplicatorOutput1 - 1, tpt.ty + kdtyReplicatorOutput1 + 1);
fJammed = ClearOutputBay(tpt.tx + kdtxReplicatorOutput2, tpt.ty + kdtyReplicatorOutput2, tpt.tx + kdtxReplicatorOutput2 + 1, tpt.ty + kdtyReplicatorOutput2 + 1) || fJammed;
// If there's nothing clogging the outputs we can consider the input
if (m_fEnabled && !fJammed) {
// loop through all the gobs on this tile to find the mobile unit and ignore ourself, or
// shots flying by
for (gid = ggobm.GetFirstGid(tpt.tx + kdtxReplicatorInput, tpt.ty + kdtyReplicatorInput); gid != kgidNull; gid = ggobm.GetNextGid(gid)) {
MobileUnitGob *pmunt = (MobileUnitGob *)ggobm.GetGob(gid);
if (pmunt == NULL)
continue;
if (!(pmunt->GetFlags() & kfGobMobileUnit))
continue;
// Something there! If it's ready, clone it.
if (!(pmunt->GetMobileUnitFlags() & kfMuntAtReplicatorInput))
continue;
// At the limit?
// TUNE:
#define kctIntervalLimitNotify 600
if (!ggobm.IsBelowLimit(knLimitMobileUnit, pmunt->GetOwner())) {
if (pmunt->GetOwner() == gpplrLocal) {
static long s_tLastNotify;
long tCurrent = gtimm.GetTickCount();
if (abs((int)(s_tLastNotify - tCurrent)) >= kctIntervalLimitNotify) {
s_tLastNotify = tCurrent;
ShowAlert(kidsUnitLimitReached);
}
}
continue;
}
// Does the player have enough credits to perform the replication?
MobileUnitConsts *pmuntc = (MobileUnitConsts *)pmunt->GetConsts();
Player *pplr = pmunt->GetOwner();
int cCredits = pplr->GetCredits();
int nCost = pmuntc->GetCost();
int cReplicationCost = GetReplicationCost(nCost);
if (cCredits < cReplicationCost) {
pplrNeedCredits = pplr;
break;
}
// Take the money!
pplr->SetCredits(cCredits - cReplicationCost, true);
// Remove highlight
pmunt->Hilight(false);
// Start the replicating animation
m_fReplicating = true;
m_ifrmLights = -1;
MobileUnitGob *pmuntClone = (MobileUnitGob *)CreateGob(pmunt->GetType());
if (pmuntClone != NULL) {
pmuntClone->Init(WcFromTc(tpt.tx + kdtxReplicatorOutput2), WcFromTc(tpt.ty + kdtyReplicatorOutput2), pmunt->GetOwner(), pmunt->GetHealth(), 0, NULL);
// Clone acquires the selection state of the original
if (pmunt->GetFlags() & kfGobSelected)
pmuntClone->Select(true);
// Replicated GalaxMiners lose their load of Galaxite
// UNDONE: if there is more of this special casing go with virtual UnitGob::Replicate()
if (pmunt->GetType() == kgtGalaxMiner)
((MinerGob *)pmunt)->SetGalaxiteAmount(0);
// If this unit is a member of a group add its clone to the group too
UnitGroup *pug = gsim.GetLevel()->GetUnitGroupMgr()->GetUnitGroup(pmunt->GetId());
if (pug != NULL)
pug->AddUnit(pmuntClone, true);
}
// Warp the original Unit to the left output port
TerrainMap *ptrmap = gsim.GetLevel()->GetTerrainMap();
ptrmap->ClearFlags(tpt.tx + kdtxReplicatorInput, tpt.ty + kdtyReplicatorInput, 1, 1, kbfMobileUnit);
ptrmap->SetFlags(tpt.tx + kdtxReplicatorOutput1, tpt.ty + kdtyReplicatorOutput1, 1, 1, kbfMobileUnit);
pmunt->SetPosition(WcFromTc(tpt.tx + kdtxReplicatorOutput1) + kwcTileHalf, WcFromTc(tpt.ty + kdtyReplicatorOutput1) + kwcTileHalf);
// Clear the bit
pmunt->SetMobileUnitFlags(pmunt->GetMobileUnitFlags() & ~kfMuntAtReplicatorInput);
// Let player know the replication process has happened
// play cool replication sound effect
gsndm.PlaySfx(ksfxReplicatorBuild);
// wait a quarter second and let the new unit announce itself
Sfx sfx = SfxFromCategory(pmuntc->sfxcSelect);
Message msgT;
memset(&msgT, 0, sizeof(msgT));
msgT.mid = kmidPlaySfx;
msgT.smidSender = m_gid;
msgT.smidReceiver = m_gid;
msgT.PlaySfx.sfx = sfx;
gsmm.SendDelayedMsg(&msgT, 24);
// Play it again in another quarter of a second to sound cool
memset(&msgT, 0, sizeof(msgT));
msgT.mid = kmidPlaySfx;
msgT.smidSender = m_gid;
msgT.smidReceiver = m_gid;
msgT.PlaySfx.sfx = sfx;
gsmm.SendDelayedMsg(&msgT, 48);
break;
}
}
if (m_fReplicating) {
m_ifrmLights++;
int nStrip = m_pmuntc->panid->GetStripIndex("l 0");
if (m_ifrmLights / 2 >= m_pmuntc->panid->GetFrameCount(nStrip)) {
m_ifrmLights = -1;
m_fReplicating = false;
}
MarkRedraw();
}
WaitingForCredits(pplrNeedCredits != NULL, false, pplrNeedCredits);
m_unvl.MinSkip();
DefUpdate();
#if 0
EndStateMachineInherit(StructGob)
#else
return knHandled;
}
} else {
return (int)StructGob::ProcessStateMachineMessage(st, pmsg);
}
return (int)StructGob::ProcessStateMachineMessage(st, pmsg);
#endif
}
void ReplicatorGob::WaitingForCredits(bool fNeed, bool fOverride, Player *pplr)
{
// we need to be using the unit's player, not our own. Remember so when the unit
// disappears we can decrement the right player's count
if (fNeed) {
Assert((m_pplrNeedCredits == NULL) || (m_pplrNeedCredits == pplr) || (fOverride));
if (fOverride)
pplr = m_pplrNeedCredits;
else
m_pplrNeedCredits = pplr;
} else {
Assert(pplr == NULL);
pplr = m_pplrNeedCredits;
m_pplrNeedCredits = NULL;
}
StructGob::WaitingForCredits(fNeed, fOverride, pplr);
}
// Replicator input point management
int ReplicatorGob::s_cReplicators;
TPoint ReplicatorGob::s_atptInput[10];
void ReplicatorGob::AddReplicatorInputPoint(TCoord tx, TCoord ty)
{
Assert(s_cReplicators < ARRAYSIZE(s_atptInput) - 1);
if (s_cReplicators >= ARRAYSIZE(s_atptInput) - 1)
return;
s_atptInput[s_cReplicators].tx = tx;
s_atptInput[s_cReplicators].ty = ty;
s_cReplicators++;
}
void ReplicatorGob::RemoveReplicatorInputPoint(TCoord tx, TCoord ty)
{
for (int n = 0; n < ARRAYSIZE(s_atptInput); n++) {
if (s_atptInput[n].tx == tx && s_atptInput[n].ty) {
memmove(&s_atptInput[n], &s_atptInput[n + 1], (s_cReplicators - n - 1) * ELEMENTSIZE(s_atptInput));
s_cReplicators--;
return;
}
}
Assert();
}
//
// ActivatorGob implementation
//
#if defined(DEBUG_HELPERS)
char *ActivatorGob::GetName()
{
return "Activator";
}
#endif
bool ActivatorGob::Init(IniReader *pini, FindProp *pfind, const char *pszName)
{
int side, tx, ty;
int cArgs = pini->GetPropertyValue(pfind, "%*d ,%d ,%d ,%d", &side, &tx, &ty);
if (cArgs < 3) {
Assert("ActivatorGob requires at least 3 valid initialization parameters");
return false;
}
// Keep players from building on top of Activators
gsim.GetLevel()->GetTerrainMap()->SetFlags(tx, ty, 1, 1, kbfReserved);
return AnimGob::Init(WcFromTc(tx), WcFromTc(ty), kfAnmLoop | kfAnmSurfaceDecalLayer, NULL, s_panidActivator, 0, ksmidNull, pszName);
}
#define knVerActivatorGobState 1
bool ActivatorGob::LoadState(Stream *pstm)
{
byte nVer = pstm->ReadByte();
if (nVer != knVerActivatorGobState)
return false;
m_fActivated = pstm->ReadByte() != 0;
m_ani.Init(s_panidActivator);
m_ani.LoadState(pstm);
m_wfAnm = kfAnmLoop | kfAnmSurfaceDecalLayer;
return AnimGob::LoadState(pstm);
}
bool ActivatorGob::SaveState(Stream *pstm)
{
pstm->WriteByte(knVerActivatorGobState);
pstm->WriteByte(m_fActivated);
m_ani.SaveState(pstm);
return AnimGob::SaveState(pstm);
}
bool ActivatorGob::IsSavable()
{
// Because AnimGob is not savable, we need to override
return true;
}
GobType ActivatorGob::GetType()
{
return kgtActivator;
}
int ActivatorGob::ProcessStateMachineMessage(State st, Message *pmsg)
{
BeginStateMachine
OnUpdate
bool fActivated = false;
for (Gid gid = ggobm.GetFirstGid(TcFromWc(m_wx), TcFromWc(m_wy)); gid != kgidNull; gid = ggobm.GetNextGid(gid)) {
MobileUnitGob *pmunt = (MobileUnitGob *)ggobm.GetGob(gid);
if (pmunt == NULL)
continue;
if (!(pmunt->GetFlags() & kfGobMobileUnit))
continue;
fActivated = true;
break;
}
if (fActivated != m_fActivated) {
m_fActivated = fActivated;
StartAnimation(&m_ani, fActivated ? 1 : 0, 0, kfAniLoop | kfAnmSurfaceDecalLayer);
gsndm.PlaySfx(fActivated ? ksfxActivatorOn : ksfxActivatorOff);
}
// Check every 3 updates for the presence of a MobileUnit on top of the Activator
m_unvl.MinSkip(3);
return AnimGob::ProcessStateMachineMessage(st, pmsg);
#if 0
EndStateMachineInherit(AnimGob)
#else
return knHandled;
}
} else {
return (int)AnimGob::ProcessStateMachineMessage(st, pmsg);
}
return (int)AnimGob::ProcessStateMachineMessage(st, pmsg);
#endif
}
} // namespace wi