#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 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