diff --git a/Phobos.vcxproj b/Phobos.vcxproj
index 4b0d943c6e..80e329e412 100644
--- a/Phobos.vcxproj
+++ b/Phobos.vcxproj
@@ -21,6 +21,7 @@
+
@@ -34,9 +35,12 @@
+
+
+
@@ -111,13 +115,16 @@
+
+
+
diff --git a/src/Ext/Cell/Body.cpp b/src/Ext/Cell/Body.cpp
new file mode 100644
index 0000000000..f691e7ee6f
--- /dev/null
+++ b/src/Ext/Cell/Body.cpp
@@ -0,0 +1,81 @@
+#include "Body.h"
+
+template<> const DWORD Extension::Canary = 0xFDC49191;
+CellExt::ExtContainer CellExt::ExtMap;
+
+// =============================
+// load / save
+
+template
+void CellExt::ExtData::Serialize(T& Stm)
+{
+ Stm
+ .Process(this->FoggedObjects)
+ ;
+}
+
+void CellExt::ExtData::LoadFromStream(PhobosStreamReader& Stm)
+{
+ Extension::LoadFromStream(Stm);
+ this->Serialize(Stm);
+}
+
+void CellExt::ExtData::SaveToStream(PhobosStreamWriter& Stm)
+{
+ Extension::SaveToStream(Stm);
+ this->Serialize(Stm);
+}
+
+
+// =============================
+// container
+
+CellExt::ExtContainer::ExtContainer() : Container("CellClass")
+{
+}
+
+CellExt::ExtContainer::~ExtContainer() = default;
+
+// =============================
+// container hooks
+
+DEFINE_HOOK(0x47BBF0, CellClass_CTOR, 0x6)
+{
+ GET(CellClass*, pItem, ECX);
+
+ CellExt::ExtMap.FindOrAllocate(pItem);
+
+ return 0;
+}
+
+DEFINE_HOOK(0x47BB60, CellClass_DTOR, 0x6)
+{
+ GET(CellClass*, pItem, ECX);
+
+ CellExt::ExtMap.Remove(pItem);
+
+ return 0;
+}
+
+DEFINE_HOOK_AGAIN(0x483C10, CellClass_SaveLoad_Prefix, 0x5)
+DEFINE_HOOK(0x4839F0, CellClass_SaveLoad_Prefix, 0x7)
+{
+ GET_STACK(CellClass*, pItem, 0x4);
+ GET_STACK(IStream*, pStm, 0x8);
+
+ CellExt::ExtMap.PrepareStream(pItem, pStm);
+
+ return 0;
+}
+
+DEFINE_HOOK(0x483C00, CellClass_Load_Suffix, 5)
+{
+ CellExt::ExtMap.LoadStatic();
+ return 0;
+}
+
+DEFINE_HOOK(0x483C79, CellClass_Save_Suffix, 0x6)
+{
+ CellExt::ExtMap.SaveStatic();
+ return 0;
+}
\ No newline at end of file
diff --git a/src/Ext/Cell/Body.h b/src/Ext/Cell/Body.h
new file mode 100644
index 0000000000..9bb65438d8
--- /dev/null
+++ b/src/Ext/Cell/Body.h
@@ -0,0 +1,49 @@
+#pragma once
+#include
+
+#include
+
+#include
+#include
+
+#include
+
+class FoggedObject;
+
+class CellExt
+{
+public:
+ using base_type = CellClass;
+
+ class ExtData final : public Extension
+ {
+ public:
+ DynamicVectorClass FoggedObjects;
+
+ ExtData(CellClass* OwnerObject) : Extension(OwnerObject),
+ FoggedObjects {}
+ { }
+
+ virtual ~ExtData() = default;
+
+ // virtual void LoadFromINIFile(CCINIClass * pINI) override;
+
+ virtual void InvalidatePointer(void* ptr, bool bRemoved) override { }
+
+ virtual void LoadFromStream(PhobosStreamReader& Stm) override;
+
+ virtual void SaveToStream(PhobosStreamWriter& Stm) override;
+ private:
+ template
+ void Serialize(T& Stm);
+ };
+
+ class ExtContainer final : public Container
+ {
+ public:
+ ExtContainer();
+ ~ExtContainer();
+ };
+
+ static ExtContainer ExtMap;
+};
\ No newline at end of file
diff --git a/src/Misc/FogOfWar.cpp b/src/Misc/FogOfWar.cpp
new file mode 100644
index 0000000000..f8a82373a2
--- /dev/null
+++ b/src/Misc/FogOfWar.cpp
@@ -0,0 +1,523 @@
+#include
+
+#include
+
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+DEFINE_HOOK(0x6B8E7A, ScenarioClass_LoadSpecialFlags, 0x5)
+{
+ ScenarioClass::Instance->SpecialFlags.FogOfWar =
+ RulesClass::Instance->FogOfWar || R->EAX() || GameModeOptionsClass::Instance->FogOfWar;
+
+ R->ECX(R->EDI());
+ return 0x6B8E8B;
+}
+
+DEFINE_HOOK(0x686C03, SetScenarioFlags_FogOfWar, 0x5)
+{
+ GET(ScenarioFlags, SFlags, EAX);
+
+ SFlags.FogOfWar = RulesClass::Instance->FogOfWar || GameModeOptionsClass::Instance->FogOfWar;
+
+ R->EDX(*reinterpret_cast(&SFlags));
+ return 0x686C0E;
+}
+
+DEFINE_HOOK(0x5F4B3E, ObjectClass_DrawIfVisible, 0x6)
+{
+ GET(ObjectClass*, pThis, ESI);
+
+ if (pThis->InLimbo)
+ return 0x5F4B7F;
+
+ if (!ScenarioClass::Instance->SpecialFlags.FogOfWar)
+ return 0x5F4B48;
+
+ switch (pThis->WhatAmI())
+ {
+ case AbstractType::Anim:
+ if (!static_cast(pThis)->Type->ShouldFogRemove)
+ return 0x5F4B48;
+ break;
+
+ case AbstractType::Unit:
+ case AbstractType::Cell:
+ break;
+
+ default:
+ return 0x5F4B48;
+ }
+
+ if (!MapClass::Instance->IsLocationFogged(pThis->GetCoords()))
+ return 0x5F4B48;
+
+ pThis->NeedsRedraw = false;
+ return 0x5F4D06;
+}
+
+DEFINE_HOOK(0x6F5190, TechnoClass_DrawExtras_CheckFog, 0x6)
+{
+ GET(TechnoClass*, pThis, ECX);
+
+ return MapClass::Instance->IsLocationFogged(pThis->GetCoords()) ? 0x6F5EEC : 0;
+}
+
+DEFINE_HOOK(0x6D6EDA, TacticalClass_Overlay_CheckFog1, 0xA)
+{
+ GET(CellClass*, pCell, EAX);
+
+ return pCell->OverlayTypeIndex == -1 || pCell->IsFogged() ? 0x6D7006 : 0x6D6EE4;
+}
+
+DEFINE_HOOK(0x6D70BC, TacticalClass_Overlay_CheckFog2, 0xA)
+{
+ GET(CellClass*, pCell, EAX);
+
+ return pCell->OverlayTypeIndex == -1 || pCell->IsFogged() ? 0x6D71A4 : 0x6D70C6;
+}
+
+DEFINE_HOOK(0x71CC8C, TerrainClass_DrawIfVisible, 0x6)
+{
+ GET(TerrainClass*, pThis, EDI);
+ pThis->NeedsRedraw = false;
+
+ return pThis->InLimbo || MapClass::Instance->IsLocationFogged(pThis->GetCoords()) ? 0x71CD8D : 0x71CC9A;
+}
+
+DEFINE_HOOK(0x4804A4, CellClass_DrawTile_DrawSmudgeIfVisible, 0x6)
+{
+ GET(CellClass*, pThis, ESI);
+
+ return pThis->IsFogged() ? 0x4804FB : 0;
+}
+
+DEFINE_HOOK(0x5865E2, MapClass_IsLocationFogged, 0x3)
+{
+ GET_STACK(CoordStruct*, pCoord, 0x4);
+
+ MapRevealer revealer(*pCoord);
+ auto pCell = MapClass::Instance->GetCellAt(revealer.Base());
+
+ R->EAX(pCell->Flags & CellFlags::EdgeRevealed ?
+ false :
+ !(pCell->GetNeighbourCell(FACING_SE)->Flags & CellFlags::EdgeRevealed));
+
+ return 0;
+}
+
+DEFINE_HOOK(0x6D7A4F, TacticalClass_DrawPixelEffects_FullFogged, 0x6)
+{
+ GET(CellClass*, pCell, ESI);
+
+ return pCell->IsFogged() ? 0x6D7BB8 : 0;
+}
+
+DEFINE_HOOK(0x4ACE3C, MapClass_TryReshroudCell_SetCopyFlag, 0x6)
+{
+ GET(CellClass*, pCell, EAX);
+
+ bool bNoFog = static_cast(pCell->AltFlags & AltCellFlags::NoFog);
+ pCell->AltFlags &= ~AltCellFlags::NoFog;
+ auto Index = TacticalClass::Instance->GetOcclusion(pCell->MapCoords, false);
+
+ if ((bNoFog || pCell->Visibility != Index) && Index >= 0 && pCell->Visibility >= -1)
+ {
+ pCell->AltFlags |= AltCellFlags::Mapped;
+ pCell->Visibility = Index;
+ }
+
+ TacticalClass::Instance->RegisterCellAsVisible(pCell);
+
+ return 0x4ACE57;
+}
+
+DEFINE_HOOK(0x4A9CA0, MapClass_RevealFogShroud, 0x8)
+{
+ GET(MapClass*, pThis, ECX);
+ GET_STACK(CellStruct*, pMapCoords, 0x4);
+ GET_STACK(HouseClass*, pHouse, 0x8);
+ GET_STACK(bool, bIncreaseShroudCounter, 0xC);
+
+ auto const pCell = pThis->GetCellAt(*pMapCoords);
+ bool bRevealed = false;
+ bool const bShouldCleanFog = !(pCell->Flags & CellFlags::EdgeRevealed);
+
+ if (bShouldCleanFog || !(pCell->AltFlags & AltCellFlags::Mapped))
+ bRevealed = true;
+
+ bool const bWasRevealed = bRevealed;
+
+ pCell->Flags = pCell->Flags & ~CellFlags::IsPlot | CellFlags::EdgeRevealed;
+ pCell->AltFlags = pCell->AltFlags & ~AltCellFlags::NoFog | AltCellFlags::Mapped;
+
+ if (bIncreaseShroudCounter)
+ pCell->IncreaseShroudCounter();
+ else
+ pCell->ReduceShroudCounter();
+
+ char Visibility = TacticalClass::Instance->GetOcclusion(*pMapCoords, false);
+ if (pCell->Visibility != Visibility)
+ {
+ bRevealed = true;
+ pCell->Visibility = Visibility;
+ }
+ if (pCell->Visibility == -1)
+ pCell->AltFlags |= AltCellFlags::NoFog;
+
+ char Foggness = TacticalClass::Instance->GetOcclusion(*pMapCoords, true);
+ if (pCell->Foggedness != Foggness)
+ {
+ bRevealed = true;
+ pCell->Foggedness = Foggness;
+ }
+ if (pCell->Foggedness == -1)
+ pCell->Flags |= CellFlags::CenterRevealed;
+
+ if (bRevealed)
+ {
+ TacticalClass::Instance->RegisterCellAsVisible(pCell);
+ pThis->RevealCheck(pCell, pHouse, bWasRevealed);
+ }
+
+ if (bShouldCleanFog)
+ pCell->CleanFog();
+
+ R->EAX(bRevealed);
+
+ return 0x4A9DC6;
+}
+
+// CellClass_CleanFog
+DEFINE_HOOK(0x486C50, CellClass_ClearFoggedObjects, 0x6)
+{
+ GET(CellClass*, pThis, ECX);
+
+ auto pExt = CellExt::ExtMap.Find(pThis);
+ for (auto const pObject : pExt->FoggedObjects)
+ {
+ if (pObject->CoveredType == FoggedObject::CoveredType::Building)
+ {
+ auto pRealCell = MapClass::Instance->GetCellAt(pObject->Location);
+
+ for (auto pFoundation = pObject->BuildingData.Type->GetFoundationData(false);
+ pFoundation->X != 0x7FFF || pFoundation->Y != 0x7FFF;
+ ++pFoundation)
+ {
+ CellStruct mapCoord =
+ {
+ pRealCell->MapCoords.X + pFoundation->X,
+ pRealCell->MapCoords.Y + pFoundation->Y
+ };
+
+ auto pCell = MapClass::Instance->GetCellAt(mapCoord);
+ if (pCell != pThis)
+ CellExt::ExtMap.Find(pCell)->FoggedObjects.Remove(pObject);
+ }
+
+ }
+ GameDelete(pObject);
+ }
+ pExt->FoggedObjects.Clear();
+
+ return 0x486D8A;
+}
+
+DEFINE_HOOK(0x486A70, CellClass_FogCell, 0x5)
+{
+ GET(CellClass*, pThis, ECX);
+
+ if (ScenarioClass::Instance->SpecialFlags.FogOfWar)
+ {
+ auto location = pThis->MapCoords;
+ for (int i = 1; i < 15; i += 2)
+ {
+ auto pCell = MapClass::Instance->GetCellAt(location);
+ auto nLevel = pCell->Level;
+
+ if (nLevel >= i - 2 && nLevel <= i)
+ {
+ if (!(pCell->Flags & CellFlags::Fogged))
+ {
+ pCell->Flags |= CellFlags::Fogged;
+
+ for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject)
+ {
+ switch (pObject->WhatAmI())
+ {
+ case AbstractType::Unit:
+ case AbstractType::Infantry:
+ case AbstractType::Aircraft:
+ pObject->Deselect();
+ break;
+
+ case AbstractType::Building:
+ if (auto pBld = abstract_cast(pObject))
+ {
+ if (pBld->IsAllFogged())
+ pBld->FreezeInFog(nullptr, pCell, !pBld->IsStrange() && pBld->Translucency != 15);
+ }
+ break;
+
+ case AbstractType::Terrain:
+ if (auto pTerrain = abstract_cast(pObject))
+ {
+ // pTerrain
+ auto pFoggedTer = GameCreate(pTerrain);
+ CellExt::ExtMap.Find(pCell)->FoggedObjects.AddItem(pFoggedTer);
+ }
+ break;
+
+ default:
+ continue;
+ }
+ }
+ if (pCell->OverlayTypeIndex != -1)
+ {
+ auto pFoggedOvl = GameCreate(pCell, true);
+ CellExt::ExtMap.Find(pCell)->FoggedObjects.AddItem(pFoggedOvl);
+ }
+ if (pCell->SmudgeTypeIndex != -1)
+ {
+ auto pFoggedSmu = GameCreate(pCell, false);
+ CellExt::ExtMap.Find(pCell)->FoggedObjects.AddItem(pFoggedSmu);
+ }
+ }
+ }
+
+ ++location.X;
+ ++location.Y;
+ }
+ }
+
+ return 0x486BE6;
+}
+
+DEFINE_HOOK(0x457AA0, BuildingClass_FreezeInFog, 0x5)
+{
+ GET(BuildingClass*, pThis, ECX);
+ GET_STACK(CellClass*, pCell, 0x8);
+ GET_STACK(bool, IsVisible, 0xC);
+
+ if (!pCell)
+ pCell = pThis->GetCell();
+
+ pThis->Deselect();
+ auto pFoggedBld = GameCreate(pThis, IsVisible);
+ CellExt::ExtMap.Find(pCell)->FoggedObjects.AddItem(pFoggedBld);
+
+ auto MapCoords = pThis->GetMapCoords();
+
+ for (auto pFoundation = pThis->Type->GetFoundationData(false);
+ pFoundation->X != 0x7FFF || pFoundation->Y != 0x7FFF;
+ ++pFoundation)
+ {
+ CellStruct currentMapCoord { MapCoords.X + pFoundation->X,MapCoords.Y + pFoundation->Y };
+ auto pCurrentCell = MapClass::Instance->GetCellAt(currentMapCoord);
+ if (pCurrentCell != pCell)
+ CellExt::ExtMap.Find(pCurrentCell)->FoggedObjects.AddItem(pFoggedBld);
+ }
+
+ return 0x457C80;
+}
+
+// 486C50 = CellClass_ClearFoggedObjects, 6 Dumplcate
+
+DEFINE_HOOK(0x70076E, TechnoClass_GetCursorOverCell_OverFog, 0x5)
+{
+ GET(CellClass*, pCell, EBP);
+ auto const pExt = CellExt::ExtMap.Find(pCell);
+
+ int nOvlIdx = -1;
+ for (auto const pObject : pExt->FoggedObjects)
+ {
+ if (pObject->Visible)
+ {
+ if (pObject->CoveredType == FoggedObject::CoveredType::Overlay)
+ nOvlIdx = pObject->OverlayData.Overlay;
+ else if (pObject->CoveredType == FoggedObject::CoveredType::Building)
+ {
+ if (HouseClass::Player->IsAlliedWith(pObject->BuildingData.Owner) && pObject->BuildingData.Type->LegalTarget)
+ R->Stack(STACK_OFFS(0x2C, 0x19), true);
+ }
+ }
+ }
+
+ if (nOvlIdx != -1)
+ R->Stack(STACK_OFFS(0x2C, 0x18), OverlayTypeClass::Array->GetItem(nOvlIdx));
+
+ return 0x700815;
+}
+
+DEFINE_HOOK(0x51F95F, InfantryClass_GetCursorOverCell_OverFog, 0x6)
+{
+ GET(InfantryClass*, pThis, EDI);
+ GET(CellClass*, pCell, EAX);
+ GET_STACK(const bool, bFog, STACK_OFFS(0x1C, -0x8));
+
+ BuildingTypeClass* pType = nullptr;
+ int ret = 0x51FA93;
+
+ do
+ {
+ if (!ScenarioClass::Instance->SpecialFlags.FogOfWar || !bFog)
+ break;
+
+ for (const auto pObject : CellExt::ExtMap.Find(pCell)->FoggedObjects)
+ {
+ if (pObject->Visible && pObject->CoveredType == FoggedObject::CoveredType::Building)
+ {
+ pType = pObject->BuildingData.Type;
+
+ if (pThis->Type->Engineer && pThis->Owner->ControlledByPlayer())
+ {
+ if (pType->BridgeRepairHut)
+ {
+ R->EAX(&pObject->Location);
+ ret = 0x51FA35;
+ }
+ else // Ares hook 51FA82 = InfantryClass_GetActionOnCell_EngineerRepairable, 6
+ ret = 0x51FA82;
+ }
+ break;
+ }
+ }
+ }
+ while (false);
+
+ R->EBP(pType);
+ return ret;
+}
+
+DEFINE_HOOK(0x6D3470, TacticalClass_DrawFoggedObject, 0x8)
+{
+ GET(TacticalClass*, pThis, ECX);
+ GET_STACK(RectangleStruct*, pRect1, 0x4);
+ GET_STACK(RectangleStruct*, pRect2, 0x8);
+ GET_STACK(bool, bForceViewBounds, 0xC);
+
+ RectangleStruct finalRect { 0,0,0,0 };
+ if (bForceViewBounds && DSurface::ViewBounds->Width > 0 && DSurface::ViewBounds->Height > 0)
+ finalRect = std::move(FoggedObject::Union(finalRect, DSurface::ViewBounds));
+ else
+ {
+ if (pRect1->Width > 0 && pRect1->Height > 0)
+ finalRect = std::move(FoggedObject::Union(finalRect, *pRect1));
+ if (pRect2->Width > 0 && pRect2->Height > 0)
+ finalRect = std::move(FoggedObject::Union(finalRect, *pRect2));
+
+ if (const auto nVisibleCellCount = pThis->VisibleCellCount)
+ {
+ RectangleStruct buffer;
+ buffer.Width = buffer.Height = 60;
+
+ for (int i = 0; i < nVisibleCellCount; ++i)
+ {
+ auto const pCell = pThis->VisibleCells[i];
+ auto location = pCell->GetCoords();
+ Point2D point;
+ TacticalClass::Instance->CoordsToClient(location, &point);
+ buffer.X = DSurface::ViewBounds->X + point.X - 30;
+ buffer.Y = DSurface::ViewBounds->Y + point.Y;
+ finalRect = std::move(FoggedObject::Union(finalRect, buffer));
+ }
+ }
+
+ for (const auto& dirty : Drawing::DirtyAreas())
+ {
+ RectangleStruct buffer = dirty.Rect;
+ buffer.Y += DSurface::ViewBounds->Y;
+ if (buffer.Width > 0 && buffer.Height > 0)
+ finalRect = std::move(FoggedObject::Union(finalRect, buffer));
+ }
+ }
+
+ if (finalRect.Width > 0 && finalRect.Height > 0)
+ {
+ finalRect.X = std::max(finalRect.X, 0);
+ finalRect.Y = std::max(finalRect.Y, 0);
+ finalRect.Width = std::min(finalRect.Width, DSurface::ViewBounds->Width - finalRect.X);
+ finalRect.Height = std::min(finalRect.Height, DSurface::ViewBounds->Height - finalRect.Y);
+
+ for (const auto pObject : FoggedObject::FoggedObjects)
+ pObject->Render(finalRect);
+ }
+
+ return 0x6D3650;
+}
+
+DEFINE_HOOK(0x4ADFF0, MapClass_RevealMapShroud, 0x5)
+{
+ GET_STACK(bool, bHideBuilding, 0x4);
+ GET_STACK(bool, bFog, 0x8);
+
+ for (auto const pTechno : *TechnoClass::Array)
+ {
+ if (pTechno->WhatAmI() != AbstractType::Building || !bHideBuilding)
+ {
+ if (pTechno->GetTechnoType()->RevealToAll ||
+ pTechno->DiscoveredByPlayer && pTechno->Owner->ControlledByPlayer() ||
+ RulesClass::Instance->AllyReveal && pTechno->Owner->IsAlliedWith(HouseClass::Player))
+ {
+ pTechno->See(0, bFog);
+ if (pTechno->IsInAir())
+ MapClass::Instance->RevealArea3(&pTechno->Location,
+ pTechno->LastSightRange - 3, pTechno->LastSightRange + 3, false);
+ }
+ }
+ }
+
+ return 0x4AE0A5;
+}
+
+DEFINE_HOOK(0x577EBF, MapClass_Reveal, 0x6)
+{
+ GET(CellClass*, pCell, EAX);
+
+ // Vanilla YR code
+ pCell->ShroudCounter = 0;
+ pCell->GapsCoveringThisCell = 0;
+ pCell->AltFlags |= AltCellFlags::Clear;
+ pCell->Flags |= CellFlags::Revealed;
+
+ // Extra process
+ pCell->CleanFog();
+
+ return 0x577EE9;
+}
+
+DEFINE_HOOK(0x586683, CellClass_DiscoverTechno, 0x5)
+{
+ GET(TechnoClass*, pTechno, EAX);
+ GET(CellClass*, pThis, ESI);
+ GET_STACK(HouseClass*, pHouse, STACK_OFFS(0x18, -0x8));
+
+ if (pTechno)
+ pTechno->DiscoveredBy(pHouse);
+ if (pThis->Jumpjet)
+ pThis->Jumpjet->DiscoveredBy(pHouse);
+
+ // EAX seems not to be used actually, so we needn't to set EAX here
+
+ return 0x586696;
+}
+
+DEFINE_HOOK(0x4FC1FF, HouseClass_PlayerDefeated_MapReveal, 0x6)
+{
+ GET(HouseClass*, pHouse, ESI);
+
+ *(int*)0xA8B538 = 1;
+ MapClass::Instance->Reveal(pHouse);
+
+ return 0x4FC214;
+}
\ No newline at end of file
diff --git a/src/Misc/MapRevealer.cpp b/src/Misc/MapRevealer.cpp
new file mode 100644
index 0000000000..41f8c352b7
--- /dev/null
+++ b/src/Misc/MapRevealer.cpp
@@ -0,0 +1,193 @@
+#include "MapRevealer.h"
+
+#include
+#include
+
+#include
+
+MapRevealer::MapRevealer(const CoordStruct& coords) :
+ BaseCell(this->TranslateBaseCell(coords)),
+ CellOffset(this->GetOffset(coords, this->Base())),
+ RequiredChecks(RequiresExtraChecks())
+{
+ auto const& Rect = MapClass::Instance->MapRect;
+ this->MapWidth = Rect.Width;
+ this->MapHeight = Rect.Height;
+
+ this->CheckedCells[0] = { 7, static_cast(this->MapWidth + 5) };
+ this->CheckedCells[1] = { 13, static_cast(this->MapWidth + 11) };
+ this->CheckedCells[2] = { static_cast(this->MapHeight + 13),
+ static_cast(this->MapHeight + this->MapWidth - 15) };
+}
+
+MapRevealer::MapRevealer(const CellStruct& cell) :
+ MapRevealer(MapClass::Instance->GetCellAt(cell)->GetCoordsWithBridge())
+{
+}
+
+template
+void MapRevealer::RevealImpl(const CoordStruct& coords, int const radius, HouseClass* const pHouse, bool const onlyOutline, bool const allowRevealByHeight, T func) const
+{
+ auto const level = coords.Z / *reinterpret_cast(0xABDE88);
+ auto const& base = this->Base();
+
+ if (this->AffectsHouse(pHouse) && this->IsCellAvailable(base) && radius > 0)
+ {
+ auto const spread = std::min(static_cast(radius), CellSpreadEnumerator::Max);
+ auto const spread_limit_sqr = (spread + 1) * (spread + 1);
+
+ auto const start = (!RulesClass::Instance->RevealByHeight && onlyOutline && spread > 2)
+ ? spread - 3 : 0u;
+
+ auto const checkLevel = allowRevealByHeight && RulesClass::Instance->RevealByHeight;
+
+ for (CellSpreadEnumerator it(spread, start); it; ++it)
+ {
+ auto const& offset = *it;
+ auto const cell = base + offset;
+
+ if (this->IsCellAvailable(cell))
+ {
+ if (std::abs(offset.X) <= static_cast(spread) && offset.MagnitudeSquared() < spread_limit_sqr)
+ {
+ if (!checkLevel || this->CheckLevel(offset, level))
+ {
+ auto pCell = MapClass::Instance->GetCellAt(cell);
+ func(pCell);
+ }
+ }
+ }
+ }
+ }
+};
+
+void MapRevealer::Reveal0(const CoordStruct& coords, int const radius, HouseClass* const pHouse, bool onlyOutline, bool unknown, bool fog, bool allowRevealByHeight, bool add) const
+{
+ this->RevealImpl(coords, radius, pHouse, onlyOutline, allowRevealByHeight, [=](CellClass* const pCell)
+ {
+ this->Process0(pCell, unknown, fog, add);
+ });
+}
+
+void MapRevealer::Reveal1(const CoordStruct& coords, int const radius, HouseClass* const pHouse, bool onlyOutline, bool fog, bool allowRevealByHeight, bool add) const
+{
+ this->RevealImpl(coords, radius, pHouse, onlyOutline, allowRevealByHeight, [=](CellClass* const pCell)
+ {
+ this->Process1(pCell, fog, add);
+ });
+}
+
+void MapRevealer::UpdateShroud(size_t start, size_t radius, bool fog) const
+{
+ if (!fog)
+ {
+ auto const& base = this->Base();
+ radius = std::min(radius, CellSpreadEnumerator::Max);
+ start = std::min(start, CellSpreadEnumerator::Max - 3);
+
+ for (CellSpreadEnumerator it(radius, start); it; ++it)
+ {
+ auto const& offset = *it;
+ auto const cell = base + offset;
+ auto const pCell = MapClass::Instance->GetCellAt(cell);
+
+ bool bFlag = false;
+ if (pCell->Visibility != 0xFF)
+ {
+ auto shroudOcculusion = TacticalClass::Instance->GetOcclusion(cell, false);
+ if (pCell->Visibility != shroudOcculusion)
+ {
+ pCell->Visibility = shroudOcculusion;
+ pCell->VisibilityChanged = true;
+ bFlag = true;
+ }
+ }
+
+ if (!ScenarioClass::Instance->SpecialFlags.FogOfWar && !bFlag)
+ {
+ continue;
+ }
+
+ if (pCell->Foggedness != 0xFF)
+ {
+ auto foggedOcclusion = TacticalClass::Instance->GetOcclusion(cell, true);
+ if (pCell->Foggedness != foggedOcclusion)
+ {
+ pCell->Foggedness = foggedOcclusion;
+ bFlag = true;
+ }
+ }
+
+ if (bFlag)
+ {
+ TacticalClass::Instance->RegisterCellAsVisible(pCell);
+ }
+
+ /*auto shroudOcclusion = TacticalClass::Instance->GetOcclusion(cell, false);
+ if (pCell->Visibility != shroudOcclusion) {
+ pCell->Visibility = shroudOcclusion;
+ pCell->VisibilityChanged = true;
+ TacticalClass::Instance->RegisterCellAsVisible(pCell);
+ }*/
+ }
+ }
+}
+
+void MapRevealer::Process0(CellClass* const pCell, bool unknown, bool fog, bool add) const
+{
+ pCell->Flags &= ~CellFlags::IsPlot;
+
+ if (this->IsCellAllowed(pCell->MapCoords))
+ {
+ if (fog)
+ {
+ if ((pCell->Flags & CellFlags::Revealed) != CellFlags::Revealed && pCell->AltFlags & AltCellFlags::Mapped)
+ {
+ MouseClass::Instance->MapCellFoggedness(&pCell->MapCoords, HouseClass::Player);
+ }
+ }
+ else
+ {
+ if ((pCell->AltFlags & AltCellFlags::Clear) != AltCellFlags::Clear || (pCell->Flags & CellFlags::Revealed) != CellFlags::Revealed)
+ {
+ if (!unknown)
+ {
+ if (add)
+ {
+ MouseClass::Instance->RevealFogShroud(&pCell->MapCoords, HouseClass::Player, false);
+ }
+ else
+ {
+ pCell->Unshroud();
+ }
+ }
+ }
+ }
+ }
+}
+
+void MapRevealer::Process1(CellClass* const pCell, bool fog, bool add) const
+{
+ pCell->Flags &= ~CellFlags::IsPlot;
+
+ if (fog)
+ {
+ if ((pCell->Flags & CellFlags::Revealed) != CellFlags::Revealed && pCell->AltFlags & AltCellFlags::Mapped)
+ {
+ MouseClass::Instance->MapCellFoggedness(&pCell->MapCoords, HouseClass::Player);
+ }
+ }
+ else
+ {
+ if (this->IsCellAllowed(pCell->MapCoords))
+ {
+ MouseClass::Instance->RevealFogShroud(&pCell->MapCoords, HouseClass::Player, add);
+ }
+ }
+}
+
+DEFINE_POINTER_LJMP(0x5673A0, MapRevealer::MapClass_RevealArea0);
+
+DEFINE_POINTER_LJMP(0x5678E0, MapRevealer::MapClass_RevealArea1);
+
+DEFINE_POINTER_LJMP(0x567DA0, MapRevealer::MapClass_RevealArea2);
\ No newline at end of file
diff --git a/src/Misc/MapRevealer.h b/src/Misc/MapRevealer.h
new file mode 100644
index 0000000000..6799d790a0
--- /dev/null
+++ b/src/Misc/MapRevealer.h
@@ -0,0 +1,140 @@
+#pragma once
+
+#include
+#include
+#include
+
+class MapRevealer
+{
+public:
+ MapRevealer(const CoordStruct& coords);
+
+ MapRevealer(const CellStruct& cell);
+
+ const CellStruct& Base() const
+ {
+ return this->BaseCell;
+ }
+
+ void Reveal0(const CoordStruct& coords, int const radius, HouseClass* const pHouse, bool onlyOutline, bool unknown, bool fog, bool allowRevealByHeight, bool add) const;
+
+ void Reveal1(const CoordStruct& coords, int const radius, HouseClass* const pHouse, bool onlyOutline, bool fog, bool allowRevealByHeight, bool add) const;
+
+ void UpdateShroud(size_t start, size_t radius, bool fog = false) const;
+
+ void Process0(CellClass* const pCell, bool unknown, bool fog, bool add) const;
+
+ void Process1(CellClass* const pCell, bool fog, bool add) const;
+
+ bool IsCellAllowed(const CellStruct& cell) const
+ {
+ if (this->RequiredChecks)
+ {
+ for (const auto& checkedCell : CheckedCells)
+ {
+ if (checkedCell == cell)
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ bool IsCellAvailable(const CellStruct& cell) const
+ {
+ auto const sum = cell.X + cell.Y;
+
+ return sum > this->MapWidth
+ && cell.X - cell.Y < this->MapWidth
+ && cell.Y - cell.X < this->MapWidth
+ && sum <= this->MapWidth + 2 * this->MapHeight;
+ }
+
+ bool CheckLevel(const CellStruct& offset, int level) const
+ {
+ auto const cellLevel = this->Base() + offset + GetRelation(offset) - this->CellOffset;
+ return MapClass::Instance->GetCellAt(cellLevel)->Level < level + CellClass::BridgeLevels;
+ }
+
+ static bool AffectsHouse(HouseClass* const pHouse)
+ {
+ auto& Player = HouseClass::Player;
+
+ if (pHouse == Player)
+ {
+ return true;
+ }
+
+ if (!pHouse || !Player)
+ {
+ return false;
+ }
+
+ return pHouse->RadarVisibleTo.Contains(Player) ||
+ (RulesClass::Instance->AllyReveal && pHouse->IsAlliedWith(Player));
+ }
+
+ static bool RequiresExtraChecks()
+ {
+ auto& Session = SessionClass::Instance;
+ return Helpers::Alex::is_any_of(Session->GameMode, GameMode::LAN, GameMode::Internet) &&
+ Session->MPGameMode && !Session->MPGameMode->vt_entry_04();
+ }
+
+ static CellStruct GetRelation(const CellStruct& offset)
+ {
+ return{ static_cast(Math::sgn(-offset.X)),
+ static_cast(Math::sgn(-offset.Y)) };
+ }
+
+private:
+ CellStruct TranslateBaseCell(const CoordStruct& coords) const
+ {
+ auto const adjust = (TacticalClass::AdjustForZ(coords.Z) / -30) << 8;
+ auto const baseCoords = coords + CoordStruct { adjust, adjust, 0 };
+ return CellClass::Coord2Cell(baseCoords);
+ }
+
+ CellStruct GetOffset(const CoordStruct& coords, const CellStruct& base) const
+ {
+ return base - CellClass::Coord2Cell(coords) - CellStruct { 2, 2 };
+ }
+
+ template
+ void RevealImpl(const CoordStruct& coords, int radius, HouseClass* pHouse, bool onlyOutline, bool allowRevealByHeight, T func) const;
+
+ CellStruct BaseCell;
+ CellStruct CellOffset;
+ CellStruct CheckedCells[3];
+ bool RequiredChecks;
+ int MapWidth;
+ int MapHeight;
+
+public:
+ // Reveal_Area
+ static void __fastcall MapClass_RevealArea0(MapClass* pThis, void*, CoordStruct* pCoord,
+ int nRadius, HouseClass* pHouse, int bOutlineOnly, bool bNoShroudUpdate, bool bFog,
+ bool bAllowRevealByHeight, bool bHideOnRadar)
+ {
+ MapRevealer const revealer(*pCoord);
+ revealer.Reveal0(*pCoord, nRadius, pHouse, bOutlineOnly, bNoShroudUpdate, bFog, bAllowRevealByHeight, bHideOnRadar);
+ revealer.UpdateShroud(0, static_cast(std::max(nRadius, 0)), false);
+ }
+
+ // Sight_From
+ static void __fastcall MapClass_RevealArea1(MapClass* pThis, void*, CoordStruct* pCoord,
+ int nRadius, HouseClass* pHouse, int bOutlineOnly, bool bNoShroudUpdate, bool bFog,
+ bool bAllowRevealByHeight, bool bIncreaseShroudCounter)
+ {
+ MapRevealer const revealer(*pCoord);
+ revealer.Reveal1(*pCoord, nRadius, pHouse, bOutlineOnly, bFog, bAllowRevealByHeight, bIncreaseShroudCounter);
+ }
+
+ static void __fastcall MapClass_RevealArea2(MapClass* pThis, void*,
+ CoordStruct* Coords, int Height, int Radius, bool bSkipReveal)
+ {
+ MapRevealer const revealer(*Coords);
+ revealer.UpdateShroud(static_cast(std::max(Height, 0)), static_cast(std::max(Radius, 0)), bSkipReveal);
+ }
+};
diff --git a/src/New/Entity/FoggedObject.cpp b/src/New/Entity/FoggedObject.cpp
new file mode 100644
index 0000000000..8f0aa505a8
--- /dev/null
+++ b/src/New/Entity/FoggedObject.cpp
@@ -0,0 +1,548 @@
+#include "FoggedObject.h"
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+DynamicVectorClass FoggedObject::FoggedObjects;
+char FoggedObject::BuildingVXLDrawer[sizeof(BuildingClass)];
+
+void FoggedObject::SaveGlobal(IStream* pStm)
+{
+ pStm->Write(&FoggedObjects.Count, sizeof(FoggedObjects.Count), nullptr);
+ for (auto const pObject : FoggedObjects)
+ {
+ pStm->Write(&pObject, sizeof(pObject), nullptr);
+ pObject->Save(pStm);
+ }
+
+ pStm->Write(BuildingVXLDrawer, sizeof(BuildingVXLDrawer), nullptr);
+}
+
+void FoggedObject::LoadGlobal(IStream* pStm)
+{
+ int nCount;
+ pStm->Read(&nCount, sizeof(nCount), nullptr);
+ while (nCount--)
+ {
+ auto pObject = GameCreate();
+ long pOldObject;
+ pStm->Read(&pOldObject, sizeof(pOldObject), nullptr);
+ SwizzleManagerClass::Instance->Here_I_Am(pOldObject, pObject);
+ pObject->Load(pStm);
+ }
+
+ pStm->Read(BuildingVXLDrawer, sizeof(BuildingVXLDrawer), nullptr);
+}
+
+FoggedObject::FoggedObject() noexcept
+{
+ FoggedObjects.AddItem(this);
+}
+
+FoggedObject::FoggedObject(BuildingClass* pBld, bool IsVisible) noexcept
+{
+ CoveredType = CoveredType::Building;
+
+ Location = pBld->Location;
+ Visible = IsVisible;
+ BuildingData.Owner = pBld->Owner;
+ BuildingData.Type = pBld->Type;
+ BuildingData.ShapeFrame = pBld->GetShapeNumber();
+ BuildingData.PrimaryFacing = pBld->PrimaryFacing;
+ BuildingData.BarrelFacing = pBld->BarrelFacing;
+ BuildingData.TurretRecoil = pBld->TurretRecoil;
+ BuildingData.BarrelRecoil = pBld->BarrelRecoil;
+ BuildingData.IsFirestormWall = pBld->Type->FirestormWall;
+ BuildingData.TurretAnimFrame = pBld->TurretAnimFrame;
+ pBld->GetRenderDimensions(&Bound);
+
+ memset(BuildingData.Anims, 0, sizeof(BuildingData.Anims));
+ auto pAnimData = BuildingData.Anims;
+ for (auto pAnim : pBld->Anims)
+ {
+ if (pAnim)
+ {
+ pAnimData->AnimType = pAnim->Type;
+ pAnimData->AnimFrame = pAnim->Animation.Value + pAnimData->AnimType->Start;
+ pAnimData->ZAdjust = pAnim->ZAdjust + pAnimData->AnimType->YDrawOffset - TacticalClass::AdjustForZ(pAnim->Location.Z);
+ pAnimData->ZAdjust -= pAnimData->AnimType->Flat ? 3 : 2;
+ ++pAnimData;
+
+ pAnim->IsFogged = true;
+ RectangleStruct buffer;
+ pAnim->GetDimensions(&buffer);
+ Bound = std::move(Drawing::Union(Bound, buffer));
+ }
+ }
+
+ TacticalClass::Instance->RegisterDirtyArea(Bound, false);
+ Bound.X += TacticalClass::Instance->TacticalPos.X;
+ Bound.Y += TacticalClass::Instance->TacticalPos.Y;
+ pBld->IsFogged = true;
+
+ if (!BuildingVXLDrawer[0] && pBld->Type->TurretAnimIsVoxel || pBld->Type->BarrelAnimIsVoxel)
+ {
+ memcpy(BuildingVXLDrawer, pBld, sizeof(BuildingClass));
+ reinterpret_cast(BuildingVXLDrawer)->BeingWarpedOut = false;
+ reinterpret_cast(BuildingVXLDrawer)->WarpFactor = 0.0f;
+ }
+
+ FoggedObjects.AddItem(this);
+}
+
+FoggedObject::FoggedObject(TerrainClass* pTerrain) noexcept
+{
+ Location = pTerrain->Location;
+ CoveredType = CoveredType::Terrain;
+
+ pTerrain->GetRenderDimensions(&Bound);
+ Bound.X += TacticalClass::Instance->TacticalPos.X - DSurface::ViewBounds->X;
+ Bound.Y += TacticalClass::Instance->TacticalPos.Y - DSurface::ViewBounds->Y;
+
+ TerrainData.Type = pTerrain->Type;
+ TerrainData.Frame = 0;
+ if (TerrainData.Type->IsAnimated)
+ TerrainData.Frame = pTerrain->Animation.Value;
+ else if (pTerrain->TimeToDie)
+ TerrainData.Frame = pTerrain->Animation.Value + 1;
+ else if (pTerrain->Health < 2)
+ TerrainData.Frame = 2;
+
+ TerrainData.Flat = TerrainData.Type->IsAnimated || pTerrain->TimeToDie;
+
+ FoggedObjects.AddItem(this);
+}
+
+FoggedObject::FoggedObject(CellClass* pCell, bool IsOverlay) noexcept
+{
+ pCell->GetCoords(&Location);
+ if (IsOverlay)
+ {
+ CoveredType = CoveredType::Overlay;
+
+ RectangleStruct containingRect;
+ RectangleStruct shapeRect;
+ pCell->ShapeRect(&shapeRect);
+ pCell->GetContainingRect(&containingRect);
+ Bound = std::move(Drawing::Union(shapeRect, containingRect));
+ Bound.X += TacticalClass::Instance->TacticalPos.X;
+ Bound.Y += TacticalClass::Instance->TacticalPos.Y;
+
+ OverlayData.Overlay = pCell->OverlayTypeIndex;
+ OverlayData.OverlayData = pCell->OverlayData;
+ }
+ else
+ {
+ CoveredType = CoveredType::Smudge;
+
+ Location.Z = pCell->Level * Unsorted::LevelHeight;
+ Point2D position;
+ TacticalClass::Instance->CoordsToClient(Location, &position);
+
+ Bound = RectangleStruct
+ {
+ position.X - 30 + TacticalClass::Instance->TacticalPos.X,
+ position.Y - 15 + TacticalClass::Instance->TacticalPos.Y,
+ 60,
+ 30
+ };
+
+ SmudgeData.Smudge = pCell->SmudgeTypeIndex;
+ SmudgeData.SmudgeData = pCell->SmudgeData;
+ SmudgeData.Height = Location.Z;
+ }
+
+ FoggedObjects.AddItem(this);
+}
+
+void FoggedObject::Load(IStream* pStm)
+{
+ pStm->Read(this, sizeof(FoggedObject), nullptr);\
+
+ if (CoveredType == CoveredType::Building)
+ {
+ SWIZZLE(BuildingData.Owner);
+ SWIZZLE(BuildingData.Type);
+ for (auto& Anim : BuildingData.Anims)
+ {
+ if (!Anim.AnimType)
+ break;
+ SWIZZLE(Anim.AnimType);
+ }
+ }
+ else if (CoveredType == CoveredType::Terrain)
+ {
+ SWIZZLE(TerrainData.Type);
+ }
+}
+
+void FoggedObject::Save(IStream* pStm)
+{
+ pStm->Write(this, sizeof(FoggedObject), nullptr);
+}
+
+FoggedObject::~FoggedObject()
+{
+ FoggedObjects.Remove(this);
+
+ if (this->CoveredType == CoveredType::Building)
+ {
+ auto pCell = MapClass::Instance->GetCellAt(Location);
+ if (auto pBld = pCell->GetBuilding())
+ {
+ pBld->IsFogged = false;
+ for (auto pAnim : pBld->Anims)
+ if (pAnim)
+ pAnim->IsFogged = false;
+ }
+ }
+}
+
+void FoggedObject::Render(const RectangleStruct& viewRect) const
+{
+ if (!Visible)
+ return;
+
+ RectangleStruct buffer = Bound;
+ buffer.X += DSurface::ViewBounds->X - TacticalClass::Instance->TacticalPos.X;
+ buffer.Y += DSurface::ViewBounds->Y - TacticalClass::Instance->TacticalPos.Y;
+ RectangleStruct finalRect = Drawing::Intersect(buffer, viewRect);
+ if (finalRect.Width <= 0 || finalRect.Height <= 0)
+ return;
+
+ switch (CoveredType)
+ {
+ case CoveredType::Building:
+ RenderAsBuilding(viewRect);
+ break;
+
+ case CoveredType::Overlay:
+ RenderAsOverlay(viewRect);
+ break;
+
+ case CoveredType::Terrain:
+ RenderAsTerrain(viewRect);
+ break;
+
+ case CoveredType::Smudge:
+ RenderAsSmudge(viewRect);
+ break;
+ }
+}
+
+RectangleStruct FoggedObject::Union(const RectangleStruct& rect1, const RectangleStruct& rect2)
+{
+ if (rect1.Width <= 0 || rect1.Height <= 0)
+ return rect2;
+ if (rect2.Width <= 0 || rect2.Height <= 0)
+ return rect1;
+
+ return
+ {
+ std::min(rect1.X,rect2.X),
+ std::min(rect1.Y,rect2.Y),
+ std::max(rect1.X + rect1.Width,rect2.X + rect2.Width),
+ std::max(rect1.Y + rect1.Height,rect2.Y + rect2.Height)
+ };
+}
+
+int FoggedObject::GetIndexID() const
+{
+ int x = Location.X / 256;
+ int y = Location.Y / 256;
+ return (y - ((x + y) << 9) - x) - static_cast(CoveredType) * 0x80000 + INT_MAX;
+}
+
+void FoggedObject::RenderAsBuilding(const RectangleStruct& viewRect) const
+{
+ auto const pType = BuildingData.Type;
+ if (pType->InvisibleInGame)
+ return;
+
+ auto pScheme = ColorScheme::Array->GetItem(BuildingData.Owner->ColorSchemeIndex);
+ auto pSHP = pType->GetImage();
+ CoordStruct coord = { Location.X - 128,Location.Y - 128,Location.Z };
+ Point2D point;
+ TacticalClass::Instance->CoordsToClient(coord, &point);
+ point.X += DSurface::ViewBounds->X - viewRect.X;
+ point.Y += DSurface::ViewBounds->Y - viewRect.Y;
+
+ auto pCell = MapClass::Instance->GetCellAt(Location);
+ ConvertClass* pConvert;
+ if (!pType->TerrainPalette)
+ pConvert = pScheme->LightConvert;
+ else
+ {
+ if (!pCell->LightConvert)
+ pCell->InitLightConvert();
+ pConvert = pCell->LightConvert;
+ }
+ if (pType->Palette)
+ pConvert = pType->Palette->GetItem(BuildingData.Owner->ColorSchemeIndex)->LightConvert;
+
+ if (pSHP)
+ {
+ RectangleStruct rect = viewRect;
+ int height = point.Y + pSHP->Height / 2;
+ if (rect.Height > height)
+ rect.Height = height;
+ int ZAdjust = -2 - TacticalClass::AdjustForZ(Location.Z);
+ int Intensity = pCell->Intensity_Normal + pType->ExtraLight;
+ if (rect.Height > 0)
+ {
+ if (BuildingData.IsFirestormWall)
+ {
+ CC_Draw_Shape(DSurface::Temp, pConvert, pSHP, BuildingData.ShapeFrame, &point, &rect,
+ BlitterFlags::ZReadWrite | BlitterFlags::Alpha | BlitterFlags::bf_400 | BlitterFlags::Centered,
+ 0, ZAdjust, ZGradient::Ground, Intensity, 0, nullptr, 0, 0, 0);
+ }
+ else
+ {
+ CC_Draw_Shape(DSurface::Temp, pConvert, pSHP, BuildingData.ShapeFrame, &point, &rect,
+ BlitterFlags::ZReadWrite | BlitterFlags::Alpha | BlitterFlags::bf_400 | BlitterFlags::Centered,
+ 0, ZAdjust, ZGradient::Deg90, Intensity, 0, nullptr, 0, 0, 0);
+ CC_Draw_Shape(DSurface::Temp, pConvert, pSHP, BuildingData.ShapeFrame + pSHP->Frames / 2, &point, &rect,
+ BlitterFlags::ZReadWrite | BlitterFlags::Alpha | BlitterFlags::bf_400 | BlitterFlags::Centered | BlitterFlags::Darken,
+ 0, ZAdjust, ZGradient::Ground, 1000, 0, nullptr, 0, 0, 0);
+ if (pType->BibShape)
+ {
+ CC_Draw_Shape(DSurface::Temp, pConvert, pType->BibShape, BuildingData.ShapeFrame, &point, &viewRect,
+ BlitterFlags::ZReadWrite | BlitterFlags::Alpha | BlitterFlags::bf_400 | BlitterFlags::Centered,
+ 0, ZAdjust - 1, ZGradient::Deg90, Intensity, 0, nullptr, 0, 0, 0);
+ }
+ }
+ }
+ Point2D turretPoint
+ {
+ point.X + pType->GetBuildingAnim(BuildingAnimSlot::Turret).Position.X,
+ point.Y + pType->GetBuildingAnim(BuildingAnimSlot::Turret).Position.Y
+ };
+ if (pType->TurretAnimIsVoxel || pType->BarrelAnimIsVoxel)
+ {
+ auto pVXLDrawer = reinterpret_cast(BuildingVXLDrawer);
+ pVXLDrawer->Type = pType;
+ pVXLDrawer->SecondaryFacing = BuildingData.PrimaryFacing;
+ pVXLDrawer->BarrelFacing = BuildingData.BarrelFacing;
+ pVXLDrawer->TurretRecoil = BuildingData.TurretRecoil;
+ pVXLDrawer->BarrelRecoil = BuildingData.BarrelRecoil;
+ pVXLDrawer->Owner = BuildingData.Owner;
+ pVXLDrawer->Location = Location;
+ pVXLDrawer->TurretAnimFrame = BuildingData.TurretAnimFrame;
+
+ auto const primaryDir = BuildingData.PrimaryFacing.current();
+ int turretFacing = 0;
+ int barrelFacing = 0;
+ if (pType->TurretVoxel.HVA)
+ turretFacing = BuildingData.TurretAnimFrame % pType->TurretVoxel.HVA->FrameCount;
+ if (pType->BarrelVoxel.HVA)
+ barrelFacing = BuildingData.TurretAnimFrame % pType->BarrelVoxel.HVA->FrameCount;
+ int val32 = primaryDir.value32();
+ int turretExtra = ((unsigned char)turretFacing << 16) | val32;
+ int barrelExtra = ((unsigned char)barrelFacing << 16) | val32;
+
+ if (pType->TurretVoxel.VXL)
+ {
+ Matrix3D matrixturret;
+ matrixturret.MakeIdentity();
+ matrixturret.RotateZ(static_cast(primaryDir.get_radian()));
+ TechnoTypeExt::ApplyTurretOffset(pType, &matrixturret, 0.125);
+
+ Vector3D negativevector = { -matrixturret.Row[0].W ,-matrixturret.Row[1].W,-matrixturret.Row[2].W };
+ Vector3D vector = { matrixturret.Row[0].W ,matrixturret.Row[1].W,matrixturret.Row[2].W };
+ Matrix3D matrixbarrel = matrixturret;
+ if (BuildingData.TurretRecoil.State != RecoilData::RecoilState::Inactive)
+ {
+ matrixturret.TranslateX(-BuildingData.TurretRecoil.TravelSoFar);
+ turretExtra = -1;
+ }
+ Matrix3D::MatrixMultiply(&matrixturret, &Matrix3D::VoxelDefaultMatrix, &matrixturret);
+
+ bool bDrawBarrel = pType->BarrelVoxel.VXL && pType->BarrelVoxel.HVA;
+ if (bDrawBarrel)
+ {
+ matrixbarrel.Translate(negativevector);
+ if (BuildingData.BarrelRecoil.State != RecoilData::RecoilState::Inactive)
+ {
+ matrixbarrel.TranslateX(-BuildingData.BarrelRecoil.TravelSoFar);
+ barrelExtra = -1;
+ }
+ matrixbarrel.RotateY(-static_cast(BuildingData.BarrelFacing.current().get_radian()));
+ matrixbarrel.Translate(vector);
+ Matrix3D::MatrixMultiply(&matrixbarrel, &Matrix3D::VoxelDefaultMatrix, &matrixbarrel);
+ }
+
+ int facetype = (((((*(unsigned int*)&primaryDir) >> 13) + 1) >> 1) & 3);
+ if (facetype == 0 || facetype == 3) // Draw barrel first
+ {
+ if (bDrawBarrel)
+ pVXLDrawer->DrawVoxel(BuildingData.Type->BarrelVoxel, 0, (short)turretExtra,
+ BuildingData.Type->VoxelCaches[3], viewRect, turretPoint, matrixbarrel,
+ pCell->Intensity_Normal, 0, 0);
+
+ pVXLDrawer->DrawVoxel(BuildingData.Type->TurretVoxel, turretFacing, (short)turretExtra,
+ BuildingData.Type->VoxelCaches[1], viewRect, turretPoint, matrixturret,
+ pCell->Intensity_Normal, 0, 0);
+ }
+ else
+ {
+ pVXLDrawer->DrawVoxel(BuildingData.Type->TurretVoxel, turretFacing, (short)turretExtra,
+ BuildingData.Type->VoxelCaches[1], viewRect, turretPoint, matrixturret,
+ pCell->Intensity_Normal, 0, 0);
+
+ if (bDrawBarrel)
+ pVXLDrawer->DrawVoxel(BuildingData.Type->BarrelVoxel, 0, (short)turretExtra,
+ BuildingData.Type->VoxelCaches[3], viewRect, turretPoint, matrixbarrel,
+ pCell->Intensity_Normal, 0, 0);
+ }
+ }
+ else if (pType->BarrelVoxel.VXL && pType->BarrelVoxel.HVA)
+ {
+ Matrix3D matrixbarrel;
+ matrixbarrel.MakeIdentity();
+ Vector3D negativevector = { -matrixbarrel.Row[0].W ,-matrixbarrel.Row[1].W,-matrixbarrel.Row[2].W };
+ Vector3D vector = { matrixbarrel.Row[0].W ,matrixbarrel.Row[1].W,matrixbarrel.Row[2].W };
+ matrixbarrel.Translate(negativevector);
+ matrixbarrel.RotateZ(static_cast(BuildingData.PrimaryFacing.current().get_radian()));
+ matrixbarrel.RotateY(-static_cast(BuildingData.BarrelFacing.current().get_radian()));
+ matrixbarrel.Translate(vector);
+ Matrix3D::MatrixMultiply(&matrixbarrel, &Matrix3D::VoxelDefaultMatrix, &matrixbarrel);
+ pVXLDrawer->DrawVoxel(BuildingData.Type->BarrelVoxel, barrelFacing, (short)barrelExtra,
+ BuildingData.Type->VoxelCaches[3], viewRect, turretPoint, matrixbarrel,
+ pCell->Intensity_Normal, 0, 0);
+ }
+ }
+ }
+
+ for (const auto& AnimData : BuildingData.Anims)
+ {
+ if (!AnimData.AnimType)
+ break;
+
+ auto pAnimType = AnimData.AnimType;
+ if (auto pAnimSHP = pAnimType->GetImage())
+ {
+ ConvertClass* pAnimConvert = pAnimType->ShouldUseCellDrawer ? pScheme->LightConvert : FileSystem::ANIM_PAL();
+
+ CC_Draw_Shape(DSurface::Temp, pAnimConvert, pAnimSHP, AnimData.AnimFrame, &point, &viewRect,
+ BlitterFlags::ZReadWrite | BlitterFlags::Alpha | BlitterFlags::bf_400 | BlitterFlags::Centered,
+ 0, AnimData.ZAdjust, pAnimType->Flat ? ZGradient::Ground : ZGradient::Deg90,
+ pAnimType->UseNormalLight ? 1000 : pCell->Intensity_Normal, 0, nullptr, 0, 0, 0);
+ if (pAnimType->Shadow)
+ {
+ CC_Draw_Shape(DSurface::Temp, pAnimConvert, pAnimSHP, AnimData.AnimFrame + pAnimSHP->Frames / 2, &point, &viewRect,
+ BlitterFlags::ZReadWrite | BlitterFlags::Alpha | BlitterFlags::bf_400 | BlitterFlags::Centered | BlitterFlags::Darken,
+ 0, AnimData.ZAdjust, ZGradient::Deg90, 1000, 0, nullptr, 0, 0, 0);
+ }
+ }
+ }
+}
+
+void FoggedObject::RenderAsSmudge(const RectangleStruct& viewRect) const
+{
+ auto const pSmudge = SmudgeTypeClass::Array->GetItem(SmudgeData.Smudge);
+ Point2D position
+ {
+ this->Bound.X - TacticalClass::Instance->TacticalPos.X - viewRect.X + DSurface::ViewBounds->X + 30,
+ this->Bound.Y - TacticalClass::Instance->TacticalPos.Y - viewRect.Y + DSurface::ViewBounds->Y
+ };
+ CellStruct MapCoord = CellClass::Coord2Cell(Location);
+ pSmudge->DrawIt(position, viewRect, SmudgeData.SmudgeData, SmudgeData.Height, MapCoord);
+}
+
+void FoggedObject::RenderAsOverlay(const RectangleStruct& viewRect) const
+{
+ if (auto pCell = MapClass::Instance->TryGetCellAt(Location))
+ {
+ CoordStruct coords =
+ {
+ (((pCell->MapCoords.X << 8) + 128) / 256) << 8,
+ (((pCell->MapCoords.Y << 8) + 128) / 256) << 8,
+ 0
+ };
+ Point2D position;
+ TacticalClass::Instance->CoordsToClient(coords, &position);
+ position.X -= 30;
+
+ std::swap(pCell->OverlayTypeIndex, const_cast(this)->OverlayData.Overlay);
+ std::swap(pCell->OverlayData, const_cast(this)->OverlayData.OverlayData);
+ pCell->DrawOverlay(position, viewRect);
+ pCell->DrawOverlayShadow(position, viewRect);
+
+ std::swap(pCell->OverlayTypeIndex, const_cast(this)->OverlayData.Overlay);
+ std::swap(pCell->OverlayData, const_cast(this)->OverlayData.OverlayData);
+ }
+}
+
+void FoggedObject::RenderAsTerrain(const RectangleStruct& viewRect) const
+{
+ auto pCell = MapClass::Instance->GetCellAt(Location);
+ if (auto pSHP = TerrainData.Type->GetImage())
+ {
+ int nZAdjust = -TacticalClass::AdjustForZ(Location.Z);
+ Point2D point;
+ TacticalClass::Instance->CoordsToClient(Location, &point);
+ point.X += DSurface::ViewBounds->X - viewRect.X;
+ point.Y += DSurface::ViewBounds->Y - viewRect.Y;
+ if (!pCell->LightConvert)
+ pCell->InitLightConvert();
+
+ BlitterFlags blitterFlag = BlitterFlags::Centered | BlitterFlags::bf_400 | BlitterFlags::Alpha;
+ if (TerrainData.Flat)
+ blitterFlag |= BlitterFlags::Flat;
+ else
+ blitterFlag |= BlitterFlags::ZReadWrite;
+
+ ConvertClass* pConvert;
+ int nIntensity;
+
+ if (TerrainData.Type->SpawnsTiberium)
+ {
+ pConvert = FileSystem::GRFTXT_TIBERIUM_PAL;
+ nIntensity = pCell->Intensity_Normal;
+ point.Y -= 16;
+ }
+ else
+ {
+ pConvert = pCell->LightConvert;
+ nIntensity = pCell->Intensity_Terrain;
+ }
+
+ CC_Draw_Shape(DSurface::Temp, pConvert, pSHP, TerrainData.Frame, &point,
+ &viewRect, blitterFlag, 0, nZAdjust - 12, ZGradient::Deg90, nIntensity,
+ 0, 0, 0, 0, 0);
+ if (Game::bDrawShadow)
+ CC_Draw_Shape(DSurface::Temp, pConvert, pSHP, TerrainData.Frame + pSHP->Frames / 2, &point,
+ &viewRect, blitterFlag | BlitterFlags::Darken, 0, nZAdjust - 3,
+ ZGradient::Ground, 1000, 0, 0, 0, 0, 0);
+ }
+}
+
+DEFINE_HOOK(0x67D32C, Put_All_FoggedObjects, 0x5)
+{
+ GET(IStream*, pStm, ESI);
+
+ FoggedObject::SaveGlobal(pStm);
+
+ return 0;
+}
+
+DEFINE_HOOK(0x67E826, Decode_All_FoggedObjects, 0x6)
+{
+ GET(IStream*, pStm, ESI);
+
+ FoggedObject::LoadGlobal(pStm);
+
+ return 0;
+}
+
+DEFINE_HOOK(0x685659, Clear_Scenario_FoggedObjects, 0xA)
+{
+ FoggedObject::FoggedObjects.Clear();
+
+ return 0;
+}
\ No newline at end of file
diff --git a/src/New/Entity/FoggedObject.h b/src/New/Entity/FoggedObject.h
new file mode 100644
index 0000000000..fd20029cfd
--- /dev/null
+++ b/src/New/Entity/FoggedObject.h
@@ -0,0 +1,95 @@
+#pragma once
+
+#include
+#include
+
+#include
+
+class TerrainClass;
+
+class FoggedObject
+{
+public:
+ static DynamicVectorClass FoggedObjects;
+
+ static void SaveGlobal(IStream* pStm);
+ static void LoadGlobal(IStream* pStm);
+
+ explicit FoggedObject() noexcept;
+ explicit FoggedObject(BuildingClass* pBld, bool IsVisible) noexcept;
+ explicit FoggedObject(TerrainClass* pTerrain) noexcept;
+
+ // Handles Smudge and Overlay Construct
+ explicit FoggedObject(CellClass* pCell, bool IsOverlay) noexcept;
+
+ void Load(IStream* pStm);
+ void Save(IStream* pStm);
+
+ //Destructor
+ virtual ~FoggedObject();
+
+ void Render(const RectangleStruct& viewRect) const;
+
+ static RectangleStruct Union(const RectangleStruct& rect1, const RectangleStruct& rect2);
+protected:
+ inline int GetIndexID() const;
+
+ void RenderAsBuilding(const RectangleStruct& viewRect) const;
+ void RenderAsSmudge(const RectangleStruct& viewRect) const;
+ void RenderAsOverlay(const RectangleStruct& viewRect) const;
+ void RenderAsTerrain(const RectangleStruct& viewRect) const;
+
+ static char BuildingVXLDrawer[sizeof(BuildingClass)];
+public:
+ enum class CoveredType : char
+ {
+ Building = 1,
+ Terrain,
+ Smudge,
+ Overlay
+ };
+
+ CoordStruct Location;
+ CoveredType CoveredType;
+ RectangleStruct Bound;
+ bool Visible { true };
+
+ union
+ {
+ struct
+ {
+ int Overlay;
+ unsigned char OverlayData;
+ } OverlayData;
+ struct
+ {
+ TerrainTypeClass* Type;
+ int Frame;
+ bool Flat;
+ } TerrainData;
+ struct
+ {
+ HouseClass* Owner;
+ BuildingTypeClass* Type;
+ int ShapeFrame;
+ FacingStruct PrimaryFacing;
+ FacingStruct BarrelFacing;
+ RecoilData TurretRecoil;
+ RecoilData BarrelRecoil;
+ bool IsFirestormWall;
+ int TurretAnimFrame;
+ struct
+ {
+ AnimTypeClass* AnimType;
+ int AnimFrame;
+ int ZAdjust;
+ } Anims[21];
+ } BuildingData;
+ struct
+ {
+ int Smudge;
+ int SmudgeData;
+ int Height;
+ } SmudgeData;
+ };
+};
\ No newline at end of file