Skip to content

Commit 4f45f26

Browse files
authored
[Minor] Fix desync caused by units with DeploysInto (#1525)
Game removes deploying vehicles from map temporarily to check if there's enough space to deploy into a building when displaying allow/disallow deploy cursor. This can cause desyncs if there are certain types of units around the deploying unit because the OccupationFlags may be accidentally cleared, or the order of the objects linked list may be scrambled. About implementation: This is a hook separated from #1479 . This solution completely abandons the method of removing and then adding, and solves the problem by directly excluding specific units. Other related repairs: Fix unit's self-destruct when using trigger 107 to teleport the MCV. Fix reconnection error when moving MCV with Teleport locomotion.
1 parent 67c83bd commit 4f45f26

File tree

7 files changed

+132
-100
lines changed

7 files changed

+132
-100
lines changed

CREDITS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,8 @@ This page lists all the individual contributions to the project by their author.
434434
- Fix an issue that units on the slope tilted at an excessive angle
435435
- Fix an issue that the first passenger who call the transport ship no longer board the transport ship when the land units call for boarding
436436
- Fix an issue that impassable invisible barrier generated by the behavior of infantry continuously entering vehicles
437+
- Fix an issue that MCV will self-destruct when using trigger 107 to teleport
438+
- Fix an issue that moving MCV with Teleport locomotion will cause reconnection error
437439
- **Ollerus**:
438440
- Build limit group enhancement
439441
- Customizable rocker amplitude

docs/Fixed-or-Improved-Logics.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ This page describes all ingame logics that are fixed or improved in Phobos witho
213213
- Fixed an issue that units on the slope tilted at an excessive angle.
214214
- Fixed an issue that impassable invisible barrier generated by the behavior of infantry continuously entering vehicles.
215215
- Fixed an issue that teleport units board transport vehicles on the bridge will create an impassable invisible barrier, which may cause the game to freeze or even crash.
216+
- Fixed an issue that MCV will self-destruct when using trigger 107 to teleport
217+
- Fixed an issue that moving MCV with Teleport locomotion will cause reconnection error
216218

217219
## Fixes / interactions with other extensions
218220

docs/Whats-New.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,10 @@ Vanilla fixes:
371371
- Fixed an issue that the first passenger who call the transport ship no longer board the transport ship when the land units call for boarding (by CrimRecya)
372372
- Fixed an issue that impassable invisible barrier generated by the behavior of infantry continuously entering vehicles (by CrimRecya)
373373
- Fixed an issue that teleport units board transport vehicles on the bridge will create an impassable invisible barrier, which may cause the game to freeze or even crash (by NetsuNegi)
374+
- Fixed an issue that moving MCV with Teleport locomotion will cause reconnection error (by CrimRecya)
375+
376+
Phobos fixes:
377+
- Fixed an issue that MCV will self-destruct when using trigger 107 to teleport (by CrimRecya)
374378
375379
Fixes / interactions with other extensions:
376380
- Allowed `AuxBuilding` and Ares' `SW.Aux/NegBuildings` to count building upgrades (by Ollerus)

src/Ext/Techno/Body.cpp

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <Utilities/AresFunctions.h>
1313

1414
TechnoExt::ExtContainer TechnoExt::ExtMap;
15+
UnitClass* TechnoExt::Deployer = nullptr;
1516

1617
TechnoExt::ExtData::~ExtData()
1718
{
@@ -400,21 +401,16 @@ bool TechnoExt::CanDeployIntoBuilding(UnitClass* pThis, bool noDeploysIntoDefaul
400401
if (!pDeployType)
401402
return noDeploysIntoDefaultValue;
402403

403-
bool canDeploy = true;
404404
auto mapCoords = CellClass::Coord2Cell(pThis->GetCoords());
405405

406406
if (pDeployType->GetFoundationWidth() > 2 || pDeployType->GetFoundationHeight(false) > 2)
407407
mapCoords += CellStruct { -1, -1 };
408408

409-
pThis->Mark(MarkType::Up);
410-
411-
pThis->Locomotor->Mark_All_Occupation_Bits(MarkType::Up);
412-
413-
if (!pDeployType->CanCreateHere(mapCoords, pThis->Owner))
414-
canDeploy = false;
415-
416-
pThis->Locomotor->Mark_All_Occupation_Bits(MarkType::Down);
417-
pThis->Mark(MarkType::Down);
409+
// The vanilla game used an inappropriate approach here, resulting in potential risk of desync.
410+
// Now, through additional checks, we can directly exclude the unit who want to deploy.
411+
TechnoExt::Deployer = pThis;
412+
const bool canDeploy = pDeployType->CanCreateHere(mapCoords, pThis->Owner);
413+
TechnoExt::Deployer = nullptr;
418414

419415
return canDeploy;
420416
}

src/Ext/Techno/Body.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ class TechnoExt
5151
bool LastRearmWasFullDelay;
5252
bool CanCloakDuringRearm; // Current rearm timer was started by DecloakToFire=no weapon.
5353
int WHAnimRemainingCreationInterval;
54-
bool CanCurrentlyDeployIntoBuilding; // Only set on UnitClass technos with DeploysInto set in multiplayer games, recalculated once per frame so no need to serialize.
5554
WeaponTypeClass* LastWeaponType;
5655
CellClass* FiringObstacleCell; // Set on firing if there is an obstacle cell between target and techno, used for updating WaveClass target etc.
5756
bool IsDetachingForCloak; // Used for checking animation detaching, set to true before calling Detach_All() on techno when this anim is attached to and to false after when cloaking only.
@@ -98,7 +97,6 @@ class TechnoExt
9897
, LastRearmWasFullDelay { false }
9998
, CanCloakDuringRearm { false }
10099
, WHAnimRemainingCreationInterval { 0 }
101-
, CanCurrentlyDeployIntoBuilding { false }
102100
, LastWeaponType {}
103101
, FiringObstacleCell {}
104102
, IsDetachingForCloak { false }
@@ -167,6 +165,8 @@ class TechnoExt
167165

168166
static ExtContainer ExtMap;
169167

168+
static UnitClass* Deployer;
169+
170170
static bool LoadGlobals(PhobosStreamReader& Stm);
171171
static bool SaveGlobals(PhobosStreamWriter& Stm);
172172

src/Ext/TerrainType/Hooks.Passable.cpp

Lines changed: 3 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -93,75 +93,9 @@ DEFINE_HOOK(0x73FB71, UnitClass_CanEnterCell_PassableTerrain, 0x6)
9393
}
9494

9595
// Buildable-upon TerrainTypes Hook #1 - Allow placing buildings on top of them.
96-
DEFINE_HOOK_AGAIN(0x47C80E, CellClass_IsClearTo_Build_BuildableTerrain, 0x5)
97-
DEFINE_HOOK(0x47C745, CellClass_IsClearTo_Build_BuildableTerrain, 0x5)
98-
{
99-
enum { Skip = 0x47C85F, SkipFlags = 0x47C6A0 };
100-
101-
GET(CellClass*, pThis, EDI);
102-
103-
auto pTerrain = pThis->GetTerrain(false);
104-
105-
if (pTerrain)
106-
{
107-
if (auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type))
108-
{
109-
if (pTypeExt->CanBeBuiltOn)
110-
{
111-
if (IS_CELL_OCCUPIED(pThis))
112-
return Skip;
113-
else
114-
return SkipFlags;
115-
}
116-
}
117-
}
118-
119-
return 0;
120-
}
121-
122-
// Buildable-upon TerrainTypes Hook #2 - Allow placing laser fences on top of them.
123-
DEFINE_HOOK(0x47C657, CellClass_IsClearTo_Build_BuildableTerrain_LF, 0x6)
124-
{
125-
enum { Skip = 0x47C6A0, Return = 0x47C6D1 };
126-
127-
GET(CellClass*, pThis, EDI);
128-
129-
auto pObj = pThis->FirstObject;
130-
131-
if (pObj)
132-
{
133-
bool isEligible = true;
134-
135-
while (true)
136-
{
137-
isEligible = pObj->WhatAmI() != AbstractType::Building;
138-
139-
if (auto const pTerrain = abstract_cast<TerrainClass*>(pObj))
140-
{
141-
isEligible = false;
142-
auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type);
143-
144-
if (pTypeExt->CanBeBuiltOn)
145-
isEligible = true;
146-
}
147-
148-
if (!isEligible)
149-
break;
150-
151-
pObj = pObj->NextObject;
152-
153-
if (!pObj)
154-
return Skip;
155-
}
156-
157-
return Return;
158-
}
159-
160-
return Skip;
161-
}
162-
96+
// DEFINE_HOOK(0x73FEC1, UnitClass_WhatAction_DeploysIntoDesyncFix, 0x6) in Hooks.DeploysInto.cpp
16397

164-
// Buildable-upon TerrainTypes Hook #3 - Draw laser fence placement even if they are on the way.
98+
// Buildable-upon TerrainTypes Hook #2 - Draw laser fence placement even if they are on the way.
16599
DEFINE_HOOK(0x6D57C1, TacticalClass_DrawLaserFencePlacement_BuildableTerrain, 0x9)
166100
{
167101
enum { ContinueChecks = 0x6D57D2, DontDraw = 0x6D59A6 };
@@ -181,7 +115,7 @@ DEFINE_HOOK(0x6D57C1, TacticalClass_DrawLaserFencePlacement_BuildableTerrain, 0x
181115
return ContinueChecks;
182116
}
183117

184-
// Buildable-upon TerrainTypes Hook #4 - Remove them when buildings are placed on them.
118+
// Buildable-upon TerrainTypes Hook #3 - Remove them when buildings are placed on them.
185119
DEFINE_HOOK(0x5684B1, MapClass_PlaceDown_BuildableTerrain, 0x6)
186120
{
187121
GET(ObjectClass*, pObject, EDI);

src/Ext/Unit/Hooks.DeploysInto.cpp

Lines changed: 113 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
#include <TerrainClass.h>
2+
#include <IsometricTileTypeClass.h>
3+
4+
#include <Ext/TerrainType/Body.h>
15
#include <Ext/CaptureManager/Body.h>
26
#include <Ext/WarheadType/Body.h>
37

@@ -120,38 +124,128 @@ DEFINE_HOOK(0x7396AD, UnitClass_Deploy_CreateBuilding, 0x6)
120124

121125
// Game removes deploying vehicles from map temporarily to check if there's enough
122126
// space to deploy into a building when displaying allow/disallow deploy cursor.
123-
// This can cause desyncs if there are certain types of units around the deploying unit.
124-
// Only reasonable way to solve this is to perform the cell clear check on every client per frame
125-
// and use that result in cursor display which is client-specific. This is now implemented in multiplayer games only.
127+
// This can cause desyncs if there are certain types of units around the deploying
128+
// unit because the OccupationFlags may be accidentally cleared, or the order of
129+
// the objects linked list may be scrambled.
126130
#pragma region DeploysIntoDesyncFix
127131

128-
DEFINE_HOOK(0x73635B, UnitClass_AI_DeploysIntoDesyncFix, 0x6)
132+
DEFINE_HOOK(0x73FEC1, UnitClass_WhatAction_DeploysIntoDesyncFix, 0x6)
129133
{
130-
if (!SessionClass::IsMultiplayer())
131-
return 0;
134+
enum { SkipGameCode = 0x73FFDF };
132135

133-
GET(UnitClass*, pThis, ESI);
136+
GET(UnitClass* const, pThis, ESI);
137+
REF_STACK(Action, action, STACK_OFFSET(0x20, 0x8));
134138

135-
if (pThis->Type->DeploysInto)
136-
TechnoExt::ExtMap.Find(pThis)->CanCurrentlyDeployIntoBuilding = TechnoExt::CanDeployIntoBuilding(pThis);
139+
if (!TechnoExt::CanDeployIntoBuilding(pThis))
140+
action = Action::NoDeploy;
137141

138-
return 0;
142+
return SkipGameCode;
139143
}
140144

141-
DEFINE_HOOK(0x73FEC1, UnitClass_WhatAction_DeploysIntoDesyncFix, 0x6)
145+
// Exclude the specific unit who want to deploy
146+
// Allow placing buildings on top of TerrainType with CanBeBuiltOn
147+
DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
142148
{
143-
if (!SessionClass::IsMultiplayer())
144-
return 0;
149+
enum { CanNotExistHere = 0x47C6D1, CanExistHere = 0x47C6A0 };
145150

146-
enum { SkipGameCode = 0x73FFDF };
151+
GET(const CellClass* const, pCell, EDI);
152+
GET(const BuildingTypeClass* const, pBuildingType, EAX);
153+
GET_STACK(HouseClass* const, pOwner, STACK_OFFSET(0x18, 0xC));
147154

148-
GET(UnitClass*, pThis, ESI);
149-
LEA_STACK(Action*, pAction, STACK_OFFSET(0x20, 0x8));
155+
if (!Game::IsActive)
156+
return CanExistHere;
150157

151-
if (!TechnoExt::ExtMap.Find(pThis)->CanCurrentlyDeployIntoBuilding)
152-
*pAction = Action::NoDeploy;
158+
if (pBuildingType->LaserFence)
159+
{
160+
for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject)
161+
{
162+
if (pObject->WhatAmI() == AbstractType::Building)
163+
{
164+
return CanNotExistHere;
165+
}
166+
else if (const auto pTerrain = abstract_cast<TerrainClass*>(pObject))
167+
{
168+
const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type);
153169

154-
return SkipGameCode;
170+
if (!pTypeExt || !pTypeExt->CanBeBuiltOn)
171+
return CanNotExistHere;
172+
}
173+
}
174+
}
175+
else if (pBuildingType->LaserFencePost || pBuildingType->Gate)
176+
{
177+
bool skipFlag = TechnoExt::Deployer ? TechnoExt::Deployer->CurrentMapCoords == pCell->MapCoords : false;
178+
179+
for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject)
180+
{
181+
if (const auto pTerrain = abstract_cast<TerrainClass*>(pObject))
182+
{
183+
const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type);
184+
185+
if (!pTypeExt || !pTypeExt->CanBeBuiltOn)
186+
return CanNotExistHere;
187+
}
188+
else if (pObject->AbstractFlags & AbstractFlags::Techno)
189+
{
190+
if (pObject == TechnoExt::Deployer)
191+
{
192+
skipFlag = true;
193+
}
194+
else if (pObject->WhatAmI() != AbstractType::Building
195+
|| pOwner != static_cast<BuildingClass*>(pObject)->Owner
196+
|| !static_cast<BuildingClass*>(pObject)->Type->LaserFence)
197+
{
198+
return CanNotExistHere;
199+
}
200+
}
201+
}
202+
203+
if (pCell->OccupationFlags & (skipFlag ? 0x1F : 0x3F))
204+
return CanNotExistHere;
205+
}
206+
else if (pBuildingType->ToTile)
207+
{
208+
const auto isoTileTypeIndex = pCell->IsoTileTypeIndex;
209+
210+
if (isoTileTypeIndex >= 0 && isoTileTypeIndex < IsometricTileTypeClass::Array.Count
211+
&& !IsometricTileTypeClass::Array.Items[isoTileTypeIndex]->Morphable)
212+
{
213+
return CanNotExistHere;
214+
}
215+
216+
for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject)
217+
{
218+
if (pObject->WhatAmI() == AbstractType::Building)
219+
return CanNotExistHere;
220+
}
221+
}
222+
else
223+
{
224+
bool skipFlag = TechnoExt::Deployer ? TechnoExt::Deployer->CurrentMapCoords == pCell->MapCoords : false;
225+
226+
for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject)
227+
{
228+
if (pObject->AbstractFlags & AbstractFlags::Techno)
229+
{
230+
if (pObject == TechnoExt::Deployer)
231+
skipFlag = true;
232+
else
233+
return CanNotExistHere;
234+
}
235+
else if (const auto pTerrain = abstract_cast<TerrainClass*>(pObject))
236+
{
237+
const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type);
238+
239+
if (!pTypeExt || !pTypeExt->CanBeBuiltOn)
240+
return CanNotExistHere;
241+
}
242+
}
243+
244+
if (pCell->OccupationFlags & (skipFlag ? 0x1F : 0x3F))
245+
return CanNotExistHere;
246+
}
247+
248+
return CanExistHere; // Continue check the overlays .etc
155249
}
156250

157251
#pragma endregion

0 commit comments

Comments
 (0)