hostile-takeover/game/GameObjects.cpp
2016-08-31 23:55:30 -04:00

2843 lines
70 KiB
C++

#include "game/ht.h"
#include "game/stateframe.h"
namespace wi {
byte grvlp[] = {
// ctx & cty
9, 9,
// reveal mask
0xf, 0xf, 0xa, 0x2, 0x2, 0x2, 0x3, 0xf, 0xf,
0xf, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf,
0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3,
0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5,
0xf, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0xf,
0xf, 0xf, 0xc, 0x4, 0x4, 0x4, 0x5, 0xf, 0xf
};
byte grvlpLarge[] = {
// ctx & cty
11, 11,
#if 0
// reveal mask
0xf, 0xf, 0xf, 0xa, 0x2, 0x2, 0x2, 0x3, 0xf, 0xf, 0xf,
0xf, 0xf, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf, 0xf,
0xf, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf,
0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3,
0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5,
0xf, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0xf,
0xf, 0xf, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0xf, 0xf,
0xf, 0xf, 0xf, 0xc, 0x4, 0x4, 0x4, 0x5, 0xf, 0xf, 0xf,
#else
// reveal mask
0xf, 0xf, 0xf, 0xa, 0x2, 0x2, 0x2, 0x3, 0xf, 0xf, 0xf,
0xf, 0xa, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x3, 0xf,
0xf, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xf,
0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3,
0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5,
0xf, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xf,
0xf, 0xc, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x5, 0xf,
0xf, 0xf, 0xf, 0xc, 0x4, 0x4, 0x4, 0x5, 0xf, 0xf, 0xf,
#endif
};
#if 0
byte grvlpMegaLarge[] = {
// ctx & cty
13, 13,
// reveal mask
0xf, 0xf, 0xf, 0xa, 0x2, 0x2, 0x2, 0x2, 0x2, 0x3, 0xf, 0xf, 0xf,
0xf, 0xf, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf, 0xf,
0xf, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf,
0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3,
0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5,
0xf, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0xf,
0xf, 0xf, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0xf, 0xf,
0xf, 0xf, 0xf, 0xc, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0xf, 0xf, 0xf,
};
#endif
// Helper function. Creates but does not Init the Gob.
Gob *CreateGob(GobType gt)
{
switch (gt) {
case kgtScenery:
return new SceneryGob();
case kgtSurfaceDecal:
return new SurfaceDecalGob();
case kgtScorch:
return new ScorchGob();
case kgtShortRangeInfantry:
return new SRInfantryGob();
case kgtLongRangeInfantry:
return new LRInfantryGob();
case kgtHumanResourceCenter:
return new HrcGob();
case kgtReactor:
return new ReactorGob();
case kgtProcessor:
return new ProcessorGob();
case kgtGalaxMiner:
return new MinerGob();
case kgtVehicleTransportStation:
return new VtsGob();
case kgtHeadquarters:
return new HqGob();
case kgtRadar:
return new RadarGob();
case kgtResearchCenter:
return new ResearchGob();
case kgtLightTank:
return new LTankGob();
case kgtMediumTank:
return new MTankGob();
case kgtRocketVehicle:
return new RTankGob();
case kgtMachineGunVehicle:
return new GTankGob();
case kgtTakeoverSpecialist:
return new SpInfantryGob();
case kgtOvermind:
return new OvermindGob();
// NOTE: probably unnecessary. Don't plan to place TankShots in a level
case kgtTankShot:
return new TankShotGob();
// NOTE: probably unnecessary. Don't plan to place Rockets in a level
case kgtRocket:
return new RocketGob();
case kgtWarehouse:
return new WarehouseGob();
case kgtMachineGunTower:
return new GunTowerGob();
case kgtRocketTower:
return new RocketTowerGob();
case kgtMobileHeadquarters:
return new MobileHqGob();
case kgtSmoke:
return new SmokeGob(0);
case kgtPuff:
return new PuffGob();
case kgtArtillery:
return new ArtilleryGob();
case kgtAndy:
return new AndyGob();
case kgtReplicator:
return new ReplicatorGob();
case kgtActivator:
return new ActivatorGob();
case kgtFox:
return new FoxGob();
}
Assert("Unrecognized GobType %d", gt);
return NULL;
}
//
// Gob implementation
//
Gob::Gob()
{
m_pgobNext = NULL;
m_gidNext = kgidNull;
m_gid = kgidNull;
m_pplr = &gplrDummy;
m_ff = kfGobRedraw;
m_trcBoundingLast.txLeft = 127;
m_trcBoundingLast.tyTop = 127;
m_trcBoundingLast.txRight = -128;
m_trcBoundingLast.tyBottom = -128;
}
Gob::~Gob()
{
}
#ifdef TRACKSTATE
dword gmpgtQuad[] = {
'GNON', // kgtNone 0
'GSRI', // kgtShortRangeInfantry 1
'GLRI', // kgtLongRangeInfantry 2
'GHRC', // kgtHumanResourceCenter 3
'GDCL', // kgtSurfaceDecal 4
'GSCN', // kgtScenery 5
'GANI', // kgtAnimation 6
'GREA', // kgtReactor 7
'GPRC', // kgtProcessor 8
'GSTU', // kgtStructure 9
'GUNI', // kgtUnit 10
'GMIN', // kgtGalaxMiner 11
'GHQR', // kgtHeadquarters 12
'GRES', // kgtResearchCenter 13
'GVTS', // kgtVehicleTransportStation 14
'GRDR', // kgtRadar 15
'GLTK', // kgtLightTank 16
'GMTK', // kgtMediumTank 17
'GGNV', // kgtMachineGunVehicle 18
'GRTV', // kgtRocketVehicle 19
'GTAK', // kgtTakeoverSpecialist 20
'GWAR', // kgtWarehouse 21
'GMHQ', // kgtMobileHeadquarters 22
'GOVR', // kgtOvermind 23
'GTST', // kgtTankShot 24
'GRKT', // kgtRocket 25
'GMGT', // kgtMachineGunTower 26
'GRTR', // kgtRocketTower 27
'GSCO', // kgtScorch 28
'GSMO', // kgtSmoke 29
'GPUF', // kgtPuff 30
'GBUL', // kgtBullet 31
'GART', // kgtArtillery 32
'GARS', // kgtArtilleryShot 33
'GAND', // kgtAndy 34
'GREP', // kgtReplicator 35
'GACT', // kgtActivator 36
'GFOX', // kgtFox 37
'GANS', // kgtAndyShot 38
};
void Gob::TrackState(StateFrame *frame) {
int i = frame->AddCountedValue(gmpgtQuad[GetType()]);
frame->AddValue('ID ', (dword)m_gid, i);
frame->AddValue('WX ', (dword)m_wx, i);
frame->AddValue('WY ', (dword)m_wy, i);
frame->AddValue('GNXT', (dword)m_gidNext, i);
frame->AddValue('ST ', (dword)m_st, i);
frame->AddValue('STNX', (dword)m_stNext, i);
dword ff = m_ff;
ff &= ~(kfGobRedraw | kfGobFlashing | kfGobDrawFlashed |
kfGobDebug | kfGobSelected | kfGobVisibleLastFrame |
kfGobIncludeFindVisible | kfGobTransitioningToVisible |
kfGobAnimationChanged | kfGobLayerSelection | kfGobLayerMask);
frame->AddValue('FF ', ff, i);
}
#endif
bool Gob::Invalidate()
{
// Get the clipping bounds, the rect that surrounds the visible pixels of
// this gob
Rect rc;
GetClippingBounds(&rc);
// Change into a tile rect
TRectSmall trcNew;
trcNew.txLeft = (char)TcFromPc(rc.left);
trcNew.tyTop = (char)TcFromPc(rc.top);
int xT = _min(rc.right + gcxTile - 1, (int)kpcMax);
trcNew.txRight = (char)TcFromPc(xT);
int yT = _min(rc.bottom + gcyTile - 1, (int)kpcMax);
trcNew.tyBottom = (char)TcFromPc(yT);
// If the gob was transitioning from off screen to on screen, use the
// current invalidation rect. Otherwise Union with the old rect so that the
// screen redraws the old location and the new location
if (m_ff & kfGobTransitioningToVisible) {
m_trcBoundingLast = trcNew;
} else {
m_trcBoundingLast.Union(&trcNew);
}
// Is this opaqued? If so nothing to do. Clip to edges of opaque rect; if all inside
// return false
if (gptrcMapOpaque != NULL) {
if (gptrcMapOpaque->left <= m_trcBoundingLast.txLeft) {
if (gptrcMapOpaque->right >= m_trcBoundingLast.txRight) {
if (gptrcMapOpaque->top <= m_trcBoundingLast.tyTop) {
if (gptrcMapOpaque->bottom >= m_trcBoundingLast.tyBottom) {
return false;
}
}
}
}
}
// Perform invalidation of what's visible
gpupdSim->InvalidateMapTileRect(&m_trcBoundingLast);
// Remember new
m_trcBoundingLast = trcNew;
return true;
}
// Marks the gob for redraw; will cause update map invalidation later
// If the gob was visible last frame, include this gob in the next
// FindVisibleGobs call. This is so that gobs transitioning from
// visible to invisible have a chance to redraw the old on-screen
// location
void Gob::MarkRedraw() {
m_ff |= kfGobRedraw;
if (m_ff & kfGobVisibleLastFrame)
m_ff |= kfGobIncludeFindVisible;
}
bool Gob::AdvanceAnimation(Animation *pani) {
if (pani->Advance(m_ff & kfGobAnimationChanged ? 1 : m_unvl.GetUpdateCount())) {
m_unvl.MinSkip(pani->GetRemainingFrameTime());
MarkRedraw();
m_ff &= ~kfGobAnimationChanged;
return true;
}
m_ff &= ~kfGobAnimationChanged;
return false;
}
void Gob::StartAnimation(Animation *pani, int nStrip, int nFrame, word wfAni) {
if (nStrip != pani->GetStrip() || nFrame != pani->GetFrame())
MarkRedraw();
if (pani->Start(nStrip, nFrame, wfAni)) {
m_unvl.MinSkip(pani->GetRemainingFrameTime());
m_ff |= kfGobAnimationChanged;
}
}
void Gob::SetAnimationStrip(Animation *pani, int nStrip) {
if (nStrip != pani->GetStrip())
MarkRedraw();
pani->SetStrip(nStrip);
m_ff |= kfGobAnimationChanged;
}
void Gob::SetAnimationFrame(Animation *pani, int nFrame) {
if (nFrame != pani->GetFrame())
MarkRedraw();
pani->SetFrame(nFrame);
m_ff |= kfGobAnimationChanged;
}
dword Gob::GetSortKey()
{
return MakeSortKey(m_wy, m_gid);
}
#if defined(WIN) && defined(DEBUG)
void Gob::ToString(char *psz)
{
sprintf(psz, "this=0x%08lx, gid=0x%08lx, gt=0x%lx, wx=0x%04lx, wy=0x%04lx", this, m_gid, GetType(), m_wx, m_wy);
}
#endif
// Clipping bounds is used to determine on-display visibility
// This bounds is a rectangle tightly enclosing all the pixels the Gob
// will be drawing to. Clipping bounds may change in size depending
// on the animation state of the Gob.
// NOTE: the values returned are in world coordinates
void Gob::GetClippingBounds(Rect *prc)
{
prc->SetEmpty();
}
// UI bounds is reused for input hit testing and for drawing the selection
// indicator. The UI bounds is an end-user 'logical' box surrounding Gobs
// they'll be tapping on. Once determined, it does not change size as the Gob
// animates. NOTE: the values returned are in world coordinates
void Gob::GetUIBounds(WRect *pwrc)
{
pwrc->SetEmpty();
}
void Gob::GetPosition(WPoint *pwpt)
{
pwpt->wx = m_wx;
pwpt->wy = m_wy;
}
void Gob::SetPosition(WCoord wx, WCoord wy)
{
ggobm.MoveGob(this, m_wx, m_wy, wx, wy);
m_wx = wx;
m_wy = wy;
MarkRedraw();
}
void Gob::GetCenter(WPoint *pwpt)
{
pwpt->wx = m_wx;
pwpt->wy = m_wy;
}
void Gob::GetTilePosition(TPoint *ptpt)
{
ptpt->tx = TcFromWc(m_wx);
ptpt->ty = TcFromWc(m_wy);
}
void Gob::GetTileRect(TRect *ptrc)
{
ptrc->left = TcFromWc(m_wx);
ptrc->top = TcFromWc(m_wy);
ptrc->right = ptrc->left + 1;
ptrc->bottom = ptrc->top + 1;
}
void Gob::GetTilePaddedWRect(WRect *pwrc)
{
pwrc->left = WcTrunc(m_wx);
pwrc->top = WcTrunc(m_wy);
pwrc->right = pwrc->left + kwcTile;
pwrc->bottom = pwrc->top + kwcTile;
}
/* Javascript (Jscript + Windows Scripting Host, actually) to generate the table below:
WScript.Echo("byte gmpDistFromDxy[10][10] = {");
for (y = 0; y < 10; y++) {
str = "";
for (x = 0; x < 10; x++) {
d = Math.sqrt(x * x + y * y);
strT = Math.round(d).toString();
if (strT.length < 2)
strT = " " + strT;
str += strT + ",";
}
WScript.Echo("\t{ " + str + " },");
}
WScript.Echo("};");
*/
byte gmpDistFromDxy[10][10] = {
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, },
{ 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, },
{ 2, 2, 3, 4, 4, 5, 6, 7, 8, 9, },
{ 3, 3, 4, 4, 5, 6, 7, 8, 9, 9, },
{ 4, 4, 4, 5, 6, 6, 7, 8, 9,10, },
{ 5, 5, 5, 6, 6, 7, 8, 9, 9,10, },
{ 6, 6, 6, 7, 7, 8, 8, 9,10,11, },
{ 7, 7, 7, 8, 8, 9, 9,10,11,11, },
{ 8, 8, 8, 9, 9, 9,10,11,11,12, },
{ 9, 9, 9, 9,10,10,11,11,12,13, },
};
bool Gob::IsGobWithinRange(Gob *pgobTarget, TCoord tcRange)
{
WPoint wpt;
wpt.wx = m_wx;
wpt.wy = m_wy;
return IsTargetWithinRange(&wpt, pgobTarget, tcRange);
}
bool Gob::IsTargetWithinRange(WPoint *pwptTarget, Gob *pgobTarget, TCoord tcRange)
{
Assert(tcRange < 10, "IsGobWithinRange tcRange must be less than 10");
TCoord tx = TcFromWc(pwptTarget->wx);
TCoord ty = TcFromWc(pwptTarget->wy);
// Test every tile covered by structures
if (pgobTarget->GetFlags() & kfGobStructure) {
TRect trcTarget;
pgobTarget->GetTileRect(&trcTarget);
for (TCoord tyTarget = trcTarget.top; tyTarget < trcTarget.bottom; tyTarget++) {
for (TCoord txTarget = trcTarget.left; txTarget < trcTarget.right; txTarget++) {
TCoord dtx = abs(tx - txTarget);
TCoord dty = abs(ty - tyTarget);
if (dtx >= 10 || dty >= 10)
continue;
if (gmpDistFromDxy[dtx][dty] <= tcRange)
return true;
}
}
return false;
} else {
TCoord dtx = abs(tx - TcFromWc(pgobTarget->m_wx));
TCoord dty = abs(ty - TcFromWc(pgobTarget->m_wy));
if (dtx >= 10 || dty >= 10)
return false;
return gmpDistFromDxy[dtx][dty] <= tcRange;
}
}
// Do-nothing default implementations
void Gob::PopupMenu()
{
}
void Gob::InitMenu(Form *pfrm)
{
}
void Gob::OnMenuItemSelected(int id)
{
}
#define knVerGobState 2
bool Gob::LoadState(Stream *pstm)
{
byte nVer = pstm->ReadByte();
if (nVer != knVerGobState)
return false;
m_st = (State)pstm->ReadWord();
m_ff |= pstm->ReadDword();
m_wx = pstm->ReadWord();
m_wy = pstm->ReadWord();
Pid pid = pstm->ReadWord();
m_pplr = gplrm.GetPlayerFromPid(pid);
Gid gid = pstm->ReadWord();
Assert(gid != kgidNull);
ggobm.AddGob(this, gid);
return pstm->IsSuccess();
}
bool Gob::SaveState(Stream *pstm)
{
pstm->WriteByte(knVerGobState);
pstm->WriteWord(m_st);
pstm->WriteDword(m_ff);
pstm->WriteWord(m_wx);
pstm->WriteWord(m_wy);
if (m_pplr != NULL) {
pstm->WriteWord(m_pplr->GetId());
} else {
pstm->WriteWord(kpidNeutral);
}
pstm->WriteWord(m_gid);
return pstm->IsSuccess();
}
bool Gob::IsSavable()
{
return true;
}
// StateMachine methods
#if defined(DEBUG_HELPERS)
char *Gob::GetName()
{
return "Gob";
}
#endif
//
// SurfaceDecalGob implementation
//
bool SurfaceDecalGob::InitClass(IniReader *pini)
{
// No interesting global parameters have been defined for SurfaceDecal yet
return true;
}
SurfaceDecalGob::SurfaceDecalGob()
{
m_ff |= kfGobLayerSurfaceDecal;
m_ptbm = NULL;
}
SurfaceDecalGob::~SurfaceDecalGob()
{
}
bool SurfaceDecalGob::Init(IniReader *pini, FindProp *pfind, const char *pszName)
{
int tx, ty;
char szBitmap[kcbFilename];
int cArgs = pini->GetPropertyValue(pfind, "%*d ,%s,%d ,%d", szBitmap, &tx, &ty);
if (cArgs < 3) {
Assert("SurfaceDecalGob requires at least 3 valid initialization parameters");
return false;
}
return Init(WcFromTc(tx), WcFromTc(ty), 0, szBitmap, NULL, pszName);
}
bool SurfaceDecalGob::Init(WCoord wx, WCoord wy, dword ff, const char *pszBitmap, TBitmap *ptbm, const char *pszName)
{
m_wx = wx;
m_wy = wy;
if (pszBitmap != NULL) {
m_ptbm = GetSharedTBitmap((char *)pszBitmap);
if (m_ptbm == NULL)
return false;
} else {
// NOTE: we assume if we were passed the bitmap that the caller is
// going to take care of deleting it at an appropriate timne.
m_ptbm = ptbm;
}
m_ff |= ff;
// Add the fresh Gob to the GobMgr.
ggobm.AddGob(this);
return true;
}
bool SurfaceDecalGob::IsSavable()
{
// Not currently savable
return false;
}
GobType SurfaceDecalGob::GetType()
{
return kgtSurfaceDecal;
}
void SurfaceDecalGob::GetClippingBounds(Rect *prc)
{
Size sizBitmap;
m_ptbm->GetSize(&sizBitmap);
prc->left = PcFromUwc(m_wx) - ((sizBitmap.cx - 1) / 2);
prc->top = PcFromUwc(m_wy) - (sizBitmap.cy - 1);
prc->right = prc->left + sizBitmap.cx;
prc->bottom = prc->top + sizBitmap.cy;
}
void SurfaceDecalGob::Draw(DibBitmap *pbm, int xViewOrigin, int yViewOrigin, int nLayer)
{
// UNDONE: bitmap origin
if (nLayer == knLayerSurfaceDecal) {
Size sizSrc;
m_ptbm->GetSize(&sizSrc);
// For now xOrigin is the center column (round up) of the bitmap,
// yOrigin is the bottom row of the bitmap
int x = PcFromUwc(m_wx) - xViewOrigin - ((sizSrc.cx - 1) / 2);
int y = PcFromUwc(m_wy) - yViewOrigin - (sizSrc.cy - 1);
m_ptbm->BltTo(pbm, x, y, m_pplr->GetSide());
}
}
int gnSequenceScorch;
ScorchGob::ScorchGob()
{
m_nScorch = 0;
m_nSequence = gnSequenceScorch++;
}
int ScorchGob::GetSequence()
{
return m_nSequence;
}
GobType ScorchGob::GetType()
{
return kgtScorch;
}
bool ScorchGob::Init(WCoord wx, WCoord wy, int nScorch)
{
m_nScorch = nScorch;
return SurfaceDecalGob::Init(wx, wy, 0, NULL, gaptbmScorches[m_nScorch], NULL);
}
#define knVerScorchGobState 2
bool ScorchGob::LoadState(Stream *pstm)
{
byte nVer = pstm->ReadByte();
if (nVer != knVerScorchGobState)
return false;
m_nScorch = pstm->ReadWord();
m_ptbm = gaptbmScorches[m_nScorch];
m_nSequence = pstm->ReadWord();
if (m_nSequence > gnSequenceScorch)
gnSequenceScorch = m_nSequence;
return SurfaceDecalGob::LoadState(pstm);
}
bool ScorchGob::SaveState(Stream *pstm)
{
pstm->WriteByte(knVerScorchGobState);
pstm->WriteWord(m_nScorch);
pstm->WriteWord(m_nSequence);
return SurfaceDecalGob::SaveState(pstm);
}
bool ScorchGob::IsSavable()
{
return true;
}
//
// SceneryGob implementation
//
bool SceneryGob::InitClass(IniReader *pini)
{
// No interesting global parameters have been defined for Scenery yet
return true;
}
SceneryGob::SceneryGob()
{
m_ptbm = NULL;
}
SceneryGob::~SceneryGob()
{
}
bool SceneryGob::Init(IniReader *pini, FindProp *pfind, const char *pszName)
{
int tx, ty;
char szBitmap[kcbFilename];
int cArgs = pini->GetPropertyValue(pfind, "%*d ,%s,%d ,%d", szBitmap, &tx, &ty);
if (cArgs < 3) {
Assert("SceneryGob requires at least 3 valid initialization parameters");
return false;
}
return Init(WcFromTc(tx), WcFromTc(ty), 0, szBitmap, pszName);
}
bool SceneryGob::Init(WCoord wx, WCoord wy, dword ff, const char *pszBitmap, const char *pszName)
{
m_wx = wx;
m_wy = wy;
m_ptbm = GetSharedTBitmap((char *)pszBitmap);
if (m_ptbm == NULL)
return false;
m_ff |= ff | kfGobLayerDepthSorted;
// Add the fresh Gob to the GobMgr. GobMgr::AddGob assigns this Gob a gid
ggobm.AddGob(this);
return true;
}
#define knVerSceneryGobState 2
bool SceneryGob::LoadState(Stream *pstm)
{
byte nVer = pstm->ReadByte();
if (nVer != knVerSceneryGobState)
return false;
char szBitmap[kcbFilename];
pstm->ReadString(szBitmap, sizeof(szBitmap));
m_ptbm = GetSharedTBitmap(szBitmap);
return Gob::LoadState(pstm);
}
bool SceneryGob::SaveState(Stream *pstm)
{
pstm->WriteByte(knVerSceneryGobState);
char szBitmap[kcbFilename];
FindSharedTBitmapFilename(m_ptbm, szBitmap, sizeof(szBitmap));
pstm->WriteString(szBitmap);
return Gob::SaveState(pstm);
}
GobType SceneryGob::GetType()
{
return kgtScenery;
}
dword SceneryGob::GetSortKey()
{
return MakeSortKey(m_wy + WcFromPc(m_ptbm->GetBaseline()), m_gid);
}
void SceneryGob::GetClippingBounds(Rect *prc)
{
Size sizBitmap;
m_ptbm->GetSize(&sizBitmap);
// Convert pixel coord size into world coord size
prc->left = PcFromUwc(m_wx);
prc->top = PcFromUwc(m_wy);
prc->right = prc->left + sizBitmap.cx;
prc->bottom = prc->top + sizBitmap.cy;
}
void SceneryGob::Draw(DibBitmap *pbm, int xViewOrigin, int yViewOrigin, int nLayer)
{
if (nLayer == knLayerDepthSorted) {
Size sizSrc;
m_ptbm->GetSize(&sizSrc);
// For now xOrigin is the center column (round up) of the bitmap,
// yOrigin is the baseline of the bitmap
int x = PcFromUwc(m_wx) - xViewOrigin;
int y = PcFromUwc(m_wy) - yViewOrigin;
m_ptbm->BltTo(pbm, x, y, m_pplr->GetSide());
}
}
//
// AnimGob implementation
//
AnimGob *CreateAnimGob(WCoord wx, WCoord wy, word wfAnm, const char *pszAniName, AnimationData *panid, int nStrip,
StateMachineId smidNotify, const char *pszName)
{
if (!ggobm.IsBelowLimit(knLimitSupport))
return NULL;
AnimGob *pgob = new AnimGob();
Assert(pgob != NULL, "out of memory!");
if (pgob == NULL)
return NULL;
if (!pgob->Init(wx, wy, wfAnm, pszAniName, panid, nStrip, smidNotify, pszName)) {
delete pgob;
return NULL;
}
return pgob;
}
AnimGob::AnimGob()
{
m_ff |= kfGobStateMachine;
m_smidNotify = ksmidNull;
}
AnimGob::~AnimGob()
{
}
bool AnimGob::Init(IniReader *pini, FindProp *pfind, const char *pszName)
{
int tx, ty;
char szAniName[kcbFilename];
int cArgs = pini->GetPropertyValue(pfind, "%*d ,%s,%d ,%d", szAniName, &tx, &ty);
if (cArgs < 3) {
Assert("AnimGob requires at least 3 valid initialization parameters");
return false;
}
return Init(WcFromTc(tx), WcFromTc(ty), 0, szAniName, NULL, 0, ksmidNull, pszName);
}
bool AnimGob::Init(WCoord wx, WCoord wy, word wfAnm, const char *pszAniName, AnimationData *panid, int nStrip,
StateMachineId smidNotify, const char *pszName)
{
m_wx = wx;
m_wy = wy;
bool fFreeAnimationData = false;
if (pszAniName != NULL) {
panid = LoadAnimationData(pszAniName);
fFreeAnimationData = true;
}
// Must be either passed a panid or be able to load one using pszAniName
if (panid == NULL)
return false;
m_ani.Init(panid);
StartAnimation(&m_ani, nStrip, 0, fFreeAnimationData ? kfAniFreeAnimationData : 0);
m_wfAnm = wfAnm;
m_smidNotify = smidNotify;
if (m_wfAnm & kfAnmSmokeFireLayer)
m_ff |= kfGobLayerSmokeFire;
else if (m_wfAnm & kfAnmSurfaceDecalLayer)
m_ff |= kfGobLayerSurfaceDecal;
else
m_ff |= kfGobLayerDepthSorted;
// Add the fresh Gob to the GobMgr. GobMgr::AddGob assigns this Gob a gid
ggobm.AddGob(this);
return true;
}
bool AnimGob::IsSavable()
{
return false;
}
bool AnimGob::OnStripDone()
{
return false;
}
GobType AnimGob::GetType()
{
return kgtAnimation;
}
void AnimGob::GetClippingBounds(Rect *prc)
{
m_ani.GetBounds(prc);
prc->Offset(PcFromUwc(m_wx), PcFromUwc(m_wy));
}
void AnimGob::Draw(DibBitmap *pbm, int xViewOrigin, int yViewOrigin, int nLayer)
{
int nLayerDesignated;
if (m_wfAnm & kfAnmSmokeFireLayer)
nLayerDesignated = knLayerSmokeFire;
else if (m_wfAnm & kfAnmSurfaceDecalLayer)
nLayerDesignated = knLayerSurfaceDecal;
else
nLayerDesignated = knLayerDepthSorted;
if (nLayer == nLayerDesignated) {
m_ani.Draw(pbm, PcFromUwc(m_wx) - xViewOrigin, PcFromUwc(m_wy) - yViewOrigin, m_pplr->GetSide());
}
}
#if defined(DEBUG_HELPERS)
char *AnimGob::GetName()
{
return "Animation";
}
#endif
int AnimGob::ProcessStateMachineMessage(State st, Message *pmsg)
{
BeginStateMachine
OnUpdate
if (!AdvanceAnimation(&m_ani)) {
if (m_wfAnm & kfAnmLoop)
StartAnimation(&m_ani, m_ani.GetStrip(), 0, 0);
// Notify the StateMachine that wants to know when the animation is
// done
if (m_smidNotify != ksmidNull)
gsmm.SendMsg(kmidAnimationComplete, m_gid, m_smidNotify);
// Allow inheriters to decide what to do when the animation is done
bool fDelete = OnStripDone();
if (!fDelete)
m_unvl.MinSkip();
// Remove the AnimGob if requested
if (fDelete || (m_wfAnm & kfAnmDeleteWhenDone)) {
ggobm.RemoveGob(this);
delete this;
return knDeleted;
}
}
EndStateMachine
}
//
// Helper functions
//
#ifdef DRAW_PATHS
static TBitmap *s_aptbmArrows[9];
void LoadArrows()
{
s_aptbmArrows[0] = LoadTBitmap("arrow0.tbm");
s_aptbmArrows[1] = LoadTBitmap("arrow1.tbm");
s_aptbmArrows[2] = LoadTBitmap("arrow2.tbm");
s_aptbmArrows[3] = LoadTBitmap("arrow3.tbm");
s_aptbmArrows[4] = LoadTBitmap("arrow4.tbm");
s_aptbmArrows[5] = LoadTBitmap("arrow5.tbm");
s_aptbmArrows[6] = LoadTBitmap("arrow6.tbm");
s_aptbmArrows[7] = LoadTBitmap("arrow7.tbm");
s_aptbmArrows[8] = LoadTBitmap("x.tbm");
}
void FreeArrows()
{
for (int i = 0; i < 9; i++) {
delete s_aptbmArrows[i];
s_aptbmArrows[i] = NULL;
}
}
void DrawArrow(DibBitmap *pbm, int x, int y, Direction dir, Side side)
{
s_aptbmArrows[dir]->BltTo(pbm, x, y, side);
}
#endif
void GetHealthColorAndLength(int nNumerator, int nDenominator, int cxWidth, Color *pclr, int *pnLength) secCode7;
void GetHealthColorAndLength(int nNumerator, int nDenominator, int cxWidth, Color *pclr, int *pnLength)
{
// Break it down into three pieces.
// 50% green (healthy), 25% yellow (heavily damaged), 25% red (close to death)
if (nNumerator * 2 > nDenominator)
*pclr = GetColor(kiclrGreen);
else if (nNumerator * 4 > nDenominator)
*pclr = GetColor(kiclrYellow);
else
*pclr = GetColor(kiclrRed);
*pnLength = ((cxWidth - 2) * nNumerator) / nDenominator;
// The health bar should show at least one pixel of life left until
// the unit/structure is completely destroyed.
if (*pnLength == 0 && nNumerator > 0)
*pnLength = 1;
}
void DrawSelectionIndicator(DibBitmap *pbm, Rect *prc, int nNumerator, int nDenominator)
{
int cxWidth = prc->Width();
int cyHeight = prc->Height();
int nCornerWidth = cxWidth / 4;
int nCornerHeight = cyHeight / 4;
int nThickness = 1;
bool fSelectionBrackets = (gwfPerfOptions & kfPerfSelectionBrackets) != 0;
#ifdef __CPU_68K
//debug
//BitmapType *pbmpScreen = WinGetBitmap(WinGetDisplayWindow());
//byte *pbBits = (byte *)BmpGetBits(pbmpScreen);
byte *pbBits = pbm->GetBits();
// Hack to make drawing a selection faster if not clipped
// OPT: Could still be faster as TBitmaps
Size sizDib;
pbm->GetSize(&sizDib);
if (prc->left >= 0 && prc->top - kcyHealthBar >= 0 && prc->right <= sizDib.cx && prc->bottom <= sizDib.cy) {
Color clrHealth;
int nLength;
GetHealthColorAndLength(nNumerator, nDenominator, cxWidth, &clrHealth, &nLength);
byte *pbDst = pbBits + (long)(prc->top - kcyHealthBar) * sizDib.cx + prc->left;
DrawSelection68k(pbDst, sizDib.cx, cxWidth, cyHeight, nCornerWidth, nCornerHeight, GetColor(kiclrWhite), nLength, clrHealth, gmpiclriclrShadow, fSelectionBrackets);
return;
}
#endif
// Slow clipped path
if (fSelectionBrackets) {
Color clr = GetColor(kiclrWhite);
// Top-left corner
pbm->Fill(prc->left, prc->top, nCornerWidth, nThickness, clr);
pbm->Fill(prc->left, prc->top, nThickness, nCornerHeight, clr);
// Top-right corner
pbm->Fill(prc->right - nCornerWidth, prc->top, nCornerWidth, nThickness, clr);
pbm->Fill(prc->right - nThickness, prc->top, nThickness, nCornerHeight, clr);
// Bottom-left corner
pbm->Fill(prc->left, prc->bottom - nThickness, nCornerWidth, nThickness, clr);
pbm->Fill(prc->left, prc->bottom - nCornerHeight, nThickness, nCornerHeight, clr);
// Bottom-right corner
pbm->Fill(prc->right - nCornerWidth, prc->bottom - nThickness, nCornerWidth, nThickness, clr);
pbm->Fill(prc->right - nThickness, prc->bottom - nCornerHeight, nThickness, nCornerHeight, clr);
}
DrawHealthIndicator(pbm, prc, nNumerator, nDenominator);
}
void DrawHealthIndicator(DibBitmap *pbm, Rect *prc, int nNumerator, int nDenominator)
{
int cxWidth = prc->Width();
Color clr;
int nLength;
GetHealthColorAndLength(nNumerator, nDenominator, cxWidth, &clr, &nLength);
#ifdef __CPU_68K
Size sizDib;
pbm->GetSize(&sizDib);
if (prc->left >= 0 && prc->top - kcyHealthBar >= 0 && prc->right <= sizDib.cx && prc->bottom <= sizDib.cy) {
byte *pbDst = pbm->GetBits() + (long)(prc->top - kcyHealthBar) * sizDib.cx + prc->left;
DrawSelection68k(pbDst, sizDib.cx, cxWidth, 0, 0, 0, 0, nLength, clr, gmpiclriclrShadow, false);
return;
}
#endif
pbm->Shadow(prc->left, prc->top - kcyHealthBar, cxWidth, kcyHealthBar);
pbm->Fill(prc->left + 1, prc->top - (kcyHealthBar - 1), nLength, kcyHealthBar - 2, clr);
}
void DrawBuildProgressIndicator(DibBitmap *pbm, Rect *prc, int nNumerator, int nDenominator)
{
Color clr = GetColor(kiclrYellow);
int cxWidth = prc->right - prc->left;
int nLength = ((cxWidth - 2) * nNumerator) / nDenominator;
int y = prc->top + prc->Height() / 2;
// gapfnt[0]->DrawText(pbm, "Building...", prc->left, y - gapfnt[0]->GetHeight());
pbm->Shadow(prc->left, y, cxWidth, 6);
pbm->Fill(prc->left + 1, y + 1, nLength, 4, clr);
}
void DrawFullnessIndicator(DibBitmap *pbm, Rect *prc, int nPips, int nPipsMax)
{
// Don't draw anything if Miner/Processor/Warehouse is completely empty
if (nPips == 0)
return;
// Allow for overflow as is needed (rarely) when capacity is exceeded
// (e.g., self-destruct a structure while at capacity)
if (nPips > nPipsMax)
nPips = nPipsMax;
Color clr = GetColor(kiclrFullnessIndicator);
int cxyPip = PcFromWc(kwcFullnessPip);
int cx = (nPipsMax * (cxyPip + 1)) + 1;
int x = prc->left + ((prc->Width() - cx) / 2);
int y = prc->bottom + 1;
pbm->Shadow(x, y - 1, cx, kcyFullnessBar);
x++;
for (int i = 0; i < nPips; i++, x += cxyPip + 1)
pbm->Fill(x, y, cxyPip, cxyPip, clr);
}
// Given an offset in x and y, CalcDir returns one of 8 direction values.
// Directions map to 8 points on a compass, incrementing clockwise from N(orth).
// 0 = N/up, 1 = NE, 2 = E/right, 3 = SE, 4 = S/down, 5 = SW, 6 = W/left, 7 = NW
//
// NOTE: this optimized calculation is somewhat inaccurate. The 360 degrees are
// unevenly divided. N/S/E/W get 53.13 degree arcs and the diagonal angles get
// 36.88 degree arcs.
Direction CalcDir(int dx, int dy)
{
int dxAbs = abs(dx);
int dyAbs = abs(dy);
if (dxAbs > dyAbs) {
if (dx >= 0) {
if (dy != 0 && (dxAbs / 2) < dyAbs) { // > 22.5 degrees
if (dy < 0) // heading up
return kdirNE;
else
return kdirSE;
} else {
return kdirE;
}
} else {
if (dy != 0 && (dxAbs / 2) < dyAbs) { // > 22.5 degrees
if (dy < 0) // heading up
return kdirNW;
else
return kdirSW;
} else {
return kdirW;
}
}
} else {
if (dy < 0) {
if (dx != 0 && (dyAbs / 2) < dxAbs) {
if (dx >= 0)
return kdirNE;
else
return kdirNW;
} else {
return kdirN;
}
} else {
if (dx != 0 && (dyAbs / 2) < dxAbs) {
if (dx >= 0)
return kdirSE;
else
return kdirSW;
} else {
return kdirS;
}
}
}
}
// OPT: this can be optimized to 1/8 is current size by packing
// the values 4 to a byte and by exploiting its diagonal symmetry
static byte s_mpnArcFromDxDy[16][16] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 3, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 2, 3, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 },
{ 0, 1, 2, 3, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1 },
{ 0, 1, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1 },
{ 0, 1, 1, 2, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1 },
{ 0, 0, 1, 2, 2, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 1 },
{ 0, 0, 1, 2, 2, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2 },
{ 0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2 },
{ 0, 0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2 },
{ 0, 0, 1, 1, 1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 2 },
{ 0, 0, 0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3 },
{ 0, 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3 },
{ 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3 },
{ 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3 },
{ 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3 },
};
static int s_adirFromArc[32] = {
0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8,
8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 0
};
Direction16 CalcDir16(int dx, int dy)
{
int dxAbs = abs(dx);
int dyAbs = abs(dy);
while (dxAbs >= 16 || dyAbs >= 16) {
dxAbs >>= 1;
dyAbs >>= 1;
}
// UNDONE: maybe use 0 return value from s_mpnArc instead
if (dxAbs == 0)
return dy < 0 ? 0 : 8;
if (dyAbs == 0)
return dx >= 0 ? 4 : 12;
int i = s_mpnArcFromDxDy[dyAbs][dxAbs];
if (dxAbs >= dyAbs)
i = 7 - i;
if (dy > 0)
i = 15 - i;
if (dx < 0)
i = 31 - i;
return s_adirFromArc[i];
}
//
// GobMgr implementation
//
#define PpgobFromGid(gid) (&m_apgobMaster[gid])
#define GidFromPpgob(ppgob) ((ppgob) - m_apgobMaster)
#define MakeUsedEntry(pw) (Gob **)((pword)(pw) & ~1)
#define MakeFreeEntry(pw) (Gob *)((pword)(pw) | 1)
#define IsFreeEntry(pw) ((pword)(pw) & 1)
#define IgidFromWXY(wx, wy) (TcFromWc(wy) * m_ctx + TcFromWc(wx))
GobMgr::GobMgr()
{
m_pgobHead = NULL;
m_pgidMap = NULL;
#ifdef MP_DEBUG_SHAREDMEM
m_apgobMaster = NULL;
#endif
m_cpgobActive = 0;
m_car = 0;
m_cSceneryGobs = 0;
m_cScorchGobs = 0;
m_cSupportGobs = 0;
}
GobMgr::~GobMgr()
{
Assert(m_cpgobActive == 0);
Assert(m_pgidMap == NULL);
Assert(m_apgobMaster == NULL);
FreeAreas();
}
bool GobMgr::Init(TCoord ctx, TCoord cty, int cpgobMax)
{
Assert(m_pgidMap == NULL);
Assert(m_apgobMaster == NULL);
m_ctx = ctx;
m_cty = cty;
m_cpgobMax = cpgobMax;
// Allocate master Gob list (+ 1 entry for free list terminator)
Gob **apgobMaster = new Gob *[m_cpgobMax + 1];
Assert(apgobMaster != NULL, "out of memory!");
if (apgobMaster == NULL)
return false;
// Alloc from storage ram
int cbT = sizeof(Gob **) * (m_cpgobMax + 1);
m_apgobMaster = (Gob **)gmmgr.AllocPtr(cbT);
if (m_apgobMaster == NULL) {
delete[] apgobMaster;
return false;
}
// Initialize free list. Notice that the Gob* in the array are holding Gob** when in the free list.
Gob **ppgobDstT = apgobMaster;
Gob **ppgobSrcT = m_apgobMaster;
Gob **ppgobFreeTail = apgobMaster + m_cpgobMax;
for (; ppgobDstT < ppgobFreeTail; ppgobDstT++, ppgobSrcT++)
*ppgobDstT = MakeFreeEntry((Gob *)(ppgobSrcT + 1));
*ppgobFreeTail = NULL;
// Move to storage ram
// Free dyn ram version
gmmgr.WritePtr(m_apgobMaster, 0, apgobMaster, cbT);
m_ppgobFreeTail = m_apgobMaster + m_cpgobMax;
m_ppgobFreeHead = m_apgobMaster;
delete[] apgobMaster;
// Allocate gidMap
Gid *pgidMap = new Gid[m_ctx * m_cty];
Assert(pgidMap != NULL, "out of memory!");
if (pgidMap == NULL)
return false;
// Initialize gidMap
int cgid = m_ctx * m_cty;
Gid *pgidT = pgidMap;
for (int i = 0; i < cgid; i++)
*pgidT++ = kgidNull;
// Alloc from storage ram
// Free dyn ram version
cbT = m_ctx * m_cty * sizeof(Gid);
m_pgidMap = (Gid *)gmmgr.AllocPtr(cbT);
if (m_pgidMap == NULL) {
delete[] pgidMap;
return false;
}
gmmgr.WritePtr(m_pgidMap, 0, pgidMap, cbT);
delete[] pgidMap;
#ifdef INCL_VALIDATEHEAP
gmmgr.Validate();
#endif
return true;
}
#define knVerGobMgr 5
bool GobMgr::SaveState(Stream *pstm)
{
pstm->WriteByte(knVerGobMgr);
// Write areas
pstm->WriteWord(m_car);
for (int iar = 0; iar < m_car; iar++) {
Area *par = &m_aar[iar];
pstm->Write(par, sizeof(*par));
if (par->aare != NULL)
pstm->Write(par->aare, par->careAlloc * ELEMENTSIZE(par->aare));
}
return pstm->IsSuccess();
}
bool GobMgr::LoadState(Stream *pstm)
{
byte nVer = pstm->ReadByte();
if (nVer != knVerGobMgr)
return false;
// Read areas
m_car = pstm->ReadWord();
for (int iar = 0; iar < m_car; iar++) {
Area *par = &m_aar[iar];
pstm->Read(par, sizeof(*par));
if (par->aare != NULL) {
par->aare = new AreaEntry[par->careAlloc];
if (par->aare == NULL) {
pstm->Read(gpbScratch, par->careAlloc * ELEMENTSIZE(par->aare));
memset(par, 0, sizeof(*par));
par->iareFree = (word)-1;
par->iareHead = (word)-1;
} else {
pstm->Read(par->aare, par->careAlloc * ELEMENTSIZE(par->aare));
}
}
}
return pstm->IsSuccess();
}
// Called between levels
void GobMgr::Reset()
{
// Free master Gob list
if (m_apgobMaster != NULL) {
gmmgr.FreePtr(m_apgobMaster);
m_apgobMaster = NULL;
}
m_cpgobActive = 0;
// Free gidMap
if (m_pgidMap != NULL) {
gmmgr.FreePtr(m_pgidMap);
m_pgidMap = NULL;
}
// Free all the Gobs
Gob *pgobT = m_pgobHead;
while (pgobT != NULL) {
Gob *pgobNext = pgobT->m_pgobNext;
delete pgobT;
pgobT = pgobNext;
}
m_pgobHead = NULL;
// Free all area lists
FreeAreas();
// Reset gob counts
m_cSceneryGobs = 0;
m_cScorchGobs = 0;
m_cSupportGobs = 0;
}
void GobMgr::AddGob(Gob *pgob, Gid gid)
{
// Add Gob to master Gob list
// New Gob entries are taken from the HEAD of the free list
// Make sure we haven't already run out of free entries
Assert(*m_ppgobFreeHead != NULL);
Assert(m_cpgobActive != m_cpgobMax);
// Todo: return failure and deal with failure at some point
if (m_cpgobActive >= m_cpgobMax)
return;
// When loading a saved game, we use the same gids for gobs.
// When loading a game fresh, we take the next available gid.
if (gid == kgidNull) {
// Add to list; take the next available gid
Gob **ppgob = m_ppgobFreeHead;
Assert(*ppgob == MakeFreeEntry(*ppgob));
m_ppgobFreeHead = MakeUsedEntry(*ppgob);
gmmgr.WritePtr(m_apgobMaster, (byte *)ppgob - (byte *)m_apgobMaster, &pgob, sizeof(Gob *));
//*ppgob = pgob;
gid = GidFromPpgob(ppgob);
} else {
// We have a gid to use. Pull it out of the free list.
// Note to self: try to make this more complicated if possible. More '*''s and macros would help.
bool fFound = false;
// handle head case seperately because m_ppgobFreeHead's contents are not marked free
Gob **ppgobUse = PpgobFromGid(gid);
if (m_ppgobFreeHead == ppgobUse) {
fFound = true;
m_ppgobFreeHead = MakeUsedEntry((Gob **)*ppgobUse);
} else {
// ppgobUse is now the address of the apgobMaster slot we're going to put a gob in.
// find the slot that points at this one in the free list & point it at the next entry
for (Gob ***pppgobT = (Gob ***)m_ppgobFreeHead; *pppgobT != NULL; pppgobT = (Gob ***)MakeUsedEntry(*pppgobT)) {
if (MakeUsedEntry(*pppgobT) == ppgobUse) {
gmmgr.WritePtr(m_apgobMaster, (byte *)pppgobT - (byte *)m_apgobMaster, ppgobUse, sizeof(Gob *));
//*pppgobT = (Gob **)*ppgobUse;
// update the tail pointer if necessary
if (m_ppgobFreeTail == ppgobUse)
m_ppgobFreeTail = (Gob **) pppgobT;
fFound = true;
break;
}
}
}
Assert(fFound);
if (!fFound)
return;
gmmgr.WritePtr(m_apgobMaster, (byte *)ppgobUse - (byte *)m_apgobMaster, &pgob, sizeof(Gob *));
//*ppgobUse = pgob;
}
// Assign gid, link it in
Assert(gid < (m_cpgobMax + 1));
pgob->m_gid = gid;
pgob->m_pgobNext = m_pgobHead;
m_pgobHead = pgob;
m_cpgobActive++;
// Add Gob to gidMap
MoveGob(pgob, -1, -1, pgob->m_wx, pgob->m_wy);
// Increment gob counts
TrackGobCounts(pgob, true);
#ifdef INCL_VALIDATEHEAP
gmmgr.Validate();
#endif
}
void GobMgr::RemoveGob(Gob *pgob)
{
// Ensure this gob isn't in any area lists still
#if defined(WIN) && !defined(CE) && defined(DEBUG)
// Validate not already in these areas
for (int iar = 0; iar < m_car; iar++) {
Area *par = &m_aar[iar];
for (word iareT = par->iareHead; iareT != (word)-1; iareT = par->aare[iareT].iareNext)
Assert(par->aare[iareT].gid != pgob->m_gid);
}
#endif
// Mark this gob invalid in the update map so this area gets drawn correctly
pgob->Invalidate();
// Remove Gob from gidMap
MoveGob(pgob, pgob->m_wx, pgob->m_wy, -1, -1);
// Remove Gob from master Gob list
// Freed Gob list entries go on the END of the free list
Gob **ppgob = PpgobFromGid(pgob->m_gid);
pgob->m_gid = kgidNull;
Gob *pgobT = NULL;
gmmgr.WritePtr(m_apgobMaster, (byte *)ppgob - (byte *)m_apgobMaster, &pgobT, sizeof(Gob *));
//*ppgob = NULL; // Mark new terminator
pgobT = MakeFreeEntry(ppgob);
gmmgr.WritePtr(m_apgobMaster, (byte *)m_ppgobFreeTail - (byte *)m_apgobMaster, &pgobT, sizeof(Gob *));
//*m_ppgobFreeTail = MakeFreeEntry(ppgob);
m_ppgobFreeTail = ppgob;
// UNDONE: OPT: removing Gobs requires a traversal of the Gob list
for (Gob** ppgobT = &m_pgobHead; *ppgobT != NULL; ppgobT = &(*ppgobT)->m_pgobNext) {
if (*ppgobT == pgob) {
*ppgobT = pgob->m_pgobNext;
pgob->m_pgobNext = NULL;
break;
}
}
m_cpgobActive--;
// Decrement gob counts
TrackGobCounts(pgob, false);
#ifdef INCL_VALIDATEHEAP
gmmgr.Validate();
#endif
}
void GobMgr::TrackGobCounts(Gob *pgob, bool fIncrement)
{
if (!(pgob->GetFlags() & kfGobUnit)) {
int nDir = fIncrement ? 1 : -1;
switch (pgob->GetType()) {
case kgtScenery:
m_cSceneryGobs += nDir;
break;
case kgtScorch:
m_cScorchGobs += nDir;
break;
default:
m_cSupportGobs += nDir;
break;
}
}
// Validate counts so we know of errors
#ifdef DEBUG
if (ggame.IsMultiplayer()) {
for (Side side = ksideNeutral; side < kcSides; side++) {
Player *pplr = gplrm.GetPlayer(side);
if (pplr == NULL)
continue;
int cStructGobs = pplr->GetUnitInstanceCountFromMask(kumStructures);
Assert(cStructGobs >= 0 && cStructGobs <= gcStructGobsLimitMP);
int cMuntGobs = pplr->GetUnitInstanceCountFromMask(kumMobileUnits);
Assert(cMuntGobs >= 0 && cMuntGobs <= gcMuntGobsLimitMP);
}
} else {
int cMuntGobsComputer = gplrm.GetUnitInstanceCountFromMask(kumMobileUnits, kfPlrComputer);
Assert(cMuntGobsComputer >= 0 && cMuntGobsComputer <= gcMuntGobsComputerLimitSP);
int cStructGobsComputer = gplrm.GetUnitInstanceCountFromMask(kumStructures, kfPlrComputer);
Assert(cStructGobsComputer >= 0 && cStructGobsComputer <= gcStructGobsComputerLimitSP + gcStructGobsComputerDeltaSP);
int cMuntGobsHuman = gplrm.GetUnitInstanceCountFromMask(kumMobileUnits, 0);
Assert(cMuntGobsHuman >= 0 && cMuntGobsHuman <= gcMuntGobsHumanLimitSP);
int cStructGobsHuman = gplrm.GetUnitInstanceCountFromMask(kumStructures, 0);
Assert(cStructGobsHuman >= 0 && cStructGobsHuman <= gcStructGobsHumanLimitSP + gcStructGobsHumanDeltaSP);
Assert(m_cSceneryGobs >= 0 && m_cSceneryGobs <= gcSceneryGobsLimit);
Assert(m_cScorchGobs >= 0 && m_cScorchGobs <= gcScorchGobsLimit);
Assert(m_cSupportGobs >= 0 && m_cSupportGobs <= gcSupportGobsLimit);
}
#endif
}
bool GobMgr::IsBelowLimit(int nLimit, Player *pplr)
{
switch (nLimit) {
case knLimitStruct:
{
Assert(pplr != NULL);
if (!ggame.IsMultiplayer()) {
int cQueued = BuilderGob::GetGlobalQueuedCount(pplr, kfUntcStructureBuilder);
if (pplr->GetFlags() & kfPlrComputer) {
int cStructGobsComputer = gplrm.GetUnitInstanceCountFromMask(kumStructures, kfPlrComputer);
return (cStructGobsComputer + cQueued) < gcStructGobsComputerLimitSP + gcStructGobsComputerDeltaSP;
} else {
int cStructGobsHuman = gplrm.GetUnitInstanceCountFromMask(kumStructures, 0);
return (cStructGobsHuman + cQueued) < gcStructGobsHumanLimitSP + gcStructGobsHumanDeltaSP;
}
} else {
int cQueued = BuilderGob::GetQueuedCount(pplr, kfUntcStructureBuilder);
return (pplr->GetUnitInstanceCountFromMask(kumStructures) + cQueued) < gcStructGobsLimitMP;
}
}
break;
case knLimitMobileUnit:
{
Assert(pplr != NULL);
if (!ggame.IsMultiplayer()) {
int cQueued = BuilderGob::GetGlobalQueuedCount(pplr, kfUntcMobileUnitBuilder);
if (pplr->GetFlags() & kfPlrComputer) {
int cMuntGobsComputer = gplrm.GetUnitInstanceCountFromMask(kumMobileUnits, kfPlrComputer);
return (cMuntGobsComputer + cQueued) < gcMuntGobsComputerLimitSP;
} else {
int cMuntGobsHuman = gplrm.GetUnitInstanceCountFromMask(kumMobileUnits, 0);
return (cMuntGobsHuman + cQueued) < gcMuntGobsHumanLimitSP;
}
} else {
int cQueued = BuilderGob::GetQueuedCount(pplr, kfUntcMobileUnitBuilder);
return (pplr->GetUnitInstanceCountFromMask(kumMobileUnits) + cQueued) < gcMuntGobsLimitMP;
}
}
break;
case knLimitScenery:
return m_cSceneryGobs < gcSceneryGobsLimit;
case knLimitScorch:
return m_cScorchGobs < gcScorchGobsLimit;
case knLimitSupport:
return m_cSupportGobs < gcSupportGobsLimit;
}
Assert();
return false;
}
bool GobMgr::MoveGob(Gob *pgob, WCoord wxOld, WCoord wyOld, WCoord wxNew, WCoord wyNew)
{
Assert(wxOld >= -1 && TcFromWc(wxOld) < m_ctx && wyOld >= -1 && TcFromWc(wyOld) < m_cty);
Assert(wxNew >= -1 && TcFromWc(wxNew) < m_ctx && wyNew >= -1 && TcFromWc(wyNew) < m_cty);
Assert(pgob->m_gid < (m_cpgobMax + 1));
int igidOld = -1;
if (wxOld != -1 && wyOld != -1)
igidOld = IgidFromWXY(wxOld, wyOld);
int igidNew = -1;
if (wxNew != -1 && wyNew != -1)
igidNew = IgidFromWXY(wxNew, wyNew);
if (igidOld == igidNew)
return false;
if (igidOld != -1) {
// Remove Gob from old location within gidMap
Gid *pgidT = m_pgidMap + igidOld;
Assert((*pgidT & kwfGidEndMarker) == 0);
if (*pgidT == pgob->m_gid) {
Gid gidT = pgob->m_gidNext;
gmmgr.WritePtr(m_pgidMap, (byte *)pgidT - (byte *)m_pgidMap, &gidT, sizeof(gidT));
} else {
while (((*pgidT) & kwfGidEndMarker) == 0) {
if (*pgidT == pgob->m_gid) {
*pgidT = pgob->m_gidNext;
Assert(pgob->m_gidNext != pgob->m_gid);
pgob->m_gidNext = kgidNull;
break;
}
pgidT = &(*PpgobFromGid(*pgidT))->m_gidNext;
}
}
}
if (igidNew != -1) {
// Add Gob to new location within gidMap
Gid *pgidT = m_pgidMap + igidNew;
pgob->m_gidNext = *pgidT;
Assert(pgob->m_gidNext != pgob->m_gid);
Gid gidT = pgob->m_gid;
gmmgr.WritePtr(m_pgidMap, (byte *)pgidT - (byte *)m_pgidMap, &gidT, sizeof(gidT));
//*pgidT = pgob->m_gid;
}
#ifdef INCL_VALIDATEHEAP
gmmgr.Validate();
#endif
return true;
}
UnitGob *GobMgr::GetUnitGob(TCoord tx, TCoord ty)
{
// Gets the unit gob at this spot. Checks shadowed gids too.
Gid gid = m_pgidMap[ty * m_ctx + tx];
while (true) {
if (gid == kgidNull)
return NULL;
Gob *pgob = GetGob(gid & ~kwfGidEndMarker, false);
if (pgob == NULL)
return NULL;
if (pgob->GetFlags() & kfGobUnit)
return (UnitGob *)pgob;
if (gid & kwfGidEndMarker)
return NULL;
gid = pgob->m_gidNext;
}
}
Gob *GobMgr::GetShadowGob(TCoord tx, TCoord ty)
{
// Get gid shadowed "inside" the end of list marker
// Don't bother clipping
Gid gid = m_pgidMap[ty * m_ctx + tx];
while (true) {
if (gid == kgidNull)
return NULL;
if ((gid & kwfGidEndMarker) != 0)
return GetGob(gid & ~kwfGidEndMarker, false);
Gob *pgob = GetGob(gid, false);
if (pgob == NULL)
return NULL;
gid = pgob->m_gidNext;
}
}
void GobMgr::ShadowGob(Gob *pgob, TCoord tx, TCoord ty, int ctx, int cty)
{
// Shadow this gid "inside" the end of list marker.
for (TCoord tyT = ty; tyT < ty + cty; tyT++) {
for (TCoord txT = tx; txT < tx + ctx; txT++) {
Gid *pgid = &m_pgidMap[tyT * m_ctx + txT];
if ((*pgid & kwfGidEndMarker) != 0) {
Gid gidT = pgob->m_gid | kwfGidEndMarker;
gmmgr.WritePtr(m_pgidMap, (byte *)pgid - (byte *)m_pgidMap, &gidT, sizeof(gidT));
} else {
while ((*pgid & kwfGidEndMarker) == 0) {
Gob *pgob = *PpgobFromGid(*pgid);
pgid = &pgob->m_gidNext;
}
Assert((*pgid & kwfGidEndMarker) != 0);
*pgid = pgob->m_gid | kwfGidEndMarker;
}
}
}
}
void GobMgr::UnshadowGob(Gob *pgob, TCoord tx, TCoord ty, int ctx, int cty)
{
// Unshadow this gob's gid from the end of list marker
for (TCoord tyT = ty; tyT < ty + cty; tyT++) {
for (TCoord txT = tx; txT < tx + ctx; txT++) {
Gid *pgid = &m_pgidMap[tyT * m_ctx + txT];
if ((*pgid & kwfGidEndMarker) != 0) {
Gid gidT = kgidNull;
gmmgr.WritePtr(m_pgidMap, (byte *)pgid - (byte *)m_pgidMap, &gidT, sizeof(gidT));
} else {
while ((*pgid & kwfGidEndMarker) == 0) {
Gob *pgob = *PpgobFromGid(*pgid);
pgid = &pgob->m_gidNext;
}
Assert((*pgid & kwfGidEndMarker) != 0);
if ((*pgid & ~kwfGidEndMarker) == pgob->m_gid)
*pgid = kgidNull;
}
}
}
}
AreaMask GobMgr::CalcAreaMask(TCoord tx, TCoord ty, int ctx, int cty)
{
AreaMask am = 0;
for (TCoord tyT = ty; tyT < ty + cty; tyT++) {
for (TCoord txT = tx; txT < tx + ctx; txT++) {
am |= CalcAreaMask(txT, tyT);
}
}
return am;
}
// Rects could be sorted to speed this up (as if it's a problem)
// Also AreaMask could be stored in UnitGob for a speed boost
AreaMask GobMgr::CalcAreaMask(TCoord tx, TCoord ty)
{
if (gsim.GetLevel()->GetTerrainMap()->GetTerrainType(tx, ty) != kttArea)
return 0;
AreaMask am = 0;
for (int nArea = 0; nArea < m_car; nArea++) {
if (m_aar[nArea].trc.PtIn(tx, ty)) {
am |= (1UL << nArea);
}
}
Assert(am != 0);
return am;
}
// Only called when there is a AreaMask change (rare).
// I have this in assembly (from pocketchess) if needed.
int GobMgr::GetAreasFromMask(AreaMask am, int *pnArea)
{
Assert(kcAreasMax <= 32);
int cArea = 0;
for (int n = 0; n < kcAreasMax && am != 0; n++) {
if (am & (1UL << n)) {
*pnArea++ = n;
cArea++;
am &= (am - 1);
}
}
return cArea;
}
void GobMgr::MoveGobBetweenAreas(Gid gid, AreaMask amOld, AreaMask amNew)
{
if (amOld == amNew)
return;
AreaMask amRemove = amOld & ~amNew;
if (amRemove != 0)
RemoveGobFromAreas(gid, amRemove);
AreaMask amAdd = amNew & ~amOld;
if (amAdd != 0)
AddGobToAreas(gid, amAdd);
}
#define kcareGrow 32
void GobMgr::AddGobToAreas(Gid gid, AreaMask am)
{
// Loop through areas
int aiar[kcAreasMax];
int car = GetAreasFromMask(am, aiar);
#if defined(WIN) && !defined(CE) && defined(DEBUG)
// Validate not already in these areas
for (int iar = 0; iar < car; iar++) {
Area *par = &m_aar[aiar[iar]];
for (word iareT = par->iareHead; iareT != (word)-1; iareT = par->aare[iareT].iareNext)
Assert(par->aare[iareT].gid != gid);
}
#endif
// Add to the following areas
for (int iar = 0; iar < car; iar++) {
// Add to list. Need more room?
Area *par = &m_aar[aiar[iar]];
if (par->iareFree == (word)-1) {
Assert(par->careAlloc == par->careUsed);
int careNew = par->careAlloc + kcareGrow;
AreaEntry *aare = new AreaEntry[careNew];
if (aare == NULL)
continue;
if (par->aare == NULL) {
par->aare = aare;
} else {
memcpy(aare, par->aare, ELEMENTSIZE(aare) * par->careAlloc);
delete[] par->aare;
par->aare = aare;
}
// Initialize onto free list
par->iareFree = par->careAlloc;
for (int iareNew = par->iareFree; iareNew < careNew - 1; iareNew++)
par->aare[iareNew].iareNext = iareNew + 1;
par->aare[careNew - 1].iareNext = (word)-1;
// New size
par->careAlloc = careNew;
}
// Take one from the free list
int iareNew = par->iareFree;
Assert(iareNew != (word)-1);
par->iareFree = par->aare[iareNew].iareNext;
// Put on used list
par->aare[iareNew].iareNext = par->iareHead;
par->iareHead = iareNew;
// This gob lives here
par->careUsed++;
par->aare[iareNew].gid = gid;
// Update info about gobs in this area
UnitGob *punt = (UnitGob *)*PpgobFromGid(gid);
Assert(punt == (UnitGob *)GetGob(gid, false));
if (punt != NULL && (punt->m_ff & kfGobUnit)) {
par->sidm |= GetSideMask(punt->GetSide());
par->um |= punt->GetConsts()->um;
}
}
}
void GobMgr::RemoveGobFromAreas(Gid gid, AreaMask am)
{
// Loop through areas
int aiar[kcAreasMax];
int car = GetAreasFromMask(am, aiar);
#if defined(WIN) && !defined(CE) && defined(DEBUG)
// Validate already in these areas
for (int iar = 0; iar < car; iar++) {
Area *par = &m_aar[aiar[iar]];
bool fFound = false;
for (word iareT = par->iareHead; iareT != (word)-1; iareT = par->aare[iareT].iareNext) {
if (par->aare[iareT].gid == gid) {
fFound = true;
break;
}
}
Assert(fFound);
}
#endif
// Remove from the following areas
for (int iar = 0; iar < car; iar++) {
// Remove from area list, put entry on free list
// Recalc side and unit masks into along the way
Area *par = &m_aar[aiar[iar]];
par->sidm = 0;
par->um = 0;
word *piareNext = &par->iareHead;
while (*piareNext != (word)-1) {
AreaEntry *pare = &par->aare[*piareNext];
if (pare->gid == gid) {
// Remove this gid; should happen only once
word iareT = *piareNext;
*piareNext = pare->iareNext;
pare->iareNext = par->iareFree;
par->iareFree = iareT;
par->careUsed--;
Assert((short)par->careUsed >= 0);
// piareNext is still point to last which is what we want
} else {
// Update info about gobs in this area
UnitGob *punt = (UnitGob *)*PpgobFromGid(pare->gid);
Assert(punt == (UnitGob *)GetGob(pare->gid, false));
if (punt != NULL && (punt->m_ff & kfGobUnit)) {
par->sidm |= GetSideMask(punt->GetSide());
par->um |= punt->GetConsts()->um;
}
// Cycle to the next
piareNext = &pare->iareNext;
}
}
}
}
Gob *GobMgr::EnumGobsInArea(Enum *penm, int nArea, SideMask sidm, UnitMask um)
{
// First time? Do overall filter match.
// Note kEnmFirst is -1, the same value used here for the "end of list"
// marker.
Area *par = &m_aar[nArea];
if (penm->m_dwUser == kEnmFirst) {
if (!(par->sidm & sidm))
return NULL;
if (!(par->um & um))
return NULL;
penm->m_dwUser = 0;
penm->m_wUser = par->iareHead;
}
while (penm->m_wUser != (word)-1) {
AreaEntry *pare = &par->aare[penm->m_wUser];
penm->m_wUser = pare->iareNext;
// Check against filter
Gob *pgobT = (Gob *)*PpgobFromGid(pare->gid);
Assert(pgobT == GetGob(pare->gid, false));
Assert(pgobT->GetFlags() & kfGobUnit);
if (!(pgobT->GetFlags() & kfGobUnit))
continue;
UnitGob *puntT = (UnitGob *)pgobT;
if (!(GetSideMask(puntT->GetSide()) & sidm))
continue;
if (!(puntT->GetConsts()->um & um))
continue;
// Matches filter, return it.
// NOTE: this doesn't return the NEAREST Gob, just the first
// found within the area
return pgobT;
}
return NULL;
}
bool GobMgr::CheckUnitsInArea(int nArea, SideMask sidm, UnitMask um)
{
// Only checks if unit type and given side, not if unit type is of given side
Assert(nArea >= 0 && nArea < m_car);
Area *par = &m_aar[nArea];
if (!(par->sidm & sidm))
return false;
if (!(par->um & um))
return false;
return true;
}
bool GobMgr::IsGobWithinArea(Gob *pgobTarget, int nArea)
{
TPoint tpt;
pgobTarget->GetTilePosition(&tpt);
Assert(nArea >= 0 && nArea < m_car);
return m_aar[nArea].trc.PtIn(tpt.tx, tpt.ty);
}
void GobMgr::GetAreaRect(int nArea, TRect *ptrc, Side side)
{
if (side == ksideNeutral || nArea >= 0) {
Assert(nArea < m_car);
*ptrc = m_aar[nArea].trc;
} else {
if (nArea == knAreaLastDiscovery) {
Assert(side != ksideNeutral);
Player *pplr = gplrm.GetPlayer(side);
TPoint tpt = pplr->GetDiscoverPoint();
ptrc->left = tpt.tx;
ptrc->top = tpt.ty;
ptrc->right = tpt.tx + 1;
ptrc->bottom = tpt.ty + 1;
}
}
}
bool GobMgr::LoadAreas(IniReader *pini)
{
// Initialize areas first
for (int iar = 0; iar < ARRAYSIZE(m_aar); iar++) {
Area *par = &m_aar[iar];
memset(par, 0, sizeof(*par));
par->iareFree = (word)-1;
par->iareHead = (word)-1;
}
m_car = 0;
// Might as well have the TriggerMgr own Areas as well for now
char szProp[128];
FindProp findArea;
while (pini->FindNextProperty(&findArea, "Areas", szProp, sizeof(szProp))) {
#ifdef DEBUG_HELPERS
strncpyz(m_aszAreaNames[m_car], szProp, 50);
#endif
int nLeft, nTop, nWidth, nHeight;
if (!pini->GetPropertyValue(&findArea, "%d,%d,%d,%d", &nLeft, &nTop, &nWidth, &nHeight))
return false;
m_aar[m_car].trc.Set(nLeft, nTop, nLeft + nWidth, nTop + nHeight);
m_car++;
Assert(m_car <= kcAreasMax);
}
return true;
}
void GobMgr::FreeAreas()
{
for (int iar = 0; iar < m_car; iar++) {
delete[] m_aar[iar].aare;
m_aar[iar].aare = NULL;
}
m_car = 0;
}
Gob *GobMgr::GetGob(Gid gid, bool fActiveOnly)
{
// Also catches kgidNull
if (gid & kwfGidEndMarker) {
return NULL;
}
Assert(gid < (m_cpgobMax + 1) * sizeof(Gob *));
Gob *pgob = *PpgobFromGid(gid);
if (pgob == NULL)
return NULL;
// Returns NULL if gid is invalid, i.e., now points to a free entry
// Free entries have their low bit set, used entries always have it clear.
if (IsFreeEntry(pgob))
return NULL;
// Most callers should consider a Gob as non-existant if it is no longer
// Active
if (fActiveOnly) {
if ((pgob->GetFlags() & kfGobActive) == 0) {
return NULL;
}
}
return pgob;
}
// Return true if a same-side structure is within the required distance
// (kctMaxDistFromNeighbor) of the passed-in location+structure size. NOTE:
// this routine does not guarantee that there isn't something in the way.
// NOTE: this routine doesn't need to live in GobMgr
const int kctMaxDistFromNeighbor = 3;
bool GobMgr::IsStructurePlacementValid(StructConsts *pstruc, TCoord tx, TCoord ty, Player *pplr)
{
// No part of the structure can be off an edge of the map
if (tx < 0 || ty < 0 || tx + pstruc->ctxReserve > m_ctx || ty + pstruc->ctyReserve > m_cty) {
return false;
}
// No part of the structure can be under the inaccessible part of the
// minimap (when the map is scrolled to the bottom right corner)
Form *pfrmMiniMap = gpmfrmm->GetFormPtr(kidfMiniMap);
if (pfrmMiniMap != NULL) {
Rect rcMiniMap;
pfrmMiniMap->GetRect(&rcMiniMap);
TCoord ctxMap, ctyMap;
ggobm.GetMapSize(&ctxMap, &ctyMap);
WCoord wxMiniMapLeft = WcFromTc(ctxMap) - WcFromUpc(rcMiniMap.Width());
WCoord wyMiniMapTop = WcFromTc(ctyMap) - WcFromUpc(rcMiniMap.Height());
WCoord wxPlacementLeft = WcFromTc(tx);
WCoord wyPlacementTop = WcFromTc(ty);
if (wxPlacementLeft > (wxMiniMapLeft - kwcTile) &&
wyPlacementTop > (wyMiniMapTop - kwcTile)) {
return false;
}
}
TRect trcSearch;
trcSearch.Set(tx - kctMaxDistFromNeighbor, ty - kctMaxDistFromNeighbor,
tx + pstruc->ctx + kctMaxDistFromNeighbor, ty + pstruc->cty + kctMaxDistFromNeighbor);
// We let the player put towers a little farther away from neighboring
// structures -- but not chain them! (see below)
if (pstruc->um & kumTowers)
trcSearch.Inflate(1, 1);
// Don't search off any edge of the map
if (trcSearch.left < 0)
trcSearch.left = 0;
if (trcSearch.right > m_ctx)
trcSearch.right = m_ctx;
if (trcSearch.top < 0)
trcSearch.top = 0;
if (trcSearch.bottom > m_cty)
trcSearch.bottom = m_cty;
for (int tyT = trcSearch.top; tyT < trcSearch.bottom; tyT++) {
for (int txT = trcSearch.left; txT < trcSearch.right; txT++) {
Gob *pgob = GetShadowGob(txT, tyT);
if (pgob == NULL)
continue;
if ((pgob->GetFlags() & (kfGobStructure | kfGobActive)) == (kfGobStructure | kfGobActive)) {
if (pgob->GetOwner() != pplr)
continue;
// Special hack to keep players from building long chains of towers
GobType gt = pgob->GetType();
if (gt != kgtMachineGunTower && gt != kgtRocketTower) {
return true;
}
}
}
}
return false;
}
struct GobSort { // gs
Gob *pgob;
dword key;
};
void SortGobs(Gob **apgob, int cpgob) secGob;
#if 0
// Called by Simulation::Draw to identify and sort the on-screen Gobs
// prior to drawing them.
int GobMgr::FindGobs(const Rect *prcBounds, Gob **apgob, int cpgobMax, byte *pbFogMap)
{
TCoord txLeft, txRight, tyTop, tyBottom;
txLeft = TcFromUpc(prcBounds->left);
// This bounds check here is to avoid exceeding the range of the
// pixel -> world coord lookup table.
int xRight = prcBounds->right + gcxTile - 1;
if (xRight >= kpcMax)
xRight = kpcMax - 1;
txRight = TcFromUpc(xRight);
tyTop = TcFromUpc(prcBounds->top);
int yBottom = prcBounds->bottom + gcyTile - 1;
if (yBottom >= kpcMax)
yBottom = kpcMax - 1;
tyBottom = TcFromUpc(yBottom);
// Expand the cells examined to include any that may contain Gobs
// that intersect the specified bounding rectangle.
// This carries some assumptions about the maximum size of Gob
// clippinging rectangle and its relation to its origin.
// Maximum Gob clipping rectangle is:
// -3/+2 tiles wide from its origin
// -3/+1 tiles high from its origin
txLeft = _max(0, txLeft - 3);
txRight = _min(m_ctx - 1, txRight + 2);
tyTop = _max(0, tyTop - 3);
tyBottom = _min(m_cty - 1, tyBottom + 1);
int ctx = txRight - txLeft;
int cpgob = 0;
Gid *pgidLeft = m_pgidMap + (tyTop * m_ctx) + txLeft;
for (int ty = tyTop; ty <= tyBottom; ty++, pgidLeft += m_ctx) {
Gid *pgidRight = pgidLeft + ctx;
for (Gid *pgidT = pgidLeft; pgidT <= pgidRight; pgidT++) {
Gid gidT = *pgidT;
Gid gidNext;
for (; (gidT & kwfGidEndMarker) == 0; gidT = gidNext) {
Gob *pgobT = *PpgobFromGid(gidT);
gidNext = pgobT->m_gidNext;
dword ffGob = pgobT->GetFlags();
// UNDONE: OPT: this could be sped up a little. We only need
// to test the clipping bounds of Gobs found when scanning the
// expanded edges of the bounds.
Rect rcClip;
pgobT->GetClippingBounds(&rcClip);
if (rcClip.left >= prcBounds->right)
goto lbNextGob;
if (rcClip.right <= prcBounds->left)
goto lbNextGob;
if (rcClip.top >= prcBounds->bottom)
goto lbNextGob;
if (rcClip.bottom <= prcBounds->top)
goto lbNextGob;
// Check if totally under fog
// Fog disappears quickly; check top left right away
if (pbFogMap != NULL) {
int txLeft = TcFromPc(rcClip.left);
if (txLeft < 0)
txLeft = 0;
int tyTop = TcFromPc(rcClip.top);
if (tyTop < 0)
tyTop = 0;
byte *pbFogT = pbFogMap + m_ctx * tyTop + txLeft;
if (IsFogOpaque(*pbFogT)) {
int xT = rcClip.right + gcxTile - 1;
if (xT >= kpcMax)
xT = kpcMax - 1;
int txRight = TcFromPc(xT);
if (txRight > m_ctx)
txRight = m_ctx;
int yT = rcClip.bottom + gcyTile - 1;
if (yT >= kpcMax)
yT = kpcMax - 1;
int tyBottom = TcFromPc(yT);
if (tyBottom > m_cty)
tyBottom = m_cty;
bool fOpaque = true;
for (int tyT = tyTop; tyT < tyBottom; tyT++) {
for (int txT = txLeft; txT < txRight; txT++) {
word wFog = *pbFogT++;
if (!IsFogOpaque(wFog)) {
fOpaque = false;
break;
}
}
if (!fOpaque)
break;
pbFogT += m_ctx - (txRight - txLeft);
}
if (fOpaque)
goto lbNextGob;
}
}
// Remember if this gob was not visible last frame; we'll need this info later
if (!(ffGob & kfGobVisibleLastFrame)) {
ffGob |= kfGobTransitioningToVisible;
} else {
ffGob &= ~kfGobTransitioningToVisible;
}
// Remember that this gob was visible in this frame
ffGob = (ffGob & ~kfGobIncludeFindVisible) | kfGobVisibleLastFrame;
pgobT->SetFlags(ffGob);
// Add gob to list
apgob[cpgob++] = pgobT;
if (cpgob == cpgobMax)
goto lbFull;
continue;
lbNextGob:
// Gob not visible, but add to list anyway if asked
// Clear the "gob was visible this frame" bit since it isn't visible this frame.
ffGob &= ~kfGobTransitioningToVisible;
if (ffGob & kfGobIncludeFindVisible) {
pgobT->SetFlags(ffGob & ~(kfGobIncludeFindVisible | kfGobVisibleLastFrame));
apgob[cpgob++] = pgobT;
if (cpgob == cpgobMax)
goto lbFull;
} else {
pgobT->SetFlags(ffGob & ~kfGobVisibleLastFrame);
}
}
}
}
lbFull:
SortGobs(apgob, cpgob);
return cpgob;
}
#else
// Called by Simulation::Draw to identify and sort the on-screen Gobs
// prior to drawing them.
int GobMgr::FindGobs(const Rect *prcBounds, Gob **apgob, int cpgobMax, byte *pbFogMap)
{
// This bounds check here is to avoid exceeding the range of the
// pixel -> world coord lookup table.
TCoord txLeft, txRight, tyTop, tyBottom;
txLeft = TcFromUpc(prcBounds->left);
int xRight = prcBounds->right + gcxTile - 1;
if (xRight >= kpcMax)
xRight = kpcMax - 1;
txRight = TcFromUpc(xRight);
tyTop = TcFromUpc(prcBounds->top);
int yBottom = prcBounds->bottom + gcyTile - 1;
if (yBottom >= kpcMax)
yBottom = kpcMax - 1;
tyBottom = TcFromUpc(yBottom);
// Increment sequence visibility counter
static word s_nSeqLastVisible;
s_nSeqLastVisible++;
// Expand by 1. Need to because we need to track gobs transitioning to invisible
// Also clip to the map
txLeft = _max(0, txLeft - 1);
txRight = _min((int)m_ctx, txRight + 1);
tyTop = _max(0, tyTop - 1);
tyBottom = _min((int)m_cty, tyBottom + 1);
int ctx = txRight - txLeft;
int cty = tyBottom - tyTop;
int cgidReturn = m_ctx - ctx;
Gob **ppgobT = apgob;
Gob **ppgobMax = &apgob[cpgobMax];
Gid *pgidT = m_pgidMap + (tyTop * m_ctx) + txLeft;
for (int ctyT = cty; ctyT != 0; ctyT--) {
for (int ctxT = ctx; ctxT != 0; ctxT--) {
Gid gidNext;
for (Gid gidT = *pgidT; gidT != kgidNull; gidT = gidNext) {
// Structure? If so, also end of list
Gob *pgobT;
if ((gidT & kwfGidEndMarker) != 0) {
// Structure
pgobT = *PpgobFromGid(gidT & ~kwfGidEndMarker);
gidNext = kgidNull;
Assert(pgobT->GetFlags() & kfGobStructure);
} else {
// Any gob
pgobT = *PpgobFromGid(gidT);
gidNext = pgobT->m_gidNext;
}
dword ffGob = pgobT->GetFlags();
// If it's a structure make sure we're visiting it once
if (ffGob & kfGobStructure) {
StructGob *pstru = (StructGob *)pgobT;
if (pstru->m_nSeqLastVisible == s_nSeqLastVisible)
break;
pstru->m_nSeqLastVisible = s_nSeqLastVisible;
}
// If it's an edge gob, check for visibility
if (ctxT == ctx || ctxT == 1 || ctyT == cty || ctyT == 1) {
Rect rcClip;
pgobT->GetClippingBounds(&rcClip);
if (rcClip.left >= prcBounds->right)
goto lbNextGob;
if (rcClip.right <= prcBounds->left)
goto lbNextGob;
if (rcClip.top >= prcBounds->bottom)
goto lbNextGob;
if (rcClip.bottom <= prcBounds->top)
goto lbNextGob;
}
// Remember if this gob was not visible last frame; we'll need this info later
if (!(ffGob & kfGobVisibleLastFrame)) {
ffGob |= kfGobTransitioningToVisible;
} else {
ffGob &= ~kfGobTransitioningToVisible;
}
// Remember that this gob was visible in this frame
ffGob = (ffGob & ~kfGobIncludeFindVisible) | kfGobVisibleLastFrame;
pgobT->SetFlags(ffGob);
// Add gob to list
*ppgobT++ = pgobT;
if (ppgobT == ppgobMax)
goto lbFull;
continue;
lbNextGob:
// Gob not visible, but add to list anyway if asked
// Clear the "gob was visible this frame" bit since it isn't visible this frame.
ffGob &= ~kfGobTransitioningToVisible;
if (ffGob & kfGobIncludeFindVisible) {
pgobT->SetFlags(ffGob & ~(kfGobIncludeFindVisible | kfGobVisibleLastFrame));
*ppgobT++ = pgobT;
if (ppgobT == ppgobMax)
goto lbFull;
} else {
pgobT->SetFlags(ffGob & ~kfGobVisibleLastFrame);
}
}
// Next gid
pgidT++;
}
// Next row
pgidT += cgidReturn;
}
lbFull:
int cpgob = (int)(ppgobT - apgob);
SortGobs(apgob, cpgob);
return cpgob;
}
#endif
void SortGobs(Gob **apgob, int cpgob)
{
if (cpgob <= 1)
return;
// Prep for sorting
#if 1
GobSort *ags = (GobSort *)gpbScratch;
#else
GobSort ags[kcpgobMax / 4];
#endif
GobSort *pgsT = ags;
Gob **ppgobT = apgob;
int i;
for (i = 0; i < cpgob; i++, pgsT++) {
pgsT->pgob = *ppgobT++;
pgsT->key = pgsT->pgob->GetSortKey();
}
// Sort (insertion sort)
GobSort *pgs;
GobSort *pgsEnd = ags + cpgob;
for (pgs = ags + 1; pgs < pgsEnd; pgs++) {
for (GobSort *pgsT = pgs; pgsT > ags && (pgsT - 1)->key > pgsT->key; pgsT--) {
GobSort gsT = *pgsT;
*pgsT = *(pgsT - 1);
*(pgsT - 1) = gsT;
}
}
// Copy sorted results to passed-in buffer
pgsT = ags;
ppgobT = apgob;
for (i = 0; i < cpgob; i++, pgsT++)
*ppgobT++ = pgsT->pgob;
}
#if 0
// For reference:
/* qsort -- qsort interface implemented by faster quicksort.
J. L. Bentley and M. D. McIlroy, SPE 23 (1993) 1249-1265.
Copyright 1993, John Wiley.
*/
/*assume sizeof(long) is a power of 2 */
#define SWAPINIT(a, es) swaptype = \
(a-(char*)0 | es) % sizeof(long) ? 2 : es > sizeof(long);
#define swapcode(TYPE, parmi, parmj, n) { \
register TYPE *pi = (TYPE *) (parmi); \
register TYPE *pj = (TYPE *) (parmj); \
do { \
register TYPE t = *pi; \
*pi++ = *pj; \
*pj++ = t; \
} while ((n -= sizeof(TYPE)) > 0); \
}
#include <stddef.h>
static void swapfunc(char *a, char *b, size_t n, int swaptype)
{ if (swaptype <= 1) swapcode(long, a, b, n)
else swapcode(char, a, b, n)
}
#define swap(a, b) \
if (swaptype == 0) { \
t = *(long*)(a); \
*(long*)(a) = *(long*)(b); \
*(long*)(b) = t; \
} else \
swapfunc(a, b, es, swaptype)
#define PVINIT(pv, pm) \
if (swaptype != 0) { pv = a; swap(pv, pm); } \
else { pv = (char*)&v; *(long*)pv = *(long*)pm; }
#define vecswap(a, b, n) if (n > 0) swapfunc(a, b, n, swaptype)
static char *med3(char *a, char *b, char *c, int (*cmp)(const void *pElement1, const void *pElement2))
{ return cmp(a, b) < 0 ?
(cmp(b, c) < 0 ? b : cmp(a, c) < 0 ? c : a)
: (cmp(b, c) > 0 ? b : cmp(a, c) > 0 ? c : a);
}
void qsort(char *a, size_t n, size_t es, int (*cmp)(const void *pElement1, const void *pElement2))
{
char *pa, *pb, *pc, *pd, *pl, *pm, *pn, *pv;
int r, swaptype;
long t, v;
size_t s;
SWAPINIT(a, es);
if (n < 7) { /* Insertion sort on smallest arrays */
for (pm = a + es; pm < a + n*es; pm += es)
for (pl = pm; pl > a && cmp(pl-es, pl) > 0; pl -= es)
swap(pl, pl-es);
return;
}
pm = a + (n/2)*es; /* Small arrays, middle element */
if (n > 7) {
pl = a;
pn = a + (n-1)*es;
if (n > 40) { /* Big arrays, pseudomedian of 9 */
s = (n/8)*es;
pl = med3(pl, pl+s, pl+2*s, cmp);
pm = med3(pm-s, pm, pm+s, cmp);
pn = med3(pn-2*s, pn-s, pn, cmp);
}
pm = med3(pl, pm, pn, cmp); /* Mid-size, med of 3 */
}
PVINIT(pv, pm); /* pv points to partition value */
pa = pb = a;
pc = pd = a + (n-1)*es;
for (;;) {
while (pb <= pc && (r = cmp(pb, pv)) <= 0) {
if (r == 0) { swap(pa, pb); pa += es; }
pb += es;
}
while (pb <= pc && (r = cmp(pc, pv)) >= 0) {
if (r == 0) { swap(pc, pd); pd -= es; }
pc -= es;
}
if (pb > pc) break;
swap(pb, pc);
pb += es;
pc -= es;
}
pn = a + n*es;
s = _min(pa-a, pb-pa ); vecswap(a, pb-s, s);
s = _min(pd-pc, pn-pd-es); vecswap(pb, pn-s, s);
if ((s = pb-pa) > es) qsort(a, s/es, es, cmp);
if ((s = pd-pc) > es) qsort(pn-s, s/es, es, cmp);
}
#endif
const int kcpgobInRangeMax = 100;
// Called by GobMgr::FindEnemyWithinRange to locate Gobs within a certain tile radius
// of a Gob looking for a fight.
int GobMgr::FindGobs(const TRect *ptrcBounds, Gob **apgob, int cpgobMax)
{
TCoord txLeft, txRight, tyTop, tyBottom;
txLeft = ptrcBounds->left;
txRight = ptrcBounds->right;
tyTop = ptrcBounds->top;
tyBottom = ptrcBounds->bottom;
// Expand the cells examined to include any that may contain Gobs
// that intersect the specified bounding rectangle.
// This carries some assumptions about the maximum size of Gob
// clippinging rectangle and its relation to its origin.
// Maximum Gob clipping rectangle is:
// -2/+2 tiles wide from its origin
// -3/+1 tiles high from its origin
txLeft = _max(0, txLeft - 2);
txRight = _min(m_ctx - 1, txRight + 2);
tyTop = _max(0, tyTop - 3);
tyBottom = _min(m_cty - 1, tyBottom + 1);
int ctx = txRight - txLeft;
int cpgob = 0;
Gid *pgidLeft = m_pgidMap + (tyTop * m_ctx) + txLeft;
for (int ty = tyTop; ty <= tyBottom; ty++, pgidLeft += m_ctx) {
Gid *pgidRight = pgidLeft + ctx;
for (Gid *pgidT = pgidLeft; pgidT <= pgidRight; pgidT++) {
Gob *pgobT;
for (Gid gidT = *pgidT; (gidT & kwfGidEndMarker) == 0; gidT = pgobT->m_gidNext) {
pgobT = *PpgobFromGid(gidT);
// Callers only care about active Gobs
if ((pgobT->GetFlags() & kfGobActive) == 0)
continue;
TRect trcClip;
pgobT->GetTileRect(&trcClip);
if (trcClip.left >= ptrcBounds->right)
continue;
if (trcClip.right <= ptrcBounds->left)
continue;
if (trcClip.top >= ptrcBounds->bottom)
continue;
if (trcClip.bottom <= ptrcBounds->top)
continue;
apgob[cpgob++] = pgobT;
if (cpgob == cpgobMax)
goto lbFull;
}
}
}
lbFull:
return cpgob;
}
#if defined(DEBUG) && defined(WIN)
Gob *GobMgr::FindEnemyWithinRange(UnitGob *punt, TCoord tcRange, bool fStructures)
{
TRect trc;
punt->GetTileRect(&trc);
trc.left -= tcRange;
trc.right += tcRange;
trc.top -= tcRange;
trc.bottom += tcRange;
Gob *apgob[kcpgobInRangeMax];
int cpgob = FindGobs(&trc, apgob, sizeof(apgob));
Assert(cpgob <= kcpgobInRangeMax);
if (cpgob == 0)
return NULL;
Gob **ppgobT = apgob;
for (int i = 0; i < cpgob; i++, ppgobT++) {
Gob *pgobT = *ppgobT;
// Don't fire on non-military targets or allies
if (!punt->IsValidTarget(pgobT))
continue;
// or enemy structures
if (!fStructures && (pgobT->GetFlags() & kfGobStructure))
continue;
// NOTE: this doesn't return the NEAREST Gob, just the first
// found within range
return pgobT;
}
return NULL;
}
#endif
} // namespace wi