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