From 80b75a65a868c6586f08ca10fc3d4c4a49806237 Mon Sep 17 00:00:00 2001 From: NetsuNegi39 Date: Fri, 14 Aug 2026 12:29:45 +0800 Subject: [PATCH 1/2] impl --- src/Ext/Cell/Body.cpp | 1 + src/Ext/Cell/Body.h | 5 ++- src/Ext/Cell/Hooks.cpp | 94 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 2 deletions(-) diff --git a/src/Ext/Cell/Body.cpp b/src/Ext/Cell/Body.cpp index 6f418fb8fd..4705300665 100644 --- a/src/Ext/Cell/Body.cpp +++ b/src/Ext/Cell/Body.cpp @@ -13,6 +13,7 @@ void CellExt::Serialize(T& Stm) Stm .Process(this->RadSites) .Process(this->RadLevels) + .Process(this->CoveringLights) .Process(this->InfantryCount) ; } diff --git a/src/Ext/Cell/Body.h b/src/Ext/Cell/Body.h index 03055ff1a1..d990bcf87a 100644 --- a/src/Ext/Cell/Body.h +++ b/src/Ext/Cell/Body.h @@ -44,8 +44,9 @@ class CellExt final : public AbstractExt } std::vector RadSites {}; - std::vector RadLevels { }; - int InfantryCount{ 0 }; + std::vector RadLevels {}; + std::vector CoveringLights {}; + int InfantryCount { 0 }; CellExt(CellClass* OwnerObject) : AbstractExt(OwnerObject) { } diff --git a/src/Ext/Cell/Hooks.cpp b/src/Ext/Cell/Hooks.cpp index 3472a5f499..a676921e8b 100644 --- a/src/Ext/Cell/Hooks.cpp +++ b/src/Ext/Cell/Hooks.cpp @@ -9,3 +9,97 @@ DEFINE_HOOK(0x480EA8, CellClass_DamageWall_AdjacentWallDamage, 0x7) pThis->DamageWall(RulesExt::Global()->AdjacentWallDamage); return SkipGameCode; } + +#pragma region LightSource Optimize + +DEFINE_HOOK(0x5547C8, LightSourceClass_CTOR_SetInCells, 0x5) +{ + GET(LightSourceClass*, pThis, ESI); + + for (CellRangeEnumerator cell(CellClass::Coord2Cell(pThis->Location), pThis->LightVisibility / Unsorted::LeptonsPerCell + 0.5); cell; ++cell) + { + const auto pCell = MapClass::Instance.TryGetCellAt(*cell); + + if (!pCell) + continue; + + const auto pCellExt = CellExt::ExtMap.Find(pCell); + pCellExt->CoveringLights.emplace_back(pThis); + } + + return 0; +} + +DEFINE_HOOK(0x555176, LightSourceClass_DTOR_ResetInCells, 0x6) +{ + GET(LightSourceClass*, pThis, ESI); + + for (CellRangeEnumerator cell(CellClass::Coord2Cell(pThis->Location), pThis->LightVisibility / Unsorted::LeptonsPerCell + 0.5); cell; ++cell) + { + const auto pCell = MapClass::Instance.TryGetCellAt(*cell); + + if (!pCell) + continue; + + const auto pCellExt = CellExt::ExtMap.Find(pCell); + const auto it = std::ranges::find(pCellExt->CoveringLights, pThis); + + if (it != pCellExt->CoveringLights.cend()) + pCellExt->CoveringLights.erase(it); + } + + return 0; +} + +DEFINE_HOOK_AGAIN(0x48444C, CellClass_ProcessColourComponents_LightSourceCount, 0x6) +DEFINE_HOOK(0x48427D, CellClass_ProcessColourComponents_LightSourceCount, 0x6) +{ + GET(CellClass*, pThis, EDI); + const auto pExt = CellExt::Fetch(pThis); + + R->ECX(static_cast(pExt->CoveringLights.size())); + return R->Origin() + 0x6; +} + +DEFINE_HOOK(0x48428B, CellClass_ProcessColourComponents_LightSourceItem, 0x6) +{ + enum { ApplyItem = 0x484294 }; + + GET(CellClass*, pThis, EDI); + GET(int, idx, EAX); + const auto pExt = CellExt::Fetch(pThis); + + R->ESI(pExt->CoveringLights[idx]); + return ApplyItem; +} + +DEFINE_JUMP(LJMP, 0x4842DC, 0x4842E2) // Skip useless code + +DEFINE_HOOK(0x554BF6, LightSourceClass_554AF0_Distance_Optimize, 0x5) +{ + enum { InRange = 0x554C4B, OutOfRange = 0x554CE4 }; + + GET(LightSourceClass*, pThis, EDI); + REF_STACK(CellStruct, cell, STACK_OFFSET(0x30, -0x24)); + const auto cellCoords = CellClass::Cell2Coord(cell); + const int diffX = pThis->Location.X - cellCoords.X; + const int diffY = pThis->Location.Y - cellCoords.Y; + const double distanceSqr = static_cast(diffX) * diffX + static_cast(diffY) * diffY; + + if (static_cast(distanceSqr) > static_cast(pThis->LightVisibility) * pThis->LightVisibility) + return OutOfRange; + + if (const auto pCell = MapClass::Instance.TryGetCellAt(cell)) + { + pCell->MarkForRedraw(); + + if (const auto pBuilding = pCell->GetBuilding()) + pBuilding->MarkForRedraw(); + } + + return InRange; +} + +DEFINE_JUMP(LJMP, 0x554D0A, 0x554D16) // Skip GScreenClass::MarkNeedsRedraw(1); + +#pragma endregion From 1a7369457f58f3c98bf817bafb6659e480eaf78a Mon Sep 17 00:00:00 2001 From: NetsuNegi39 Date: Sat, 15 Aug 2026 15:18:15 +0800 Subject: [PATCH 2/2] fix with TerrainClass --- src/Ext/Cell/Body.cpp | 1 + src/Ext/Cell/Body.h | 1 + src/Ext/Cell/Hooks.cpp | 41 ++++++++++- src/Ext/TerrainType/Hooks.cpp | 128 ++++++++++++++++++++++++++++++++++ 4 files changed, 168 insertions(+), 3 deletions(-) diff --git a/src/Ext/Cell/Body.cpp b/src/Ext/Cell/Body.cpp index 4705300665..b15ec41418 100644 --- a/src/Ext/Cell/Body.cpp +++ b/src/Ext/Cell/Body.cpp @@ -14,6 +14,7 @@ void CellExt::Serialize(T& Stm) .Process(this->RadSites) .Process(this->RadLevels) .Process(this->CoveringLights) + .Process(this->CoveringTerrains) .Process(this->InfantryCount) ; } diff --git a/src/Ext/Cell/Body.h b/src/Ext/Cell/Body.h index d990bcf87a..b09f3e297d 100644 --- a/src/Ext/Cell/Body.h +++ b/src/Ext/Cell/Body.h @@ -46,6 +46,7 @@ class CellExt final : public AbstractExt std::vector RadSites {}; std::vector RadLevels {}; std::vector CoveringLights {}; + std::vector CoveringTerrains {}; int InfantryCount { 0 }; CellExt(CellClass* OwnerObject) : AbstractExt(OwnerObject) diff --git a/src/Ext/Cell/Hooks.cpp b/src/Ext/Cell/Hooks.cpp index a676921e8b..ace50eda3f 100644 --- a/src/Ext/Cell/Hooks.cpp +++ b/src/Ext/Cell/Hooks.cpp @@ -12,6 +12,11 @@ DEFINE_HOOK(0x480EA8, CellClass_DamageWall_AdjacentWallDamage, 0x7) #pragma region LightSource Optimize +namespace LightSourceTemp +{ + std::vector RedrawTerrains {}; +} + DEFINE_HOOK(0x5547C8, LightSourceClass_CTOR_SetInCells, 0x5) { GET(LightSourceClass*, pThis, ESI); @@ -23,7 +28,7 @@ DEFINE_HOOK(0x5547C8, LightSourceClass_CTOR_SetInCells, 0x5) if (!pCell) continue; - const auto pCellExt = CellExt::ExtMap.Find(pCell); + const auto pCellExt = CellExt::Fetch(pCell); pCellExt->CoveringLights.emplace_back(pThis); } @@ -41,7 +46,7 @@ DEFINE_HOOK(0x555176, LightSourceClass_DTOR_ResetInCells, 0x6) if (!pCell) continue; - const auto pCellExt = CellExt::ExtMap.Find(pCell); + const auto pCellExt = CellExt::Fetch(pCell); const auto it = std::ranges::find(pCellExt->CoveringLights, pThis); if (it != pCellExt->CoveringLights.cend()) @@ -95,11 +100,41 @@ DEFINE_HOOK(0x554BF6, LightSourceClass_554AF0_Distance_Optimize, 0x5) if (const auto pBuilding = pCell->GetBuilding()) pBuilding->MarkForRedraw(); + + auto& redrawTerrains = LightSourceTemp::RedrawTerrains; + const auto pCellExt = CellExt::Fetch(pCell); + + for (const auto pCoveringTerrain : pCellExt->CoveringTerrains) + { + if (std::ranges::find(redrawTerrains, pCoveringTerrain) != redrawTerrains.cend()) + continue; + + redrawTerrains.emplace_back(pCoveringTerrain); + } } return InRange; } -DEFINE_JUMP(LJMP, 0x554D0A, 0x554D16) // Skip GScreenClass::MarkNeedsRedraw(1); +DEFINE_HOOK(0x554D0A, LightSourceClass_554AF0_CellRedraw, 0x7) +{ + enum { SkipGScreenRedraw = 0x554D16 }; + + auto& redrawTerrains = LightSourceTemp::RedrawTerrains; + + if (!redrawTerrains.empty()) + { + for (const auto pTerrain : redrawTerrains) + { + RectangleStruct rect; + pTerrain->GetRenderDimensions(&rect); + TacticalClass::Instance->RegisterDirtyArea(rect, false); + } + + redrawTerrains.clear(); + } + + return SkipGScreenRedraw; +} #pragma endregion diff --git a/src/Ext/TerrainType/Hooks.cpp b/src/Ext/TerrainType/Hooks.cpp index 4412887e54..e897423a42 100644 --- a/src/Ext/TerrainType/Hooks.cpp +++ b/src/Ext/TerrainType/Hooks.cpp @@ -1,6 +1,7 @@ #include "Body.h" #include +#include namespace TerrainTypeTemp { @@ -344,3 +345,130 @@ DEFINE_HOOK(0x568432, MapClass_PlaceDown_0x0TerrainTypes, 0x8) return 0; } + +#pragma region LightSource Dirty + +static bool RectangleIntersectsDiamond(const RectangleStruct& rect, int cx, int cy, int rx, int ry) +{ + const int rl = rect.X; + const int rt = rect.Y; + const int rr = rect.X + rect.Width; + const int rb = rect.Y + rect.Height; + + if (rr < cx - rx || rl > cx + rx || rb < cy - ry || rt > cy + ry) + return false; + + auto InDiamond = [=](int x, int y) + { + return std::abs(static_cast(x - cx) / rx) + std::abs(static_cast(y - cy) / ry) <= 1.0; + }; + + auto InRect = [=](int x, int y) + { + return x >= rl && x <= rr && y >= rt && y <= rb; + }; + + if (InRect(cx, cy)) + return true; + + if (InDiamond(rl, rt) + || InDiamond(rr, rt) + || InDiamond(rl, rb) + || InDiamond(rr, rb)) + return true; + + if (InRect(cx - rx, cy) + || InRect(cx + rx, cy) + || InRect(cx, cy - ry) + || InRect(cx, cy + ry)) + return true; + + return false; +} + +static std::vector GetTerrainCoveredCells(TerrainClass* pThis) +{ + const auto baseCell = pThis->GetMapCoords(); + RectangleStruct rect; + pThis->GetRenderDimensions(&rect); + + const auto tacticalPos = TacticalClass::Instance->TacticalPos; + const auto anchor = TacticalClass::CoordsToScreen(pThis->GetCoords()) - tacticalPos; + + const int leftW = anchor.X - rect.X; + const int rightW = rect.X + rect.Width - anchor.X; + + if (rightW > leftW) + rect.Width = 2 * leftW; + + const int range = std::max(4, (rect.Height + 60) / 30); + const int rx = Unsorted::CellWidthInPixels / 2; + const int ry = Unsorted::CellHeightInPixels / 2; + std::vector result; + + for (int cy = baseCell.Y - range; cy <= baseCell.Y + range; ++cy) + { + for (int cx = baseCell.X - range; cx <= baseCell.X + range; ++cx) + { + if (cx + cy > baseCell.X + baseCell.Y) + continue; + + const CellStruct cell { static_cast(cx), static_cast(cy) }; + const auto pCell = MapClass::Instance.TryGetCellAt(cell); + + if (!pCell) + continue; + + const auto cellClient = TacticalClass::CoordsToScreen(pCell->GetCoords()) - tacticalPos; + + if (!RectangleIntersectsDiamond(rect, cellClient.X, cellClient.Y, rx, ry)) + continue; + + result.emplace_back(pCell); + } + } + + return result; +} + +DEFINE_HOOK(0x71D0E7, TerrainClass_Unlimbo_Covering, 0x6) +{ + enum { ReturnTrue = 0x71D132 }; + + GET_STACK(TerrainClass*, pThis, STACK_OFFSET(0x20, -0x10)); + GET(CellClass*, pCell, EAX); + const auto covering = GetTerrainCoveredCells(pThis); + + for (const auto pCovering : covering) + CellExt::Fetch(pCovering)->CoveringTerrains.emplace_back(pThis); + + const int overlayIdx = pCell->OverlayTypeIndex; + + if (overlayIdx != -1 && OverlayTypeClass::Array[overlayIdx]->Tiberium) + { + pCell->OverlayTypeIndex = -1; + pCell->OverlayData = 0; + } + + return ReturnTrue; +} + +DEFINE_HOOK(0x71CA1C, TerrainClass_Limbo_ResetCovering, 0x5) +{ + GET(TerrainClass*, pThis, EDI); + const auto covering = GetTerrainCoveredCells(pThis); + + for (const auto pCovering : covering) + { + auto& coveringTerrains = CellExt::Fetch(pCovering)->CoveringTerrains; + const auto it = std::ranges::find(coveringTerrains, pThis); + + if (it != coveringTerrains.cend()) + coveringTerrains.erase(it); + } + + return 0; +} + + +#pragma endregion