Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2152ca8
initial forward port https://github.com/Phobos-developers/Phobos/pul…
ayylmaoRotE Sep 18, 2025
10e3238
Fix: vision crash on SpySat=yes or high Reveal=x WH - still have issu…
ayylmaoRotE Sep 19, 2025
cd5a380
Fixed SpySat=yes / Reveal=-1
ayylmaoRotE Sep 19, 2025
0e3a19c
introduce RemoveShroudOnly
ayylmaoRotE Sep 19, 2025
54c95c0
First iteration of fixing fog items, lasers fixed
ayylmaoRotE Sep 19, 2025
103903e
Fixed NaturalParticleSystem from showing through fog
ayylmaoRotE Sep 20, 2025
51dcd16
Potential IsAlliedWith crash fix
ayylmaoRotE Sep 20, 2025
67ee743
Fixed Mouse pointer crash
ayylmaoRotE Sep 20, 2025
94c1f5a
Fix RefinerySmokeParticleSystem under fog, added debugging log
ayylmaoRotE Sep 21, 2025
5ff91b5
Improved fog particle hiding and add cursor safety
ayylmaoRotE Sep 21, 2025
830ecf6
Fix EBolt and LaserTrailClass from showing in fog
ayylmaoRotE Sep 21, 2025
fd39d7e
Fixed LaserDrawClass from showing under fog (includes DiskLaser), and…
ayylmaoRotE Sep 21, 2025
54d20a6
Fix RadBeam showing under fog
ayylmaoRotE Sep 22, 2025
076117c
fixed MindControl Link showing under fog
ayylmaoRotE Sep 23, 2025
6876f4f
Fix Phobos FlyingStrings from showing. also fix cursor crash round 2
ayylmaoRotE Sep 23, 2025
0fdd772
Implemented RemoveShroudGlobally to support FoW without SW hacks, Rem…
ayylmaoRotE Sep 23, 2025
0e70f90
Fixed issue with flickering/vanishing objects e.g. BuildingTypes, Ove…
ayylmaoRotE Sep 23, 2025
d324c83
Fog proxy system overhaul - comprehensive fixes for flickering, perfo…
ayylmaoRotE Sep 23, 2025
54f93dc
Fixed Ebolt, RadBeam and Shrapnel ownerless Ebolts
ayylmaoRotE Sep 24, 2025
c6c81cb
Removed RotE feats from project that were pulled by accident
ayylmaoRotE Sep 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Phobos.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,13 @@
<ClCompile Include="src\Misc\Hooks.PCX.cpp" />
<ClCompile Include="src\Misc\Hooks.UI.cpp" />
<ClCompile Include="src\Misc\Hooks.LaserDraw.cpp" />
<ClCompile Include="src\Misc\Hooks.RadBeam.cpp" />
<ClCompile Include="src\Misc\Hooks.Timers.cpp" />
<ClCompile Include="src\Misc\Hooks.INIInheritance.cpp" />
<ClCompile Include="src\Misc\Hooks.Overlay.cpp" />
<ClCompile Include="src\Misc\FogOfWar.cpp" />
<ClCompile Include="src\Misc\MapRevealer.cpp" />
<ClCompile Include="src\New\Entity\FoggedObject.cpp" />
<ClCompile Include="src\New\Type\Affiliated\TypeConvertGroup.cpp" />
<ClCompile Include="src\Ext\BuildingType\Hooks.Upgrade.cpp" />
<ClCompile Include="src\Phobos.COM.cpp" />
Expand Down Expand Up @@ -260,6 +264,9 @@
<ClInclude Include="src\Misc\FlyingStrings.h" />
<ClInclude Include="src\Misc\PhobosToolTip.h" />
<ClInclude Include="src\Misc\SyncLogging.h" />
<ClInclude Include="src\Misc\MapRevealer.h" />
<ClInclude Include="src\Misc\Hooks.RadBeam.h" />
<ClInclude Include="src\New\Entity\FoggedObject.h" />
<ClInclude Include="src\New\Entity\BannerClass.h" />
<ClInclude Include="src\New\Type\BannerTypeClass.h" />
<ClInclude Include="src\New\Type\Affiliated\TypeConvertGroup.h" />
Expand Down
14 changes: 13 additions & 1 deletion src/Ext/Building/Hooks.Refinery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,19 @@ DEFINE_HOOK(0x522E4F, InfantryClass_SlaveGiveMoney_CheckBalanceAfter, 0x6)
else if (auto const pBldTypeExt = BuildingTypeExt::ExtMap.TryFind(slaveMiner->GetTechnoType()->DeploysInto))
{
if (pBldTypeExt->DisplayIncome.Get(RulesExt::Global()->DisplayIncome.Get()))
FlyingStrings::AddMoneyString(money, slaveMiner->Owner, RulesExt::Global()->DisplayIncome_Houses.Get(), slaveMiner->Location);
{
// Only show flying strings if slave miner location is visible (not fogged)
if (auto const pCell = MapClass::Instance.TryGetCellAt(slaveMiner->Location))
{
if (!pCell->IsFogged() && !pCell->IsShrouded())
{
FlyingStrings::AddMoneyString(
money, slaveMiner->Owner,
RulesExt::Global()->DisplayIncome_Houses.Get(),
slaveMiner->Location);
}
}
}
}

return 0;
Expand Down
11 changes: 10 additions & 1 deletion src/Ext/Building/Hooks.Selling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,16 @@ DEFINE_HOOK(0x4D9F7B, FootClass_Sell, 0x6)
}

if (RulesExt::Global()->DisplayIncome.Get())
FlyingStrings::AddMoneyString(money, pOwner, RulesExt::Global()->DisplayIncome_Houses.Get(), pThis->Location);
{
// Only show flying strings if building location is visible (not fogged)
if (auto const pCell = MapClass::Instance.TryGetCellAt(pThis->Location))
{
if (!pCell->IsFogged() && !pCell->IsShrouded())
{
FlyingStrings::AddMoneyString(money, pOwner, RulesExt::Global()->DisplayIncome_Houses.Get(), pThis->Location);
}
}
}

return ReadyToVanish;
}
Expand Down
40 changes: 39 additions & 1 deletion src/Ext/Building/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,33 @@
#include <Ext/WarheadType/Body.h>
#include <TacticalClass.h>
#include <PlanningTokenClass.h>
#include <Misc/FogOfWar.h>
#include <algorithm> // swap-erase helper

// stable, single-pass erase of the first matching pointer; preserves order
template<typename T>
static __forceinline void stable_erase_first(std::vector<T*>& v, T* value)
{
for (size_t i = 0, n = v.size(); i < n; ++i)
{
if (v[i] == value) { v.erase(v.begin() + i); return; }
}
}

// 🔧 Optimized: Small deterministic swap-erase. Order is not semantically used for RestrictedFactoryPlants.
template<typename TCont, typename TValue>
__forceinline void swap_erase_first(TCont& v, const TValue& value)
{
for (size_t i = 0, n = v.size(); i < n; ++i)
{
if (v[i] == value)
{
if (i + 1 != n) { std::swap(v[i], v[n - 1]); }
v.pop_back();
return;
}
}
}

#pragma region Update

Expand Down Expand Up @@ -316,7 +343,18 @@ DEFINE_HOOK(0x440EBB, BuildingClass_Unlimbo_NaturalParticleSystem_CampaignSkip,
{
enum { DoNotCreateParticle = 0x440F61 };
GET(BuildingClass* const, pThis, ESI);
return BuildingExt::ExtMap.Find(pThis)->IsCreatedFromMapFile ? DoNotCreateParticle : 0;

// Skip for map-placed buildings
if (BuildingExt::ExtMap.Find(pThis)->IsCreatedFromMapFile) {
return DoNotCreateParticle;
}

// Skip for enemy buildings under fog to prevent information leaks
if (FoW::EnemyTechnoUnderFog(pThis)) {
return DoNotCreateParticle;
}

return 0;
}

DEFINE_HOOK(0x4519A2, BuildingClass_UpdateAnim_SetParentBuilding, 0x6)
Expand Down
93 changes: 90 additions & 3 deletions src/Ext/Bullet/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,16 +233,77 @@ inline void BulletExt::SimulatedFiringLaser(BulletClass* pBullet, HouseClass* pH
// Make sure pBullet and pBullet->WeaponType is not empty before call
inline void BulletExt::SimulatedFiringElectricBolt(BulletClass* pBullet)
{
// Debug logging for shrapnel EBolt creation
static int shrapnelCount = 0;
if (shrapnelCount++ < 5) {
Debug::Log("[FOW] SimulatedFiringElectricBolt called (shrapnel #%d)\n", shrapnelCount);
}

// Can not use 0x6FD460 because the firer may die
const auto pWeapon = pBullet->WeaponType;

if (!pWeapon->IsElectricBolt)
if (!pWeapon->IsElectricBolt) {
if (shrapnelCount <= 5) {
Debug::Log("[FOW] Not IsElectricBolt weapon, skipping\n");
}
return;
}

if (shrapnelCount <= 5) {
Debug::Log("[FOW] IsElectricBolt=true, proceeding with fog check\n");
}

const auto targetCoords = pBullet->Type->Inviso ? pBullet->Location : pBullet->TargetCoords;

// FOG CHECK: Only create EBolt if either source or target is visible
if (ScenarioClass::Instance && ScenarioClass::Instance->SpecialFlags.FogOfWar) {
if (shrapnelCount <= 5) {
Debug::Log("[FOW] Fog of war enabled, checking visibility\n");
}
if (HouseClass::CurrentPlayer && !HouseClass::CurrentPlayer->SpySatActive) {
// Check source location using cell-based fog (consistent with other EBolt fog checks)
bool sourceVisible = true;
bool targetVisible = true;

auto sourceCs = CellClass::Coord2Cell(pBullet->SourceCoords);
if (auto* sourceCell = MapClass::Instance.GetCellAt(sourceCs)) {
sourceVisible = !sourceCell->IsFogged();
}

auto targetCs = CellClass::Coord2Cell(targetCoords);
if (auto* targetCell = MapClass::Instance.GetCellAt(targetCs)) {
targetVisible = !targetCell->IsFogged();
}

if (shrapnelCount <= 5) {
Debug::Log("[FOW] Source visible: %d, Target visible: %d\n", sourceVisible ? 1 : 0, targetVisible ? 1 : 0);
}

// Only create EBolt if either endpoint is visible
if (!sourceVisible && !targetVisible) {
if (shrapnelCount <= 5) {
Debug::Log("[FOW] Both endpoints fogged, SKIPPING EBolt creation\n");
}
return; // Both fogged, skip creating EBolt entirely
} else {
if (shrapnelCount <= 5) {
Debug::Log("[FOW] At least one endpoint visible, creating EBolt\n");
}
}
} else {
if (shrapnelCount <= 5) {
Debug::Log("[FOW] SpySat active or no current player, creating EBolt\n");
}
}
} else {
if (shrapnelCount <= 5) {
Debug::Log("[FOW] No fog of war, creating EBolt\n");
}
}

const auto pBolt = EBoltExt::CreateEBolt(pWeapon);
pBolt->AlternateColor = pWeapon->IsAlternateColor;

const auto targetCoords = pBullet->Type->Inviso ? pBullet->Location : pBullet->TargetCoords;
pBolt->Fire(pBullet->SourceCoords, targetCoords, 0);

if (const auto particle = WeaponTypeExt::ExtMap.Find(pWeapon)->Bolt_ParticleSystem.Get(RulesClass::Instance->DefaultSparkSystem))
Expand All @@ -257,12 +318,38 @@ inline void BulletExt::SimulatedFiringRadBeam(BulletClass* pBullet, HouseClass*
if (!pWeapon->IsRadBeam)
return;

const auto targetCoords = pBullet->Type->Inviso ? pBullet->Location : pBullet->TargetCoords;

// FOG CHECK: Only create RadBeam if either source or target is visible
if (ScenarioClass::Instance && ScenarioClass::Instance->SpecialFlags.FogOfWar) {
if (HouseClass::CurrentPlayer && !HouseClass::CurrentPlayer->SpySatActive) {
// Check source location using cell-based fog (consistent with other fog checks)
bool sourceVisible = true;
bool targetVisible = true;

auto sourceCs = CellClass::Coord2Cell(pBullet->SourceCoords);
if (auto* sourceCell = MapClass::Instance.GetCellAt(sourceCs)) {
sourceVisible = !sourceCell->IsFogged();
}

auto targetCs = CellClass::Coord2Cell(targetCoords);
if (auto* targetCell = MapClass::Instance.GetCellAt(targetCs)) {
targetVisible = !targetCell->IsFogged();
}

// Only create RadBeam if either endpoint is visible
if (!sourceVisible && !targetVisible) {
return; // Both fogged, skip creating RadBeam entirely
}
}
}

const auto pWH = pWeapon->Warhead;
const bool isTemporal = pWH && pWH->Temporal;
const auto pRadBeam = RadBeam::Allocate(isTemporal ? RadBeamType::Temporal : RadBeamType::RadBeam);

pRadBeam->SetCoordsSource(pBullet->SourceCoords);
pRadBeam->SetCoordsTarget((pBullet->Type->Inviso ? pBullet->Location : pBullet->TargetCoords));
pRadBeam->SetCoordsTarget(targetCoords);

const auto pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon);

Expand Down
28 changes: 24 additions & 4 deletions src/Ext/CaptureManager/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,30 @@ DEFINE_HOOK(0x4721E6, CaptureManagerClass_DrawLinkToVictim, 0x6)

if (EnumFunctions::CanTargetHouse(pExt->MindControlLink_VisibleToHouse, pAttacker->Owner, HouseClass::CurrentPlayer))
{
auto nVictimCoord = pVictim->Location;
nVictimCoord.Z += pVictim->GetTechnoType()->LeptonMindControlOffset;
auto const nFLH = pAttacker->GetFLH(-1 - nNodeCount % 5, CoordStruct::Empty);
DrawALinkTo(nFLH, nVictimCoord, pAttacker->Owner->Color);
// Fog of war gating: only draw if both attacker and victim are visible
bool attackerVisible = true;
bool victimVisible = true;

if (ScenarioClass::Instance && ScenarioClass::Instance->SpecialFlags.FogOfWar &&
HouseClass::CurrentPlayer && !HouseClass::CurrentPlayer->SpySatActive)
{
auto attackerCell = CellClass::Coord2Cell(pAttacker->Location);
auto victimCell = CellClass::Coord2Cell(pVictim->Location);

if (auto* cell = MapClass::Instance.GetCellAt(attackerCell))
attackerVisible = !cell->IsFogged();

if (auto* cell = MapClass::Instance.GetCellAt(victimCell))
victimVisible = !cell->IsFogged();
}

if (attackerVisible && victimVisible)
{
auto nVictimCoord = pVictim->Location;
nVictimCoord.Z += pVictim->GetTechnoType()->LeptonMindControlOffset;
auto const nFLH = pAttacker->GetFLH(-1 - nNodeCount % 5, CoordStruct::Empty);
DrawALinkTo(nFLH, nVictimCoord, pAttacker->Owner->Color);
}
}

R->EBP(nNodeCount);
Expand Down
3 changes: 2 additions & 1 deletion src/Ext/Cell/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ void CellExt::ExtData::Serialize(T& Stm)
Stm
.Process(this->RadSites)
.Process(this->RadLevels)
.Process(this->FoggedObjects)
;
}

Expand Down Expand Up @@ -70,7 +71,7 @@ DEFINE_HOOK(0x47BDA1, CellClass_CTOR, 0x5)
{
GET(CellClass*, pItem, ESI);

CellExt::ExtMap.Allocate(pItem);
CellExt::ExtMap.FindOrAllocate(pItem);

return 0;
}
Expand Down
9 changes: 8 additions & 1 deletion src/Ext/Cell/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
#include <Utilities/Constructs.h>
#include <Utilities/Template.h>

#include <New/Entity/FoggedObject.h>

class FoggedObject;

class CellExt
{
public:
Expand Down Expand Up @@ -36,8 +40,11 @@ class CellExt
public:
std::vector<RadSiteClass*> RadSites {};
std::vector<RadLevel> RadLevels { };
DynamicVectorClass<FoggedObject*> FoggedObjects;
int InCleanFog { 0 };

ExtData(CellClass* OwnerObject) : Extension<CellClass>(OwnerObject)
ExtData(CellClass* OwnerObject) : Extension<CellClass>(OwnerObject),
FoggedObjects {}
{ }

virtual ~ExtData() = default;
Expand Down
Loading
Loading