mirror of
https://github.com/spiffcode/hostile-takeover.git
synced 2025-12-16 12:08:36 +00:00
2843 lines
70 KiB
C++
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
|