diff --git a/CREDITS.md b/CREDITS.md
index 59b2dc7b82..fb20cee01a 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -541,6 +541,8 @@ This page lists all the individual contributions to the project by their author.
- Fix an issue that barrel anim data will be incorrectly overwritten by turret anim data if the techno's section exists in the map file
- Jumpjet Climbing Logic Enhancement
- Fix for pathfinding crashes on big maps due to too small pathfinding node buffer
+ - Building placing and deploying logic enhancement
+ - Customized laser fence
- **Ollerus**:
- Build limit group enhancement
- Customizable rocker amplitude
diff --git a/Phobos.vcxproj b/Phobos.vcxproj
index a9e600eb42..6e802e0d1d 100644
--- a/Phobos.vcxproj
+++ b/Phobos.vcxproj
@@ -194,6 +194,7 @@
+
diff --git a/YRpp b/YRpp
index 3668c001a4..ad950ad2e6 160000
--- a/YRpp
+++ b/YRpp
@@ -1 +1 @@
-Subproject commit 3668c001a4b67f82dcfa2c5c2cb02a44680c1416
+Subproject commit ad950ad2e68982594533f1bc80eea662937ec056
diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md
index 2663b2e295..2bf8f2d872 100644
--- a/docs/Fixed-or-Improved-Logics.md
+++ b/docs/Fixed-or-Improved-Logics.md
@@ -1545,6 +1545,31 @@ HeightShadowScaling.MinScale=0.0 ; floating point value
ShadowSizeCharacteristicHeight= ; integer, height in leptons
```
+### Customized laser fence
+
+- Now `LaserFence` can be customized by setting `LaserFencePost.Fence` on `LaserFencePost=true` buildings.
+ - `LaserFencePost.Fence` defines which kind of laser fence can connect this kind of laser fence post. If they have different `LaserFencePost.Fence`, they will not be connected.
+
+In `rulesmd.ini`:
+```ini
+[SOMEBUILDING] ; BuildingType, `LaserFencePost=yes`
+LaserFencePost.Fence= ; BuildingType
+```
+
+### Buildable-upon TechnoTypes
+
+- Now technos have `CanBeBuiltOn=true` can simply removed when building is placed on them.
+- Buildings like land mines can be placed in positions already occupied by infantry, vehicles, or aircraft after setting `CanBuildUnderUnits` to true.
+
+In `rulesmd.ini`:
+```ini
+[SOMETECHNO] ; TechnoType
+CanBeBuiltOn=false ; boolean
+
+[SOMEBUILDING] ; BuildingType
+CanBuildUnderUnits=false ; boolean
+```
+
## Terrains
### Animated TerrainTypes
diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md
index 2d314b67d1..50816df890 100644
--- a/docs/New-or-Enhanced-Logics.md
+++ b/docs/New-or-Enhanced-Logics.md
@@ -661,6 +661,43 @@ SpyEffect.VictimSuperWeapon= ; SuperWeaponType
SpyEffect.InfiltratorSuperWeapon= ; SuperWeaponType
```
+### Building placing and deploying logic enhancement
+
+- In vanilla games, buildings are always cannot placing or deploying on the cells that other infantries or units on. Now this can be changed by setting `ExtendedBuildingPlacing` to true, when you try to place the building on these cells, it will check whether the occupiers can be scatter by yourself (include your own technos and allies technos) and whether there are enough spaces to scatter. If can, it will record which building you are placing and show a preview to you and your allies, then start a timer to record this placement and order the occupiers to leave this building area. When the area is cleared, the building will be truly place down and the production queue will be restored to original state. But when the timer expires or an unexpected situation has occurred which make the building impossible be constructed here anymore, it will stop the action and play "cannot deploy here", then you should re-place or re-deploy the building in a valid space.
+- `LimboBuild` controls whether building can be automatically placed like `LimboDelivery`.
+ - `LimboBuildID` defines the numeric ID of the building placed by `LimboBuild`.
+- `PlaceBuilding.Extra` controls whether the actual placement type of the building can be changed by holding the left mouse button and changing the mouse position when placing.
+ - `DefaultPlacingDirection` controls the default placing direction at the beginning of the game and every time after placing the building.
+ - `PlaceBuilding.OnLand` controls buildings can be replaced when placed on land.
+ - `PlaceBuilding.OnWater` controls buildings can be replaced when placed on water.
+ - `PlaceBuilding.DirectionShape` and `PlaceBuilding.DirectionPalette` controls what additional directional guidance shape looks like when placing `PlaceBuilding.Extra=true` buildings.
+
+In `rulesmd.ini`:
+```ini
+[General]
+ExtendedBuildingPlacing=false ; boolean
+
+[SOMEBUILDING] ; BuildingType
+LimboBuild=false ; boolean
+LimboBuildID=-1 ; integer
+PlaceBuilding.Extra=false ; boolean
+PlaceBuilding.OnLand= ; List of BuildingTypes
+PlaceBuilding.OnWater= ; List of BuildingTypes
+PlaceBuilding.DirectionShape= ; filename with .shp extension
+PlaceBuilding.DirectionPalette= ; filename with .pal extension
+```
+
+In `ra2md.ini`:
+```ini
+[Phobos]
+DefaultPlacingDirection=0 ; integer, 0-31
+```
+
+```{note}
+- `PlaceBuilding.OnLand` and `PlaceBuilding.OnWater` are only work for players.
+- The replacement building and the original building must have the same `BuildCat`, and neither can have `LimboBuild` or `PlaceAnywhere`.
+```
+
## Infantry
### Customizable FLH when infantry is prone or deployed
diff --git a/docs/Whats-New.md b/docs/Whats-New.md
index 787d5253bc..01f6b7a7b5 100644
--- a/docs/Whats-New.md
+++ b/docs/Whats-New.md
@@ -436,6 +436,8 @@ New:
- [Restore turret recoil effect](Fixed-or-Improved-Logics.md#turret-recoil) (by CrimRecya)
- [Customize hardcoded projectile initial facing behavior](Fixed-or-Improved-Logics.md#customizing-initial-facing-behavior) (by Starkku)
- Health bar permanently displayed (by FlyStar)
+- Building placing and deploying logic enhancement (by CrimRecya)
+- Customized laser fence (by CrimRecya)
Vanilla fixes:
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
@@ -455,6 +457,7 @@ Vanilla fixes:
Phobos fixes:
- Fixed the bug that `AllowAirstrike=no` cannot completely prevent air strikes from being launched against it (by NetsuNegi)
- Fixed an issue that `FireAngle` was not taken into account when drawing barrel in `TurretShadow` (by CrimRecya)
+- Fixed a bug that sometimes caused weapon/warhead detonations from features such as `ExtraWarheads`, animation damage or `Crit.Warhead` to unintentionally move from its intended position (by Starkku)
Fixes / interactions with other extensions:
- Allowed `AuxBuilding` and Ares' `SW.Aux/NegBuildings` to count building upgrades (by Ollerus)
diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp
index acad4d12e9..11c9cb88bc 100644
--- a/src/Ext/BuildingType/Body.cpp
+++ b/src/Ext/BuildingType/Body.cpp
@@ -1,8 +1,12 @@
#include "Body.h"
-#include
+#include
+
#include
+#include
+#include
#include
+#include
BuildingTypeExt::ExtContainer BuildingTypeExt::ExtMap;
@@ -120,6 +124,489 @@ int BuildingTypeExt::GetUpgradesAmount(BuildingTypeClass* pBuilding, HouseClass*
return isUpgrade ? result : -1;
}
+BuildingTypeClass* BuildingTypeExt::ExtData::GetAnotherPlacingType(size_t direction, bool onWater)
+{
+ const auto pType = this->OwnerObject();
+
+ if (pType->PlaceAnywhere || this->LimboBuild)
+ return nullptr;
+
+ const auto& types = onWater ? this->PlaceBuilding_OnWater : this->PlaceBuilding_OnLand;
+ const size_t size = types.size();
+
+ if (!size)
+ return nullptr;
+
+ direction = (direction + (16u / size)) & 0x1Fu;
+ const auto pAnotherType = types[static_cast(direction * size / 32u)];
+
+ if (pAnotherType->BuildCat != pType->BuildCat
+ || pAnotherType->PlaceAnywhere
+ || BuildingTypeExt::ExtMap.Find(pAnotherType)->LimboBuild)
+ {
+ return nullptr;
+ }
+
+ return pAnotherType;
+}
+
+// Check whether can call the occupiers leave
+bool BuildingTypeExt::CheckOccupierCanLeave(HouseClass* pBuildingHouse, HouseClass* pOccupierHouse)
+{
+ if (!pOccupierHouse || !pBuildingHouse)
+ return false;
+ else if (pBuildingHouse == pOccupierHouse)
+ return true;
+ else if (pOccupierHouse->IsAlliedWith(pBuildingHouse))
+ return true;
+ else if (SessionClass::IsCampaign() && pBuildingHouse->IsControlledByHuman() && pOccupierHouse->IsControlledByHuman())
+ return true;
+
+ return false;
+}
+
+// Force occupiers leave, return: whether it should stop right now
+bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, CellStruct topLeftCell, HouseClass* pHouse, TechnoClass* pExceptTechno)
+{
+ // Step 1: Find the technos inside of the building place grid.
+ auto infantryCount = CellStruct::Empty;
+ std::vector checkedTechnos;
+ checkedTechnos.reserve(24);
+ std::vector checkedCells;
+ checkedCells.reserve(24);
+
+ for (auto pFoundation = pBuildingType->GetFoundationData(false); *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation)
+ {
+ auto currentCell = topLeftCell + *pFoundation;
+
+ if (const auto pCell = MapClass::Instance.TryGetCellAt(currentCell))
+ {
+ for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject)
+ {
+ const auto absType = pObject->WhatAmI();
+
+ if (absType == AbstractType::Infantry || absType == AbstractType::Unit)
+ {
+ const auto pFoot = static_cast(pObject);
+
+ if (!TechnoTypeExt::ExtMap.Find(pFoot->GetTechnoType())->CanBeBuiltOn && pFoot != pExceptTechno) // No need to check house
+ {
+ if (pFoot->GetCurrentSpeed() <= 0 || !pFoot->Locomotor->Is_Moving())
+ {
+ if (absType == AbstractType::Infantry)
+ ++infantryCount.X;
+
+ checkedTechnos.push_back(pFoot);
+ }
+ }
+ }
+ }
+
+ checkedCells.push_back(pCell);
+ }
+ }
+
+ if (checkedTechnos.size() <= 0) // All in moving
+ return false;
+
+ // Step 2: Find the cells around the building.
+ std::vector optionalCells;
+ optionalCells.reserve(24);
+
+ for (auto pFoundation = pBuildingType->FoundationOutside; *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation)
+ {
+ auto searchCell = topLeftCell + *pFoundation;
+
+ if (const auto pSearchCell = MapClass::Instance.TryGetCellAt(searchCell))
+ {
+ if (!(pSearchCell->OccupationFlags & 0x80)
+ && pSearchCell->IsClearToMove(SpeedType::Amphibious, true, true, -1, MovementZone::Amphibious, -1, false))
+ {
+ optionalCells.push_back(pSearchCell);
+ }
+ }
+ }
+
+ if (optionalCells.size() <= 0) // There is no place for scattering
+ return true;
+
+ // Step 3: Sort the technos by the distance out of the foundation.
+ std::sort(&checkedTechnos[0], &checkedTechnos[checkedTechnos.size()],[optionalCells](TechnoClass* pTechnoA, TechnoClass* pTechnoB)
+ {
+ int minA = INT_MAX;
+ int minB = INT_MAX;
+
+ for (const auto& pOptionalCell : optionalCells) // If there are many valid cells at start, it means most of occupiers will near to the edge
+ {
+ if (minA <= 65536) // If distance squared is lower or equal to 256^2, then no need to calculate any more because it is on the edge
+ {
+ if (minB <= 65536)
+ break;
+ }
+ else
+ {
+ auto curA = static_cast(pTechnoA->GetMapCoords().DistanceFromSquared(pOptionalCell->MapCoords));
+
+ if (curA < minA)
+ minA = curA;
+
+ if (minB <= 65536)
+ continue;
+ }
+
+ auto curB = static_cast(pTechnoB->GetMapCoords().DistanceFromSquared(pOptionalCell->MapCoords));
+
+ if (curB < minB)
+ minB = curB;
+ }
+
+ return minA > minB;
+ });
+
+ // Step 4: Core, successively find the farthest techno and its closest valid destination.
+ std::vector reCheckedTechnos;
+ reCheckedTechnos.reserve(12);
+
+ struct InfantryCountInCell // Temporary struct
+ {
+ CellClass* position;
+ int count;
+ };
+ std::vector infantryCells;
+ infantryCells.reserve(4);
+
+ struct TechnoWithDestination // Also temporary struct
+ {
+ TechnoClass* techno;
+ CellClass* destination;
+ };
+ std::vector finalOrder;
+ finalOrder.reserve(24);
+
+ do
+ {
+ // Step 4.1: Push the technos discovered just now back to the vector.
+ for (const auto& pRecheckedTechno : reCheckedTechnos)
+ {
+ if (pRecheckedTechno->WhatAmI() == AbstractType::Infantry)
+ ++infantryCount.X;
+
+ checkedTechnos.push_back(pRecheckedTechno);
+ }
+
+ reCheckedTechnos.clear();
+
+ // Step 4.2: Check the techno vector.
+ for (const auto& pCheckedTechno : checkedTechnos)
+ {
+ // Step 4.2.1: Search the closest valid cell to be the destination.
+ const auto location = pCheckedTechno->GetMapCoords();
+ const auto pCheckedType = pCheckedTechno->GetTechnoType();
+ const bool isInfantry = pCheckedTechno->WhatAmI() == AbstractType::Infantry;
+ auto tryGetInfantryDestinationCell = [&]() -> CellClass*
+ {
+ if (isInfantry) // Try to maximizing cells utilization
+ {
+ if (infantryCells.size() && infantryCount.Y >= (infantryCount.X / 3 + (infantryCount.X % 3 ? 1 : 0)))
+ {
+ std::sort(&infantryCells[0], &infantryCells[infantryCells.size()],[location](InfantryCountInCell cellA, InfantryCountInCell cellB){
+ return cellA.position->MapCoords.DistanceFromSquared(location) < cellB.position->MapCoords.DistanceFromSquared(location);
+ });
+
+ for (auto& infantryCell : infantryCells)
+ {
+ if (infantryCell.count < 3 && infantryCell.position->IsClearToMove(pCheckedType->SpeedType, true, true, -1, pCheckedType->MovementZone, -1, false))
+ {
+ ++infantryCell.count;
+ return infantryCell.position;
+ }
+ }
+ }
+ }
+
+ return nullptr;
+ };
+ auto pDestinationCell = tryGetInfantryDestinationCell();
+
+ if (!pDestinationCell)
+ {
+ std::sort(&optionalCells[0], &optionalCells[optionalCells.size()],[location](CellClass* pCellA, CellClass* pCellB){
+ return pCellA->MapCoords.DistanceFromSquared(location) < pCellB->MapCoords.DistanceFromSquared(location);
+ });
+ const auto minDistanceSquaredFactor = optionalCells[0]->MapCoords.DistanceFromSquared(location);
+ std::vector deleteCells;
+ deleteCells.reserve(4);
+
+ for (const auto& pOptionalCell : optionalCells)
+ {
+ if (!pDestinationCell) // First find a feasible destination
+ {
+ std::vector optionalTechnos;
+ optionalTechnos.reserve(4);
+ auto pObject = pOptionalCell->FirstObject;
+ bool valid = true;
+
+ for (; pObject; pObject = pObject->NextObject)
+ {
+ const auto absType = pObject->WhatAmI();
+
+ if (absType == AbstractType::Infantry || absType == AbstractType::Unit)
+ {
+ const auto pCurTechno = static_cast(pObject);
+
+ if (!BuildingTypeExt::CheckOccupierCanLeave(pHouse, pCurTechno->Owner))
+ {
+ deleteCells.push_back(pOptionalCell);
+ valid = false;
+ break;
+ }
+
+ optionalTechnos.push_back(pCurTechno);
+ }
+ // Other types will be checked by IsClearToMove
+ }
+
+ if (valid && pOptionalCell->IsClearToMove(pCheckedType->SpeedType, true, true, -1, pCheckedType->MovementZone, -1, false))
+ {
+ // Record the foots on the destination cell, they also need to be evacuated
+ for (const auto& pOptionalTechno : optionalTechnos)
+ reCheckedTechnos.push_back(pOptionalTechno);
+
+ if (isInfantry) // Not need to remove it now
+ {
+ infantryCells.emplace_back(InfantryCountInCell{ pOptionalCell, 1 });
+ ++infantryCount.Y;
+ }
+
+ pDestinationCell = pOptionalCell;
+
+ // Prioritize selecting empty cells
+ if (!pObject || pOptionalCell->MapCoords.DistanceFromSquared(location) > minDistanceSquaredFactor)
+ break;
+ }
+ }
+ else if (pOptionalCell->MapCoords.DistanceFromSquared(location) <= minDistanceSquaredFactor) // Not too far
+ {
+ // Only check empty cell
+ if (!pOptionalCell->FirstObject && pOptionalCell->IsClearToMove(pCheckedType->SpeedType, true, true, -1, pCheckedType->MovementZone, -1, false))
+ {
+ if (isInfantry) // Not need to remove it now
+ {
+ infantryCells.emplace_back(InfantryCountInCell{ pOptionalCell, 1 });
+ ++infantryCount.Y;
+ }
+
+ pDestinationCell = pOptionalCell;
+ break;
+ }
+ }
+ else // End immediately if the distance is longer
+ {
+ break;
+ }
+ }
+
+ if (!pDestinationCell) // Can not build
+ return true;
+
+ for (const auto& pDeleteCell : deleteCells) // Mark the invalid cells
+ {
+ checkedCells.push_back(pDeleteCell);
+ optionalCells.erase(std::remove(optionalCells.begin(), optionalCells.end(), pDeleteCell), optionalCells.end());
+ }
+ }
+
+ // Step 4.2.2: Mark the cell and push back its surrounded cells, then prepare for the command.
+ if (std::find(checkedCells.begin(), checkedCells.end(), pDestinationCell) == checkedCells.end())
+ checkedCells.push_back(pDestinationCell);
+
+ if (std::find(optionalCells.begin(), optionalCells.end(), pDestinationCell) != optionalCells.end())
+ {
+ optionalCells.erase(std::remove(optionalCells.begin(), optionalCells.end(), pDestinationCell), optionalCells.end());
+ auto searchCell = pDestinationCell->MapCoords - CellStruct { 1, 1 };
+
+ for (int i = 0; i < 4; ++i)
+ {
+ for (int j = 0; j < 2; ++j)
+ {
+ if (const auto pSearchCell = MapClass::Instance.TryGetCellAt(searchCell))
+ {
+ if (std::find(checkedCells.begin(), checkedCells.end(), pSearchCell) == checkedCells.end()
+ && std::find(optionalCells.begin(), optionalCells.end(), pSearchCell) == optionalCells.end()
+ && !(pSearchCell->OccupationFlags & 0x80)
+ && pSearchCell->IsClearToMove(SpeedType::Amphibious, true, true, -1, MovementZone::Amphibious, -1, false))
+ {
+ optionalCells.push_back(pSearchCell);
+ }
+ }
+
+ if (i % 2)
+ searchCell.Y += static_cast((i / 2) ? -1 : 1);
+ else
+ searchCell.X += static_cast((i / 2) ? -1 : 1);
+ }
+ }
+ }
+
+ finalOrder.emplace_back(TechnoWithDestination { pCheckedTechno, pDestinationCell });
+ }
+
+ checkedTechnos.clear();
+ }
+ while (reCheckedTechnos.size());
+
+ // Step 5: Confirm command execution.
+ for (const auto& thisOrder : finalOrder)
+ {
+ const auto pCheckedTechno = thisOrder.techno;
+ const auto pDestinationCell = thisOrder.destination;
+ const auto absType = pCheckedTechno->WhatAmI();
+
+ if (absType == AbstractType::Infantry)
+ {
+ const auto pInfantry = static_cast(pCheckedTechno);
+
+ if (pInfantry->IsDeployed())
+ pInfantry->PlayAnim(Sequence::Undeploy, true);
+
+ pInfantry->SetDestination(pDestinationCell, true);
+ }
+ else if (absType == AbstractType::Unit)
+ {
+ const auto pUnit = static_cast(pCheckedTechno);
+
+ if (pUnit->Deployed && !(pUnit->Deploying || pUnit->Undeploying))
+ pUnit->QueueMission(Mission::Unload, false);
+
+ pUnit->SetDestination(pDestinationCell, true);
+ }
+ }
+
+ return false;
+}
+
+bool BuildingTypeExt::IsSameBuildingType(BuildingTypeClass* pType1, BuildingTypeClass* pType2)
+{
+ return ((pType1->BuildCat != BuildCat::Combat) == (pType2->BuildCat != BuildCat::Combat));
+}
+
+bool BuildingTypeExt::BuildLimboBuilding(BuildingClass* pBuilding)
+{
+ const auto pBuildingType = pBuilding->Type;
+
+ if (BuildingTypeExt::ExtMap.Find(pBuildingType)->LimboBuild)
+ {
+ const EventClass event
+ (
+ pBuilding->Owner->ArrayIndex,
+ EventType::Place,
+ AbstractType::Building,
+ pBuildingType->GetArrayIndex(),
+ pBuildingType->Naval,
+ CellStruct { 1, 1 }
+ );
+ EventClass::AddEvent(event);
+
+ return true;
+ }
+
+ return false;
+}
+
+void BuildingTypeExt::CreateLimboBuilding(BuildingClass* pBuilding, BuildingTypeClass* pType, HouseClass* pOwner, int ID)
+{
+ if (pBuilding || (pBuilding = static_cast(pType->CreateObject(pOwner)), pBuilding))
+ {
+ // All of these are mandatory
+ pBuilding->InLimbo = false;
+ pBuilding->IsAlive = true;
+ pBuilding->IsOnMap = true;
+
+ // Jun 3, 2023 - Starkku: For reasons beyond my comprehension, the discovery logic is checked for certain logics like power drain/output in campaign only.
+ // Normally on unlimbo the buildings are revealed to current player if unshrouded or if game is a campaign and to non-player houses always.
+ // Because of the unique nature of LimboDelivered buildings, this has been adjusted to always reveal to the current player in singleplayer
+ // and to the owner of the building regardless, removing the shroud check from the equation since they don't physically exist
+ if (SessionClass::IsCampaign())
+ pBuilding->DiscoveredBy(HouseClass::CurrentPlayer);
+
+ pBuilding->DiscoveredBy(pOwner);
+
+ pOwner->RegisterGain(pBuilding, false);
+ pOwner->RecheckTechTree = true;
+ pOwner->RecheckPower = true;
+ pOwner->Buildings.AddItem(pBuilding);
+
+ // Different types of building logics
+ if (pType->ConstructionYard)
+ pOwner->ConYards.AddItem(pBuilding); // why would you do that????
+
+ if (pType->SecretLab)
+ pOwner->SecretLabs.AddItem(pBuilding);
+
+ auto const pBuildingExt = BuildingExt::ExtMap.Find(pBuilding);
+ auto const pOwnerExt = HouseExt::ExtMap.Find(pOwner);
+
+ if (pType->FactoryPlant)
+ {
+ if (pBuildingExt->TypeExtData->FactoryPlant_AllowTypes.size() > 0 || pBuildingExt->TypeExtData->FactoryPlant_DisallowTypes.size() > 0)
+ {
+ pOwnerExt->RestrictedFactoryPlants.push_back(pBuilding);
+ }
+ else
+ {
+ pOwner->FactoryPlants.AddItem(pBuilding);
+ pOwner->CalculateCostMultipliers();
+ }
+ }
+
+ // BuildingClass::Place is already called in DiscoveredBy
+ // it added OrePurifier and xxxGainSelfHeal to House counter already
+
+ // LimboKill ID
+ pBuildingExt->LimboID = ID;
+
+ // Add building to list of owned limbo buildings
+ pOwnerExt->OwnedLimboDeliveredBuildings.push_back(pBuilding);
+ auto const pBldType = pBuilding->Type;
+
+ if (!pBldType->Insignificant && !pBldType->DontScore)
+ pOwnerExt->AddToLimboTracking(pBldType);
+
+ auto const pTechnoExt = TechnoExt::ExtMap.Find(pBuilding);
+ auto const pTechnoTypeExt = pTechnoExt->TypeExtData;
+
+ if (pTechnoTypeExt->AutoDeath_Behavior.isset())
+ {
+ ScenarioExt::Global()->AutoDeathObjects.push_back(pTechnoExt);
+
+ if (pTechnoTypeExt->AutoDeath_AfterDelay > 0)
+ pTechnoExt->AutoDeathTimer.Start(pTechnoTypeExt->AutoDeath_AfterDelay);
+ }
+ }
+}
+
+bool BuildingTypeExt::DeleteLimboBuilding(BuildingClass* pBuilding, int ID)
+{
+ const auto pBuildingExt = BuildingExt::ExtMap.Find(pBuilding);
+
+ if (pBuildingExt->LimboID != ID)
+ return false;
+
+ if (pBuildingExt->TypeExtData->LimboBuildID == ID)
+ {
+ const auto pHouse = pBuilding->Owner;
+ const auto index = pBuilding->Type->ArrayIndex;
+
+ for (auto& pBaseNode : pHouse->Base.BaseNodes)
+ {
+ if (pBaseNode.BuildingTypeIndex == index)
+ pBaseNode.Placed = false;
+ }
+ }
+
+ return true;
+}
+
void BuildingTypeExt::ExtData::Initialize()
{ }
@@ -168,6 +655,16 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->SellBuildupLength.Read(exINI, pSection, "SellBuildupLength");
this->IsDestroyableObstacle.Read(exINI, pSection, "IsDestroyableObstacle");
+ this->LimboBuild.Read(exINI, pSection, "LimboBuild");
+ this->LimboBuildID.Read(exINI, pSection, "LimboBuildID");
+ this->LaserFencePost_Fence.Read(exINI, pSection, "LaserFencePost.Fence");
+ this->PlaceBuilding_OnLand.Read(exINI, pSection, "PlaceBuilding.OnLand");
+ this->PlaceBuilding_OnWater.Read(exINI, pSection, "PlaceBuilding.OnWater");
+ this->PlaceBuilding_DirectionShape.Read(exINI, pSection, "PlaceBuilding.DirectionShape");
+ this->PlaceBuilding_DirectionPalette.LoadFromINI(pINI, pSection, "PlaceBuilding.DirectionPalette");
+ this->PlaceBuilding_Extra.Read(exINI, pSection, "PlaceBuilding.Extra");
+ this->CanBuildUnderUnits.Read(exINI, pSection, "CanBuildUnderUnits");
+
this->FactoryPlant_AllowTypes.Read(exINI, pSection, "FactoryPlant.AllowTypes");
this->FactoryPlant_DisallowTypes.Read(exINI, pSection, "FactoryPlant.DisallowTypes");
@@ -309,6 +806,15 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm)
.Process(this->ConsideredVehicle)
.Process(this->ZShapePointMove_OnBuildup)
.Process(this->SellBuildupLength)
+ .Process(this->LimboBuild)
+ .Process(this->LimboBuildID)
+ .Process(this->LaserFencePost_Fence)
+ .Process(this->PlaceBuilding_OnLand)
+ .Process(this->PlaceBuilding_OnWater)
+ .Process(this->PlaceBuilding_DirectionShape)
+ .Process(this->PlaceBuilding_DirectionPalette)
+ .Process(this->PlaceBuilding_Extra)
+ .Process(this->CanBuildUnderUnits)
.Process(this->AircraftDockingDirs)
.Process(this->FactoryPlant_AllowTypes)
.Process(this->FactoryPlant_DisallowTypes)
diff --git a/src/Ext/BuildingType/Body.h b/src/Ext/BuildingType/Body.h
index 9cf41a7af4..4965d1cb64 100644
--- a/src/Ext/BuildingType/Body.h
+++ b/src/Ext/BuildingType/Body.h
@@ -64,6 +64,16 @@ class BuildingTypeExt
Valueable SellBuildupLength;
Valueable IsDestroyableObstacle;
+ Valueable LimboBuild;
+ Valueable LimboBuildID;
+ Valueable LaserFencePost_Fence;
+ ValueableVector PlaceBuilding_OnLand;
+ ValueableVector PlaceBuilding_OnWater;
+ Valueable PlaceBuilding_DirectionShape;
+ CustomPalette PlaceBuilding_DirectionPalette;
+ Valueable PlaceBuilding_Extra;
+ Valueable CanBuildUnderUnits;
+
Valueable IsAnimDelayedBurst;
std::vector> AircraftDockingDirs;
@@ -136,6 +146,15 @@ class BuildingTypeExt
, ConsideredVehicle {}
, ZShapePointMove_OnBuildup { false }
, SellBuildupLength { 23 }
+ , LimboBuild { false }
+ , LimboBuildID { -1 }
+ , LaserFencePost_Fence {}
+ , PlaceBuilding_OnLand {}
+ , PlaceBuilding_OnWater {}
+ , PlaceBuilding_DirectionShape { nullptr }
+ , PlaceBuilding_DirectionPalette {}
+ , PlaceBuilding_Extra { false }
+ , CanBuildUnderUnits { false }
, AircraftDockingDirs {}
, FactoryPlant_AllowTypes {}
, FactoryPlant_DisallowTypes {}
@@ -163,6 +182,8 @@ class BuildingTypeExt
, HasPowerUpAnim {}
{ }
+ BuildingTypeClass* GetAnotherPlacingType(size_t direction, bool onWater);
+
// Ares 0.A functions
int GetSuperWeaponCount() const;
int GetSuperWeaponIndex(int index, HouseClass* pHouse) const;
@@ -203,4 +224,10 @@ class BuildingTypeExt
static bool CanUpgrade(BuildingClass* pBuilding, BuildingTypeClass* pUpgradeType, HouseClass* pUpgradeOwner);
static int CountOwnedNowWithDeployOrUpgrade(BuildingTypeClass* pBuilding, HouseClass* pHouse);
static int GetUpgradesAmount(BuildingTypeClass* pBuilding, HouseClass* pHouse);
+ static bool CheckOccupierCanLeave(HouseClass* pBuildingHouse, HouseClass* pOccupierHouse);
+ static bool CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, CellStruct topLeftCell, HouseClass* pHouse, TechnoClass* pExceptTechno = nullptr);
+ static bool IsSameBuildingType(BuildingTypeClass* pType1, BuildingTypeClass* pType2);
+ static bool BuildLimboBuilding(BuildingClass* pBuilding);
+ static void CreateLimboBuilding(BuildingClass* pBuilding, BuildingTypeClass* pType, HouseClass* pOwner, int ID);
+ static bool DeleteLimboBuilding(BuildingClass* pBuilding, int ID);
};
diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp
index 89f9cdf41e..a3691ba2a4 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -1,10 +1,22 @@
#include "Body.h"
-#include
+#include
+#include
+#include
+#include
+#include
-#include "Ext/Rules/Body.h"
+#include
+#include
+#include
+#include
+#include
-// AIConstructionYard Hook #1 -> sub_740810 - Check number of construction yard before deploy.
+/*
+ In sub_740810
+
+ - AIConstructionYard Hook #1 -> Check number of construction yard before deploy
+*/
DEFINE_HOOK(0x740A11, UnitClass_Mission_Guard_AIAutoDeployMCV, 0x6)
{
enum { SkipGameCode = 0x740A50 };
@@ -14,7 +26,11 @@ DEFINE_HOOK(0x740A11, UnitClass_Mission_Guard_AIAutoDeployMCV, 0x6)
return (!RulesExt::Global()->AIAutoDeployMCV && pMCV->Owner->NumConYards > 0) ? SkipGameCode : 0;
}
-// AIConstructionYard Hook #2 -> sub_7393C0 - Skip useless base center setting.
+/*
+ In sub_7393C0
+
+ - AIConstructionYard Hook #2 -> Skip useless base center setting
+*/
DEFINE_HOOK(0x739889, UnitClass_TryToDeploy_AISetBaseCenter, 0x6)
{
enum { SkipGameCode = 0x73992B };
@@ -24,7 +40,11 @@ DEFINE_HOOK(0x739889, UnitClass_TryToDeploy_AISetBaseCenter, 0x6)
return (!RulesExt::Global()->AISetBaseCenter && pMCV->Owner->NumConYards > 1) ? SkipGameCode : 0;
}
-// AIConstructionYard Hook #3 -> sub_4FD500 - Update better base center.
+/*
+ In sub_4FD500
+
+ - AIConstructionYard Hook #3 -> Update better base center
+*/
DEFINE_HOOK(0x4FD538, HouseClass_AIHouseUpdate_CheckAIBaseCenter, 0x7)
{
if (RulesExt::Global()->AIBiasSpawnCell && !SessionClass::IsCampaign())
@@ -70,75 +90,11 @@ DEFINE_HOOK(0x4FD538, HouseClass_AIHouseUpdate_CheckAIBaseCenter, 0x7)
return 0;
}
-// AIConstructionYard Hook #4-1 -> sub_443C60 - Prohibit AI from building construction yard and clean up invalid walls nodes.
-DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
-{
- enum { CanNotBuild = 0x4454E6, BuildFailed = 0x445696 };
-
- GET(BaseNodeClass* const, pBaseNode, EBX);
- GET(BuildingClass* const, pBuilding, EDI);
- GET(const CellStruct, topLeftCell, EDX);
-
- const auto pBuildingType = pBuilding->Type;
-
- // Prohibit AI from building construction yard
- if (RulesExt::Global()->AIForbidConYard && pBuildingType->ConstructionYard)
- {
- if (pBaseNode)
- {
- pBaseNode->Placed = true;
- pBaseNode->Attempts = 0;
- }
-
- return BuildFailed;
- }
-
- // Clean up invalid walls nodes
- if (RulesExt::Global()->AICleanWallNode && pBuildingType->Wall)
- {
- auto notValidWallNode = [topLeftCell]()
- {
- const auto pCell = MapClass::Instance.GetCellAt(topLeftCell);
-
- for (int i = 0; i < 8; ++i)
- {
- if (const auto pAdjBuilding = pCell->GetNeighbourCell(static_cast(i))->GetBuilding())
- {
- if (pAdjBuilding->Type->ProtectWithWall)
- return false;
- }
- }
-
- return true;
- };
-
- if (notValidWallNode())
- return CanNotBuild;
- }
-
- return 0;
-}
-
-// AIConstructionYard Hook #4-2 -> sub_42EB50 - Prohibit AI from building construction yard.
-DEFINE_HOOK(0x42EB8E, BaseClass_GetBaseNodeIndex_CheckValidBaseNode, 0x6)
-{
- enum { Valid = 0x42EBC3, Invalid = 0x42EBAE };
-
- GET(BaseClass* const, pBase, ESI);
- GET(BaseNodeClass* const, pBaseNode, EAX);
-
- if (RulesExt::Global()->AIForbidConYard && pBaseNode->Placed)
- {
- const auto index = pBaseNode->BuildingTypeIndex;
-
- if (index >= 0 && index < BuildingTypeClass::Array.Count && BuildingTypeClass::Array.Items[index]->ConstructionYard)
- return Invalid;
- }
-
- return reinterpret_cast(0x50CAD0)(pBase->Owner, pBaseNode) ? Valid : Invalid;
-}
+/*
+ In sub_7393C0
-// AIConstructionYard Hook #4-3 -> sub_7393C0 - Prohibit AI from building construction yard.
+ - AIConstructionYard Hook #4-3 -> Prohibit AI from building construction yard
+*/
DEFINE_HOOK(0x7397F4, UnitClass_TryToDeploy_SkipSetShouldRebuild, 0x7)
{
enum { SkipRebuildFlag = 0x7397FB };
@@ -148,7 +104,11 @@ DEFINE_HOOK(0x7397F4, UnitClass_TryToDeploy_SkipSetShouldRebuild, 0x7)
return (pBuilding->Type->ConstructionYard && RulesExt::Global()->AIForbidConYard) ? SkipRebuildFlag : 0;
}
-// AIConstructionYard Hook #4-4 -> sub_440580 - Prohibit AI from building construction yard.
+/*
+ In sub_440580
+
+ - AIConstructionYard Hook #4-4 -> Prohibit AI from building construction yard
+*/
DEFINE_HOOK(0x440B7A, BuildingClass_Unlimbo_SkipSetShouldRebuild, 0x7)
{
enum { SkipRebuildFlag = 0x440B81 };
@@ -158,7 +118,11 @@ DEFINE_HOOK(0x440B7A, BuildingClass_Unlimbo_SkipSetShouldRebuild, 0x7)
return (pBuilding->Type->ConstructionYard && RulesExt::Global()->AIForbidConYard) ? SkipRebuildFlag : 0;
}
-// AIConstructionYard Hook #5-1 -> sub_588570 - Only expand walls on nodes.
+/*
+ In sub_588570
+
+ - AIConstructionYard Hook #5-1 -> Only expand walls on nodes
+*/
DEFINE_HOOK(0x5885D1, MapClass_BuildingToFirestormWall_SkipExtraWalls, 0x6)
{
enum { NextDirection = 0x588730 };
@@ -184,7 +148,11 @@ DEFINE_HOOK(0x5885D1, MapClass_BuildingToFirestormWall_SkipExtraWalls, 0x6)
return NextDirection;
}
-// AIConstructionYard Hook #5-2 -> sub_588750 - Only expand walls on nodes.
+/*
+ In sub_588750
+
+ - AIConstructionYard Hook #5-2 -> Only expand walls on nodes
+*/
DEFINE_HOOK(0x5887C1, MapClass_BuildingToWall_SkipExtraWalls, 0x6)
{
enum { NextDirection = 0x588935 };
@@ -209,3 +177,1711 @@ DEFINE_HOOK(0x5887C1, MapClass_BuildingToWall_SkipExtraWalls, 0x6)
return NextDirection;
}
+
+/*
+ In sub_6D5730
+
+ - Buildable-upon TerrainTypes Hook #2 -> Draw laser fence placement even if they are on the way
+*/
+DEFINE_HOOK(0x6D57C1, TacticalClass_DrawLaserFencePlacement_BuildableTerrain, 0x9)
+{
+ enum { ContinueChecks = 0x6D57D2, DontDraw = 0x6D59A6 };
+
+ GET(CellClass*, pCell, ESI);
+
+ if (auto const pTerrain = pCell->GetTerrain(false))
+ return TerrainTypeExt::ExtMap.Find(pTerrain->Type)->CanBeBuiltOn ? ContinueChecks : DontDraw;
+
+ return ContinueChecks;
+}
+
+/*
+ In sub_5683C0
+
+ - Buildable-upon TerrainTypes Hook #3 -> Remove them when buildings are placed on them
+ - Buildable-upon TechnoTypes Hook #7 -> Remove some of them when buildings are placed on them
+*/
+DEFINE_HOOK(0x5684B1, MapClass_PlaceDown_BuildableUponTypes, 0x6)
+{
+ GET(ObjectClass*, pPlaceObject, EDI);
+ GET(CellClass*, pCell, EAX);
+
+ if (pPlaceObject->WhatAmI() == AbstractType::Building)
+ {
+ for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject)
+ {
+ if (const auto pTechno = abstract_cast(pObject))
+ {
+ if (TechnoTypeExt::ExtMap.Find(pTechno->GetTechnoType())->CanBeBuiltOn)
+ {
+ pTechno->KillPassengers(nullptr);
+ pTechno->Stun();
+ pTechno->Limbo();
+ pTechno->UnInit();
+ }
+ }
+ else if (const auto pTerrain = abstract_cast(pObject))
+ {
+ if (TerrainTypeExt::ExtMap.Find(pTerrain->Type)->CanBeBuiltOn)
+ {
+ pCell->RemoveContent(pTerrain, false);
+ TerrainTypeExt::Remove(pTerrain);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ In sub_5FD270
+
+ - Buildable-upon TerrainTypes Hook #4 -> Allow placing buildings on top of them
+*/
+DEFINE_HOOK(0x5FD2B6, OverlayClass_Unlimbo_SkipTerrainCheck, 0x9)
+{
+ enum { Unlimbo = 0x5FD2CA, NoUnlimbo = 0x5FD2C3 };
+
+ GET(CellClass* const, pCell, EAX);
+
+ if (!Game::IsActive)
+ return Unlimbo;
+
+ if (auto const pTerrain = pCell->GetTerrain(false))
+ {
+ if (!TerrainTypeExt::ExtMap.Find(pTerrain->Type)->CanBeBuiltOn)
+ return NoUnlimbo;
+
+ pCell->RemoveContent(pTerrain, false);
+ TerrainTypeExt::Remove(pTerrain);
+ }
+
+ return Unlimbo;
+}
+
+// Buildable Proximity Helper
+namespace ProximityTemp
+{
+ bool Exist = false;
+ bool Mouse = false;
+ CellClass* CurrentCell = nullptr;
+ BuildingTypeClass* BuildType = nullptr;
+}
+
+/*
+ In sub_4A8EB0
+
+ - BaseNormal extra checking Hook #1-1 -> Set context and clear up data
+*/
+DEFINE_HOOK(0x4A8F20, DisplayClass_BuildingProximityCheck_SetContext, 0x5)
+{
+ GET(BuildingTypeClass*, pType, ESI);
+
+ ProximityTemp::BuildType = pType;
+
+ return 0;
+}
+
+/*
+ In sub_4A8EB0
+
+ - BaseNormal extra checking Hook #1-2 -> Check allowed building
+*/
+DEFINE_HOOK(0x4A8FD7, DisplayClass_BuildingProximityCheck_BuildArea, 0x6)
+{
+ enum { SkipBuilding = 0x4A902C };
+
+ GET(BuildingClass*, pCellBuilding, ESI);
+
+ if (BuildingTypeExt::ExtMap.Find(pCellBuilding->Type)->NoBuildAreaOnBuildup && pCellBuilding->CurrentMission == Mission::Construction)
+ return SkipBuilding;
+
+ auto const& pBuildingsAllowed = BuildingTypeExt::ExtMap.Find(ProximityTemp::BuildType)->Adjacent_Allowed;
+
+ if (pBuildingsAllowed.size() > 0 && !pBuildingsAllowed.Contains(pCellBuilding->Type))
+ return SkipBuilding;
+
+ auto const& pBuildingsDisallowed = BuildingTypeExt::ExtMap.Find(ProximityTemp::BuildType)->Adjacent_Disallowed;
+
+ if (pBuildingsDisallowed.size() > 0 && pBuildingsDisallowed.Contains(pCellBuilding->Type))
+ return SkipBuilding;
+
+ return 0;
+}
+
+// Let the game do the proximity and shroud check when the cell which mouse is pointing at has not changed
+DEFINE_JUMP(LJMP, 0x4AACD9, 0x4AACF5);
+DEFINE_JUMP(LJMP, 0x4A9361, 0x4A9371);
+
+static inline bool IsSameFenceType(const BuildingTypeClass* const pPostType, const BuildingTypeClass* const pFenceType)
+{
+ if (const auto pSpecificType = BuildingTypeExt::ExtMap.Find(pPostType)->LaserFencePost_Fence.Get())
+ {
+ if (pSpecificType != pFenceType)
+ return false;
+ }
+ else
+ {
+ const auto count = BuildingTypeClass::Array.Count;
+
+ for (int i = 0; i < count; ++i)
+ {
+ const auto pSearchType = BuildingTypeClass::Array.Items[i];
+
+ if (pSearchType->LaserFence)
+ {
+ if (pSearchType != pFenceType)
+ return false;
+
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+static inline bool CheckCanNotExistHere(FootClass* const pTechno, HouseClass* const pOwner,
+ bool expand, bool& skipFlag, bool& builtOnCanBeBuiltOn, bool& landFootOnly, bool canBuildUnderUnits)
+{
+ if (pTechno == TechnoExt::Deployer)
+ {
+ skipFlag = true;
+ return false;
+ }
+
+ const auto pTechnoType = pTechno->GetTechnoType();
+
+ if (canBuildUnderUnits || TechnoTypeExt::ExtMap.Find(pTechnoType)->CanBeBuiltOn)
+ builtOnCanBeBuiltOn = true;
+ else if (!expand || pTechnoType->Speed <= 0 || !BuildingTypeExt::CheckOccupierCanLeave(pOwner, pTechno->Owner))
+ return true;
+ else
+ landFootOnly = true;
+
+ return false;
+}
+
+/*
+ In sub_47C620
+
+ - Buildable-upon TerrainTypes Hook #1 -> Allow placing buildings on top of them
+ - Buildable-upon TechnoTypes Hook #1 -> Rewrite and check whether allow placing buildings on top of them
+ - Customized Laser Fence Hook #1 -> Forbid placing laser fence post on inappropriate laser fence
+ - Fix DeploysInto Desync core Hook -> Exclude the specific unit who want to deploy
+*/
+DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
+{
+ enum { CanNotExistHere = 0x47C6D1, CanExistHere = 0x47C6A0 };
+
+ GET(const CellClass* const, pCell, EDI);
+ GET(const BuildingTypeClass* const, pBuildingType, EAX);
+ GET_STACK(HouseClass* const, pOwner, STACK_OFFSET(0x18, 0xC));
+
+ ProximityTemp::Exist = false;
+
+ if (!Game::IsActive)
+ return CanExistHere;
+
+ const bool expand = RulesExt::Global()->ExtendedBuildingPlacing.Get();
+ const bool canBuildUnderUnits = BuildingTypeExt::ExtMap.Find(pBuildingType)->CanBuildUnderUnits.Get();
+ bool landFootOnly = false;
+
+ if (pBuildingType->LaserFence)
+ {
+ for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject)
+ {
+ switch (pObject->WhatAmI())
+ {
+ case AbstractType::Building:
+ {
+ if (!TechnoTypeExt::ExtMap.Find(static_cast(pObject)->Type)->CanBeBuiltOn)
+ return CanNotExistHere;
+
+ break;
+ }
+
+ case AbstractType::Terrain:
+ {
+ if (!TerrainTypeExt::ExtMap.Find(static_cast(pObject)->Type)->CanBeBuiltOn)
+ return CanNotExistHere;
+
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+ }
+ else if (pBuildingType->LaserFencePost || pBuildingType->Gate)
+ {
+ bool skipFlag = TechnoExt::Deployer ? TechnoExt::Deployer->CurrentMapCoords == pCell->MapCoords : false;
+ bool builtOnCanBeBuiltOn = false;
+
+ for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject)
+ {
+ switch (pObject->WhatAmI())
+ {
+ case AbstractType::Aircraft:
+ {
+ if (!canBuildUnderUnits && !TechnoTypeExt::ExtMap.Find(static_cast(pObject)->Type)->CanBeBuiltOn)
+ return CanNotExistHere;
+
+ builtOnCanBeBuiltOn = true;
+ break;
+ }
+
+ case AbstractType::Building:
+ {
+ const auto pBuilding = static_cast(pObject);
+ const auto pType = pBuilding->Type;
+
+ if (TechnoTypeExt::ExtMap.Find(pType)->CanBeBuiltOn)
+ builtOnCanBeBuiltOn = true;
+ else if (pOwner != pBuilding->Owner || !pType->LaserFence)
+ return CanNotExistHere;
+ else if (pBuildingType->LaserFencePost && !IsSameFenceType(pBuildingType, pType))
+ return CanNotExistHere;
+
+ break;
+ }
+
+ case AbstractType::Infantry:
+ case AbstractType::Unit:
+ {
+ if (CheckCanNotExistHere(static_cast(pObject), pOwner, expand, skipFlag, builtOnCanBeBuiltOn, landFootOnly, canBuildUnderUnits))
+ return CanNotExistHere;
+
+ break;
+ }
+
+ case AbstractType::Terrain:
+ {
+ if (!TerrainTypeExt::ExtMap.Find(static_cast(pObject)->Type)->CanBeBuiltOn)
+ return CanNotExistHere;
+
+ builtOnCanBeBuiltOn = true;
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ if (!landFootOnly && !builtOnCanBeBuiltOn && (pCell->OccupationFlags & (skipFlag ? 0x1F : 0x3F)))
+ {
+ if (expand)
+ landFootOnly = true;
+ else
+ return CanNotExistHere;
+ }
+ }
+ else if (pBuildingType->ToTile)
+ {
+ const auto isoTileTypeIndex = pCell->IsoTileTypeIndex;
+
+ if (isoTileTypeIndex >= 0 && isoTileTypeIndex < IsometricTileTypeClass::Array.Count && !IsometricTileTypeClass::Array.Items[isoTileTypeIndex]->Morphable)
+ return CanNotExistHere;
+
+ for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject)
+ {
+ if (const auto pBuilding = abstract_cast(pObject))
+ {
+ if (!TechnoTypeExt::ExtMap.Find(pBuilding->Type)->CanBeBuiltOn)
+ return CanNotExistHere;
+ }
+ }
+ }
+ else
+ {
+ bool skipFlag = TechnoExt::Deployer ? TechnoExt::Deployer->CurrentMapCoords == pCell->MapCoords : false;
+ bool builtOnCanBeBuiltOn = false;
+
+ for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject)
+ {
+ switch (pObject->WhatAmI())
+ {
+ case AbstractType::Aircraft:
+ {
+ if (canBuildUnderUnits)
+ {
+ builtOnCanBeBuiltOn = true;
+ break;
+ }
+
+ // No break
+ }
+ case AbstractType::Building:
+ {
+ if (!TechnoTypeExt::ExtMap.Find(pObject->GetTechnoType())->CanBeBuiltOn)
+ return CanNotExistHere;
+
+ builtOnCanBeBuiltOn = true;
+ break;
+ }
+
+ case AbstractType::Infantry:
+ case AbstractType::Unit:
+ {
+ if (CheckCanNotExistHere(static_cast(pObject), pOwner, expand, skipFlag, builtOnCanBeBuiltOn, landFootOnly, canBuildUnderUnits))
+ return CanNotExistHere;
+
+ break;
+ }
+
+ case AbstractType::Terrain:
+ {
+ if (!TerrainTypeExt::ExtMap.Find(static_cast(pObject)->Type)->CanBeBuiltOn)
+ return CanNotExistHere;
+
+ builtOnCanBeBuiltOn = true;
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ if (!landFootOnly && !builtOnCanBeBuiltOn && (pCell->OccupationFlags & (skipFlag ? 0x1F : 0x3F)))
+ {
+ if (expand)
+ landFootOnly = true;
+ else
+ return CanNotExistHere;
+ }
+ }
+
+ if (landFootOnly)
+ ProximityTemp::Exist = true;
+
+ return CanExistHere; // Continue check the overlays .etc
+}
+
+/*
+ In sub_47EC90
+
+ - Buildable-upon TechnoTypes Hook #2-1 -> Record cell before draw it then skip vanilla AltFlags check
+*/
+DEFINE_HOOK(0x47EEBC, CellClass_DrawPlaceGrid_RecordCell, 0x6)
+{
+ enum { DontDrawAlt = 0x47EF1A, DrawVanillaAlt = 0x47EED6 };
+
+ GET(CellClass* const, pCell, ESI);
+ GET(const bool, zero, EDX);
+
+ const BlitterFlags flags = BlitterFlags::Centered | BlitterFlags::bf_400;
+ ProximityTemp::CurrentCell = pCell;
+
+ if (!(pCell->AltFlags & AltCellFlags::ContainsBuilding))
+ {
+ if (!RulesExt::Global()->ExtendedBuildingPlacing)
+ {
+ R->EDX(flags | (zero ? BlitterFlags::Zero : BlitterFlags::Nonzero));
+ return DrawVanillaAlt;
+ }
+ else if (BuildingTypeClass* const pType = abstract_cast(DisplayClass::Instance.CurrentBuildingTypeCopy))
+ {
+ R->Stack(STACK_OFFSET(0x30, -0x1D), pCell->CanThisExistHere(pType->SpeedType, pType, HouseClass::CurrentPlayer));
+ R->EDX(flags | BlitterFlags::TransLucent75);
+ return DontDrawAlt;
+ }
+ }
+
+ R->EDX(flags | (zero ? BlitterFlags::Zero : BlitterFlags::Nonzero));
+ return DontDrawAlt;
+}
+
+static inline void ClearPlacingBuildingData(PlacingBuildingStruct* const pPlace)
+{
+ pPlace->Type = nullptr;
+ pPlace->DrawType = nullptr;
+ pPlace->Times = 0;
+ pPlace->Timer.Stop();
+ pPlace->TopLeft = CellStruct::Empty;
+ pPlace->PlaceType = 0;
+}
+
+static inline void ClearCurrentBuildingData(DisplayClass* const pDisplay)
+{
+ pDisplay->SetActiveFoundation(nullptr);
+ pDisplay->CurrentBuilding = nullptr;
+ pDisplay->CurrentBuildingType = nullptr;
+ pDisplay->CurrentBuildingOwnerArrayIndexCopy = -1;
+
+ if (!Unsorted::ArmageddonMode)
+ {
+ reinterpret_cast(0x4A8D50)(pDisplay, nullptr); // Clear CurrentFoundationCopy_Data
+ pDisplay->CurrentBuildingCopy = nullptr;
+ pDisplay->CurrentBuildingTypeCopy = nullptr;
+ }
+}
+
+template
+static inline void PlayConstructionYardAnim(BuildingClass* const pFactory)
+{
+ const auto pFactoryType = pFactory->Type;
+
+ if (pFactoryType->ConstructionYard)
+ {
+ if constexpr (slam)
+ VocClass::PlayGlobal(RulesClass::Instance->BuildingSlam, 0x2000, 1.0);
+
+ pFactory->DestroyNthAnim(BuildingAnimSlot::PreProduction);
+ pFactory->DestroyNthAnim(BuildingAnimSlot::Idle);
+
+ const bool damaged = pFactory->GetHealthPercentage() <= RulesClass::Instance->ConditionYellow;
+ const auto pAnimName = damaged ? pFactoryType->BuildingAnim[8].Damaged : pFactoryType->BuildingAnim[8].Anim;
+
+ if (pAnimName && *pAnimName)
+ pFactory->PlayAnim(pAnimName, BuildingAnimSlot::Production, damaged, false);
+ }
+}
+
+static inline bool CheckBuildingFoundation(BuildingTypeClass* const pBuildingType, const CellStruct topLeftCell, HouseClass* const pHouse, bool& noOccupy)
+{
+ for (auto pFoundation = pBuildingType->GetFoundationData(true); *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation)
+ {
+ if (const auto pCell = MapClass::Instance.TryGetCellAt(topLeftCell + *pFoundation))
+ {
+ if (!pCell->CanThisExistHere(pBuildingType->SpeedType, pBuildingType, pHouse))
+ return false;
+ else if (ProximityTemp::Exist)
+ noOccupy = false;
+ }
+ }
+
+ ProximityTemp::Exist = false;
+ return true;
+}
+
+// Place Another Type Helper
+namespace PlaceTypeTemp
+{
+ size_t PlaceType = 0;
+}
+
+/*
+ In sub_4FB0E0
+
+ - Place Another Type Hook #1 -> Replace the factory product
+ - Buildable-upon TechnoTypes Hook #3 -> Hang up place event if there is only infantries and units on the cell
+ - Limbo Build Hook #1 -> Skip check limbo building
+*/
+DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5)
+{
+ enum { CanBuild = 0x4FB23C, TemporarilyCanNotBuild = 0x4FB5BA, CanNotBuild = 0x4FB35F, BuildSucceeded = 0x4FB649 };
+
+ GET(HouseClass* const, pHouse, EBP);
+ GET(TechnoClass* const, pTechno, ESI);
+ GET(FactoryClass* const, pPrimary, EBX);
+ GET(BuildingClass* const, pFactory, EDI);
+ GET_STACK(const CellStruct, topLeftCell, STACK_OFFSET(0x3C, 0x10));
+
+ if (pTechno->WhatAmI() != AbstractType::Building)
+ {
+ pFactory->SendCommand(RadioCommand::RequestLink, pTechno);
+
+ if (pTechno->Unlimbo(CoordStruct{ (topLeftCell.X << 8) + 128, (topLeftCell.Y << 8) + 128, 0 }, DirType::North))
+ return CanBuild;
+
+ ProximityTemp::Mouse = true;
+ return CanNotBuild;
+ }
+
+ const auto pDisplay = &DisplayClass::Instance;
+ auto pBuilding = static_cast(pTechno);
+ auto pBuildingType = pBuilding->Type;
+ const auto pBufferBuilding = pBuilding;
+ const auto pBufferType = pBuildingType;
+ const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pBuildingType);
+
+ if (pTypeExt->LimboBuild)
+ {
+ BuildingTypeExt::CreateLimboBuilding(pBuilding, pBuildingType, pHouse, pTypeExt->LimboBuildID);
+
+ if (pDisplay->CurrentBuilding == pBuilding && HouseClass::CurrentPlayer == pHouse)
+ ClearCurrentBuildingData(pDisplay);
+
+ PlayConstructionYardAnim(pFactory);
+ return BuildSucceeded;
+ }
+
+ const bool upgrade = pBuildingType->PowersUpBuilding[0];
+ const bool expand = RulesExt::Global()->ExtendedBuildingPlacing && !pBuildingType->PlaceAnywhere && !upgrade;
+ const size_t placeType = PlaceTypeTemp::PlaceType;
+
+ if (pTypeExt->PlaceBuilding_Extra)
+ {
+ if (const auto pSelectType = pTypeExt->GetAnotherPlacingType(((placeType >> 1u) & 0x1Fu), (placeType & 1u)))
+ pBuildingType = pSelectType;
+ }
+
+ bool revert = upgrade;
+
+ if (expand)
+ {
+ bool noOccupy = true;
+ bool canBuild = CheckBuildingFoundation(pBuildingType, topLeftCell, pHouse, noOccupy);
+ const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
+ auto& place = pBufferType->BuildCat != BuildCat::Combat ? pHouseExt->Common : pHouseExt->Combat;
+
+ do
+ {
+ if (canBuild)
+ {
+ if (noOccupy)
+ break; // Can Build
+
+ do
+ {
+ if (topLeftCell != place.TopLeft || pBufferType != place.Type) // New command
+ {
+ place.Type = pBufferType;
+ place.DrawType = pBuildingType;
+ place.Times = 30;
+ place.TopLeft = topLeftCell;
+ place.PlaceType = placeType;
+ }
+ else if (place.Times <= 0)
+ {
+ break; // Time out
+ }
+
+ if (!(place.Times % 5) && BuildingTypeExt::CleanUpBuildingSpace(pBuildingType, topLeftCell, pHouse))
+ break; // No place for cleaning
+
+ if (pHouse == HouseClass::CurrentPlayer && place.Times == 30)
+ ClearCurrentBuildingData(pDisplay);
+
+ --place.Times;
+ place.Timer.Start(8);
+
+ return TemporarilyCanNotBuild;
+ }
+ while (false);
+ }
+
+ revert = place.Times == 30 || !place.Type;
+ ClearPlacingBuildingData(&place);
+
+ if (revert)
+ ProximityTemp::Mouse = true;
+
+ return CanNotBuild;
+ }
+ while (false);
+
+ revert = !place.Type;
+ ClearPlacingBuildingData(&place);
+ }
+
+ if (pBufferType != pBuildingType)
+ pBuilding = static_cast(pBuildingType->CreateObject(pHouse));
+
+ pFactory->SendCommand(RadioCommand::RequestLink, pBuilding);
+
+ if (pBuilding->Unlimbo(CoordStruct{ (topLeftCell.X << 8) + 128, (topLeftCell.Y << 8) + 128, 0 }, DirType::North))
+ {
+ if (pBufferBuilding != pBuilding)
+ {
+ if (HouseClass::CurrentPlayer == pHouse)
+ {
+ if (!pDisplay->CurrentBuilding)
+ {
+ Phobos::Config::CurrentPlacingDirection = Phobos::Config::DefaultPlacingDirection;
+ }
+ else if (pDisplay->CurrentBuilding == pBufferBuilding)
+ {
+ pDisplay->CurrentBuilding = pBuilding;
+
+ Phobos::Config::CurrentPlacingDirection = Phobos::Config::DefaultPlacingDirection;
+ }
+
+ if (pDisplay->CurrentBuildingType == pBufferType)
+ pDisplay->CurrentBuildingType = pBuildingType;
+
+ if (pDisplay->CurrentBuildingCopy == pBufferBuilding)
+ pDisplay->CurrentBuildingCopy = pBuilding;
+
+ if (pDisplay->CurrentBuildingTypeCopy == pBufferType)
+ pDisplay->CurrentBuildingTypeCopy = pBuildingType;
+
+ if (Make_Global(0xB0FE5C) == pBufferBuilding) // Q
+ Make_Global(0xB0FE5C) = pBuilding;
+
+ if (Make_Global(0xB0FE60) == pBufferBuilding) // W
+ Make_Global(0xB0FE60) = pBuilding;
+ }
+
+ pBufferBuilding->UnInit();
+ pPrimary->Object = pBuilding;
+ R->ESI(pBuilding);
+ }
+ else if (HouseClass::CurrentPlayer == pHouse
+ && (!pDisplay->CurrentBuilding
+ || pDisplay->CurrentBuilding == pBuilding))
+ {
+ Phobos::Config::CurrentPlacingDirection = Phobos::Config::DefaultPlacingDirection;
+ }
+
+ return CanBuild;
+ }
+
+ if (pBufferBuilding != pBuilding)
+ pBuilding->UnInit();
+
+ if (revert)
+ ProximityTemp::Mouse = true;
+
+ return CanNotBuild;
+}
+
+/*
+ In sub_4A91B0
+
+ - Place Another Type Hook #2 -> Replace current building type for check
+*/
+DEFINE_HOOK(0x4A937D, DisplayClass_CallBuildingPlaceCheck_ReplaceBuildingType, 0x8)
+{
+ enum { SkipGameCode = 0x4A943A };
+
+ GET(const CellStruct, cell, EBP);
+
+ const auto pDisplay = &DisplayClass::Instance;
+
+ auto updateCurrentFoundation = [pDisplay, cell]()
+ {
+ if (pDisplay->CurrentFoundation_CenterCell != cell)
+ {
+ auto oldCell = pDisplay->CurrentFoundation_CenterCell;
+
+ if (oldCell != CellStruct::Empty)
+ {
+ oldCell += pDisplay->CurrentFoundation_TopLeftOffset;
+ pDisplay->MarkFoundation(&oldCell, false);
+ }
+
+ auto newCell = cell;
+
+ if (newCell != CellStruct::Empty)
+ {
+ newCell += pDisplay->CurrentFoundation_TopLeftOffset;
+ pDisplay->MarkFoundation(&newCell, true);
+ }
+
+ pDisplay->CurrentFoundation_CenterCell = cell;
+ }
+ };
+
+ const auto pCurrentBuilding = abstract_cast(pDisplay->CurrentBuilding);
+ const auto pTypeExt = pCurrentBuilding ? BuildingTypeExt::ExtMap.Find(pCurrentBuilding->Type) : nullptr;
+
+ if (pTypeExt && pTypeExt->PlaceBuilding_Extra)
+ {
+ if (!ScrollClass::Instance.unknown_byte_554A) // 555A: AnyMouseButtonDown
+ updateCurrentFoundation();
+ else // bp
+ R->EBP(pDisplay->CurrentFoundation_CenterCell.X);
+
+ const auto delta = cell - pDisplay->CurrentFoundation_CenterCell;
+
+ if (delta.Y || delta.X)
+ Phobos::Config::CurrentPlacingDirection = DirStruct(Math::atan2(-delta.Y, delta.X)).GetFacing<32>();
+
+ auto getAnotherPlacingType = [pDisplay, pTypeExt]() -> BuildingTypeClass*
+ {
+ if (pTypeExt)
+ {
+ const auto pCenterCell = MapClass::Instance.GetCellAt(pDisplay->CurrentFoundation_CenterCell);
+ const bool onWater = pCenterCell->LandType == LandType::Water;
+ return pTypeExt->GetAnotherPlacingType(Phobos::Config::CurrentPlacingDirection, onWater);
+ }
+
+ return nullptr;
+ };
+
+ if (const auto pAnotherType = getAnotherPlacingType())
+ {
+ if (pDisplay->CurrentBuildingType && pDisplay->CurrentBuildingType != pAnotherType)
+ {
+ pDisplay->CurrentBuildingType = pAnotherType;
+ pDisplay->SetActiveFoundation(pAnotherType->GetFoundationData(true));
+ }
+ }
+ else if (pCurrentBuilding)
+ {
+ if (pDisplay->CurrentBuildingType && pDisplay->CurrentBuildingType != pCurrentBuilding->Type)
+ {
+ pDisplay->CurrentBuildingType = pCurrentBuilding->Type;
+ pDisplay->SetActiveFoundation(pCurrentBuilding->Type->GetFoundationData(true));
+ }
+ }
+ }
+ else
+ {
+ updateCurrentFoundation();
+ }
+
+ return SkipGameCode;
+}
+
+/*
+ In sub_4AB9B0
+
+ - Place Another Type Hook #3 -> Keep current foundation center cell
+*/
+DEFINE_HOOK(0x4AB9FF, DisplayClass_LeftMouseButtonUp_MaintainCell, 0x6)
+{
+ enum { ContinueCheckUpgrade = 0x4ABA84, SkipCheckUpgrade = 0x4ABAA4 };
+
+ const auto pCellStruct = &DisplayClass::Instance.CurrentFoundation_CenterCell;
+
+ R->EBX(pCellStruct);
+
+ const auto pType = DisplayClass::Instance.CurrentBuildingType;
+
+ if (pType->WhatAmI() != AbstractType::BuildingType || !static_cast(pType)->PowersUpBuilding[0])
+ return SkipCheckUpgrade;
+
+ const auto pCellBuilding = MapClass::Instance.GetCellAt(*pCellStruct)->GetBuilding();
+
+ if (!pCellBuilding)
+ return SkipCheckUpgrade;
+ else
+ R->ESI(pCellBuilding);
+
+ return ContinueCheckUpgrade;
+}
+
+/*
+ In sub_4AB9B0
+
+ - Place Another Type Hook #4 -> Replace event
+*/
+DEFINE_HOOK(0x4ABAC0, DisplayClass_LeftMouseButtonUp_ReplaceBuildingType, 0x6)
+{
+ enum { SkipGameCode = 0x4ABBB3 };
+
+ const auto pDisplay = &DisplayClass::Instance;
+
+ const auto centerCell = pDisplay->CurrentFoundation_CenterCell;
+ const auto placeCell = centerCell + pDisplay->CurrentFoundation_TopLeftOffset;
+
+ const auto pPlace = pDisplay->CurrentBuilding;
+ const auto pType = pPlace->GetType(); // Should not use CurrentBuildingType
+ const auto pBuildingType = abstract_cast(pType);
+
+ int placeType = 0;
+
+ if (pBuildingType && BuildingTypeExt::ExtMap.Find(pBuildingType)->PlaceBuilding_Extra)
+ {
+ const auto pCenterCell = MapClass::Instance.GetCellAt(centerCell);
+ placeType |= pCenterCell->LandType == LandType::Water;
+ placeType |= (Phobos::Config::CurrentPlacingDirection << 1);
+ }
+ else
+ {
+ const auto pTechnoType = TechnoTypeExt::GetTechnoType(pType);
+ placeType |= (pTechnoType && pTechnoType->Naval);
+ }
+
+ const int arrayIndex = pType->GetArrayIndex();
+ const auto absType = pPlace->WhatAmI();
+
+ EventClass::OutList.Add(EventClass(
+ HouseClass::CurrentPlayer->ArrayIndex,
+ EventType::Place,
+ absType,
+ arrayIndex,
+ placeType,
+ placeCell
+ ));
+
+ return SkipGameCode;
+}
+
+/*
+ In sub_4C6CB0
+
+ - Place Another Type Hook #5 -> Replace placing action
+*/
+DEFINE_HOOK(0x4C70E1, EventClass_RespondToEvent_SetPlaceType, 0x8)
+{
+ enum { SkipGameCode = 0x4C7110 };
+
+ GET(EventClass* const, pThis, ESI);
+ GET(HouseClass* const, pHouse, EDI);
+
+ const auto cell = pThis->Place.Location;
+ const int flags = pThis->Place.IsNaval;
+ const bool isNaval = flags & 1;
+ const int arrayIndex = pThis->Place.HeapID;
+ const auto absType = pThis->Place.RTTIType;
+
+ if (absType == AbstractType::Building || absType == AbstractType::BuildingType)
+ PlaceTypeTemp::PlaceType = static_cast(flags);
+
+ reinterpret_cast
+ (0x4FB0E0)(pHouse, absType, arrayIndex, isNaval, &cell); // UnitFromFactory
+
+ PlaceTypeTemp::PlaceType = 0;
+
+ return SkipGameCode;
+}
+
+/*
+ In sub_4FB0E0
+
+ - Buildable-upon TechnoTypes Hook #4-1 -> Check whether need to skip the replace command
+*/
+DEFINE_HOOK(0x4FB395, HouseClass_UnitFromFactory_SkipMouseReturn, 0x6)
+{
+ enum { SkipGameCode = 0x4FB489, CheckMouseCoords = 0x4FB3E3 };
+
+ if (!RulesExt::Global()->ExtendedBuildingPlacing)
+ return 0;
+
+ if (ProximityTemp::Mouse)
+ {
+ ProximityTemp::Mouse = false;
+ return 0;
+ }
+
+ R->EBX(0);
+
+ if (!DisplayClass::Instance.CurrentBuildingTypeCopy)
+ return SkipGameCode;
+
+ R->ECX(DisplayClass::Instance.CurrentBuildingTypeCopy);
+ return CheckMouseCoords;
+}
+
+/*
+ In sub_4FB0E0
+
+ - Buildable-upon TechnoTypes Hook #4-2 -> Check whether need to skip the clear command
+*/
+DEFINE_HOOK(0x4FB339, HouseClass_UnitFromFactory_SkipMouseClear, 0x6)
+{
+ enum { SkipGameCode = 0x4FB4A0 };
+
+ GET(TechnoClass* const, pTechno, ESI);
+
+ if (RulesExt::Global()->ExtendedBuildingPlacing)
+ {
+ if (const auto pBuilding = abstract_cast(pTechno))
+ {
+ if (const auto pCurrentType = abstract_cast(DisplayClass::Instance.CurrentBuildingType))
+ {
+ if (!BuildingTypeExt::IsSameBuildingType(pBuilding->Type, pCurrentType))
+ return SkipGameCode;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ In sub_4FAA10
+
+ - Buildable-upon TechnoTypes Hook #4-3 -> Check whether need to skip the clear command
+*/
+DEFINE_HOOK(0x4FAB83, HouseClass_AbandonProductionOf_SkipMouseClear, 0x7)
+{
+ enum { SkipGameCode = 0x4FABA4 };
+
+ GET(const int, index, EBX);
+
+ if (RulesExt::Global()->ExtendedBuildingPlacing && index >= 0)
+ {
+ if (const auto pCurrentBuildingType = abstract_cast(DisplayClass::Instance.CurrentBuildingType))
+ {
+ if (!BuildingTypeExt::IsSameBuildingType(BuildingTypeClass::Array.Items[index], pCurrentBuildingType))
+ return SkipGameCode;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ In sub_4C9FF0
+
+ - Buildable-upon TechnoTypes Hook #5 -> Restart timer and clear buffer when abandon building production
+*/
+DEFINE_HOOK(0x4CA05B, FactoryClass_AbandonProduction_AbandonCurrentBuilding, 0x5)
+{
+ GET(FactoryClass*, pFactory, ESI);
+
+ if (RulesExt::Global()->ExtendedBuildingPlacing)
+ {
+ const auto pBuilding = abstract_cast(pFactory->Object);
+
+ if (!pBuilding)
+ return 0;
+
+ const auto pHouseExt = HouseExt::ExtMap.Find(pFactory->Owner);
+ auto& place = pBuilding->Type->BuildCat != BuildCat::Combat ? pHouseExt->Common : pHouseExt->Combat;
+ ClearPlacingBuildingData(&place);
+ }
+
+ return 0;
+}
+
+/*
+ In sub_443C60
+
+ - Buildable-upon TechnoTypes Hook #6 -> Try to clean up the building space when AI is building
+ - AIConstructionYard Hook #4-1 -> Prohibit AI from building construction yard and clean up invalid walls nodes.
+ - Limbo Build Hook #2 -> Skip check limbo building
+*/
+DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
+{
+ enum { CanBuild = 0x4452F0, TemporarilyCanNotBuild = 0x445237, CanNotBuild = 0x4454E6, BuildSucceeded = 0x4454D4, BuildFailed = 0x445696 };
+
+ GET(BaseNodeClass* const, pBaseNode, EBX);
+ GET(BuildingClass* const, pBuilding, EDI);
+ GET(BuildingClass* const, pFactory, ESI);
+ GET(const CellStruct, topLeftCell, EDX);
+
+ const auto pBuildingType = pBuilding->Type;
+
+ // Prohibit AI from building construction yard
+ if (RulesExt::Global()->AIForbidConYard && pBuildingType->ConstructionYard)
+ {
+ if (pBaseNode)
+ {
+ pBaseNode->Placed = true;
+ pBaseNode->Attempts = 0;
+ }
+
+ return BuildFailed;
+ }
+
+ // Clean up invalid walls nodes
+ if (RulesExt::Global()->AICleanWallNode && pBuildingType->Wall)
+ {
+ auto notValidWallNode = [topLeftCell]()
+ {
+ const auto pCell = MapClass::Instance.GetCellAt(topLeftCell);
+
+ for (int i = 0; i < 8; ++i)
+ {
+ if (const auto pAdjBuilding = pCell->GetNeighbourCell(static_cast(i))->GetBuilding())
+ {
+ if (pAdjBuilding->Type->ProtectWithWall)
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ if (notValidWallNode())
+ return CanNotBuild;
+ }
+
+ const auto pHouse = pFactory->Owner;
+ const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pBuildingType);
+
+ if (pTypeExt->LimboBuild)
+ {
+ BuildingTypeExt::CreateLimboBuilding(pBuilding, pBuildingType, pHouse, pTypeExt->LimboBuildID);
+
+ if (pBaseNode)
+ {
+ pBaseNode->Placed = true;
+ pBaseNode->Attempts = 0;
+
+ if (pHouse->ProducingBuildingTypeIndex == pBuildingType->ArrayIndex)
+ pHouse->ProducingBuildingTypeIndex = -1;
+ }
+
+ PlayConstructionYardAnim(pFactory);
+ return BuildSucceeded;
+ }
+
+ if (!RulesExt::Global()->ExtendedBuildingPlacing)
+ return 0;
+
+ if (topLeftCell != CellStruct::Empty && !pBuildingType->PlaceAnywhere)
+ {
+ if (!pBuildingType->PowersUpBuilding[0])
+ {
+ bool noOccupy = true;
+ bool canBuild = CheckBuildingFoundation(pBuildingType, topLeftCell, pHouse, noOccupy);
+ const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
+ auto& place = pBuildingType->BuildCat != BuildCat::Combat ? pHouseExt->Common : pHouseExt->Combat;
+
+ do
+ {
+ if (canBuild)
+ {
+ if (noOccupy)
+ break; // Can Build
+
+ do
+ {
+ if (topLeftCell != place.TopLeft || pBuildingType != place.Type) // New command
+ {
+ place.Type = pBuildingType;
+ place.DrawType = pBuildingType;
+ place.TopLeft = topLeftCell;
+ }
+
+ if (!place.Timer.HasTimeLeft())
+ {
+ place.Timer.Start(40);
+
+ if (BuildingTypeExt::CleanUpBuildingSpace(pBuildingType, topLeftCell, pHouse))
+ break; // No place for cleaning
+ }
+
+ return TemporarilyCanNotBuild;
+ }
+ while (false);
+ }
+
+ ClearPlacingBuildingData(&place);
+ return CanNotBuild;
+ }
+ while (false);
+
+ ClearPlacingBuildingData(&place);
+ }
+ else
+ {
+ const auto pCell = MapClass::Instance.GetCellAt(topLeftCell);
+ const auto pCellBuilding = pCell->GetBuilding();
+
+ if (!pCellBuilding || !reinterpret_cast(0x452670)(pCellBuilding, pBuildingType, pHouse)) // CanUpgradeBuilding
+ return CanNotBuild;
+ }
+ }
+
+ if (pBuilding->Unlimbo(CoordStruct{ (topLeftCell.X << 8) + 128, (topLeftCell.Y << 8) + 128, 0 }, DirType::North))
+ {
+ PlayConstructionYardAnim(pFactory);
+ return CanBuild;
+ }
+
+ return CanNotBuild;
+}
+
+static inline bool CanDrawGrid(bool draw)
+{
+ if (ProximityTemp::Exist)
+ {
+ ProximityTemp::Exist = false;
+ return false;
+ }
+
+ return draw;
+}
+
+// Laser fence use GetBuilding to check whether can build and draw, so no need to change
+
+/*
+ In sub_6D5C50
+
+ - Buildable-upon TechnoTypes Hook #8-1 -> Don't draw overlay wall grid when have occupiers
+*/
+DEFINE_HOOK(0x6D5D38, TacticalClass_DrawOverlayWallGrid_DisableWhenHaveTechnos, 0x8)
+{
+ enum { Valid = 0x6D5D40, Invalid = 0x6D5F0F };
+
+ GET(bool, valid, EAX);
+
+ return CanDrawGrid(valid) ? Valid : Invalid;
+}
+
+/*
+ In sub_6D59D0
+
+ - Buildable-upon TechnoTypes Hook #8-2 -> Don't draw firestorm wall grid when have occupiers
+*/
+DEFINE_HOOK(0x6D5A9D, TacticalClass_DrawFirestormWallGrid_DisableWhenHaveTechnos, 0x8)
+{
+ enum { Valid = 0x6D5AA5, Invalid = 0x6D5C2F };
+
+ GET(bool, valid, EAX);
+
+ return CanDrawGrid(valid) ? Valid : Invalid;
+}
+
+/*
+ In sub_588750
+
+ - Buildable-upon TechnoTypes Hook #8-3 -> Don't place overlay wall when have occupiers
+*/
+DEFINE_HOOK(0x588873, MapClass_BuildingToWall_DisableWhenHaveTechnos, 0x8)
+{
+ enum { Valid = 0x58887B, Invalid = 0x588935 };
+
+ GET(bool, valid, EAX);
+
+ return CanDrawGrid(valid) ? Valid : Invalid;
+}
+
+/*
+ In sub_588570
+
+ - Buildable-upon TechnoTypes Hook #8-4 -> Don't place firestorm wall when have occupiers
+*/
+DEFINE_HOOK(0x588664, MapClass_BuildingToFirestormWall_DisableWhenHaveTechnos, 0x8)
+{
+ enum { Valid = 0x58866C, Invalid = 0x588730 };
+
+ GET(bool, valid, EAX);
+
+ return CanDrawGrid(valid) ? Valid : Invalid;
+}
+
+/*
+ In sub_7393C0
+
+ - Buildable-upon TechnoTypes Hook #9-1 -> Try to clean up the building space when is deploying
+*/
+DEFINE_HOOK(0x73946C, UnitClass_TryToDeploy_CleanUpDeploySpace, 0x6)
+{
+ enum { CanDeploy = 0x73958A, TemporarilyCanNotDeploy = 0x73953B, CanNotDeploy = 0x7394E0 };
+
+ GET(UnitClass* const, pThis, EBP);
+ GET(CellStruct, topLeftCell, ESI);
+
+ if (!RulesExt::Global()->ExtendedBuildingPlacing)
+ return 0;
+
+ const auto pTechnoExt = TechnoExt::ExtMap.Find(pThis);
+ const auto pBuildingType = pThis->Type->DeploysInto;
+ const auto pHouseExt = HouseExt::ExtMap.Find(pThis->Owner);
+ auto& vec = pHouseExt->OwnedDeployingUnits;
+
+ if (pBuildingType->GetFoundationWidth() > 2 || pBuildingType->GetFoundationHeight(false) > 2)
+ topLeftCell -= CellStruct { 1, 1 };
+
+ R->Stack(STACK_OFFSET(0x28, -0x14), topLeftCell);
+
+ if (!pBuildingType->PlaceAnywhere)
+ {
+ bool noOccupy = true;
+ bool canBuild = CheckBuildingFoundation(pBuildingType, topLeftCell, pThis->Owner, noOccupy);
+
+ do
+ {
+ if (canBuild)
+ {
+ if (noOccupy)
+ break; // Can build
+
+ do
+ {
+ if (!pTechnoExt->UnitAutoDeployTimer.InProgress())
+ {
+ if (BuildingTypeExt::CleanUpBuildingSpace(pBuildingType, topLeftCell, pThis->Owner, pThis))
+ break; // No place for cleaning
+
+ if (vec.size() == 0 || std::find(vec.begin(), vec.end(), pThis) == vec.end())
+ vec.push_back(pThis);
+
+ pTechnoExt->UnitAutoDeployTimer.Start(40);
+ }
+
+ if (pThis->PrimaryFacing.Current().GetFacing<256>() != static_cast(pBuildingType->DeployFacing))
+ {
+ const auto pLoco = pThis->Locomotor;
+
+ if (!pLoco->Is_Moving_Now())
+ pLoco->Do_Turn(DirStruct(static_cast(pBuildingType->DeployFacing)));
+ }
+
+ return TemporarilyCanNotDeploy;
+ }
+ while (false);
+ }
+
+ if (vec.size() > 0)
+ vec.erase(std::remove(vec.begin(), vec.end(), pThis), vec.end());
+
+ pTechnoExt->UnitAutoDeployTimer.Stop();
+
+ return CanNotDeploy;
+ }
+ while (false);
+ }
+
+ if (vec.size() > 0)
+ vec.erase(std::remove(vec.begin(), vec.end(), pThis), vec.end());
+
+ pTechnoExt->UnitAutoDeployTimer.Stop();
+
+ return CanDeploy;
+}
+
+/*
+ In sub_73FD50
+
+ - Buildable-upon TechnoTypes Hook #9-2 -> Push the owner house into deploy check
+*/
+DEFINE_HOOK(0x73FF8F, UnitClass_MouseOverObject_ShowDeployCursor, 0x6)
+{
+ if (RulesExt::Global()->ExtendedBuildingPlacing) // This IF check is not so necessary
+ {
+ GET(const UnitClass* const, pUnit, ESI);
+ LEA_STACK(HouseClass**, pHousePtr, STACK_OFFSET(0x20, -0x20));
+ *pHousePtr = pUnit->Owner;
+ }
+
+ return 0;
+}
+
+/*
+ In sub_4C6CB0
+
+ - Buildable-upon TechnoTypes Hook #10 -> Stop deploy when get stop command
+*/
+DEFINE_HOOK(0x4C7665, EventClass_RespondToEvent_StopDeployInIdleEvent, 0x6)
+{
+ if (RulesExt::Global()->ExtendedBuildingPlacing) // This IF check is not so necessary
+ {
+ GET(const UnitClass* const, pUnit, ESI);
+
+ if (pUnit->Type->DeploysInto)
+ {
+ const auto mission = pUnit->CurrentMission;
+
+ if (mission == Mission::Guard || mission == Mission::Unload)
+ {
+ if (const auto pHouseExt = HouseExt::ExtMap.TryFind(pUnit->Owner))
+ {
+ auto& vec = pHouseExt->OwnedDeployingUnits;
+
+ if (vec.size() > 0)
+ vec.erase(std::remove(vec.begin(), vec.end(), pUnit), vec.end());
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ In sub_4F8440
+
+ - Buildable-upon TechnoTypes Hook #11 -> Check whether can place again in each house
+*/
+DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6)
+{
+ GET(HouseClass* const, pHouse, ESI);
+
+ if (!pHouse->IsControlledByHuman())
+ return 0;
+
+ if (!RulesExt::Global()->ExtendedBuildingPlacing)
+ return 0;
+
+ const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
+ auto buildCurrent = [&pHouse, &pHouseExt](BuildingTypeClass* pType, CellStruct cell, size_t placeType)
+ {
+ if (!pType)
+ return;
+
+ auto currentCanBuild = [&pHouse, &pType]() -> const bool
+ {
+ auto const bitsOwners = pType->GetOwners();
+
+ for(auto const& pConYard : pHouse->ConYards)
+ {
+ if (pConYard->InLimbo || !pConYard->HasPower || pConYard->Deactivated)
+ continue;
+
+ if (pConYard->CurrentMission == Mission::Selling || pConYard->QueuedMission == Mission::Selling)
+ continue;
+
+ const auto pConYardType = pConYard->Type;
+
+ if (pConYardType->Factory != AbstractType::BuildingType || !pConYardType->InOwners(bitsOwners))
+ continue;
+
+ return true;
+ }
+
+ return false;
+ };
+
+ if (!currentCanBuild())
+ {
+ ClearPlacingBuildingData(pType->BuildCat != BuildCat::Combat ? &pHouseExt->Common : &pHouseExt->Combat);
+
+ if (pHouse == HouseClass::CurrentPlayer)
+ VoxClass::Play(GameStrings::EVA_CannotDeployHere);
+ }
+ else if (pHouse == HouseClass::CurrentPlayer) // Prevent unexpected wrong event
+ {
+ const int place = static_cast(placeType);
+ const auto arrayIndex = pType->GetArrayIndex();
+ EventClass::OutList.Add(EventClass(pHouse->ArrayIndex, EventType::Place, AbstractType::Building, arrayIndex, place, cell));
+ }
+ };
+
+ auto& commonPlace = pHouseExt->Common;
+
+ if (commonPlace.Timer.Completed())
+ {
+ commonPlace.Timer.Stop();
+ buildCurrent(commonPlace.Type, commonPlace.TopLeft, commonPlace.PlaceType);
+ }
+
+ auto& combatPlace = pHouseExt->Combat;
+
+ if (combatPlace.Timer.Completed())
+ {
+ combatPlace.Timer.Stop();
+ buildCurrent(combatPlace.Type, combatPlace.TopLeft, combatPlace.PlaceType);
+ }
+
+ if (pHouseExt->OwnedDeployingUnits.size() > 0)
+ {
+ auto& vec = pHouseExt->OwnedDeployingUnits;
+
+ for (auto it = vec.begin(); it != vec.end(); )
+ {
+ const auto pUnit = *it;
+
+ if (!pUnit->InLimbo
+ && pUnit->IsOnMap
+ && !pUnit->IsSinking
+ && pUnit->Owner == pHouse
+ && !pUnit->Destination
+ && !pUnit->ParasiteEatingMe
+ && !pUnit->TemporalTargetingMe
+ && pUnit->Type->DeploysInto)
+ {
+ const auto mission = pUnit->CurrentMission;
+
+ if (mission == Mission::Unload)
+ {
+ ++it;
+ continue;
+ }
+ else if (mission == Mission::Guard)
+ {
+ if (const auto pExt = TechnoExt::ExtMap.Find(pUnit))
+ {
+ if (!(pExt->UnitAutoDeployTimer.GetTimeLeft() % 8))
+ pUnit->QueueMission(Mission::Unload, true);
+
+ ++it;
+ continue;
+ }
+ }
+ }
+
+ it = vec.erase(it);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ In sub_6D5030
+
+ - Buildable-upon TechnoTypes Hook #12 -> Draw the placing building preview
+*/
+DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6)
+{
+ if (!RulesExt::Global()->ExtendedBuildingPlacing)
+ return 0;
+
+ const auto pPlayer = HouseClass::CurrentPlayer;
+ const auto pDisplay = &DisplayClass::Instance;
+
+ auto drawImage = [&pDisplay](BuildingTypeClass* const pType, HouseClass* const pHouse, const CellStruct cell)
+ {
+ const auto pCell = pDisplay->TryGetCellAt(cell);
+
+ if (!pCell || cell == CellStruct::Empty || pType->PowersUpBuilding[0])
+ return;
+
+ auto pImage = pType->LoadBuildup();
+ int imageFrame = 0;
+
+ if (pImage)
+ imageFrame = ((pImage->Frames / 2) - 1);
+ else
+ pImage = pType->GetImage();
+
+ if (!pImage)
+ return;
+
+ BlitterFlags blitFlags = BlitterFlags::TransLucent75 | BlitterFlags::Centered | BlitterFlags::Nonzero | BlitterFlags::MultiPass;
+ auto rect = DSurface::Temp->GetRect();
+ rect.Height -= 32;
+ auto point = TacticalClass::Instance->CoordsToClient(CellClass::Cell2Coord(cell, (1 + pCell->GetFloorHeight(Point2D::Empty)))).first;
+ point.Y -= 15;
+
+ const auto ColorSchemeIndex = pHouse->ColorSchemeIndex;
+ const auto Palettes = pType->Palette;
+ const auto pColor = Palettes ? Palettes->Items[ColorSchemeIndex] : ColorScheme::Array.Items[ColorSchemeIndex];
+ DSurface::Temp->DrawSHP(pColor->LightConvert, pImage, imageFrame, &point, &rect, blitFlags, 0, 0, ZGradient::Ground, 1000, 0, nullptr, 0, 0, 0);
+ };
+
+ for (const auto& pHouse : HouseClass::Array)
+ {
+ if (pPlayer->IsObserver() || pHouse->IsAlliedWith(pPlayer))
+ {
+ const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
+
+ if (const auto pType = abstract_cast(pDisplay->CurrentBuildingTypeCopy))
+ drawImage(pType, pHouse, (pDisplay->CurrentFoundationCopy_TopLeftOffset + pDisplay->CurrentFoundationCopy_CenterCell));
+
+ if (const auto pType = pHouseExt->Common.DrawType)
+ drawImage(pType, pHouse, pHouseExt->Common.TopLeft);
+
+ if (const auto pType = pHouseExt->Combat.DrawType)
+ drawImage(pType, pHouse, pHouseExt->Combat.TopLeft);
+
+ if (pHouseExt->OwnedDeployingUnits.size() <= 0)
+ continue;
+
+ for (const auto& pUnit : pHouseExt->OwnedDeployingUnits)
+ {
+ const auto pType = pUnit->Type->DeploysInto;
+
+ if (!pType)
+ continue;
+
+ auto displayCell = CellClass::Coord2Cell(pUnit->GetCoords()); // pUnit->GetMapCoords();
+
+ if (pType->GetFoundationWidth() > 2 || pType->GetFoundationHeight(false) > 2)
+ displayCell -= CellStruct { 1, 1 };
+
+ drawImage(pType, pHouse, displayCell);
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ In sub_6A8B30
+
+ - Limbo Build Hook #3 -> Automatic spawn buildings
+*/
+DEFINE_HOOK(0x6A8E34, StripClass_Update_AutoBuildBuildings, 0x7)
+{
+ enum { SkipGameCode = 0x6A8F49 };
+
+ GET(BuildingClass* const, pBuilding, ESI);
+
+ if (BuildingTypeExt::BuildLimboBuilding(pBuilding))
+ return SkipGameCode;
+
+ return 0;
+}
+
+/*
+ In sub_42EB50
+
+ - Limbo Build Hook #4 -> Check Base Node
+ - AIConstructionYard Hook #4-2 -> Prohibit AI from building construction yard
+*/
+DEFINE_HOOK(0x42EB8E, BaseClass_GetBaseNodeIndex_CheckValidBaseNode, 0x6)
+{
+ enum { Valid = 0x42EBC3, Invalid = 0x42EBAE };
+
+ GET(BaseClass* const, pBase, ESI);
+ GET(BaseNodeClass* const, pBaseNode, EAX);
+
+ if (pBaseNode->Placed)
+ {
+ const auto index = pBaseNode->BuildingTypeIndex;
+
+ if (index >= 0 && index < BuildingTypeClass::Array.Count)
+ {
+ const auto pType = BuildingTypeClass::Array.Items[index];
+
+ if ((pType->ConstructionYard && RulesExt::Global()->AIForbidConYard) || BuildingTypeExt::ExtMap.Find(pType)->LimboBuild)
+ return Invalid;
+ }
+ }
+
+ return reinterpret_cast(0x50CAD0)(pBase->Owner, pBaseNode) ? Valid : Invalid;
+}
+
+/*
+ In sub_453060
+
+ - Customized Laser Fence Hook #2 -> Select the specific laser fence type
+*/
+DEFINE_HOOK(0x452E2C, BuildingClass_CreateLaserFence_FindSpecificIndex, 0x5)
+{
+ enum { SkipGameCode = 0x452E50 };
+
+ GET(BuildingClass* const, pThis, EDI);
+
+ if (const auto pFenceType = BuildingTypeExt::ExtMap.Find(pThis->Type)->LaserFencePost_Fence.Get())
+ {
+ if (pFenceType->LaserFence)
+ {
+ R->EBP(pFenceType->ArrayIndex);
+ R->EAX(BuildingTypeClass::Array.Count);
+ return SkipGameCode;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ In sub_440580
+
+ - Customized Laser Fence Hook #3 -> Skip uninit inappropriate laser fence
+*/
+DEFINE_HOOK(0x440AE9, BuildingClass_Unlimbo_SkipUninitFence, 0x7)
+{
+ enum { SkipGameCode = 0x440B07 };
+
+ GET(BuildingClass* const, pFence, EDI);
+ GET(BuildingClass* const, pThis, ESI);
+
+ return IsSameFenceType(pThis->Type, pFence->Type) ? 0 : SkipGameCode;
+}
+
+static inline bool IsMatchedPostType(const BuildingTypeClass* const pThisType, const BuildingTypeClass* const pPostType)
+{
+ const auto pThisTypeExt = BuildingTypeExt::ExtMap.Find(pThisType);
+ const auto pPostTypeExt = BuildingTypeExt::ExtMap.Find(pPostType);
+
+ return pThisTypeExt->LaserFencePost_Fence.Get() == pPostTypeExt->LaserFencePost_Fence.Get();
+}
+
+/*
+ In sub_452BB0
+
+ - Customized Laser Fence Hook #4 -> Only accept specific fence post
+*/
+DEFINE_HOOK(0x452CB4, BuildingClass_FindLaserFencePost_CheckLaserFencePost, 0x7)
+{
+ enum { SkipGameCode = 0x452D2C };
+
+ GET(BuildingClass* const, pPost, ESI);
+ GET(BuildingClass* const, pThis, EDI);
+
+ return IsMatchedPostType(pThis->Type, pPost->Type) ? 0 : SkipGameCode;
+}
+
+/*
+ In sub_6D5730
+
+ - Customized Laser Fence Hook #5 -> Break draw inappropriate laser fence grids
+*/
+DEFINE_HOOK(0x6D5815, TacticalClass_DrawLaserFenceGrid_SkipDrawLaserFence, 0x6)
+{
+ enum { SkipGameCode = 0x6D59A6 };
+
+ GET(BuildingTypeClass* const, pPostType, ECX);
+
+ // Have used CurrentBuilding->Type yet, so simply use static_cast
+ return IsMatchedPostType(static_cast(DisplayClass::Instance.CurrentBuilding)->Type, pPostType) ? 0 : SkipGameCode;
+}
+
+/*
+ In sub_45C300
+
+ - Fix foundation outside Hook -> Fix incorrect building type's FoundationOutside
+*/
+DEFINE_HOOK(0x45DD04, BuildingTypeClass_InitFoundationOutsides, 0x5)
+{
+ auto& foundationOutsides = Make_Global(0x89D368);
+ constexpr auto CellStruct_OutsideEnd = CellStruct { 0x7FFF, 0x7FFF };
+
+ // 4x3 remove incorrect (3,2), (3,1), (3,0) and add missing (4,3), (4,2), (4,1), (4,0), (4,-1)
+ {
+ auto& outside4x3 = foundationOutsides[12];
+ outside4x3[6] = CellStruct { 4, 2 };
+ outside4x3[8] = CellStruct { 4, 1 };
+ outside4x3[10] = CellStruct { 4, 0 };
+ outside4x3[16] = CellStruct { 4, -1 };
+ outside4x3[17] = CellStruct { 4, 3 };
+ outside4x3[18] = CellStruct_OutsideEnd;
+ }
+
+ // 2x6 add missing (-1,4) and (2,4)
+ {
+ auto& outside2x6 = foundationOutsides[15];
+ outside2x6[18] = CellStruct { -1, 4 };
+ outside2x6[19] = CellStruct { 2, 4 };
+ outside2x6[20] = CellStruct_OutsideEnd;
+ }
+
+ // 2x5 add missing (-1,4) and (2,4)
+ {
+ auto& outside2x5 = foundationOutsides[16];
+ outside2x5[16] = CellStruct { -1, 4 };
+ outside2x5[17] = CellStruct { 2, 4 };
+ outside2x5[18] = CellStruct_OutsideEnd;
+ }
+
+ // 4x4 remove duplicate (-1,4) and (4,4)
+ {
+ auto& outside4x4 = foundationOutsides[18];
+ outside4x4[16] = CellStruct { 4, 3 };
+ outside4x4[20] = CellStruct_OutsideEnd;
+ outside4x4[21] = CellStruct::Empty;
+ outside4x4[22] = CellStruct::Empty;
+ }
+
+ // 3x4 add missing (-1,3) and (3,3)
+ {
+ auto& outside3x4 = foundationOutsides[19];
+ outside3x4[16] = CellStruct { -1, 3 };
+ outside3x4[17] = CellStruct { 3, 3 };
+ outside3x4[18] = CellStruct_OutsideEnd;
+ }
+
+ // 6x4 fix the entire error without (2,-1)
+ {
+ auto& outside6x4 = foundationOutsides[20];
+ outside6x4[1] = CellStruct { -1, -1 };
+ outside6x4[2] = CellStruct { 0, -1 };
+ outside6x4[3] = CellStruct { 1, -1 };
+ outside6x4[4] = CellStruct { 3, -1 };
+ outside6x4[5] = CellStruct { 4, -1 };
+ outside6x4[6] = CellStruct { 5, -1 };
+ outside6x4[7] = CellStruct { 6, -1 };
+ outside6x4[8] = CellStruct { -1, 0 };
+ outside6x4[9] = CellStruct { 6, 0 };
+ outside6x4[10] = CellStruct { -1, 1 };
+ outside6x4[11] = CellStruct { 6, 1 };
+ outside6x4[12] = CellStruct { -1, 2 };
+ outside6x4[13] = CellStruct { 6, 2 };
+ outside6x4[14] = CellStruct { -1, 3 };
+ outside6x4[15] = CellStruct { 6, 3 };
+ outside6x4[16] = CellStruct { -1, 4 };
+ outside6x4[17] = CellStruct { 0, 4 };
+ outside6x4[18] = CellStruct { 1, 4 };
+ outside6x4[19] = CellStruct { 2, 4 };
+ outside6x4[20] = CellStruct { 3, 4 };
+ outside6x4[21] = CellStruct { 4, 4 };
+ outside6x4[22] = CellStruct { 5, 4 };
+ outside6x4[23] = CellStruct { 6, 4 };
+ outside6x4[24] = CellStruct_OutsideEnd;
+ }
+
+ return 0;
+}
diff --git a/src/Ext/BuildingType/Hooks.cpp b/src/Ext/BuildingType/Hooks.cpp
index d47fda7159..770b5a287e 100644
--- a/src/Ext/BuildingType/Hooks.cpp
+++ b/src/Ext/BuildingType/Hooks.cpp
@@ -2,6 +2,7 @@
#include
#include
+#include
#include
#include
@@ -70,32 +71,72 @@ DEFINE_HOOK(0x458623, BuildingClass_KillOccupiers_Replace_MuzzleFix, 0x7)
DEFINE_HOOK(0x6D528A, TacticalClass_DrawPlacement_PlacementPreview, 0x6)
{
- if (!RulesExt::Global()->PlacementPreview || !Phobos::Config::ShowPlacementPreview)
+ const auto pDisplay = &DisplayClass::Instance;
+ const auto pBuilding = abstract_cast(pDisplay->CurrentBuilding);
+
+ if (!pBuilding)
return 0;
- const auto pBuilding = specific_cast(DisplayClass::Instance.CurrentBuilding);
- const auto pType = pBuilding ? pBuilding->Type : nullptr;
- const auto pTypeExt = BuildingTypeExt::ExtMap.TryFind(pType);
- const bool isShow = pTypeExt && pTypeExt->PlacementPreview;
+ const auto pRulesExt = RulesExt::Global();
+ const auto pTactical = TacticalClass::Instance;
- if (isShow)
+ do
{
- CellClass* pCell = nullptr;
- {
- const CellStruct nDisplayCell = Make_Global(0x88095C);
- const CellStruct nDisplayCell_Offset = Make_Global(0x880960);
+ const auto pType = pBuilding->Type;
+ const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pType);
- pCell = MapClass::Instance.TryGetCellAt(nDisplayCell + nDisplayCell_Offset);
- if (!pCell)
- return 0;
- }
+ if (!pTypeExt->PlaceBuilding_Extra)
+ break;
+
+ const auto pShape = pTypeExt->PlaceBuilding_DirectionShape.Get();
+
+ if (!pShape || pShape->Frames <= 0)
+ break;
+
+ const auto pCell = MapClass::Instance.GetCellAt(pDisplay->CurrentFoundation_CenterCell);
+ const auto& types = pCell->LandType == LandType::Water ? pTypeExt->PlaceBuilding_OnWater : pTypeExt->PlaceBuilding_OnLand;
+ const size_t size = types.size();
+
+ if (!size)
+ break;
+
+ constexpr BlitterFlags blit = BlitterFlags::Centered | BlitterFlags::TransLucent50 | BlitterFlags::bf_400 | BlitterFlags::Zero;
+ const auto pPalette = pTypeExt->PlaceBuilding_DirectionPalette.GetOrDefaultConvert(FileSystem::PALETTE_PAL);
+
+ const auto location = CoordStruct { (pCell->MapCoords.X << 8), (pCell->MapCoords.Y << 8), 0 };
+ const int height = pCell->Level * 15;
+ const int zAdjust = -height - (pCell->SlopeIndex ? 12 : 2);
+ const auto position = pTactical->CoordsToScreen(location) - pTactical->TacticalPos - Point2D { 0, (1 + height) };
+
+ const auto direction = (Phobos::Config::CurrentPlacingDirection + (16u / size)) & 0x1Fu;
+ const int frameIndex = Math::min(static_cast(pShape->Frames - 1), static_cast(direction * size / 32u));
+
+ DSurface::Temp->DrawSHP(pPalette, pShape, frameIndex, &position,
+ &DSurface::ViewBounds, blit, 0, zAdjust, ZGradient::Ground, 1000, 0, 0, 0, 0, 0);
+ }
+ while (false);
+
+ if (!pRulesExt->PlacementPreview || !Phobos::Config::ShowPlacementPreview)
+ return 0;
+
+ // DrawType
+ const auto pType = abstract_cast(pDisplay->CurrentBuildingType);
+ const auto pTypeExt = pType ? BuildingTypeExt::ExtMap.Find(pType) : nullptr;
+
+ if (pTypeExt && pTypeExt->PlacementPreview)
+ {
+ const auto pCell = MapClass::Instance.TryGetCellAt(pDisplay->CurrentFoundation_CenterCell + pDisplay->CurrentFoundation_TopLeftOffset);
+
+ if (!pCell)
+ return 0;
int nImageFrame = 0;
- SHPStruct* pImage = pTypeExt->PlacementPreview_Shape.GetSHP();
+ auto pImage = pTypeExt->PlacementPreview_Shape.GetSHP();
{
if (!pImage)
{
pImage = pType->LoadBuildup();
+
if (pImage)
nImageFrame = ((pImage->Frames / 2) - 1);
else
@@ -112,26 +153,26 @@ DEFINE_HOOK(0x6D528A, TacticalClass_DrawPlacement_PlacementPreview, 0x6)
Point2D point;
{
const CoordStruct offset = pTypeExt->PlacementPreview_Offset;
- const int nHeight = offset.Z + pCell->GetFloorHeight({ 0, 0 });
- const CoordStruct coords = CellClass::Cell2Coord(pCell->MapCoords, nHeight);
+ const int height = offset.Z + pCell->GetFloorHeight({ 0, 0 });
+ const CoordStruct coords = CellClass::Cell2Coord(pCell->MapCoords, height);
- point = TacticalClass::Instance->CoordsToClient(coords).first;
+ point = pTactical->CoordsToClient(coords).first;
point.X += offset.X;
point.Y += offset.Y;
}
- const BlitterFlags blitFlags = pTypeExt->PlacementPreview_Translucency.Get(RulesExt::Global()->PlacementPreview_Translucency) |
- BlitterFlags::Centered | BlitterFlags::Nonzero | BlitterFlags::MultiPass;
+ const BlitterFlags blitFlags = pTypeExt->PlacementPreview_Translucency.Get(pRulesExt->PlacementPreview_Translucency)
+ | BlitterFlags::Centered | BlitterFlags::Nonzero | BlitterFlags::MultiPass;
- ConvertClass* pPalette = pTypeExt->PlacementPreview_Remap.Get()
+ const auto pPalette = pTypeExt->PlacementPreview_Remap.Get()
? pBuilding->GetDrawer()
: pTypeExt->PlacementPreview_Palette.GetOrDefaultConvert(FileSystem::UNITx_PAL);
- DSurface* pSurface = DSurface::Temp;
- RectangleStruct rect = pSurface->GetRect();
+ const auto pSurface = DSurface::Temp;
+ auto rect = pSurface->GetRect();
rect.Height -= 32; // account for bottom bar
- CC_Draw_Shape(pSurface, pPalette, pImage, nImageFrame, &point, &rect, blitFlags,
+ pSurface->DrawSHP(pPalette, pImage, nImageFrame, &point, &rect, blitFlags,
0, 0, ZGradient::Ground, 1000, 0, nullptr, 0, 0, 0);
}
@@ -194,49 +235,6 @@ DEFINE_HOOK(0x5F5416, ObjectClass_ReceiveDamage_CanC4DamageRounding, 0x6)
return SkipGameCode;
}
-#pragma region BuildingProximity
-
-namespace ProximityTemp
-{
- BuildingTypeClass* pType = nullptr;
-}
-
-DEFINE_HOOK(0x4A8F20, DisplayClass_BuildingProximityCheck_SetContext, 0x5)
-{
- GET(BuildingTypeClass*, pType, ESI);
-
- ProximityTemp::pType = pType;
-
- return 0;
-}
-
-DEFINE_HOOK(0x4A8FD7, DisplayClass_BuildingProximityCheck_BuildArea, 0x6)
-{
- enum { SkipBuilding = 0x4A902C };
-
- GET(BuildingClass*, pCellBuilding, ESI);
-
- auto const pTypeExt = BuildingTypeExt::ExtMap.Find(pCellBuilding->Type);
-
- if (pTypeExt->NoBuildAreaOnBuildup && pCellBuilding->CurrentMission == Mission::Construction)
- return SkipBuilding;
-
- auto const pTmpTypeExt = BuildingTypeExt::ExtMap.Find(ProximityTemp::pType);
- auto const& pBuildingsAllowed = pTmpTypeExt->Adjacent_Allowed;
-
- if (pBuildingsAllowed.size() > 0 && !pBuildingsAllowed.Contains(pCellBuilding->Type))
- return SkipBuilding;
-
- auto const& pBuildingsDisallowed = pTmpTypeExt->Adjacent_Disallowed;
-
- if (pBuildingsDisallowed.size() > 0 && pBuildingsDisallowed.Contains(pCellBuilding->Type))
- return SkipBuilding;
-
- return 0;
-}
-
-#pragma endregion
-
DEFINE_HOOK(0x6FE3F1, TechnoClass_FireAt_OccupyDamageBonus, 0xB)
{
enum { ApplyDamageBonus = 0x6FE405 };
diff --git a/src/Ext/Bullet/Body.cpp b/src/Ext/Bullet/Body.cpp
index 97278e359f..52df43ede5 100644
--- a/src/Ext/Bullet/Body.cpp
+++ b/src/Ext/Bullet/Body.cpp
@@ -3,6 +3,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -394,6 +395,38 @@ void BulletExt::ApplyArcingFix(BulletClass* pThis, const CoordStruct& sourceCoor
}
}
+// Detonate weapon/warhead using master bullet instance.
+void BulletExt::Detonate(const CoordStruct& coords, TechnoClass* pOwner, int damage, HouseClass* pFiringHouse, AbstractClass* pTarget, bool isBright, WeaponTypeClass* pWeapon, WarheadTypeClass* pWarhead)
+{
+ auto pBullet = ScenarioExt::Global()->MasterDetonationBullet;
+
+ if (pWeapon)
+ {
+ pBullet->Type = pWeapon->Projectile;
+ pBullet->SetWeaponType(pWeapon);
+ }
+ else
+ {
+ pBullet->Type = BulletTypeExt::GetDefaultBulletType();
+ }
+
+ pBullet->Owner = pOwner;
+ pBullet->Health = damage;
+ pBullet->Target = pTarget;
+ pBullet->WH = pWarhead;
+ pBullet->Bright = isBright;
+
+ if (pFiringHouse)
+ {
+ auto const pBulletExt = BulletExt::ExtMap.Find(pBullet);
+ pBulletExt->FirerHouse = pFiringHouse;
+ }
+
+ pBullet->SetLocation(coords);
+ pBullet->Explode(true);
+}
+
+
// =============================
// load / save
diff --git a/src/Ext/Bullet/Body.h b/src/Ext/Bullet/Body.h
index 2430325a39..df7c92b280 100644
--- a/src/Ext/Bullet/Body.h
+++ b/src/Ext/Bullet/Body.h
@@ -72,6 +72,7 @@ class BulletExt
static ExtContainer ExtMap;
+ static void Detonate(const CoordStruct& coords, TechnoClass* pOwner, int damage, HouseClass* pFiringHouse, AbstractClass* pTarget, bool isBright, WeaponTypeClass* pWeapon, WarheadTypeClass* pWarhead);
static void ApplyArcingFix(BulletClass* pThis, const CoordStruct& sourceCoords, const CoordStruct& targetCoords, BulletVelocity& velocity);
static void SimulatedFiringUnlimbo(BulletClass* pBullet, HouseClass* pHouse, WeaponTypeClass* pWeapon, const CoordStruct& sourceCoords, bool randomVelocity);
diff --git a/src/Ext/Bullet/Hooks.cpp b/src/Ext/Bullet/Hooks.cpp
index 586e3802bf..d62c818c49 100644
--- a/src/Ext/Bullet/Hooks.cpp
+++ b/src/Ext/Bullet/Hooks.cpp
@@ -2,6 +2,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -364,17 +365,20 @@ DEFINE_HOOK(0x467CCA, BulletClass_AI_TargetSnapChecks, 0x6)
DEFINE_HOOK(0x468E61, BulletClass_Explode_TargetSnapChecks1, 0x6)
{
- enum { SkipChecks = 0x468E7B };
+ enum { Snap = 0x468E7B, SkipChecks = 0x468FF4 };
GET(BulletClass*, pThis, ESI);
+ if (pThis == ScenarioExt::Global()->MasterDetonationBullet)
+ return SkipChecks;
+
auto const pType = pThis->Type;
// Do not require Airburst=no to check target snapping for Inviso / Trajectory=Straight projectiles
if (pType->Inviso)
{
R->EAX(pType);
- return SkipChecks;
+ return Snap;
}
else if (pType->Arcing || pType->ROT > 0)
{
@@ -386,7 +390,7 @@ DEFINE_HOOK(0x468E61, BulletClass_Explode_TargetSnapChecks1, 0x6)
if (pExt->Trajectory && CheckTrajectoryCanNotAlwaysSnap(pExt->Trajectory->Flag()) && !pExt->SnappedToTarget)
{
R->EAX(pType);
- return SkipChecks;
+ return Snap;
}
return 0;
@@ -394,17 +398,20 @@ DEFINE_HOOK(0x468E61, BulletClass_Explode_TargetSnapChecks1, 0x6)
DEFINE_HOOK(0x468E9F, BulletClass_Explode_TargetSnapChecks2, 0x6)
{
- enum { SkipInitialChecks = 0x468EC7, SkipSetCoordinate = 0x468F23 };
+ enum { SkipInitialChecksOnly = 0x468EC7, SkipSetCoordinate = 0x468F23, SkipChecks = 0x468FF4 };
GET(BulletClass*, pThis, ESI);
+ if (pThis == ScenarioExt::Global()->MasterDetonationBullet)
+ return SkipChecks;
+
auto const pType = pThis->Type;
// Do not require EMEffect=no & Airburst=no to check target coordinate snapping for Inviso projectiles.
if (pType->Inviso)
{
R->EAX(pType);
- return SkipInitialChecks;
+ return SkipInitialChecksOnly;
}
else if (pType->Arcing || pType->ROT > 0)
{
diff --git a/src/Ext/House/Body.cpp b/src/Ext/House/Body.cpp
index cf2dceb1b2..3e84701205 100644
--- a/src/Ext/House/Body.cpp
+++ b/src/Ext/House/Body.cpp
@@ -629,6 +629,9 @@ void HouseExt::ExtData::Serialize(T& Stm)
.Process(this->PowerPlantEnhancers)
.Process(this->OwnedLimboDeliveredBuildings)
.Process(this->OwnedCountedHarvesters)
+ .Process(this->OwnedDeployingUnits)
+ .Process(this->Common)
+ .Process(this->Combat)
.Process(this->LimboAircraft)
.Process(this->LimboBuildings)
.Process(this->LimboInfantry)
diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h
index 8f99bffc31..eb333ed9d9 100644
--- a/src/Ext/House/Body.h
+++ b/src/Ext/House/Body.h
@@ -9,6 +9,16 @@
#include