From 13c9ba665353ca41a399b2ed002658017eea90b7 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Thu, 26 Dec 2024 18:50:52 +0800
Subject: [PATCH 01/58] Core
---
CREDITS.md | 1 +
Phobos.vcxproj | 1 +
docs/Fixed-or-Improved-Logics.md | 21 +
docs/New-or-Enhanced-Logics.md | 18 +
docs/Whats-New.md | 1 +
src/Ext/BuildingType/Body.cpp | 534 +++++++++-
src/Ext/BuildingType/Body.h | 15 +
src/Ext/BuildingType/Hooks.Placing.cpp | 1308 ++++++++++++++++++++++++
src/Ext/BuildingType/Hooks.cpp | 42 -
src/Ext/House/Body.cpp | 5 +
src/Ext/House/Body.h | 11 +
src/Ext/House/Hooks.cpp | 6 +
src/Ext/Rules/Body.cpp | 3 +
src/Ext/Rules/Body.h | 4 +
src/Ext/SWType/FireSuperWeapon.cpp | 75 +-
src/Ext/Techno/Body.cpp | 9 +-
src/Ext/Techno/Body.h | 2 +
src/Ext/TechnoType/Body.cpp | 5 +
src/Ext/TechnoType/Body.h | 4 +
src/Ext/TerrainType/Hooks.Passable.cpp | 117 ---
20 files changed, 1948 insertions(+), 234 deletions(-)
create mode 100644 src/Ext/BuildingType/Hooks.Placing.cpp
diff --git a/CREDITS.md b/CREDITS.md
index 4c788fe55f..b6b5e8a618 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -372,6 +372,7 @@ This page lists all the individual contributions to the project by their author.
- Allow to change the speed of gas particles
- **CrimRecya**
- Fix `LimboKill` not working reliably
+ - Building placing and deploying logic enhancement
- **Ollerus**
- Build limit group enhancement
- Customizable rocker amplitude
diff --git a/Phobos.vcxproj b/Phobos.vcxproj
index 79495c5f4c..09275d7e6f 100644
--- a/Phobos.vcxproj
+++ b/Phobos.vcxproj
@@ -166,6 +166,7 @@
+
diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md
index 6d56a1369f..35adb563f6 100644
--- a/docs/Fixed-or-Improved-Logics.md
+++ b/docs/Fixed-or-Improved-Logics.md
@@ -1023,6 +1023,27 @@ ForbidParallelAIQueues.Building=no ; boolean
ForbidParallelAIQueues=false ; boolean
```
+### Custom 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.
+
+In `rulesmd.ini`:
+```ini
+[SOMETECHNO] ; TechnoType
+CanBeBuiltOn=false ; boolean
+```
+
## Terrains
### Animated TerrainTypes
diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md
index e553a34732..5a8905d100 100644
--- a/docs/New-or-Enhanced-Logics.md
+++ b/docs/New-or-Enhanced-Logics.md
@@ -581,6 +581,24 @@ 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 `ExpandBuildingPlace` 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 non-player 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. Note that when the building has been recorded and is trying to place, unless the production queue has vanished (such as construction yard is no longer exist), it will continue to function normally until the conditions are not met.
+ - `AutoUpgrade` controls whether building upgrades can be automatically placed on the correct and earliest built building.
+ - `LimboBuild` controls whether building can be automatically placed like `LimboDelivery`.
+ - `LimboBuildID` defines the numeric ID of the building placed by `LimboBuild`.
+
+In `rulesmd.ini`:
+```ini
+[General]
+ExpandBuildingPlace=false ; boolean
+
+[SOMEBUILDING] ; BuildingType
+AutoUpgrade=false ; boolean
+LimboBuild=false ; boolean
+LimboBuildID=-1 ; boolean
+```
+
## Infantry
### Customizable FLH When Infantry Is Prone Or Deployed
diff --git a/docs/Whats-New.md b/docs/Whats-New.md
index 37c9f39e2f..0ffd0a8b45 100644
--- a/docs/Whats-New.md
+++ b/docs/Whats-New.md
@@ -441,6 +441,7 @@ New:
- Allow customizing extra tint intensity for Iron Curtain & Force Shield (by Starkku)
- Option to enable parsing 8-bit RGB values from `[ColorAdd]` instead of RGB565 (by Starkku)
- Customizing height and speed at which subterranean units travel (by Starkku)
+- Building placing and deploying logic enhancement (by CrimRecya)
- Option for Warhead damage to penetrate Iron Curtain or Force Shield (by Starkku)
- Option for Warhead to remove all shield types at once (by Starkku)
- Allow customizing voxel light source position (by Kerbiter, Morton, based on knowledge of thomassnedon)
diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp
index 105956e045..fee7d4341a 100644
--- a/src/Ext/BuildingType/Body.cpp
+++ b/src/Ext/BuildingType/Body.cpp
@@ -1,8 +1,13 @@
#include "Body.h"
-#include
+#include
+#include
+
#include
+#include
+#include
#include
+#include
BuildingTypeExt::ExtContainer BuildingTypeExt::ExtMap;
@@ -97,6 +102,524 @@ int BuildingTypeExt::GetUpgradesAmount(BuildingTypeClass* pBuilding, HouseClass*
return isUpgrade ? result : -1;
}
+// Check whether can call the occupiers leave
+bool BuildingTypeExt::CheckOccupierCanLeave(HouseClass* pBuildingHouse, HouseClass* pOccupierHouse)
+{
+ if (!pOccupierHouse)
+ return false;
+ else if (pBuildingHouse == pOccupierHouse)
+ return true;
+ else if (SessionClass::IsCampaign() && pOccupierHouse->IsInPlayerControl)
+ return true;
+ else if (!pOccupierHouse->IsControlledByHuman() && pOccupierHouse->IsAlliedWith(pBuildingHouse))
+ 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->GetCellAt(currentCell))
+ {
+ auto pObject = pCell->FirstObject;
+
+ while (pObject)
+ {
+ const auto absType = pObject->WhatAmI();
+
+ if (absType == AbstractType::Infantry || absType == AbstractType::Unit)
+ {
+ const auto pCellTechno = static_cast(pObject);
+ const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pCellTechno->GetTechnoType());
+
+ if ((!pTypeExt || !pTypeExt->CanBeBuiltOn) && pCellTechno != pExceptTechno) // No need to check house
+ {
+ const auto pFoot = static_cast(pCellTechno);
+
+ if (pFoot->GetCurrentSpeed() <= 0 || (locomotion_cast(pFoot->Locomotor) && !pFoot->Locomotor->Is_Moving()))
+ {
+ if (absType == AbstractType::Infantry)
+ ++infantryCount.X;
+
+ checkedTechnos.push_back(pCellTechno);
+ }
+ }
+ }
+
+ pObject = pObject->NextObject;
+ }
+
+ 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->GetCellAt(searchCell))
+ {
+ if (std::find(checkedCells.begin(), checkedCells.end(), pSearchCell) == checkedCells.end() // TODO If there is a cellflag (or CellExt) that can be used …
+ && !pSearchCell->GetBuilding()
+ && 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
+ {
+ auto curA = static_cast(pTechnoA->GetMapCoords().DistanceFromSquared(pOptionalCell->MapCoords));
+
+ if (curA < minA)
+ minA = curA;
+ }
+
+ if (minB > 65536)
+ {
+ 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)
+ {
+ CellClass* pDestinationCell = nullptr;
+
+ // Step 4.2.1: Search the closest valid cell to be the destination.
+ do
+ {
+ const auto location = pCheckedTechno->GetMapCoords();
+ const bool isInfantry = pCheckedTechno->WhatAmI() == AbstractType::Infantry;
+ const auto pCheckedType = pCheckedTechno->GetTechnoType();
+
+ 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 (static_cast(pCheckedTechno)->Destination == infantryCell.position)
+ {
+ infantryCell.count = 3;
+ }
+ else if (infantryCell.count < 3 && infantryCell.position->IsClearToMove(pCheckedType->SpeedType, true, true, -1, pCheckedType->MovementZone, -1, false))
+ {
+ pDestinationCell = infantryCell.position;
+ ++infantryCell.count;
+
+ break;
+ }
+ }
+
+ if (pDestinationCell)
+ break; // Complete
+ }
+ }
+
+ std::sort(&optionalCells[0], &optionalCells[optionalCells.size()],[location](CellClass* pCellA, CellClass* pCellB){
+ return pCellA->MapCoords.DistanceFromSquared(location) < pCellB->MapCoords.DistanceFromSquared(location);
+ });
+ const auto minDistanceSquared = optionalCells[0]->MapCoords.DistanceFromSquared(location);
+
+ for (const auto& pOptionalCell : optionalCells) // Prioritize selecting empty cells
+ {
+ if (!pOptionalCell->FirstObject && pOptionalCell->IsClearToMove(pCheckedType->SpeedType, true, true, -1, pCheckedType->MovementZone, -1, false))
+ {
+ if (isInfantry) // Not need to remove it now
+ {
+ infantryCells.push_back(InfantryCountInCell{ pOptionalCell, 1 });
+ ++infantryCount.Y;
+ }
+
+ if (pOptionalCell->MapCoords.DistanceFromSquared(location) < (minDistanceSquared * 4)) // Empty cell is not too far
+ pDestinationCell = pOptionalCell;
+
+ break;
+ }
+ }
+
+ if (!pDestinationCell)
+ {
+ std::vector deleteCells;
+ deleteCells.reserve(8);
+
+ for (const auto& pOptionalCell : optionalCells)
+ {
+ auto pCurObject = pOptionalCell->FirstObject;
+ std::vector optionalTechnos;
+ optionalTechnos.reserve(4);
+ bool valid = true;
+
+ while (pCurObject)
+ {
+ const auto absType = pCurObject->WhatAmI();
+
+ if (absType == AbstractType::Infantry || absType == AbstractType::Unit)
+ {
+ const auto pCurTechno = static_cast(pCurObject);
+
+ if (!BuildingTypeExt::CheckOccupierCanLeave(pHouse, pCurTechno->Owner)) // Means invalid for all
+ {
+ deleteCells.push_back(pOptionalCell);
+ valid = false;
+ break;
+ }
+
+ optionalTechnos.push_back(pCurTechno);
+ }
+
+ pCurObject = pCurObject->NextObject;
+ }
+
+ if (valid && pOptionalCell->IsClearToMove(pCheckedType->SpeedType, true, true, -1, pCheckedType->MovementZone, -1, false))
+ {
+ for (const auto& pOptionalTechno : optionalTechnos)
+ {
+ reCheckedTechnos.push_back(pOptionalTechno);
+ }
+
+ if (isInfantry) // Not need to remove it now
+ {
+ infantryCells.push_back(InfantryCountInCell{ pOptionalCell, 1 });
+ ++infantryCount.Y;
+ }
+
+ pDestinationCell = pOptionalCell;
+ break;
+ }
+ }
+
+ for (const auto& pDeleteCell : deleteCells) // Mark the invalid cells
+ {
+ checkedCells.push_back(pDeleteCell);
+ optionalCells.erase(std::remove(optionalCells.begin(), optionalCells.end(), pDeleteCell), optionalCells.end());
+ }
+ }
+ }
+ while (false);
+
+ // Step 4.2.2: Mark the cell and push back its surrounded cells, then prepare for the command.
+ if (pDestinationCell)
+ {
+ 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->GetCellAt(searchCell))
+ {
+ if (std::find(checkedCells.begin(), checkedCells.end(), pSearchCell) == checkedCells.end()
+ && std::find(optionalCells.begin(), optionalCells.end(), pSearchCell) == optionalCells.end()
+ && !pSearchCell->GetBuilding()
+ && 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);
+ }
+ }
+ }
+
+ const auto thisOrder = TechnoWithDestination { pCheckedTechno, pDestinationCell };
+ finalOrder.push_back(thisOrder);
+ }
+ else // Can not build
+ {
+ return true;
+ }
+ }
+
+ checkedTechnos.clear();
+ }
+ while (reCheckedTechnos.size());
+
+ // Step 5: Confirm command execution.
+ for (const auto& pThisOrder : finalOrder)
+ {
+ const auto pCheckedTechno = pThisOrder.techno;
+ const auto pDestinationCell = pThisOrder.destination;
+ const auto absType = pCheckedTechno->WhatAmI();
+ pCheckedTechno->ForceMission(Mission::Guard);
+
+ if (absType == AbstractType::Infantry)
+ {
+ const auto pInfantry = static_cast(pCheckedTechno);
+
+ if (pInfantry->IsDeployed())
+ pInfantry->PlayAnim(Sequence::Undeploy, true);
+
+ pInfantry->SetDestination(pDestinationCell, false);
+ pInfantry->QueueMission(Mission::QMove, false); // To force every three infantries gather together, it should be QMove
+ }
+ else if (absType == AbstractType::Unit)
+ {
+ const auto pUnit = static_cast(pCheckedTechno);
+
+ if (pUnit->Deployed)
+ pUnit->Undeploy();
+
+ pUnit->SetDestination(pDestinationCell, false);
+ pUnit->QueueMission(Mission::Move, false);
+ }
+ }
+
+ return false;
+}
+
+bool BuildingTypeExt::AutoUpgradeBuilding(BuildingClass* pBuilding)
+{
+ const auto pBuildingType = pBuilding->Type;
+
+ if (!pBuildingType->PowersUpBuilding[0])
+ return false;
+
+ if (const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pBuildingType))
+ {
+ if (pTypeExt->AutoUpgrade)
+ {
+ const auto pHouse = pBuilding->Owner;
+ const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
+
+ for (const auto& pOwned : pHouse->Buildings)
+ {
+ if (reinterpret_cast(0x452670)(pOwned, pBuildingType, pHouse)) // CanUpgradeBuilding
+ {
+ if (pOwned->IsAlive && pOwned->Health > 0 && pOwned->IsOnMap && !pOwned->InLimbo && pOwned->CurrentMission != Mission::Selling)
+ {
+ const auto cell = pOwned->GetMapCoords();
+
+ if (cell != CellStruct::Empty && !pHouseExt->OwnsLimboDeliveredBuilding(pOwned))
+ {
+ const EventClass event
+ (
+ pHouse->ArrayIndex,
+ EventType::Place,
+ AbstractType::Building,
+ pBuildingType->GetArrayIndex(),
+ pBuildingType->Naval,
+ cell
+ );
+ EventClass::AddEvent(event);
+
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+bool BuildingTypeExt::BuildLimboBuilding(BuildingClass* pBuilding)
+{
+ const auto pBuildingType = pBuilding->Type;
+
+ if (const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pBuildingType))
+ {
+ if (pTypeExt->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;
+
+ // 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 - Starkku
+ if (SessionClass::IsCampaign())
+ pBuilding->DiscoveredBy(HouseClass::CurrentPlayer);
+
+ pBuilding->DiscoveredBy(pOwner);
+
+ pOwner->RegisterGain(pBuilding, false);
+ pOwner->UpdatePower();
+ pOwner->RecheckTechTree = true;
+ pOwner->RecheckPower = true;
+ pOwner->RecheckRadar = 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);
+
+ if (!pBuilding->Type->Insignificant && !pBuilding->Type->DontScore)
+ pOwnerExt->AddToLimboTracking(pBuilding->Type);
+
+ 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()
{ }
@@ -149,6 +672,11 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->SellBuildupLength.Read(exINI, pSection, "SellBuildupLength");
this->IsDestroyableObstacle.Read(exINI, pSection, "IsDestroyableObstacle");
+ this->AutoUpgrade.Read(exINI, pSection, "AutoUpgrade");
+ this->LimboBuild.Read(exINI, pSection, "LimboBuild");
+ this->LimboBuildID.Read(exINI, pSection, "LimboBuildID");
+ this->LaserFencePost_Fence.Read(exINI, pSection, "LaserFencePost.Fence");
+
this->FactoryPlant_AllowTypes.Read(exINI, pSection, "FactoryPlant.AllowTypes");
this->FactoryPlant_DisallowTypes.Read(exINI, pSection, "FactoryPlant.DisallowTypes");
@@ -272,6 +800,10 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm)
.Process(this->ConsideredVehicle)
.Process(this->ZShapePointMove_OnBuildup)
.Process(this->SellBuildupLength)
+ .Process(this->AutoUpgrade)
+ .Process(this->LimboBuild)
+ .Process(this->LimboBuildID)
+ .Process(this->LaserFencePost_Fence)
.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 f7b00ae5e4..113cf45b89 100644
--- a/src/Ext/BuildingType/Body.h
+++ b/src/Ext/BuildingType/Body.h
@@ -64,6 +64,11 @@ class BuildingTypeExt
Valueable SellBuildupLength;
Valueable IsDestroyableObstacle;
+ Valueable AutoUpgrade;
+ Valueable LimboBuild;
+ Valueable LimboBuildID;
+ Valueable LaserFencePost_Fence;
+
std::vector> AircraftDockingDirs;
ValueableVector FactoryPlant_AllowTypes;
@@ -115,6 +120,10 @@ class BuildingTypeExt
, ConsideredVehicle {}
, ZShapePointMove_OnBuildup { false }
, SellBuildupLength { 23 }
+ , AutoUpgrade { false }
+ , LimboBuild { false }
+ , LimboBuildID { -1 }
+ , LaserFencePost_Fence {}
, AircraftDockingDirs {}
, FactoryPlant_AllowTypes {}
, FactoryPlant_DisallowTypes {}
@@ -165,4 +174,10 @@ class BuildingTypeExt
static int GetEnhancedPower(BuildingClass* pBuilding, HouseClass* pHouse);
static bool CanUpgrade(BuildingClass* pBuilding, BuildingTypeClass* pUpgradeType, HouseClass* pUpgradeOwner);
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 AutoUpgradeBuilding(BuildingClass* pBuilding);
+ 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
new file mode 100644
index 0000000000..82c7543f97
--- /dev/null
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -0,0 +1,1308 @@
+#include "Body.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+// Buildable-upon TerrainTypes Hook #2 -> sub_6D5730 - 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))
+ {
+ auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type);
+
+ if (pTypeExt->CanBeBuiltOn)
+ return ContinueChecks;
+
+ return DontDraw;
+ }
+
+ return ContinueChecks;
+}
+
+// Buildable-upon TerrainTypes Hook #3 -> sub_5683C0 - Remove them when buildings are placed on them.
+// Buildable-upon TechnoTypes Hook #7 -> sub_5683C0 - Remove some of them when buildings are placed on them.
+DEFINE_HOOK(0x5684B1, MapClass_PlaceDown_BuildableUponTypes, 0x6)
+{
+ GET(ObjectClass*, pObject, EDI);
+ GET(CellClass*, pCell, EAX);
+
+ if (pObject->WhatAmI() == AbstractType::Building)
+ {
+ auto pCellObject = pCell->FirstObject;
+
+ while (pCellObject)
+ {
+ const auto absType = pCellObject->WhatAmI();
+
+ if (absType == AbstractType::Infantry || absType == AbstractType::Unit || absType == AbstractType::Aircraft || absType == AbstractType::Building)
+ {
+ const auto pTechno = static_cast(pCellObject);
+ const auto pType = pTechno->GetTechnoType();
+ const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType);
+
+ if (pTypeExt && pTypeExt->CanBeBuiltOn)
+ {
+ pTechno->KillPassengers(nullptr);
+ pTechno->Stun();
+ pTechno->Limbo();
+ pTechno->UnInit();
+ }
+ }
+ else if (const auto pTerrain = abstract_cast(pCellObject))
+ {
+ const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type);
+
+ if (pTypeExt && pTypeExt->CanBeBuiltOn)
+ {
+ pCell->RemoveContent(pTerrain, false);
+ TerrainTypeExt::Remove(pTerrain);
+ }
+ }
+
+ pCellObject = pCellObject->NextObject;
+ }
+ }
+
+ return 0;
+}
+
+// Buildable-upon TerrainTypes Hook #4 -> sub_5FD270 - 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;
+
+ auto pCellObject = pCell->FirstObject;
+
+ while (pCellObject)
+ {
+ if (const auto pTerrain = abstract_cast(pCellObject))
+ {
+ const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type);
+
+ if (!pTypeExt || !pTypeExt->CanBeBuiltOn)
+ return NoUnlimbo;
+
+ pCell->RemoveContent(pTerrain, false);
+ TerrainTypeExt::Remove(pTerrain);
+ }
+
+ pCellObject = pCellObject->NextObject;
+ }
+
+ return Unlimbo;
+}
+
+// Buildable Proximity Helper
+namespace ProximityTemp
+{
+ bool Exist = false;
+ bool Mouse = false;
+ CellClass* CurrentCell = nullptr;
+ BuildingTypeClass* BuildType = nullptr;
+}
+
+// BaseNormal extra checking Hook #1-1 -> sub_4A8EB0 - Set context and clear up data
+DEFINE_HOOK(0x4A8F20, DisplayClass_BuildingProximityCheck_SetContext, 0x5)
+{
+ GET(BuildingTypeClass*, pType, ESI);
+
+ ProximityTemp::BuildType = pType;
+
+ return 0;
+}
+
+// BaseNormal extra checking Hook #1-3 -> sub_4A8EB0 - Check allowed building
+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& 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;
+}
+
+// Buildable-upon TerrainTypes Hook #1 -> sub_47C620 - Allow placing buildings on top of them
+// Buildable-upon TechnoTypes Hook #1 -> sub_47C620 - Rewrite and check whether allow placing buildings on top of them
+// Customized Laser Fence Hook #1 -> sub_47C620 - Forbid placing laser fence post on inappropriate laser fence
+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 auto expand = RulesExt::Global()->ExpandBuildingPlace.Get();
+ bool landFootOnly = false;
+
+ if (pBuildingType->LaserFence)
+ {
+ auto pObject = pCell->FirstObject;
+
+ while (pObject)
+ {
+ const auto absType = pObject->WhatAmI();
+
+ if (absType == AbstractType::Building)
+ {
+ const auto pBuilding = static_cast(pObject);
+ const auto pType = pBuilding->Type;
+ const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType);
+
+ if (!pTypeExt || !pTypeExt->CanBeBuiltOn)
+ return CanNotExistHere;
+ }
+ else if (const auto pTerrain = abstract_cast(pObject))
+ {
+ const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type);
+
+ if (!pTypeExt || !pTypeExt->CanBeBuiltOn)
+ return CanNotExistHere;
+ }
+
+ pObject = pObject->NextObject;
+ }
+ }
+ else if (pBuildingType->LaserFencePost || pBuildingType->Gate)
+ {
+ bool builtOnTechno = false;
+ auto pObject = pCell->FirstObject;
+
+ while (pObject)
+ {
+ const auto absType = pObject->WhatAmI();
+
+ if (absType == AbstractType::Aircraft)
+ {
+ const auto pAircraft = static_cast(pObject);
+ const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pAircraft->Type);
+
+ if (pTypeExt && pTypeExt->CanBeBuiltOn)
+ builtOnTechno = true;
+ else
+ return CanNotExistHere;
+ }
+ else if (absType == AbstractType::Building)
+ {
+ const auto pBuilding = static_cast(pObject);
+ const auto pType = pBuilding->Type;
+ const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType);
+
+ if (pTypeExt && pTypeExt->CanBeBuiltOn)
+ {
+ builtOnTechno = true;
+ }
+ else if (pOwner != pBuilding->Owner || !pType->LaserFence)
+ {
+ return CanNotExistHere;
+ }
+ else if (pBuildingType->LaserFencePost)
+ {
+ if (const auto pFenceType = BuildingTypeExt::ExtMap.Find(pBuildingType)->LaserFencePost_Fence.Get())
+ {
+ if (pFenceType != pType)
+ return CanNotExistHere;
+ }
+ else // Vanilla search
+ {
+ 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 != pType)
+ return CanNotExistHere;
+ else
+ break;
+ }
+ }
+ }
+ }
+ }
+ else if (absType == AbstractType::Infantry || absType == AbstractType::Unit)
+ {
+ const auto pTechno = static_cast(pObject);
+ const auto pTechnoType = pTechno->GetTechnoType();
+ const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pTechnoType);
+
+ if (pTypeExt && pTypeExt->CanBeBuiltOn)
+ builtOnTechno = true;
+ else if (!expand || pTechnoType->Speed <= 0 || !BuildingTypeExt::CheckOccupierCanLeave(pOwner, pTechno->Owner))
+ return CanNotExistHere;
+ else
+ landFootOnly = true;
+ }
+ else if (const auto pTerrain = abstract_cast(pObject))
+ {
+ const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type);
+
+ if (pTypeExt && pTypeExt->CanBeBuiltOn)
+ builtOnTechno = true;
+ else
+ return CanNotExistHere;
+ }
+
+ pObject = pObject->NextObject;
+ }
+
+ if (!landFootOnly && !builtOnTechno && (pCell->OccupationFlags & 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;
+
+ auto pObject = pCell->FirstObject;
+
+ while (pObject)
+ {
+ const auto absType = pObject->WhatAmI();
+
+ if (absType == AbstractType::Building)
+ {
+ const auto pBuilding = static_cast(pObject);
+ const auto pType = pBuilding->Type;
+ const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType);
+
+ if (!pTypeExt || !pTypeExt->CanBeBuiltOn)
+ return CanNotExistHere;
+ }
+
+ pObject = pObject->NextObject;
+ }
+ }
+ else
+ {
+ bool builtOnTechno = false;
+ auto pObject = pCell->FirstObject;
+
+ while (pObject)
+ {
+ const auto absType = pObject->WhatAmI();
+
+ if (absType == AbstractType::Aircraft || absType == AbstractType::Building)
+ {
+ const auto pTechno = static_cast(pObject);
+ const auto pTechnoType = pTechno->GetTechnoType();
+ const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pTechnoType);
+
+ if (pTypeExt && pTypeExt->CanBeBuiltOn)
+ builtOnTechno = true;
+ else
+ return CanNotExistHere;
+ }
+ else if (absType == AbstractType::Infantry || absType == AbstractType::Unit)
+ {
+ const auto pTechno = static_cast(pObject);
+ const auto pTechnoType = pTechno->GetTechnoType();
+ const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pTechnoType);
+
+ if (pTypeExt && pTypeExt->CanBeBuiltOn)
+ builtOnTechno = true;
+ else if (!expand || pTechnoType->Speed <= 0 || !BuildingTypeExt::CheckOccupierCanLeave(pOwner, pTechno->Owner))
+ return CanNotExistHere;
+ else
+ landFootOnly = true;
+ }
+ else if (const auto pTerrain = abstract_cast(pObject))
+ {
+ const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type);
+
+ if (pTypeExt && pTypeExt->CanBeBuiltOn)
+ builtOnTechno = true;
+ else
+ return CanNotExistHere;
+ }
+
+ pObject = pObject->NextObject;
+ }
+
+ if (!landFootOnly && !builtOnTechno && (pCell->OccupationFlags & 0x3F))
+ {
+ if (expand)
+ landFootOnly = true;
+ else
+ return CanNotExistHere;
+ }
+ }
+
+ if (landFootOnly)
+ ProximityTemp::Exist = true;
+
+ return CanExistHere; // Continue check the overlays .etc
+}
+
+// Buildable-upon TechnoTypes Hook #2-1 -> sub_47EC90 - 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()->ExpandBuildingPlace)
+ {
+ R->EDX(flags | (zero ? BlitterFlags::Zero : BlitterFlags::Nonzero));
+ return DrawVanillaAlt;
+ }
+ else if (BuildingTypeClass* const pType = abstract_cast(reinterpret_cast(DisplayClass::Instance->unknown_1194)))
+ {
+ 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;
+}
+
+// Buildable-upon TechnoTypes Hook #3 -> sub_4FB0E0 - Hang up place event if there is only infantries and units on the cell
+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(BuildingClass* const, pFactory, EDI);
+ GET_STACK(const CellStruct, topLeftCell, STACK_OFFSET(0x3C, 0x10));
+
+ if (pTechno->WhatAmI() == AbstractType::Building && RulesExt::Global()->ExpandBuildingPlace)
+ {
+ const auto pBuilding = static_cast(pTechno);
+ const auto pBuildingType = pBuilding->Type;
+ const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pBuildingType);
+ const auto pDisplay = DisplayClass::Instance();
+
+ if (pTypeExt->LimboBuild)
+ {
+ BuildingTypeExt::CreateLimboBuilding(pBuilding, pBuildingType, pHouse, pTypeExt->LimboBuildID);
+
+ if (pDisplay->CurrentBuilding == pBuilding && HouseClass::CurrentPlayer == pHouse)
+ {
+ pDisplay->SetActiveFoundation(nullptr);
+ pDisplay->CurrentBuilding = nullptr;
+ pDisplay->CurrentBuildingType = nullptr;
+ pDisplay->unknown_11AC = 0xFFFFFFFF;
+
+ if (!Unsorted::ArmageddonMode)
+ {
+ reinterpret_cast(0x4A8D50)(pDisplay, nullptr); // Clear CurrentFoundationCopy_Data
+ pDisplay->unknown_1190 = 0;
+ pDisplay->unknown_1194 = 0;
+ }
+ }
+
+ const auto pFactoryType = pFactory->Type;
+
+ if (pFactoryType->ConstructionYard)
+ {
+ 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);
+ }
+
+ return BuildSucceeded;
+ }
+ else
+ {
+ if (!pBuildingType->PlaceAnywhere && !pBuildingType->PowersUpBuilding[0])
+ {
+ const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
+ bool canBuild = true;
+ bool noOccupy = true;
+
+ for (auto pFoundation = pBuildingType->GetFoundationData(false); *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation)
+ {
+ const auto currentCoord = topLeftCell + *pFoundation;
+ const auto pCell = pDisplay->GetCellAt(currentCoord);
+
+ if (!pCell->CanThisExistHere(pBuildingType->SpeedType, pBuildingType, pHouse))
+ {
+ canBuild = false;
+ break;
+ }
+ else if (ProximityTemp::Exist)
+ {
+ noOccupy = false;
+ }
+ }
+
+ ProximityTemp::Exist = false;
+
+ do
+ {
+ if (canBuild)
+ {
+ if (noOccupy)
+ break; // Can Build
+
+ do
+ {
+ if (topLeftCell != pHouseExt->CurrentBuildingTopLeft || pBuildingType != pHouseExt->CurrentBuildingType) // New command
+ {
+ pHouseExt->CurrentBuildingType = pBuildingType;
+ pHouseExt->CurrentBuildingTimes = 30;
+ pHouseExt->CurrentBuildingTopLeft = topLeftCell;
+ }
+ else if (pHouseExt->CurrentBuildingTimes <= 0)
+ {
+ break; // Time out
+ }
+
+ if (!(pHouseExt->CurrentBuildingTimes % 5) && BuildingTypeExt::CleanUpBuildingSpace(pBuildingType, topLeftCell, pHouse))
+ break; // No place for cleaning
+
+ if (pHouse == HouseClass::CurrentPlayer && pHouseExt->CurrentBuildingTimes == 30)
+ {
+ pDisplay->SetActiveFoundation(nullptr);
+ pDisplay->CurrentBuilding = nullptr;
+ pDisplay->CurrentBuildingType = nullptr;
+ pDisplay->unknown_11AC = 0xFFFFFFFF;
+
+ if (!Unsorted::ArmageddonMode)
+ {
+ reinterpret_cast(0x4A8D50)(pDisplay, nullptr); // Clear CurrentFoundationCopy_Data
+ pDisplay->unknown_1190 = 0;
+ pDisplay->unknown_1194 = 0;
+ }
+ }
+
+ --pHouseExt->CurrentBuildingTimes;
+ pHouseExt->CurrentBuildingTimer.Start(8);
+
+ return TemporarilyCanNotBuild;
+ }
+ while (false);
+ }
+
+ if (pHouseExt->CurrentBuildingType == pBuildingType)
+ {
+ pHouseExt->CurrentBuildingType = nullptr;
+ pHouseExt->CurrentBuildingTimes = 0;
+ pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
+ pHouseExt->CurrentBuildingTimer.Stop();
+
+ if (pHouseExt->CurrentBuildingTimes != 30)
+ return CanNotBuild;
+ }
+
+ ProximityTemp::Mouse = true;
+ return CanNotBuild;
+ }
+ while (false);
+
+ if (pHouseExt->CurrentBuildingType == pBuildingType)
+ {
+ pHouseExt->CurrentBuildingType = nullptr;
+ pHouseExt->CurrentBuildingTimes = 0;
+ pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
+ pHouseExt->CurrentBuildingTimer.Stop();
+ }
+ }
+ }
+ }
+
+ 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;
+}
+
+// Buildable-upon TechnoTypes Hook #4-1 -> sub_4FB0E0 - Check whether need to skip the replace command
+DEFINE_HOOK(0x4FB395, HouseClass_UnitFromFactory_SkipMouseReturn, 0x6)
+{
+ enum { SkipGameCode = 0x4FB489 };
+
+ if (!RulesExt::Global()->ExpandBuildingPlace)
+ return 0;
+
+ if (ProximityTemp::Mouse)
+ {
+ ProximityTemp::Mouse = false;
+ return 0;
+ }
+
+ R->EBX(0);
+ return SkipGameCode;
+}
+
+// Buildable-upon TechnoTypes Hook #4-2 -> sub_4FB0E0 - Check whether need to skip the clear command
+DEFINE_HOOK(0x4FB319, HouseClass_UnitFromFactory_SkipMouseClear, 0x5)
+{
+ enum { SkipGameCode = 0x4FB4A0 };
+
+ GET(TechnoClass* const, pTechno, ESI);
+
+ if (const auto pBuilding = abstract_cast(pTechno))
+ {
+ if (RulesExt::Global()->ExpandBuildingPlace && reinterpret_cast(DisplayClass::Instance->unknown_1194) != pBuilding->Type)
+ return SkipGameCode;
+ }
+
+ return 0;
+}
+
+// Buildable-upon TechnoTypes Hook #4-3 -> sub_4FB0E0 - Check whether need to skip the clear command
+DEFINE_HOOK(0x4FAB83, HouseClass_AbandonProductionOf_SkipMouseClear, 0x7)
+{
+ enum { SkipGameCode = 0x4FABA4 };
+
+ GET(const int, index, EBX);
+
+ return (RulesExt::Global()->ExpandBuildingPlace && index >= 0 && BuildingTypeClass::Array->Items[index] != DisplayClass::Instance->CurrentBuildingType) ? SkipGameCode : 0;
+}
+
+// Buildable-upon TechnoTypes Hook #5 -> sub_4C9FF0 - Restart timer and clear buffer when abandon building production
+DEFINE_HOOK(0x4CA05B, FactoryClass_AbandonProduction_AbandonCurrentBuilding, 0x5)
+{
+ GET(FactoryClass*, pFactory, ESI);
+
+ if (RulesExt::Global()->ExpandBuildingPlace)
+ {
+ const auto pHouseExt = HouseExt::ExtMap.Find(pFactory->Owner);
+
+ if (!pHouseExt || !pHouseExt->CurrentBuildingType)
+ return 0;
+
+ const auto pTechno = pFactory->Object;
+
+ if (pTechno->WhatAmI() != AbstractType::Building || pHouseExt->CurrentBuildingType != static_cast(pTechno)->Type)
+ return 0;
+
+ pHouseExt->CurrentBuildingType = nullptr;
+ pHouseExt->CurrentBuildingTimes = 0;
+ pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
+ pHouseExt->CurrentBuildingTimer.Stop();
+ }
+
+ return 0;
+}
+
+// Buildable-upon TechnoTypes Hook #6 -> sub_443C60 - Try to clean up the building space when AI is building
+DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
+{
+ enum { CanBuild = 0x4452F0, TemporarilyCanNotBuild = 0x445237, CanNotBuild = 0x4454E6, BuildSucceeded = 0x4454D4 };
+
+ GET(BaseNodeClass* const, pBaseNode, EBX);
+ GET(BuildingClass* const, pBuilding, EDI);
+ GET(BuildingClass* const, pFactory, ESI);
+ GET(const CellStruct, topLeftCell, EDX);
+
+ if (!RulesExt::Global()->ExpandBuildingPlace)
+ return 0;
+
+ const auto pBuildingType = pBuilding->Type;
+
+ if (topLeftCell != CellStruct::Empty && !pBuildingType->PlaceAnywhere)
+ {
+ 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;
+ }
+
+ const auto pFactoryType = pFactory->Type;
+
+ if (pFactoryType->ConstructionYard)
+ {
+ 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);
+ }
+
+ return BuildSucceeded;
+ }
+ else if (!pBuildingType->PowersUpBuilding[0])
+ {
+ const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
+ bool canBuild = true;
+ bool noOccupy = true;
+
+ for (auto pFoundation = pBuildingType->GetFoundationData(false); *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation)
+ {
+ const auto currentCoord = topLeftCell + *pFoundation;
+ const auto pCell = MapClass::Instance->GetCellAt(currentCoord);
+
+ if (!pCell->CanThisExistHere(pBuildingType->SpeedType, pBuildingType, pHouse))
+ {
+ canBuild = false;
+ break;
+ }
+ else if (ProximityTemp::Exist)
+ {
+ noOccupy = false;
+ }
+ }
+
+ ProximityTemp::Exist = false;
+
+ do
+ {
+ if (canBuild)
+ {
+ if (noOccupy)
+ break; // Can Build
+
+ do
+ {
+ if (topLeftCell != pHouseExt->CurrentBuildingTopLeft || pBuildingType != pHouseExt->CurrentBuildingType) // New command
+ {
+ pHouseExt->CurrentBuildingType = pBuildingType;
+ pHouseExt->CurrentBuildingTimes = 30;
+ pHouseExt->CurrentBuildingTopLeft = topLeftCell;
+ }
+ else if (pHouseExt->CurrentBuildingTimes <= 0)
+ {
+ // This will be different from what vanilla do, but the vanilla way will still be used if in campaign
+ if (!SessionClass::IsCampaign())
+ break; // Time out
+
+ pHouseExt->CurrentBuildingTimes = 30;
+ }
+
+ if (!pHouseExt->CurrentBuildingTimer.HasTimeLeft())
+ {
+ pHouseExt->CurrentBuildingTimes -= 5;
+ pHouseExt->CurrentBuildingTimer.Start(40);
+
+ if (BuildingTypeExt::CleanUpBuildingSpace(pBuildingType, topLeftCell, pHouse))
+ break; // No place for cleaning
+ }
+
+ return TemporarilyCanNotBuild;
+ }
+ while (false);
+ }
+
+ pHouseExt->CurrentBuildingType = nullptr;
+ pHouseExt->CurrentBuildingTimes = 0;
+ pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
+ pHouseExt->CurrentBuildingTimer.Stop();
+
+ return CanNotBuild;
+ }
+ while (false);
+
+ pHouseExt->CurrentBuildingType = nullptr;
+ pHouseExt->CurrentBuildingTimes = 0;
+ pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
+ pHouseExt->CurrentBuildingTimer.Stop();
+ }
+ else if (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))
+ {
+ const auto pFactoryType = pFactory->Type;
+
+ if (pFactoryType->ConstructionYard)
+ {
+ 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);
+ }
+
+ return CanBuild;
+ }
+
+ return CanNotBuild;
+}
+
+// Laser fence use GetBuilding to check whether can build and draw, so no need to change
+// Buildable-upon TechnoTypes Hook #8-1 -> sub_6D5C50 - 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);
+
+ if (ProximityTemp::Exist)
+ {
+ ProximityTemp::Exist = false;
+ return Invalid;
+ }
+
+ return valid ? Valid : Invalid;
+}
+
+// Buildable-upon TechnoTypes Hook #8-2 -> sub_6D59D0 - 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);
+
+ if (ProximityTemp::Exist)
+ {
+ ProximityTemp::Exist = false;
+ return Invalid;
+ }
+
+ return valid ? Valid : Invalid;
+}
+
+// Buildable-upon TechnoTypes Hook #8-3 -> sub_588750 - Don't place overlay wall when have occupiers
+DEFINE_HOOK(0x588873, MapClass_BuildingToWall_DisableWhenHaveTechnos, 0x8)
+{
+ enum { Valid = 0x58887B, Invalid = 0x588935 };
+
+ GET(bool, valid, EAX);
+
+ if (ProximityTemp::Exist)
+ {
+ ProximityTemp::Exist = false;
+ return Invalid;
+ }
+
+ return valid ? Valid : Invalid;
+}
+
+// Buildable-upon TechnoTypes Hook #8-4 -> sub_588570 - Don't place firestorm wall when have occupiers
+DEFINE_HOOK(0x588664, MapClass_BuildingToFirestormWall_DisableWhenHaveTechnos, 0x8)
+{
+ enum { Valid = 0x58866C, Invalid = 0x588730 };
+
+ GET(bool, valid, EAX);
+
+ if (ProximityTemp::Exist)
+ {
+ ProximityTemp::Exist = false;
+ return Invalid;
+ }
+
+ return valid ? Valid : Invalid;
+}
+
+// Buildable-upon TechnoTypes Hook #9-1 -> sub_7393C0 - 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, pUnit, EBP);
+ GET(CellStruct, topLeftCell, ESI);
+
+ if (!RulesExt::Global()->ExpandBuildingPlace)
+ return 0;
+
+ const auto pTechnoExt = TechnoExt::ExtMap.Find(pUnit);
+ const auto pBuildingType = pUnit->Type->DeploysInto;
+ const auto pHouseExt = HouseExt::ExtMap.Find(pUnit->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 canBuild = true;
+ bool noOccupy = true;
+
+ for (auto pFoundation = pBuildingType->GetFoundationData(false); *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation)
+ {
+ const auto currentCoord = topLeftCell + *pFoundation;
+ const auto pCell = MapClass::Instance->GetCellAt(currentCoord);
+
+ if (!pCell->CanThisExistHere(pBuildingType->SpeedType, pBuildingType, pUnit->Owner))
+ {
+ canBuild = false;
+ break;
+ }
+ else if (ProximityTemp::Exist)
+ {
+ noOccupy = false;
+ }
+ }
+
+ ProximityTemp::Exist = false;
+
+ do
+ {
+ if (canBuild)
+ {
+ if (noOccupy)
+ break; // Can build
+
+ do
+ {
+ if (pTechnoExt && !pTechnoExt->UnitAutoDeployTimer.InProgress())
+ {
+ if (BuildingTypeExt::CleanUpBuildingSpace(pBuildingType, topLeftCell, pUnit->Owner, pUnit))
+ break; // No place for cleaning
+
+ if (vec.size() == 0 || std::find(vec.begin(), vec.end(), pUnit) == vec.end())
+ vec.push_back(pUnit);
+
+ pTechnoExt->UnitAutoDeployTimer.Start(40);
+ }
+
+ return TemporarilyCanNotDeploy;
+ }
+ while (false);
+ }
+
+ if (vec.size() > 0)
+ vec.erase(std::remove(vec.begin(), vec.end(), pUnit), vec.end());
+
+ if (pTechnoExt)
+ pTechnoExt->UnitAutoDeployTimer.Stop();
+
+ return CanNotDeploy;
+ }
+ while (false);
+ }
+
+ if (vec.size() > 0)
+ vec.erase(std::remove(vec.begin(), vec.end(), pUnit), vec.end());
+
+ if (pTechnoExt)
+ pTechnoExt->UnitAutoDeployTimer.Stop();
+
+ return CanDeploy;
+}
+
+// Buildable-upon TechnoTypes Hook #9-2 -> sub_73FD50 - Push the owner house into deploy check
+DEFINE_HOOK(0x73FF8F, UnitClass_MouseOverObject_ShowDeployCursor, 0x6)
+{
+ if (RulesExt::Global()->ExpandBuildingPlace) // 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;
+}
+
+// Buildable-upon TechnoTypes Hook #10 -> sub_4C6CB0 - Stop deploy when get stop command
+DEFINE_HOOK(0x4C7665, EventClass_RespondToEvent_StopDeployInIdleEvent, 0x6)
+{
+ if (RulesExt::Global()->ExpandBuildingPlace) // 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.Find(pUnit->Owner))
+ {
+ auto& vec = pHouseExt->OwnedDeployingUnits;
+
+ if (vec.size() > 0)
+ vec.erase(std::remove(vec.begin(), vec.end(), pUnit), vec.end());
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+// Buildable-upon TechnoTypes Hook #11 -> sub_4F8440 - Check whether can place again in each house
+DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6)
+{
+ GET(const HouseClass* const, pHouse, ESI);
+
+ if (!pHouse->IsControlledByHuman() || !RulesExt::Global()->ExpandBuildingPlace)
+ return 0;
+
+ if (pHouse->RecheckTechTree)
+ {
+ if (const auto pFactory = pHouse->GetPrimaryFactory(AbstractType::BuildingType, false, BuildCat::DontCare))
+ {
+ if (pFactory->IsDone())
+ {
+ if (const auto pBuilding = abstract_cast(pFactory->Object))
+ BuildingTypeExt::AutoUpgradeBuilding(pBuilding);
+ }
+ }
+
+ if (const auto pFactory = pHouse->GetPrimaryFactory(AbstractType::BuildingType, false, BuildCat::Combat))
+ {
+ if (pFactory->IsDone())
+ {
+ if (const auto pBuilding = abstract_cast(pFactory->Object))
+ BuildingTypeExt::AutoUpgradeBuilding(pBuilding);
+ }
+ }
+ }
+
+ if (const auto pHouseExt = HouseExt::ExtMap.Find(pHouse))
+ {
+ if (pHouse == HouseClass::CurrentPlayer) // Prevent unexpected wrong event
+ {
+ const auto pBuildingType = pHouseExt->CurrentBuildingType;
+
+ if (pHouseExt->CurrentBuildingTimer.Completed() && pBuildingType)
+ {
+ pHouseExt->CurrentBuildingTimer.Stop();
+ const EventClass event
+ (
+ pHouse->ArrayIndex,
+ EventType::Place,
+ AbstractType::Building,
+ pBuildingType->GetArrayIndex(),
+ pBuildingType->Naval,
+ pHouseExt->CurrentBuildingTopLeft
+ );
+ EventClass::AddEvent(event);
+ }
+ }
+
+ 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->CurrentMission == Mission::Guard && !pUnit->ParasiteEatingMe && !pUnit->TemporalTargetingMe)
+ {
+ if (const auto pType = pUnit->Type)
+ {
+ if (pType->DeploysInto)
+ {
+ 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;
+}
+
+// Buildable-upon TechnoTypes Hook #12 -> sub_6D5030 - Draw the placing building preview
+DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6)
+{
+ const auto pPlayer = HouseClass::CurrentPlayer();
+
+ for (const auto& pHouse : *HouseClass::Array)
+ {
+ if (pPlayer->IsObserver() || pHouse->IsAlliedWith(pPlayer))
+ {
+ const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
+ const bool isPlayer = pHouse == pPlayer;
+ const auto pDisplay = DisplayClass::Instance();
+ {
+ auto pType = pHouseExt->CurrentBuildingType;
+
+ if (pType || (isPlayer && (pType = abstract_cast(reinterpret_cast(pDisplay->unknown_1194)), pType)))
+ {
+ auto pCell = pDisplay->TryGetCellAt(pHouseExt->CurrentBuildingTopLeft);
+
+ if (pCell || (isPlayer && (pCell = pDisplay->TryGetCellAt(pDisplay->CurrentFoundationCopy_TopLeftOffset + pDisplay->CurrentFoundationCopy_CenterCell), pCell)))
+ {
+ auto pImage = pType->LoadBuildup();
+ int imageFrame = 0;
+
+ if (pImage)
+ imageFrame = ((pImage->Frames / 2) - 1);
+ else
+ pImage = pType->GetImage();
+
+ if (pImage)
+ {
+ 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(pCell->MapCoords, (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);
+ }
+ }
+ }
+ }
+
+ if (pHouseExt->OwnedDeployingUnits.size() > 0)
+ {
+ for (const auto& pUnit : pHouseExt->OwnedDeployingUnits)
+ {
+ if (pUnit && pUnit->Type)
+ {
+ if (const auto pType = pUnit->Type->DeploysInto)
+ {
+ auto displayCell = CellClass::Coord2Cell(pUnit->GetCoords()); // pUnit->GetMapCoords();
+
+ if (pType->GetFoundationWidth() > 2 || pType->GetFoundationHeight(false) > 2)
+ displayCell -= CellStruct { 1, 1 };
+
+ if (const auto pCell = pDisplay->TryGetCellAt(displayCell))
+ {
+ auto pImage = pType->LoadBuildup();
+ int imageFrame = 0;
+
+ if (pImage)
+ imageFrame = ((pImage->Frames / 2) - 1);
+ else
+ pImage = pType->GetImage();
+
+ if (pImage)
+ {
+ 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(pCell->MapCoords, (1 + pCell->GetFloorHeight(Point2D::Empty)))).first;
+ point.Y -= 15;
+
+ const auto ColorSchemeIndex = pUnit->Owner->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);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+// Auto Build Hook -> sub_6A8B30 - Auto Build Buildings
+DEFINE_HOOK(0x6A8E34, StripClass_Update_AutoBuildBuildings, 0x7)
+{
+ enum { SkipSetStripShortCut = 0x6A8E4D };
+
+ GET(BuildingClass* const, pBuilding, ESI);
+
+ return (RulesExt::Global()->ExpandBuildingPlace && (BuildingTypeExt::BuildLimboBuilding(pBuilding) || BuildingTypeExt::AutoUpgradeBuilding(pBuilding))) ? SkipSetStripShortCut : 0;
+}
+
+// Limbo Build Hook -> sub_42EB50 - Check Base Node
+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 (BuildingTypeExt::ExtMap.Find(pType)->LimboBuild)
+ return Invalid;
+ }
+ }
+
+ return reinterpret_cast(0x50CAD0)(pBase->Owner, pBaseNode) ? Valid : Invalid;
+}
+
+// Customized Laser Fence Hook #2 -> sub_453060 - Select the specific laser fence type
+DEFINE_HOOK(0x452E2C, BuildingClass_CreateLaserFence_FindSpecificIndex, 0x5)
+{
+ enum { SkipGameCode = 0x452E50 };
+
+ GET(BuildingClass* const, pThis, EDI);
+
+ if (const auto pExt = BuildingTypeExt::ExtMap.Find(pThis->Type))
+ {
+ if (const auto pFenceType = pExt->LaserFencePost_Fence.Get())
+ {
+ if (pFenceType->LaserFence)
+ {
+ R->EBP(pFenceType->ArrayIndex);
+ R->EAX(BuildingTypeClass::Array->Count);
+ return SkipGameCode;
+ }
+ }
+ }
+
+ return 0;
+}
+
+// Customized Laser Fence Hook #3 -> sub_440580 - 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);
+
+ if (const auto pExt = BuildingTypeExt::ExtMap.Find(pThis->Type))
+ {
+ if (const auto pFenceType = pExt->LaserFencePost_Fence.Get())
+ {
+ if (pFenceType != pFence->Type)
+ return SkipGameCode;
+ }
+ else // Vanilla search
+ {
+ 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 != pFence->Type)
+ return SkipGameCode;
+ else
+ break;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+// Customized Laser Fence Hook #4 -> sub_452BB0 - 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);
+
+ if (const auto pThisTypeExt = BuildingTypeExt::ExtMap.Find(pThis->Type))
+ {
+ if (const auto pPostTypeExt = BuildingTypeExt::ExtMap.Find(pPost->Type))
+ {
+ if (pThisTypeExt->LaserFencePost_Fence.Get() != pPostTypeExt->LaserFencePost_Fence.Get())
+ return SkipGameCode;
+ }
+ }
+
+ return 0;
+}
+
+// Customized Laser Fence Hook #5 -> sub_6D5730 - 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
+ if (const auto pPlaceTypeExt = BuildingTypeExt::ExtMap.Find(static_cast(DisplayClass::Instance->CurrentBuilding)->Type))
+ {
+ if (const auto pPostTypeExt = BuildingTypeExt::ExtMap.Find(pPostType))
+ {
+ if (pPlaceTypeExt->LaserFencePost_Fence.Get() != pPostTypeExt->LaserFencePost_Fence.Get())
+ return SkipGameCode;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/Ext/BuildingType/Hooks.cpp b/src/Ext/BuildingType/Hooks.cpp
index 6576ffc5e8..8c1622e254 100644
--- a/src/Ext/BuildingType/Hooks.cpp
+++ b/src/Ext/BuildingType/Hooks.cpp
@@ -217,45 +217,3 @@ DEFINE_HOOK(0x5F5416, ObjectClass_ReceiveDamage_CanC4DamageRounding, 0x6)
return SkipGameCode;
}
-
-#pragma region BuildingProximity
-
-namespace ProximityTemp
-{
- BuildingTypeClass* pType = nullptr;
-}
-
-DEFINE_HOOK(0x4A8F20, isplayClass_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& pBuildingsAllowed = BuildingTypeExt::ExtMap.Find(ProximityTemp::pType)->Adjacent_Allowed;
-
- if (pBuildingsAllowed.size() > 0 && !pBuildingsAllowed.Contains(pCellBuilding->Type))
- return SkipBuilding;
-
- auto const& pBuildingsDisallowed = BuildingTypeExt::ExtMap.Find(ProximityTemp::pType)->Adjacent_Disallowed;
-
- if (pBuildingsDisallowed.size() > 0 && pBuildingsDisallowed.Contains(pCellBuilding->Type))
- return SkipBuilding;
-
- return 0;
-}
-
-#pragma endregion
diff --git a/src/Ext/House/Body.cpp b/src/Ext/House/Body.cpp
index 5c4b25b69f..8d1e0b5a17 100644
--- a/src/Ext/House/Body.cpp
+++ b/src/Ext/House/Body.cpp
@@ -607,6 +607,11 @@ void HouseExt::ExtData::Serialize(T& Stm)
Stm
.Process(this->PowerPlantEnhancers)
.Process(this->OwnedLimboDeliveredBuildings)
+ .Process(this->OwnedDeployingUnits)
+ .Process(this->CurrentBuildingType)
+ .Process(this->CurrentBuildingTopLeft)
+ .Process(this->CurrentBuildingTimer)
+ .Process(this->CurrentBuildingTimes)
.Process(this->LimboAircraft)
.Process(this->LimboBuildings)
.Process(this->LimboInfantry)
diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h
index 0b0cca3a1c..c1e6bb9144 100644
--- a/src/Ext/House/Body.h
+++ b/src/Ext/House/Body.h
@@ -24,6 +24,12 @@ class HouseExt
std::map PowerPlantEnhancers;
std::vector OwnedLimboDeliveredBuildings;
+ std::vector OwnedDeployingUnits;
+ BuildingTypeClass* CurrentBuildingType;
+ CellStruct CurrentBuildingTopLeft;
+ CDTimerClass CurrentBuildingTimer;
+ int CurrentBuildingTimes;
+
CounterClass LimboAircraft; // Currently owned aircraft in limbo
CounterClass LimboBuildings; // Currently owned buildings in limbo
CounterClass LimboInfantry; // Currently owned infantry in limbo
@@ -65,6 +71,11 @@ class HouseExt
ExtData(HouseClass* OwnerObject) : Extension(OwnerObject)
, PowerPlantEnhancers {}
, OwnedLimboDeliveredBuildings {}
+ , OwnedDeployingUnits {}
+ , CurrentBuildingType { nullptr }
+ , CurrentBuildingTopLeft {}
+ , CurrentBuildingTimer {}
+ , CurrentBuildingTimes { 0 }
, LimboAircraft {}
, LimboBuildings {}
, LimboInfantry {}
diff --git a/src/Ext/House/Hooks.cpp b/src/Ext/House/Hooks.cpp
index fa005f0ff1..dc144af374 100644
--- a/src/Ext/House/Hooks.cpp
+++ b/src/Ext/House/Hooks.cpp
@@ -208,6 +208,12 @@ DEFINE_HOOK(0x7015C9, TechnoClass_Captured_UpdateTracking, 0x6)
pNewOwnerExt->AddToLimboTracking(pType);
}
+ if (RulesExt::Global()->ExpandBuildingPlace && pThis->WhatAmI() == AbstractType::Unit && pType->DeploysInto)
+ {
+ auto& vec = pOwnerExt->OwnedDeployingUnits;
+ vec.erase(std::remove(vec.begin(), vec.end(), pThis), vec.end());
+ }
+
if (auto pMe = generic_cast(pThis))
{
bool I_am_human = pThis->Owner->IsControlledByHuman();
diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp
index bc72f69570..2e49e25de6 100644
--- a/src/Ext/Rules/Body.cpp
+++ b/src/Ext/Rules/Body.cpp
@@ -137,6 +137,8 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI)
this->HeightShadowScaling = false;
this->HeightShadowScaling_MinScale.Read(exINI, GameStrings::AudioVisual, "HeightShadowScaling.MinScale");
+ this->ExpandBuildingPlace.Read(exINI, GameStrings::General, "ExpandBuildingPlace");
+
this->AllowParallelAIQueues.Read(exINI, "GlobalControls", "AllowParallelAIQueues");
this->ForbidParallelAIQueues_Aircraft.Read(exINI, "GlobalControls", "ForbidParallelAIQueues.Aircraft");
this->ForbidParallelAIQueues_Building.Read(exINI, "GlobalControls", "ForbidParallelAIQueues.Building");
@@ -330,6 +332,7 @@ void RulesExt::ExtData::Serialize(T& Stm)
.Process(this->AirShadowBaseScale_log)
.Process(this->HeightShadowScaling)
.Process(this->HeightShadowScaling_MinScale)
+ .Process(this->ExpandBuildingPlace)
.Process(this->AllowParallelAIQueues)
.Process(this->ForbidParallelAIQueues_Aircraft)
.Process(this->ForbidParallelAIQueues_Building)
diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h
index b2cce2d4cd..0556802157 100644
--- a/src/Ext/Rules/Body.h
+++ b/src/Ext/Rules/Body.h
@@ -93,6 +93,8 @@ class RulesExt
Valueable HeightShadowScaling_MinScale;
double AirShadowBaseScale_log;
+ Valueable ExpandBuildingPlace;
+
Valueable AllowParallelAIQueues;
Valueable ForbidParallelAIQueues_Aircraft;
Valueable ForbidParallelAIQueues_Building;
@@ -223,6 +225,8 @@ class RulesExt
, HeightShadowScaling_MinScale { 0.0 }
, AirShadowBaseScale_log { 0.693376137 }
+ , ExpandBuildingPlace { false }
+
, AllowParallelAIQueues { true }
, ForbidParallelAIQueues_Aircraft { false }
, ForbidParallelAIQueues_Building { false }
diff --git a/src/Ext/SWType/FireSuperWeapon.cpp b/src/Ext/SWType/FireSuperWeapon.cpp
index 5b570d4b3b..39c7c4bbc5 100644
--- a/src/Ext/SWType/FireSuperWeapon.cpp
+++ b/src/Ext/SWType/FireSuperWeapon.cpp
@@ -11,7 +11,6 @@
#include "Ext/House/Body.h"
#include "Ext/WarheadType/Body.h"
#include "Ext/WeaponType/Body.h"
-#include
// ============= New SuperWeapon Effects================
@@ -59,76 +58,7 @@ inline void LimboCreate(BuildingTypeClass* pType, HouseClass* pOwner, int ID)
return;
}
- if (auto const pBuilding = static_cast(pType->CreateObject(pOwner)))
- {
- // All of these are mandatory
- pBuilding->InLimbo = false;
- pBuilding->IsAlive = true;
- pBuilding->IsOnMap = true;
-
- // 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 - Starkku
- if (SessionClass::IsCampaign())
- pBuilding->DiscoveredBy(HouseClass::CurrentPlayer);
-
- pBuilding->DiscoveredBy(pOwner);
-
- pOwner->RegisterGain(pBuilding, false);
- pOwner->UpdatePower();
- pOwner->RecheckTechTree = true;
- pOwner->RecheckPower = true;
- pOwner->RecheckRadar = 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);
-
- if (!pBuilding->Type->Insignificant && !pBuilding->Type->DontScore)
- pOwnerExt->AddToLimboTracking(pBuilding->Type);
-
- 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);
- }
-
- }
+ BuildingTypeExt::CreateLimboBuilding(nullptr, pType, pOwner, ID);
}
void SWTypeExt::ExtData::ApplyLimboDelivery(HouseClass* pHouse)
@@ -176,9 +106,8 @@ void SWTypeExt::ExtData::ApplyLimboKill(HouseClass* pHouse)
for (auto it = vec.begin(); it != vec.end(); )
{
BuildingClass* const pBuilding = *it;
- auto const pBuildingExt = BuildingExt::ExtMap.Find(pBuilding);
- if (pBuildingExt->LimboID == limboKillID)
+ if (BuildingTypeExt::DeleteLimboBuilding(pBuilding, limboKillID))
{
it = vec.erase(it);
diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp
index 0d5c6ccf02..43dc1d21e0 100644
--- a/src/Ext/Techno/Body.cpp
+++ b/src/Ext/Techno/Body.cpp
@@ -7,7 +7,7 @@
#include
#include
#include
-
+#include
#include
TechnoExt::ExtContainer TechnoExt::ExtMap;
@@ -24,6 +24,12 @@ TechnoExt::ExtData::~ExtData()
vec.erase(std::remove(vec.begin(), vec.end(), this), vec.end());
}
+ if (RulesExt::Global()->ExpandBuildingPlace && pThis->WhatAmI() == AbstractType::Unit && pType->DeploysInto)
+ {
+ auto& vec = HouseExt::ExtMap.Find(pThis->Owner)->OwnedDeployingUnits;
+ vec.erase(std::remove(vec.begin(), vec.end(), pThis), vec.end());
+ }
+
if (pThis->WhatAmI() != AbstractType::Aircraft && pThis->WhatAmI() != AbstractType::Building
&& pType->Ammo > 0 && pTypeExt->ReloadInTransport)
{
@@ -497,6 +503,7 @@ void TechnoExt::ExtData::Serialize(T& Stm)
.Process(this->CanCloakDuringRearm)
.Process(this->WHAnimRemainingCreationInterval)
.Process(this->FiringObstacleCell)
+ .Process(this->UnitAutoDeployTimer)
.Process(this->IsDetachingForCloak)
.Process(this->OriginalPassengerOwner)
.Process(this->HasRemainingWarpInDelay)
diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h
index dd958380bd..70272bb85b 100644
--- a/src/Ext/Techno/Body.h
+++ b/src/Ext/Techno/Body.h
@@ -50,6 +50,7 @@ class TechnoExt
int WHAnimRemainingCreationInterval;
bool CanCurrentlyDeployIntoBuilding; // Only set on UnitClass technos with DeploysInto set in multiplayer games, recalculated once per frame so no need to serialize.
CellClass* FiringObstacleCell; // Set on firing if there is an obstacle cell between target and techno, used for updating WaveClass target etc.
+ CDTimerClass UnitAutoDeployTimer;
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.
// Used for Passengers.SyncOwner.RevertOnExit instead of TechnoClass::InitialOwner / OriginallyOwnedByHouse,
@@ -87,6 +88,7 @@ class TechnoExt
, WHAnimRemainingCreationInterval { 0 }
, CanCurrentlyDeployIntoBuilding { false }
, FiringObstacleCell {}
+ , UnitAutoDeployTimer {}
, IsDetachingForCloak { false }
, OriginalPassengerOwner {}
, HasRemainingWarpInDelay { false }
diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp
index 0091ceb22e..db1a57c864 100644
--- a/src/Ext/TechnoType/Body.cpp
+++ b/src/Ext/TechnoType/Body.cpp
@@ -454,6 +454,9 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->BuildLimitGroup_ExtraLimit_Nums.Read(exINI, pSection, "BuildLimitGroup.ExtraLimit.Nums");
this->BuildLimitGroup_ExtraLimit_MaxCount.Read(exINI, pSection, "BuildLimitGroup.ExtraLimit.MaxCount");
this->BuildLimitGroup_ExtraLimit_MaxNum.Read(exINI, pSection, "BuildLimitGroup.ExtraLimit.MaxNum");
+
+ this->CanBeBuiltOn.Read(exINI, pSection, "CanBeBuiltOn");
+
this->Wake.Read(exINI, pSection, "Wake");
this->Wake_Grapple.Read(exINI, pSection, "Wake.Grapple");
this->Wake_Sinking.Read(exINI, pSection, "Wake.Sinking");
@@ -825,6 +828,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm)
.Process(this->BuildLimitGroup_ExtraLimit_MaxCount)
.Process(this->BuildLimitGroup_ExtraLimit_MaxNum)
+ .Process(this->CanBeBuiltOn)
+
.Process(this->Wake)
.Process(this->Wake_Grapple)
.Process(this->Wake_Sinking)
diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h
index 3335acac05..fa00d5bc2d 100644
--- a/src/Ext/TechnoType/Body.h
+++ b/src/Ext/TechnoType/Body.h
@@ -226,6 +226,8 @@ class TechnoTypeExt
ValueableVector BuildLimitGroup_ExtraLimit_MaxCount;
Valueable BuildLimitGroup_ExtraLimit_MaxNum;
+ Valueable CanBeBuiltOn;
+
Nullable Wake;
Nullable Wake_Grapple;
Nullable Wake_Sinking;
@@ -449,6 +451,8 @@ class TechnoTypeExt
, BuildLimitGroup_ExtraLimit_MaxCount {}
, BuildLimitGroup_ExtraLimit_MaxNum { 0 }
+ , CanBeBuiltOn { false }
+
, Wake { }
, Wake_Grapple { }
, Wake_Sinking { }
diff --git a/src/Ext/TerrainType/Hooks.Passable.cpp b/src/Ext/TerrainType/Hooks.Passable.cpp
index 82157e0cd0..51ce195660 100644
--- a/src/Ext/TerrainType/Hooks.Passable.cpp
+++ b/src/Ext/TerrainType/Hooks.Passable.cpp
@@ -6,11 +6,6 @@
#include
-constexpr bool IS_CELL_OCCUPIED(CellClass* pCell)
-{
- return pCell->OccupationFlags & 0x20 || pCell->OccupationFlags & 0x40 || pCell->OccupationFlags & 0x80 || pCell->GetInfantry(false);
-}
-
// Passable TerrainTypes Hook #1 - Do not set occupy bits.
DEFINE_HOOK(0x71C110, TerrainClass_SetOccupyBit_PassableTerrain, 0x6)
{
@@ -91,115 +86,3 @@ DEFINE_HOOK(0x73FB71, UnitClass_CanEnterCell_PassableTerrain, 0x6)
return 0;
}
-
-// Buildable-upon TerrainTypes Hook #1 - Allow placing buildings on top of them.
-DEFINE_HOOK_AGAIN(0x47C80E, CellClass_IsClearTo_Build_BuildableTerrain, 0x5)
-DEFINE_HOOK(0x47C745, CellClass_IsClearTo_Build_BuildableTerrain, 0x5)
-{
- enum { Skip = 0x47C85F, SkipFlags = 0x47C6A0 };
-
- GET(CellClass*, pThis, EDI);
-
- auto pTerrain = pThis->GetTerrain(false);
-
- if (pTerrain)
- {
- if (auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type))
- {
- if (pTypeExt->CanBeBuiltOn)
- {
- if (IS_CELL_OCCUPIED(pThis))
- return Skip;
- else
- return SkipFlags;
- }
- }
- }
-
- return 0;
-}
-
-// Buildable-upon TerrainTypes Hook #2 - Allow placing laser fences on top of them.
-DEFINE_HOOK(0x47C657, CellClass_IsClearTo_Build_BuildableTerrain_LF, 0x6)
-{
- enum { Skip = 0x47C6A0, Return = 0x47C6D1 };
-
- GET(CellClass*, pThis, EDI);
-
- auto pObj = pThis->FirstObject;
-
- if (pObj)
- {
- bool isEligible = true;
-
- while (true)
- {
- isEligible = pObj->WhatAmI() != AbstractType::Building;
-
- if (auto const pTerrain = abstract_cast(pObj))
- {
- isEligible = false;
- auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type);
-
- if (pTypeExt->CanBeBuiltOn)
- isEligible = true;
- }
-
- if (!isEligible)
- break;
-
- pObj = pObj->NextObject;
-
- if (!pObj)
- return Skip;
- }
-
- return Return;
- }
-
- return Skip;
-}
-
-
-// Buildable-upon TerrainTypes Hook #3 - 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))
- {
- auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type);
-
- if (pTypeExt->CanBeBuiltOn)
- return ContinueChecks;
-
- return DontDraw;
- }
-
- return ContinueChecks;
-}
-
-// Buildable-upon TerrainTypes Hook #4 - Remove them when buildings are placed on them.
-DEFINE_HOOK(0x5684B1, MapClass_PlaceDown_BuildableTerrain, 0x6)
-{
- GET(ObjectClass*, pObject, EDI);
- GET(CellClass*, pCell, EAX);
-
- if (pObject->WhatAmI() == AbstractType::Building)
- {
- if (auto const pTerrain = pCell->GetTerrain(false))
- {
- auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type);
-
- if (pTypeExt->CanBeBuiltOn)
- {
- pCell->RemoveContent(pTerrain, false);
- TerrainTypeExt::Remove(pTerrain);
- }
- }
- }
-
- return 0;
-}
From e541fa17179fdd74c304b820bfd38dad60f89497 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Sun, 29 Dec 2024 19:52:02 +0800
Subject: [PATCH 02/58] Fix merge
---
src/Ext/BuildingType/Hooks.Placing.cpp | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp
index 82c7543f97..c976a311da 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -397,7 +397,7 @@ DEFINE_HOOK(0x47EEBC, CellClass_DrawPlaceGrid_RecordCell, 0x6)
R->EDX(flags | (zero ? BlitterFlags::Zero : BlitterFlags::Nonzero));
return DrawVanillaAlt;
}
- else if (BuildingTypeClass* const pType = abstract_cast(reinterpret_cast(DisplayClass::Instance->unknown_1194)))
+ 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);
@@ -435,13 +435,13 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5)
pDisplay->SetActiveFoundation(nullptr);
pDisplay->CurrentBuilding = nullptr;
pDisplay->CurrentBuildingType = nullptr;
- pDisplay->unknown_11AC = 0xFFFFFFFF;
+ pDisplay->CurrentBuildingOwnerArrayIndex = -1;
if (!Unsorted::ArmageddonMode)
{
reinterpret_cast(0x4A8D50)(pDisplay, nullptr); // Clear CurrentFoundationCopy_Data
- pDisplay->unknown_1190 = 0;
- pDisplay->unknown_1194 = 0;
+ pDisplay->CurrentBuildingCopy = nullptr;
+ pDisplay->CurrentBuildingTypeCopy = nullptr;
}
}
@@ -517,13 +517,13 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5)
pDisplay->SetActiveFoundation(nullptr);
pDisplay->CurrentBuilding = nullptr;
pDisplay->CurrentBuildingType = nullptr;
- pDisplay->unknown_11AC = 0xFFFFFFFF;
+ pDisplay->CurrentBuildingOwnerArrayIndex = -1;
if (!Unsorted::ArmageddonMode)
{
reinterpret_cast(0x4A8D50)(pDisplay, nullptr); // Clear CurrentFoundationCopy_Data
- pDisplay->unknown_1190 = 0;
- pDisplay->unknown_1194 = 0;
+ pDisplay->CurrentBuildingCopy = nullptr;
+ pDisplay->CurrentBuildingTypeCopy = nullptr;
}
}
@@ -598,7 +598,7 @@ DEFINE_HOOK(0x4FB319, HouseClass_UnitFromFactory_SkipMouseClear, 0x5)
if (const auto pBuilding = abstract_cast(pTechno))
{
- if (RulesExt::Global()->ExpandBuildingPlace && reinterpret_cast(DisplayClass::Instance->unknown_1194) != pBuilding->Type)
+ if (RulesExt::Global()->ExpandBuildingPlace && DisplayClass::Instance->CurrentBuildingTypeCopy != pBuilding->Type)
return SkipGameCode;
}
@@ -1093,7 +1093,7 @@ DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6)
{
auto pType = pHouseExt->CurrentBuildingType;
- if (pType || (isPlayer && (pType = abstract_cast(reinterpret_cast(pDisplay->unknown_1194)), pType)))
+ if (pType || (isPlayer && (pType = abstract_cast(DisplayClass::Instance->CurrentBuildingTypeCopy), pType)))
{
auto pCell = pDisplay->TryGetCellAt(pHouseExt->CurrentBuildingTopLeft);
From 88907573ba1e6353295fbd2b17a8b4772180153b Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Fri, 3 Jan 2025 19:37:35 +0800
Subject: [PATCH 03/58] Simplify
---
src/Ext/BuildingType/Hooks.Placing.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp
index c976a311da..08c348f356 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -1003,7 +1003,7 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6)
if (pHouse->RecheckTechTree)
{
- if (const auto pFactory = pHouse->GetPrimaryFactory(AbstractType::BuildingType, false, BuildCat::DontCare))
+ if (const auto pFactory = pHouse->Primary_ForBuildings)
{
if (pFactory->IsDone())
{
@@ -1012,7 +1012,7 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6)
}
}
- if (const auto pFactory = pHouse->GetPrimaryFactory(AbstractType::BuildingType, false, BuildCat::Combat))
+ if (const auto pFactory = pHouse->Primary_ForDefenses)
{
if (pFactory->IsDone())
{
From 76c066a2a2a8f3eb52e820998b8c7b1111979481 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Thu, 9 Jan 2025 22:52:51 +0800
Subject: [PATCH 04/58] Fix that chronominer will not leave the placing area
---
src/Ext/BuildingType/Body.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp
index fee7d4341a..1a68ca7946 100644
--- a/src/Ext/BuildingType/Body.cpp
+++ b/src/Ext/BuildingType/Body.cpp
@@ -1,7 +1,6 @@
#include "Body.h"
#include
-#include
#include
#include
@@ -148,7 +147,7 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel
{
const auto pFoot = static_cast(pCellTechno);
- if (pFoot->GetCurrentSpeed() <= 0 || (locomotion_cast(pFoot->Locomotor) && !pFoot->Locomotor->Is_Moving()))
+ if (pFoot->GetCurrentSpeed() <= 0 || !pFoot->Locomotor->Is_Moving())
{
if (absType == AbstractType::Infantry)
++infantryCount.X;
From f46b30323444563cc00ef8e6fa83bf81eb6423bb Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Thu, 9 Jan 2025 22:54:07 +0800
Subject: [PATCH 05/58] No need to cover the mission of vehicle units
---
src/Ext/BuildingType/Body.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp
index 1a68ca7946..92e6876610 100644
--- a/src/Ext/BuildingType/Body.cpp
+++ b/src/Ext/BuildingType/Body.cpp
@@ -445,7 +445,6 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel
pUnit->Undeploy();
pUnit->SetDestination(pDestinationCell, false);
- pUnit->QueueMission(Mission::Move, false);
}
}
From f84af42fac83487f49fba9437526d0c0cc3d04e5 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Sat, 18 Jan 2025 16:17:46 +0800
Subject: [PATCH 06/58] Fix a typo
---
src/Ext/BuildingType/Hooks.Placing.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp
index 08c348f356..14dfe2f1ca 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -1082,6 +1082,7 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6)
DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6)
{
const auto pPlayer = HouseClass::CurrentPlayer();
+ const auto pDisplay = DisplayClass::Instance();
for (const auto& pHouse : *HouseClass::Array)
{
@@ -1089,7 +1090,6 @@ DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6)
{
const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
const bool isPlayer = pHouse == pPlayer;
- const auto pDisplay = DisplayClass::Instance();
{
auto pType = pHouseExt->CurrentBuildingType;
@@ -1199,7 +1199,7 @@ DEFINE_HOOK(0x42EB8E, BaseClass_GetBaseNodeIndex_CheckValidBaseNode, 0x6)
{
const auto pType = BuildingTypeClass::Array->Items[index];
- if (BuildingTypeExt::ExtMap.Find(pType)->LimboBuild)
+ if (RulesExt::Global()->ExpandBuildingPlace && BuildingTypeExt::ExtMap.Find(pType)->LimboBuild)
return Invalid;
}
}
From aebc930aaedab46ec3599ccaf08a4a5dd8f63b11 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Sun, 19 Jan 2025 00:02:44 +0800
Subject: [PATCH 07/58] Small optimize
---
src/Ext/BuildingType/Hooks.Placing.cpp | 46 ++++++++++----------------
1 file changed, 18 insertions(+), 28 deletions(-)
diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp
index 14dfe2f1ca..9de5cee471 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -799,6 +799,17 @@ DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
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
// Buildable-upon TechnoTypes Hook #8-1 -> sub_6D5C50 - Don't draw overlay wall grid when have occupiers
DEFINE_HOOK(0x6D5D38, TacticalClass_DrawOverlayWallGrid_DisableWhenHaveTechnos, 0x8)
@@ -807,13 +818,7 @@ DEFINE_HOOK(0x6D5D38, TacticalClass_DrawOverlayWallGrid_DisableWhenHaveTechnos,
GET(bool, valid, EAX);
- if (ProximityTemp::Exist)
- {
- ProximityTemp::Exist = false;
- return Invalid;
- }
-
- return valid ? Valid : Invalid;
+ return CanDrawGrid(valid) ? Valid : Invalid;
}
// Buildable-upon TechnoTypes Hook #8-2 -> sub_6D59D0 - Don't draw firestorm wall grid when have occupiers
@@ -823,13 +828,7 @@ DEFINE_HOOK(0x6D5A9D, TacticalClass_DrawFirestormWallGrid_DisableWhenHaveTechnos
GET(bool, valid, EAX);
- if (ProximityTemp::Exist)
- {
- ProximityTemp::Exist = false;
- return Invalid;
- }
-
- return valid ? Valid : Invalid;
+ return CanDrawGrid(valid) ? Valid : Invalid;
}
// Buildable-upon TechnoTypes Hook #8-3 -> sub_588750 - Don't place overlay wall when have occupiers
@@ -839,13 +838,7 @@ DEFINE_HOOK(0x588873, MapClass_BuildingToWall_DisableWhenHaveTechnos, 0x8)
GET(bool, valid, EAX);
- if (ProximityTemp::Exist)
- {
- ProximityTemp::Exist = false;
- return Invalid;
- }
-
- return valid ? Valid : Invalid;
+ return CanDrawGrid(valid) ? Valid : Invalid;
}
// Buildable-upon TechnoTypes Hook #8-4 -> sub_588570 - Don't place firestorm wall when have occupiers
@@ -855,13 +848,7 @@ DEFINE_HOOK(0x588664, MapClass_BuildingToFirestormWall_DisableWhenHaveTechnos, 0
GET(bool, valid, EAX);
- if (ProximityTemp::Exist)
- {
- ProximityTemp::Exist = false;
- return Invalid;
- }
-
- return valid ? Valid : Invalid;
+ return CanDrawGrid(valid) ? Valid : Invalid;
}
// Buildable-upon TechnoTypes Hook #9-1 -> sub_7393C0 - Try to clean up the building space when is deploying
@@ -1081,6 +1068,9 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6)
// Buildable-upon TechnoTypes Hook #12 -> sub_6D5030 - Draw the placing building preview
DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6)
{
+ if (!RulesExt::Global()->ExpandBuildingPlace)
+ return 0;
+
const auto pPlayer = HouseClass::CurrentPlayer();
const auto pDisplay = DisplayClass::Instance();
From c05a5498b5a478869c4e3906af430ab1d0fa05d8 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Tue, 21 Jan 2025 01:07:41 +0800
Subject: [PATCH 08/58] A missing function
---
src/Ext/BuildingType/Body.cpp | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp
index 3cb2e2d095..fb05704f2a 100644
--- a/src/Ext/BuildingType/Body.cpp
+++ b/src/Ext/BuildingType/Body.cpp
@@ -524,6 +524,29 @@ bool BuildingTypeExt::BuildLimboBuilding(BuildingClass* pBuilding)
return false;
}
+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))
From edb06f8c1e669866e93a84780ee208d277c6daa4 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Tue, 21 Jan 2025 01:07:56 +0800
Subject: [PATCH 09/58] New 4 functions
---
docs/New-or-Enhanced-Logics.md | 14 +-
src/Ext/BuildingType/Body.cpp | 126 ++++---
src/Ext/BuildingType/Body.h | 12 +-
src/Ext/BuildingType/Hooks.Placing.cpp | 438 ++++++++++++++++++-------
src/Ext/BuildingType/Hooks.cpp | 5 +-
src/Ext/House/Body.cpp | 1 +
src/Ext/House/Body.h | 2 +
7 files changed, 414 insertions(+), 184 deletions(-)
diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md
index 6efcb921fa..d72950f97d 100644
--- a/docs/New-or-Enhanced-Logics.md
+++ b/docs/New-or-Enhanced-Logics.md
@@ -584,9 +584,12 @@ 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 `ExpandBuildingPlace` 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 non-player 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. Note that when the building has been recorded and is trying to place, unless the production queue has vanished (such as construction yard is no longer exist), it will continue to function normally until the conditions are not met.
- - `AutoUpgrade` controls whether building upgrades can be automatically placed on the correct and earliest built building.
- - `LimboBuild` controls whether building can be automatically placed like `LimboDelivery`.
+- `AutoBuilding` controls whether building can be automatically placed.
+ - `AutoBuilding.Gap` controls the gap of automatically placed buildings.
+- `LimboBuild` controls whether building can be automatically placed like `LimboDelivery`.
- `LimboBuildID` defines the numeric ID of the building placed by `LimboBuild`.
+- `PlaceBuilding.OnLand` controls building with `Naval=yes` will become which building when placed on land.
+- `PlaceBuilding.OnWater` controls building with `Naval=no` will become which building when placed on water.
In `rulesmd.ini`:
```ini
@@ -594,9 +597,12 @@ In `rulesmd.ini`:
ExpandBuildingPlace=false ; boolean
[SOMEBUILDING] ; BuildingType
-AutoUpgrade=false ; boolean
+AutoBuilding=false ; boolean
+AutoBuilding.Gap=0 ; integer
LimboBuild=false ; boolean
-LimboBuildID=-1 ; boolean
+LimboBuildID=-1 ; integer
+PlaceBuilding.OnLand= ; BuildingType
+PlaceBuilding.OnWater= ; BuildingType
```
## Infantry
diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp
index fb05704f2a..a858b12b63 100644
--- a/src/Ext/BuildingType/Body.cpp
+++ b/src/Ext/BuildingType/Body.cpp
@@ -452,68 +452,94 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel
return false;
}
-bool BuildingTypeExt::AutoUpgradeBuilding(BuildingClass* pBuilding)
+bool BuildingTypeExt::AutoPlaceBuilding(BuildingClass* pBuilding)
{
- const auto pBuildingType = pBuilding->Type;
+ const auto pType = pBuilding->Type;
+ const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pType);
- if (!pBuildingType->PowersUpBuilding[0])
+ if (!pTypeExt->AutoBuilding)
return false;
- if (const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pBuildingType))
+ const auto pHouse = pBuilding->Owner;
+
+ if (pHouse->Buildings.Count <= 0)
+ return false;
+
+ const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
+
+ if (pType->PowersUpBuilding[0])
{
- if (pTypeExt->AutoUpgrade)
+ for (const auto& pOwned : pHouse->Buildings)
{
- const auto pHouse = pBuilding->Owner;
- const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
+ if (!reinterpret_cast(0x452670)(pOwned, pType, pHouse)) // CanUpgradeBuilding
+ continue;
- for (const auto& pOwned : pHouse->Buildings)
- {
- if (reinterpret_cast(0x452670)(pOwned, pBuildingType, pHouse)) // CanUpgradeBuilding
- {
- if (pOwned->IsAlive && pOwned->Health > 0 && pOwned->IsOnMap && !pOwned->InLimbo && pOwned->CurrentMission != Mission::Selling)
- {
- const auto cell = pOwned->GetMapCoords();
+ if (!pOwned->IsAlive || pOwned->Health <= 0 || !pOwned->IsOnMap || pOwned->InLimbo || pOwned->CurrentMission == Mission::Selling)
+ continue;
- if (cell != CellStruct::Empty && !pHouseExt->OwnsLimboDeliveredBuilding(pOwned))
- {
- const EventClass event
- (
- pHouse->ArrayIndex,
- EventType::Place,
- AbstractType::Building,
- pBuildingType->GetArrayIndex(),
- pBuildingType->Naval,
- cell
- );
- EventClass::AddEvent(event);
-
- return true;
- }
- }
- }
- }
- }
- }
+ const auto cell = pOwned->GetMapCoords();
- return false;
-}
+ if (cell == CellStruct::Empty || pHouseExt->OwnsLimboDeliveredBuilding(pOwned))
+ continue;
-bool BuildingTypeExt::BuildLimboBuilding(BuildingClass* pBuilding)
-{
- const auto pBuildingType = pBuilding->Type;
+ const EventClass event
+ (
+ pHouse->ArrayIndex,
+ EventType::Place,
+ AbstractType::Building,
+ pType->GetArrayIndex(),
+ pType->Naval,
+ cell
+ );
+ EventClass::AddEvent(event);
- if (const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pBuildingType))
+ return true;
+ }
+ }
+ else
{
- if (pTypeExt->LimboBuild)
+ const auto speedType = pType->Naval ? SpeedType::Float : SpeedType::Track;
+ const auto buildGap = pTypeExt->AutoBuilding_Gap * 2;
+ const auto width = pType->GetFoundationWidth() + buildGap;
+ const auto height = pType->GetFoundationHeight(false) + buildGap;
+ const auto offset = CellSpread::GetNeighbourOffset(Unsorted::CurrentFrame() & 7u);
+ const auto buildOffset = CellStruct { static_cast(pTypeExt->AutoBuilding_Gap), static_cast(pTypeExt->AutoBuilding_Gap) };
+ const auto foundation = pType->GetFoundationData(true);
+
+ for (const auto& pOwned : pHouse->Buildings)
{
+ if (!pOwned->IsAlive || pOwned->Health <= 0 || !pOwned->IsOnMap || pOwned->InLimbo || pOwned->CurrentMission == Mission::Selling)
+ continue;
+
+ const auto baseCell = pOwned->GetMapCoords();
+
+ if (baseCell == CellStruct::Empty || !pOwned->Type->BaseNormal || pHouseExt->OwnsLimboDeliveredBuilding(pOwned))
+ continue;
+
+ auto cell = pType->PlaceAnywhere ? baseCell : MapClass::Instance->NearByLocation(baseCell, speedType, -1, MovementZone::Normal, false,
+ width, height, false, false, false, false, (baseCell + offset), false, true);
+
+ if (cell == CellStruct::Empty)
+ break;
+
+ cell += buildOffset;
+
+ if (!reinterpret_cast(0x4A8EB0)(MapClass::Instance(),
+ pType, pHouse->ArrayIndex, foundation, &cell) // Adjacent
+ || !reinterpret_cast(0x4A9070)(MapClass::Instance(),
+ pType, pHouse->ArrayIndex, foundation, &cell)) // NoShroud
+ {
+ continue;
+ }
+
const EventClass event
(
- pBuilding->Owner->ArrayIndex,
+ pHouse->ArrayIndex,
EventType::Place,
AbstractType::Building,
- pBuildingType->GetArrayIndex(),
- pBuildingType->Naval,
- CellStruct { 1, 1 }
+ pType->GetArrayIndex(),
+ pType->Naval,
+ cell
);
EventClass::AddEvent(event);
@@ -694,10 +720,13 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->SellBuildupLength.Read(exINI, pSection, "SellBuildupLength");
this->IsDestroyableObstacle.Read(exINI, pSection, "IsDestroyableObstacle");
- this->AutoUpgrade.Read(exINI, pSection, "AutoUpgrade");
+ this->AutoBuilding.Read(exINI, pSection, "AutoBuilding");
+ this->AutoBuilding_Gap.Read(exINI, pSection, "AutoBuilding.Gap");
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->FactoryPlant_AllowTypes.Read(exINI, pSection, "FactoryPlant.AllowTypes");
this->FactoryPlant_DisallowTypes.Read(exINI, pSection, "FactoryPlant.DisallowTypes");
@@ -824,10 +853,13 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm)
.Process(this->ConsideredVehicle)
.Process(this->ZShapePointMove_OnBuildup)
.Process(this->SellBuildupLength)
- .Process(this->AutoUpgrade)
+ .Process(this->AutoBuilding)
+ .Process(this->AutoBuilding_Gap)
.Process(this->LimboBuild)
.Process(this->LimboBuildID)
.Process(this->LaserFencePost_Fence)
+ .Process(this->PlaceBuilding_OnLand)
+ .Process(this->PlaceBuilding_OnWater)
.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 4c13d11341..7c27fc6647 100644
--- a/src/Ext/BuildingType/Body.h
+++ b/src/Ext/BuildingType/Body.h
@@ -64,10 +64,13 @@ class BuildingTypeExt
Valueable SellBuildupLength;
Valueable IsDestroyableObstacle;
- Valueable AutoUpgrade;
+ Valueable AutoBuilding;
+ Valueable AutoBuilding_Gap;
Valueable LimboBuild;
Valueable LimboBuildID;
Valueable LaserFencePost_Fence;
+ Valueable PlaceBuilding_OnLand;
+ Valueable PlaceBuilding_OnWater;
std::vector> AircraftDockingDirs;
@@ -122,10 +125,13 @@ class BuildingTypeExt
, ConsideredVehicle {}
, ZShapePointMove_OnBuildup { false }
, SellBuildupLength { 23 }
- , AutoUpgrade { false }
+ , AutoBuilding { false }
+ , AutoBuilding_Gap { 1 }
, LimboBuild { false }
, LimboBuildID { -1 }
, LaserFencePost_Fence {}
+ , PlaceBuilding_OnLand {}
+ , PlaceBuilding_OnWater {}
, AircraftDockingDirs {}
, FactoryPlant_AllowTypes {}
, FactoryPlant_DisallowTypes {}
@@ -179,7 +185,7 @@ class BuildingTypeExt
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 AutoUpgradeBuilding(BuildingClass* pBuilding);
+ static bool AutoPlaceBuilding(BuildingClass* pBuilding);
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 9de5cee471..1c75592078 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -409,6 +409,35 @@ DEFINE_HOOK(0x47EEBC, CellClass_DrawPlaceGrid_RecordCell, 0x6)
return DontDrawAlt;
}
+static inline BuildingTypeClass* GetAnotherPlacingType(BuildingTypeClass* pType, CellStruct checkCell, bool opposite)
+{
+ if (!pType->PlaceAnywhere)
+ {
+ const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pType);
+
+ if (!pTypeExt->LimboBuild)
+ {
+ const auto onWater = MapClass::Instance->GetCellAt(checkCell)->LandType == LandType::Water;
+
+ if (const auto pAnotherType = (opposite ^ onWater) ? (pType->Naval ? nullptr : pTypeExt->PlaceBuilding_OnWater) : (pType->Naval ? pTypeExt->PlaceBuilding_OnLand : nullptr))
+ {
+ if (pAnotherType->BuildCat == pType->BuildCat && !pAnotherType->PlaceAnywhere && !BuildingTypeExt::ExtMap.Find(pAnotherType)->LimboBuild)
+ return pAnotherType;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+static inline BuildingTypeClass* GetAnotherPlacingType(DisplayClass* pDisplay)
+{
+ if (const auto pCurrentBuilding = abstract_cast(pDisplay->CurrentBuilding))
+ return GetAnotherPlacingType(pCurrentBuilding->Type, pDisplay->CurrentFoundation_CenterCell, false);
+
+ return nullptr;
+}
+
// Buildable-upon TechnoTypes Hook #3 -> sub_4FB0E0 - Hang up place event if there is only infantries and units on the cell
DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5)
{
@@ -416,15 +445,58 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5)
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 && RulesExt::Global()->ExpandBuildingPlace)
+ if (pTechno->WhatAmI() == AbstractType::Building)
{
- const auto pBuilding = static_cast(pTechno);
- const auto pBuildingType = pBuilding->Type;
- const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pBuildingType);
const auto pDisplay = DisplayClass::Instance();
+ auto pBuilding = static_cast(pTechno);
+ auto pBuildingType = pBuilding->Type;
+ const auto pBufferBuilding = pBuilding;
+ const auto pBufferType = pBuildingType;
+
+ auto checkCell = topLeftCell;
+
+ if (pBuildingType->Gate)
+ {
+ if (pBuildingType->GetFoundationWidth() > 2)
+ checkCell.X += 1;
+ else if (pBuildingType->GetFoundationHeight(false) > 2)
+ checkCell.Y += 1;
+ }
+ else if (pBuildingType->GetFoundationWidth() > 2 || pBuildingType->GetFoundationHeight(false) > 2)
+ {
+ checkCell += CellStruct { 1, 1 };
+ }
+
+ if (const auto pOtherType = GetAnotherPlacingType(pBuildingType, checkCell, false))
+ {
+ pBuildingType = pOtherType;
+ }
+ else if (const auto pAnotherType = GetAnotherPlacingType(pBuildingType, topLeftCell, true)) // Center cell may be different, so make assumptions
+ {
+ checkCell = topLeftCell;
+
+ if (pAnotherType->Gate)
+ {
+ if (pAnotherType->GetFoundationWidth() > 2)
+ checkCell.X += 1;
+ else if (pAnotherType->GetFoundationHeight(false) > 2)
+ checkCell.Y += 1;
+ }
+ else if (pAnotherType->GetFoundationWidth() > 2 || pAnotherType->GetFoundationHeight(false) > 2)
+ {
+ checkCell += CellStruct { 1, 1 };
+ }
+
+ // If the land occupation of the two buildings is different, the larger one will prevail, And the smaller one may not be placed on the shore.
+ if ((MapClass::Instance->GetCellAt(checkCell)->LandType == LandType::Water) ^ pBuildingType->Naval)
+ pBuildingType = pAnotherType;
+ }
+
+ const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pBuildingType);
if (pTypeExt->LimboBuild)
{
@@ -435,7 +507,7 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5)
pDisplay->SetActiveFoundation(nullptr);
pDisplay->CurrentBuilding = nullptr;
pDisplay->CurrentBuildingType = nullptr;
- pDisplay->CurrentBuildingOwnerArrayIndex = -1;
+ pDisplay->CurrentBuildingOwnerArrayIndexCopy = -1;
if (!Unsorted::ArmageddonMode)
{
@@ -463,103 +535,148 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5)
return BuildSucceeded;
}
- else
+
+ if (RulesExt::Global()->ExpandBuildingPlace && !pBuildingType->PlaceAnywhere && !pBuildingType->PowersUpBuilding[0])
{
- if (!pBuildingType->PlaceAnywhere && !pBuildingType->PowersUpBuilding[0])
+ const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
+ bool canBuild = true;
+ bool noOccupy = true;
+
+ for (auto pFoundation = pBuildingType->GetFoundationData(false); *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation)
{
- const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
- bool canBuild = true;
- bool noOccupy = true;
+ const auto currentCoord = topLeftCell + *pFoundation;
+ const auto pCell = pDisplay->GetCellAt(currentCoord);
- for (auto pFoundation = pBuildingType->GetFoundationData(false); *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation)
+ if (!pCell->CanThisExistHere(pBuildingType->SpeedType, pBuildingType, pHouse))
{
- const auto currentCoord = topLeftCell + *pFoundation;
- const auto pCell = pDisplay->GetCellAt(currentCoord);
-
- if (!pCell->CanThisExistHere(pBuildingType->SpeedType, pBuildingType, pHouse))
- {
- canBuild = false;
- break;
- }
- else if (ProximityTemp::Exist)
- {
- noOccupy = false;
- }
+ canBuild = false;
+ break;
}
+ else if (ProximityTemp::Exist)
+ {
+ noOccupy = false;
+ }
+ }
- ProximityTemp::Exist = false;
+ ProximityTemp::Exist = false;
- do
+ do
+ {
+ if (canBuild)
{
- if (canBuild)
- {
- if (noOccupy)
- break; // Can Build
+ if (noOccupy)
+ break; // Can Build
- do
+ do
+ {
+ if (topLeftCell != pHouseExt->CurrentBuildingTopLeft || pBufferType != pHouseExt->CurrentBuildingType) // New command
{
- if (topLeftCell != pHouseExt->CurrentBuildingTopLeft || pBuildingType != pHouseExt->CurrentBuildingType) // New command
- {
- pHouseExt->CurrentBuildingType = pBuildingType;
- pHouseExt->CurrentBuildingTimes = 30;
- pHouseExt->CurrentBuildingTopLeft = topLeftCell;
- }
- else if (pHouseExt->CurrentBuildingTimes <= 0)
- {
- break; // Time out
- }
+ pHouseExt->CurrentBuildingType = pBufferType;
+ pHouseExt->CurrentBuildingDrawType = pBuildingType;
+ pHouseExt->CurrentBuildingTimes = 30;
+ pHouseExt->CurrentBuildingTopLeft = topLeftCell;
+ }
+ else if (pHouseExt->CurrentBuildingTimes <= 0)
+ {
+ break; // Time out
+ }
- if (!(pHouseExt->CurrentBuildingTimes % 5) && BuildingTypeExt::CleanUpBuildingSpace(pBuildingType, topLeftCell, pHouse))
- break; // No place for cleaning
+ if (!(pHouseExt->CurrentBuildingTimes % 5) && BuildingTypeExt::CleanUpBuildingSpace(pBuildingType, topLeftCell, pHouse))
+ break; // No place for cleaning
- if (pHouse == HouseClass::CurrentPlayer && pHouseExt->CurrentBuildingTimes == 30)
- {
- pDisplay->SetActiveFoundation(nullptr);
- pDisplay->CurrentBuilding = nullptr;
- pDisplay->CurrentBuildingType = nullptr;
- pDisplay->CurrentBuildingOwnerArrayIndex = -1;
+ if (pHouse == HouseClass::CurrentPlayer && pHouseExt->CurrentBuildingTimes == 30)
+ {
+ 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;
- }
+ if (!Unsorted::ArmageddonMode)
+ {
+ reinterpret_cast(0x4A8D50)(pDisplay, nullptr); // Clear CurrentFoundationCopy_Data
+ pDisplay->CurrentBuildingCopy = nullptr;
+ pDisplay->CurrentBuildingTypeCopy = nullptr;
}
-
- --pHouseExt->CurrentBuildingTimes;
- pHouseExt->CurrentBuildingTimer.Start(8);
-
- return TemporarilyCanNotBuild;
}
- while (false);
- }
- if (pHouseExt->CurrentBuildingType == pBuildingType)
- {
- pHouseExt->CurrentBuildingType = nullptr;
- pHouseExt->CurrentBuildingTimes = 0;
- pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
- pHouseExt->CurrentBuildingTimer.Stop();
+ --pHouseExt->CurrentBuildingTimes;
+ pHouseExt->CurrentBuildingTimer.Start(8);
- if (pHouseExt->CurrentBuildingTimes != 30)
- return CanNotBuild;
+ return TemporarilyCanNotBuild;
}
-
- ProximityTemp::Mouse = true;
- return CanNotBuild;
+ while (false);
}
- while (false);
- if (pHouseExt->CurrentBuildingType == pBuildingType)
+ if (pHouseExt->CurrentBuildingType == pBufferType)
{
pHouseExt->CurrentBuildingType = nullptr;
+ pHouseExt->CurrentBuildingDrawType = nullptr;
pHouseExt->CurrentBuildingTimes = 0;
pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
pHouseExt->CurrentBuildingTimer.Stop();
+
+ if (pHouseExt->CurrentBuildingTimes != 30)
+ return CanNotBuild;
+ }
+
+ ProximityTemp::Mouse = true;
+ return CanNotBuild;
+ }
+ while (false);
+
+ if (pHouseExt->CurrentBuildingType == pBufferType)
+ {
+ pHouseExt->CurrentBuildingType = nullptr;
+ pHouseExt->CurrentBuildingDrawType = nullptr;
+ pHouseExt->CurrentBuildingTimes = 0;
+ pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
+ pHouseExt->CurrentBuildingTimer.Stop();
+ }
+ }
+
+ 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 == pBufferBuilding)
+ pDisplay->CurrentBuilding = pBuilding;
+
+ 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);
}
+
+ return CanBuild;
}
+
+ if (pBufferBuilding != pBuilding)
+ pBuilding->UnInit();
+
+ ProximityTemp::Mouse = true;
+ return CanNotBuild;
}
pFactory->SendCommand(RadioCommand::RequestLink, pTechno);
@@ -589,6 +706,29 @@ DEFINE_HOOK(0x4FB395, HouseClass_UnitFromFactory_SkipMouseReturn, 0x6)
return SkipGameCode;
}
+static inline bool IsSameBuildingType(BuildingTypeClass* pType1, BuildingTypeClass* pType2)
+{
+ if (pType1 == pType2)
+ return true;
+
+ if (pType1->BuildCat != pType2->BuildCat || pType1->PlaceAnywhere || pType2->PlaceAnywhere)
+ return false;
+
+ const auto pType1Ext = BuildingTypeExt::ExtMap.Find(pType1);
+ const auto pType2Ext = BuildingTypeExt::ExtMap.Find(pType2);
+
+ if (pType1Ext->LimboBuild || pType2Ext->LimboBuild)
+ return false;
+
+ if (pType1Ext->PlaceBuilding_OnLand == pType2 || pType1Ext->PlaceBuilding_OnWater == pType2)
+ return true;
+
+ if (pType2Ext->PlaceBuilding_OnLand == pType1 || pType2Ext->PlaceBuilding_OnWater == pType1)
+ return true;
+
+ return false;
+}
+
// Buildable-upon TechnoTypes Hook #4-2 -> sub_4FB0E0 - Check whether need to skip the clear command
DEFINE_HOOK(0x4FB319, HouseClass_UnitFromFactory_SkipMouseClear, 0x5)
{
@@ -596,10 +736,16 @@ DEFINE_HOOK(0x4FB319, HouseClass_UnitFromFactory_SkipMouseClear, 0x5)
GET(TechnoClass* const, pTechno, ESI);
- if (const auto pBuilding = abstract_cast(pTechno))
+ if (RulesExt::Global()->ExpandBuildingPlace)
{
- if (RulesExt::Global()->ExpandBuildingPlace && DisplayClass::Instance->CurrentBuildingTypeCopy != pBuilding->Type)
- return SkipGameCode;
+ if (const auto pBuilding = abstract_cast(pTechno))
+ {
+ if (const auto pBufferType = abstract_cast(DisplayClass::Instance->CurrentBuildingTypeCopy))
+ {
+ if (!IsSameBuildingType(pBuilding->Type, pBufferType))
+ return SkipGameCode;
+ }
+ }
}
return 0;
@@ -612,7 +758,16 @@ DEFINE_HOOK(0x4FAB83, HouseClass_AbandonProductionOf_SkipMouseClear, 0x7)
GET(const int, index, EBX);
- return (RulesExt::Global()->ExpandBuildingPlace && index >= 0 && BuildingTypeClass::Array->Items[index] != DisplayClass::Instance->CurrentBuildingType) ? SkipGameCode : 0;
+ if (RulesExt::Global()->ExpandBuildingPlace && index >= 0)
+ {
+ if (const auto pCurrentBuildingType = abstract_cast(DisplayClass::Instance->CurrentBuildingType))
+ {
+ if (!IsSameBuildingType(BuildingTypeClass::Array->Items[index], pCurrentBuildingType))
+ return SkipGameCode;
+ }
+ }
+
+ return 0;
}
// Buildable-upon TechnoTypes Hook #5 -> sub_4C9FF0 - Restart timer and clear buffer when abandon building production
@@ -627,12 +782,13 @@ DEFINE_HOOK(0x4CA05B, FactoryClass_AbandonProduction_AbandonCurrentBuilding, 0x5
if (!pHouseExt || !pHouseExt->CurrentBuildingType)
return 0;
- const auto pTechno = pFactory->Object;
+ const auto pBuilding = abstract_cast(pFactory->Object);
- if (pTechno->WhatAmI() != AbstractType::Building || pHouseExt->CurrentBuildingType != static_cast(pTechno)->Type)
+ if (!pBuilding || (pFactory->Owner->IsControlledByHuman() && pHouseExt->CurrentBuildingType != pBuilding->Type))
return 0;
pHouseExt->CurrentBuildingType = nullptr;
+ pHouseExt->CurrentBuildingDrawType = nullptr;
pHouseExt->CurrentBuildingTimes = 0;
pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
pHouseExt->CurrentBuildingTimer.Stop();
@@ -651,48 +807,48 @@ DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
GET(BuildingClass* const, pFactory, ESI);
GET(const CellStruct, topLeftCell, EDX);
- if (!RulesExt::Global()->ExpandBuildingPlace)
- return 0;
-
const auto pBuildingType = pBuilding->Type;
+ const auto pHouse = pFactory->Owner;
+ const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pBuildingType);
- if (topLeftCell != CellStruct::Empty && !pBuildingType->PlaceAnywhere)
+ if (pTypeExt->LimboBuild)
{
- const auto pHouse = pFactory->Owner;
- const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pBuildingType);
+ BuildingTypeExt::CreateLimboBuilding(pBuilding, pBuildingType, pHouse, pTypeExt->LimboBuildID);
- if (pTypeExt->LimboBuild)
+ if (pBaseNode)
{
- BuildingTypeExt::CreateLimboBuilding(pBuilding, pBuildingType, pHouse, pTypeExt->LimboBuildID);
+ pBaseNode->Placed = true;
+ pBaseNode->Attempts = 0;
- if (pBaseNode)
- {
- pBaseNode->Placed = true;
- pBaseNode->Attempts = 0;
+ if (pHouse->ProducingBuildingTypeIndex == pBuildingType->ArrayIndex)
+ pHouse->ProducingBuildingTypeIndex = -1;
+ }
- if (pHouse->ProducingBuildingTypeIndex == pBuildingType->ArrayIndex)
- pHouse->ProducingBuildingTypeIndex = -1;
- }
+ const auto pFactoryType = pFactory->Type;
- const auto pFactoryType = pFactory->Type;
+ if (pFactoryType->ConstructionYard)
+ {
+ VocClass::PlayGlobal(RulesClass::Instance->BuildingSlam, 0x2000, 1.0);
- if (pFactoryType->ConstructionYard)
- {
- VocClass::PlayGlobal(RulesClass::Instance->BuildingSlam, 0x2000, 1.0);
+ pFactory->DestroyNthAnim(BuildingAnimSlot::PreProduction);
+ pFactory->DestroyNthAnim(BuildingAnimSlot::Idle);
- 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;
- 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);
+ }
- if (pAnimName && *pAnimName)
- pFactory->PlayAnim(pAnimName, BuildingAnimSlot::Production, damaged, false);
- }
+ return BuildSucceeded;
+ }
- return BuildSucceeded;
- }
- else if (!pBuildingType->PowersUpBuilding[0])
+ if (!RulesExt::Global()->ExpandBuildingPlace)
+ return 0;
+
+ if (topLeftCell != CellStruct::Empty && !pBuildingType->PlaceAnywhere)
+ {
+ if (!pBuildingType->PowersUpBuilding[0])
{
const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
bool canBuild = true;
@@ -728,6 +884,7 @@ DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
if (topLeftCell != pHouseExt->CurrentBuildingTopLeft || pBuildingType != pHouseExt->CurrentBuildingType) // New command
{
pHouseExt->CurrentBuildingType = pBuildingType;
+ pHouseExt->CurrentBuildingDrawType = pBuildingType;
pHouseExt->CurrentBuildingTimes = 30;
pHouseExt->CurrentBuildingTopLeft = topLeftCell;
}
@@ -755,6 +912,7 @@ DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
}
pHouseExt->CurrentBuildingType = nullptr;
+ pHouseExt->CurrentBuildingDrawType = nullptr;
pHouseExt->CurrentBuildingTimes = 0;
pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
pHouseExt->CurrentBuildingTimer.Stop();
@@ -764,6 +922,7 @@ DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
while (false);
pHouseExt->CurrentBuildingType = nullptr;
+ pHouseExt->CurrentBuildingDrawType = nullptr;
pHouseExt->CurrentBuildingTimes = 0;
pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
pHouseExt->CurrentBuildingTimer.Stop();
@@ -985,30 +1144,30 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6)
{
GET(const HouseClass* const, pHouse, ESI);
- if (!pHouse->IsControlledByHuman() || !RulesExt::Global()->ExpandBuildingPlace)
+ if (!pHouse->IsControlledByHuman())
return 0;
- if (pHouse->RecheckTechTree)
+ if (const auto pFactory = pHouse->Primary_ForBuildings)
{
- if (const auto pFactory = pHouse->Primary_ForBuildings)
+ if (pFactory->IsDone())
{
- if (pFactory->IsDone())
- {
- if (const auto pBuilding = abstract_cast(pFactory->Object))
- BuildingTypeExt::AutoUpgradeBuilding(pBuilding);
- }
+ if (const auto pBuilding = abstract_cast(pFactory->Object))
+ BuildingTypeExt::AutoPlaceBuilding(pBuilding);
}
+ }
- if (const auto pFactory = pHouse->Primary_ForDefenses)
+ if (const auto pFactory = pHouse->Primary_ForDefenses)
+ {
+ if (pFactory->IsDone())
{
- if (pFactory->IsDone())
- {
- if (const auto pBuilding = abstract_cast(pFactory->Object))
- BuildingTypeExt::AutoUpgradeBuilding(pBuilding);
- }
+ if (const auto pBuilding = abstract_cast(pFactory->Object))
+ BuildingTypeExt::AutoPlaceBuilding(pBuilding);
}
}
+ if (!RulesExt::Global()->ExpandBuildingPlace)
+ return 0;
+
if (const auto pHouseExt = HouseExt::ExtMap.Find(pHouse))
{
if (pHouse == HouseClass::CurrentPlayer) // Prevent unexpected wrong event
@@ -1065,6 +1224,31 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6)
return 0;
}
+DEFINE_HOOK_AGAIN(0x4ABA47, DisplayClass_PreparePassesProximityCheck_ReplaceBuildingType, 0x6)
+DEFINE_HOOK(0x4A946E, DisplayClass_PreparePassesProximityCheck_ReplaceBuildingType, 0x6)
+{
+ const auto pDisplay = DisplayClass::Instance();
+
+ if (const auto pAnotherType = GetAnotherPlacingType(pDisplay))
+ {
+ if (pDisplay->CurrentBuildingType && pDisplay->CurrentBuildingType != pAnotherType)
+ {
+ pDisplay->CurrentBuildingType = pAnotherType;
+ pDisplay->SetActiveFoundation(pAnotherType->GetFoundationData(true));
+ }
+ }
+ else if (const auto pCurrentBuilding = abstract_cast(pDisplay->CurrentBuilding))
+ {
+ if (pDisplay->CurrentBuildingType && pDisplay->CurrentBuildingType != pCurrentBuilding->Type)
+ {
+ pDisplay->CurrentBuildingType = pCurrentBuilding->Type;
+ pDisplay->SetActiveFoundation(pCurrentBuilding->Type->GetFoundationData(true));
+ }
+ }
+
+ return 0;
+}
+
// Buildable-upon TechnoTypes Hook #12 -> sub_6D5030 - Draw the placing building preview
DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6)
{
@@ -1081,9 +1265,9 @@ DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6)
const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
const bool isPlayer = pHouse == pPlayer;
{
- auto pType = pHouseExt->CurrentBuildingType;
+ auto pType = pHouseExt->CurrentBuildingDrawType;
- if (pType || (isPlayer && (pType = abstract_cast(DisplayClass::Instance->CurrentBuildingTypeCopy), pType)))
+ if (pType || (isPlayer && (pType = abstract_cast(pDisplay->CurrentBuildingTypeCopy), pType)))
{
auto pCell = pDisplay->TryGetCellAt(pHouseExt->CurrentBuildingTopLeft);
@@ -1170,7 +1354,7 @@ DEFINE_HOOK(0x6A8E34, StripClass_Update_AutoBuildBuildings, 0x7)
GET(BuildingClass* const, pBuilding, ESI);
- return (RulesExt::Global()->ExpandBuildingPlace && (BuildingTypeExt::BuildLimboBuilding(pBuilding) || BuildingTypeExt::AutoUpgradeBuilding(pBuilding))) ? SkipSetStripShortCut : 0;
+ return (BuildingTypeExt::BuildLimboBuilding(pBuilding) || BuildingTypeExt::AutoPlaceBuilding(pBuilding)) ? SkipSetStripShortCut : 0;
}
// Limbo Build Hook -> sub_42EB50 - Check Base Node
@@ -1189,7 +1373,7 @@ DEFINE_HOOK(0x42EB8E, BaseClass_GetBaseNodeIndex_CheckValidBaseNode, 0x6)
{
const auto pType = BuildingTypeClass::Array->Items[index];
- if (RulesExt::Global()->ExpandBuildingPlace && BuildingTypeExt::ExtMap.Find(pType)->LimboBuild)
+ if (BuildingTypeExt::ExtMap.Find(pType)->LimboBuild)
return Invalid;
}
}
diff --git a/src/Ext/BuildingType/Hooks.cpp b/src/Ext/BuildingType/Hooks.cpp
index 8c1622e254..65d1af1911 100644
--- a/src/Ext/BuildingType/Hooks.cpp
+++ b/src/Ext/BuildingType/Hooks.cpp
@@ -73,11 +73,10 @@ DEFINE_HOOK(0x6D528A, TacticalClass_DrawPlacement_PlacementPreview, 0x6)
return 0;
auto pBuilding = specific_cast(DisplayClass::Instance->CurrentBuilding);
- auto pType = pBuilding ? pBuilding->Type : nullptr;
+ auto pType = specific_cast(DisplayClass::Instance->CurrentBuildingType);
auto pTypeExt = pType ? BuildingTypeExt::ExtMap.Find(pType) : nullptr;
- bool isShow = pTypeExt && pTypeExt->PlacementPreview;
- if (isShow)
+ if (pBuilding && pTypeExt && pTypeExt->PlacementPreview)
{
CellClass* pCell = nullptr;
{
diff --git a/src/Ext/House/Body.cpp b/src/Ext/House/Body.cpp
index 8d1e0b5a17..099751c623 100644
--- a/src/Ext/House/Body.cpp
+++ b/src/Ext/House/Body.cpp
@@ -609,6 +609,7 @@ void HouseExt::ExtData::Serialize(T& Stm)
.Process(this->OwnedLimboDeliveredBuildings)
.Process(this->OwnedDeployingUnits)
.Process(this->CurrentBuildingType)
+ .Process(this->CurrentBuildingDrawType)
.Process(this->CurrentBuildingTopLeft)
.Process(this->CurrentBuildingTimer)
.Process(this->CurrentBuildingTimes)
diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h
index ddd4ed30b8..b45cc6f083 100644
--- a/src/Ext/House/Body.h
+++ b/src/Ext/House/Body.h
@@ -26,6 +26,7 @@ class HouseExt
std::vector OwnedDeployingUnits;
BuildingTypeClass* CurrentBuildingType;
+ BuildingTypeClass* CurrentBuildingDrawType;
CellStruct CurrentBuildingTopLeft;
CDTimerClass CurrentBuildingTimer;
int CurrentBuildingTimes;
@@ -73,6 +74,7 @@ class HouseExt
, OwnedLimboDeliveredBuildings {}
, OwnedDeployingUnits {}
, CurrentBuildingType { nullptr }
+ , CurrentBuildingDrawType { nullptr }
, CurrentBuildingTopLeft {}
, CurrentBuildingTimer {}
, CurrentBuildingTimes { 0 }
From 4f7c444dab02337cb78e380cbcc6257466d04955 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Thu, 23 Jan 2025 16:34:08 +0800
Subject: [PATCH 10/58] Fix a typo
---
src/Ext/BuildingType/Hooks.Placing.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp
index 1c75592078..baba1377a0 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -730,7 +730,7 @@ static inline bool IsSameBuildingType(BuildingTypeClass* pType1, BuildingTypeCla
}
// Buildable-upon TechnoTypes Hook #4-2 -> sub_4FB0E0 - Check whether need to skip the clear command
-DEFINE_HOOK(0x4FB319, HouseClass_UnitFromFactory_SkipMouseClear, 0x5)
+DEFINE_HOOK(0x4FB339, HouseClass_UnitFromFactory_SkipMouseClear, 0x6)
{
enum { SkipGameCode = 0x4FB4A0 };
@@ -740,9 +740,9 @@ DEFINE_HOOK(0x4FB319, HouseClass_UnitFromFactory_SkipMouseClear, 0x5)
{
if (const auto pBuilding = abstract_cast(pTechno))
{
- if (const auto pBufferType = abstract_cast(DisplayClass::Instance->CurrentBuildingTypeCopy))
+ if (const auto pCurrentType = abstract_cast(DisplayClass::Instance->CurrentBuildingType))
{
- if (!IsSameBuildingType(pBuilding->Type, pBufferType))
+ if (!IsSameBuildingType(pBuilding->Type, pCurrentType))
return SkipGameCode;
}
}
From 09f31164910abbda8909f98bf9e73d518ecbbef2 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Thu, 23 Jan 2025 20:33:32 +0800
Subject: [PATCH 11/58] Fix naval typo and fit with wall for `AutoBuilding`
---
src/Ext/BuildingType/Body.cpp | 177 ++++++++++++++++++-------
src/Ext/BuildingType/Hooks.Placing.cpp | 23 ++--
2 files changed, 140 insertions(+), 60 deletions(-)
diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp
index a858b12b63..f5483694c7 100644
--- a/src/Ext/BuildingType/Body.cpp
+++ b/src/Ext/BuildingType/Body.cpp
@@ -457,7 +457,7 @@ bool BuildingTypeExt::AutoPlaceBuilding(BuildingClass* pBuilding)
const auto pType = pBuilding->Type;
const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pType);
- if (!pTypeExt->AutoBuilding)
+ if (!pTypeExt->AutoBuilding || pType->LaserFence || pType->Gate || pType->ToTile)
return false;
const auto pHouse = pBuilding->Owner;
@@ -465,89 +465,166 @@ bool BuildingTypeExt::AutoPlaceBuilding(BuildingClass* pBuilding)
if (pHouse->Buildings.Count <= 0)
return false;
+ const auto foundation = pType->GetFoundationData(true);
+
+ auto canBuildHere = [&pType, &pHouse, &foundation](CellStruct cell)
+ {
+ return reinterpret_cast(0x4A8EB0)(MapClass::Instance(),
+ pType, pHouse->ArrayIndex, foundation, &cell) // Adjacent
+ && reinterpret_cast(0x4A9070)(MapClass::Instance(),
+ pType, pHouse->ArrayIndex, foundation, &cell); // NoShroud
+ };
+
const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
- if (pType->PowersUpBuilding[0])
+ auto getMapCell = [&pHouseExt](BuildingClass* pBuilding)
+ {
+ if (!pBuilding->IsAlive || pBuilding->Health <= 0 || !pBuilding->IsOnMap || pBuilding->InLimbo || pHouseExt->OwnsLimboDeliveredBuilding(pBuilding))
+ return CellStruct::Empty;
+
+ return pBuilding->GetMapCoords();
+ };
+
+ auto addPlaceEvent = [&pType, &pHouse](CellStruct cell)
+ {
+ const EventClass event (pHouse->ArrayIndex, EventType::Place, AbstractType::Building, pType->GetArrayIndex(), pType->Naval, cell);
+ EventClass::AddEvent(event);
+ };
+
+ if (pType->LaserFencePost || pType->Wall)
{
for (const auto& pOwned : pHouse->Buildings)
{
- if (!reinterpret_cast(0x452670)(pOwned, pType, pHouse)) // CanUpgradeBuilding
- continue;
+ const auto pOwnedType = pOwned->Type;
- if (!pOwned->IsAlive || pOwned->Health <= 0 || !pOwned->IsOnMap || pOwned->InLimbo || pOwned->CurrentMission == Mission::Selling)
+ if (!pOwnedType->ProtectWithWall)
continue;
- const auto cell = pOwned->GetMapCoords();
+ const auto baseCell = getMapCell(pOwned);
- if (cell == CellStruct::Empty || pHouseExt->OwnsLimboDeliveredBuilding(pOwned))
+ if (baseCell == CellStruct::Empty)
continue;
- const EventClass event
- (
- pHouse->ArrayIndex,
- EventType::Place,
- AbstractType::Building,
- pType->GetArrayIndex(),
- pType->Naval,
- cell
- );
- EventClass::AddEvent(event);
+ const auto width = pOwnedType->GetFoundationWidth();
+ const auto height = pOwnedType->GetFoundationHeight(true);
+ auto cell = CellStruct::Empty;
+ int index = 0, check = width + 1, count = 0;
+
+ for (auto pFoundation = pOwnedType->FoundationOutside; *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation)
+ {
+ if (++index != check)
+ continue;
+
+ check += (++count & 1) ? 1 : (height * 2 + width + 1);
+ const auto outsideCell = baseCell + *pFoundation;
+ const auto pCell = MapClass::Instance->TryGetCellAt(outsideCell);
+ if (pCell && pCell->CanThisExistHere(pOwnedType->SpeedType, pOwnedType, pHouse) && canBuildHere(outsideCell))
+ {
+ addPlaceEvent(outsideCell);
+ return true;
+ }
+ }
+
+ for (auto pFoundation = pOwnedType->FoundationOutside; *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation)
+ {
+ const auto outsideCell = baseCell + *pFoundation;
+ const auto pCell = MapClass::Instance->TryGetCellAt(outsideCell);
+
+ if (pCell && pCell->CanThisExistHere(pOwnedType->SpeedType, pOwnedType, pHouse) && canBuildHere(outsideCell))
+ cell = outsideCell;
+ }
+
+ if (cell == CellStruct::Empty)
+ continue;
+
+ addPlaceEvent(cell);
return true;
}
+
+ return false;
}
- else
+ else if (pType->PlaceAnywhere)
{
- const auto speedType = pType->Naval ? SpeedType::Float : SpeedType::Track;
- const auto buildGap = pTypeExt->AutoBuilding_Gap * 2;
- const auto width = pType->GetFoundationWidth() + buildGap;
- const auto height = pType->GetFoundationHeight(false) + buildGap;
- const auto offset = CellSpread::GetNeighbourOffset(Unsorted::CurrentFrame() & 7u);
- const auto buildOffset = CellStruct { static_cast(pTypeExt->AutoBuilding_Gap), static_cast(pTypeExt->AutoBuilding_Gap) };
- const auto foundation = pType->GetFoundationData(true);
+ for (const auto& pOwned : pHouse->Buildings)
+ {
+ if (!pOwned->Type->BaseNormal)
+ continue;
+
+ const auto cell = getMapCell(pOwned);
+ if (cell == CellStruct::Empty || !canBuildHere(cell))
+ continue;
+
+ addPlaceEvent(cell);
+ return true;
+ }
+
+ return false;
+ }
+ else if (pType->PowersUpBuilding[0])
+ {
for (const auto& pOwned : pHouse->Buildings)
{
- if (!pOwned->IsAlive || pOwned->Health <= 0 || !pOwned->IsOnMap || pOwned->InLimbo || pOwned->CurrentMission == Mission::Selling)
+ if (!reinterpret_cast(0x452670)(pOwned, pType, pHouse)) // CanUpgradeBuilding
continue;
- const auto baseCell = pOwned->GetMapCoords();
+ const auto cell = getMapCell(pOwned);
- if (baseCell == CellStruct::Empty || !pOwned->Type->BaseNormal || pHouseExt->OwnsLimboDeliveredBuilding(pOwned))
+ if (cell == CellStruct::Empty || pOwned->CurrentMission == Mission::Selling || !canBuildHere(cell))
continue;
+ addPlaceEvent(cell);
+ return true;
+ }
+
+ return false;
+ }
+
+ const auto buildGap = static_cast(pTypeExt->AutoBuilding_Gap + pType->ProtectWithWall ? 1 : 0);
+ const auto doubleGap = buildGap * 2;
+ const auto width = pType->GetFoundationWidth() + doubleGap;
+ const auto height = pType->GetFoundationHeight(true) + doubleGap;
+ const auto speedType = pType->SpeedType == SpeedType::Float ? SpeedType::Float : SpeedType::Track;
+ const auto buildable = speedType != SpeedType::Float;
+
+ auto tryBuildAt = [&](DynamicVectorClass& vector, bool baseNormal)
+ {
+ for (const auto& pBase : vector)
+ {
+ if (baseNormal && !pBase->Type->BaseNormal)
+ continue;
+
+ const auto baseCell = getMapCell(pBase);
+
+ if (baseCell == CellStruct::Empty)
+ continue;
+
+ // TODO The construction area does not actually need to be so large, the surrounding space should be able to be occupied by other things
+ // TODO It would be better if the Buildable check could be fit with ExpandBuildingPlace within this function.
+ // TODO Similarly, it would be better if the following Adjacent & NoShroud check could be made within this function.
auto cell = pType->PlaceAnywhere ? baseCell : MapClass::Instance->NearByLocation(baseCell, speedType, -1, MovementZone::Normal, false,
- width, height, false, false, false, false, (baseCell + offset), false, true);
+ width, height, false, false, false, false, CellStruct::Empty, false, buildable);
if (cell == CellStruct::Empty)
- break;
+ return false;
- cell += buildOffset;
+ cell += CellStruct { buildGap, buildGap };
- if (!reinterpret_cast(0x4A8EB0)(MapClass::Instance(),
- pType, pHouse->ArrayIndex, foundation, &cell) // Adjacent
- || !reinterpret_cast(0x4A9070)(MapClass::Instance(),
- pType, pHouse->ArrayIndex, foundation, &cell)) // NoShroud
- {
+ if (!canBuildHere(cell))
continue;
- }
-
- const EventClass event
- (
- pHouse->ArrayIndex,
- EventType::Place,
- AbstractType::Building,
- pType->GetArrayIndex(),
- pType->Naval,
- cell
- );
- EventClass::AddEvent(event);
+ addPlaceEvent(cell);
return true;
}
- }
- return false;
+ return false;
+ };
+
+ if (pHouse->ConYards.Count > 0 && tryBuildAt(pHouse->ConYards, false))
+ return true;
+
+ return tryBuildAt(pHouse->Buildings, true);
}
bool BuildingTypeExt::BuildLimboBuilding(BuildingClass* pBuilding)
diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp
index baba1377a0..3c86a8f95d 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -1147,21 +1147,24 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6)
if (!pHouse->IsControlledByHuman())
return 0;
- if (const auto pFactory = pHouse->Primary_ForBuildings)
+ if (pHouse->RecheckTechTree || !(Unsorted::CurrentFrame() % 15))
{
- if (pFactory->IsDone())
+ if (const auto pFactory = pHouse->Primary_ForBuildings)
{
- if (const auto pBuilding = abstract_cast(pFactory->Object))
- BuildingTypeExt::AutoPlaceBuilding(pBuilding);
+ if (pFactory->IsDone())
+ {
+ if (const auto pBuilding = abstract_cast(pFactory->Object))
+ BuildingTypeExt::AutoPlaceBuilding(pBuilding);
+ }
}
- }
- if (const auto pFactory = pHouse->Primary_ForDefenses)
- {
- if (pFactory->IsDone())
+ if (const auto pFactory = pHouse->Primary_ForDefenses)
{
- if (const auto pBuilding = abstract_cast(pFactory->Object))
- BuildingTypeExt::AutoPlaceBuilding(pBuilding);
+ if (pFactory->IsDone())
+ {
+ if (const auto pBuilding = abstract_cast(pFactory->Object))
+ BuildingTypeExt::AutoPlaceBuilding(pBuilding);
+ }
}
}
From 3da47510acf029533b381203af6d4fa626dce8d6 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Fri, 24 Jan 2025 15:01:58 +0800
Subject: [PATCH 12/58] Fix confusion
---
src/Ext/BuildingType/Hooks.Placing.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp
index 3c86a8f95d..52040d4edb 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -1147,7 +1147,7 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6)
if (!pHouse->IsControlledByHuman())
return 0;
- if (pHouse->RecheckTechTree || !(Unsorted::CurrentFrame() % 15))
+ if (pHouse == HouseClass::CurrentPlayer && (pHouse->RecheckTechTree || !(Unsorted::CurrentFrame() % 15)))
{
if (const auto pFactory = pHouse->Primary_ForBuildings)
{
From 2a78d8e72c7f183f0eb9b0c709f72d824d6b3810 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Fri, 24 Jan 2025 15:03:30 +0800
Subject: [PATCH 13/58] Rename key and add global setting
---
docs/New-or-Enhanced-Logics.md | 26 ++++++++++++++++----------
src/Ext/BuildingType/Body.cpp | 4 ++--
src/Ext/BuildingType/Body.h | 4 ++--
src/Ext/BuildingType/Hooks.Placing.cpp | 26 +++++++++++++-------------
src/Ext/House/Hooks.cpp | 2 +-
src/Ext/Rules/Body.cpp | 7 +++++--
src/Ext/Rules/Body.h | 8 ++++++--
src/Ext/Techno/Body.cpp | 2 +-
8 files changed, 46 insertions(+), 33 deletions(-)
diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md
index d72950f97d..3fbcf892d4 100644
--- a/docs/New-or-Enhanced-Logics.md
+++ b/docs/New-or-Enhanced-Logics.md
@@ -583,8 +583,8 @@ 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 `ExpandBuildingPlace` 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 non-player 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. Note that when the building has been recorded and is trying to place, unless the production queue has vanished (such as construction yard is no longer exist), it will continue to function normally until the conditions are not met.
-- `AutoBuilding` controls whether building can be automatically placed.
+- 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 non-player 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. Note that when the building has been recorded and is trying to place, unless the production queue has vanished (such as construction yard is no longer exist), it will continue to function normally until the conditions are not met.
+- `AutoBuilding` controls whether building can be automatically placed. Default to `[General]->AutoBuilding`
- `AutoBuilding.Gap` controls the gap of automatically placed buildings.
- `LimboBuild` controls whether building can be automatically placed like `LimboDelivery`.
- `LimboBuildID` defines the numeric ID of the building placed by `LimboBuild`.
@@ -594,15 +594,21 @@ SpyEffect.InfiltratorSuperWeapon= ; SuperWeaponType
In `rulesmd.ini`:
```ini
[General]
-ExpandBuildingPlace=false ; boolean
+ExtendedBuildingPlacing=false ; boolean
+AutoBuilding=false ; boolean
-[SOMEBUILDING] ; BuildingType
-AutoBuilding=false ; boolean
-AutoBuilding.Gap=0 ; integer
-LimboBuild=false ; boolean
-LimboBuildID=-1 ; integer
-PlaceBuilding.OnLand= ; BuildingType
-PlaceBuilding.OnWater= ; BuildingType
+[SOMEBUILDING] ; BuildingType
+AutoBuilding= ; boolean
+AutoBuilding.Gap=0 ; integer
+LimboBuild=false ; boolean
+LimboBuildID=-1 ; integer
+PlaceBuilding.OnLand= ; BuildingType
+PlaceBuilding.OnWater= ; BuildingType
+```
+
+```{note}
+- `AutoBuilding` not support buildings with `LaserFence=true` , `Gate=true` or `ToTile=true`.
+- `PlaceBuilding.OnLand` and `PlaceBuilding.OnWater` are only work for players.
```
## Infantry
diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp
index f5483694c7..a520f53b27 100644
--- a/src/Ext/BuildingType/Body.cpp
+++ b/src/Ext/BuildingType/Body.cpp
@@ -457,7 +457,7 @@ bool BuildingTypeExt::AutoPlaceBuilding(BuildingClass* pBuilding)
const auto pType = pBuilding->Type;
const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pType);
- if (!pTypeExt->AutoBuilding || pType->LaserFence || pType->Gate || pType->ToTile)
+ if (!pTypeExt->AutoBuilding.Get(RulesExt::Global()->AutoBuilding) || pType->LaserFence || pType->Gate || pType->ToTile)
return false;
const auto pHouse = pBuilding->Owner;
@@ -601,7 +601,7 @@ bool BuildingTypeExt::AutoPlaceBuilding(BuildingClass* pBuilding)
continue;
// TODO The construction area does not actually need to be so large, the surrounding space should be able to be occupied by other things
- // TODO It would be better if the Buildable check could be fit with ExpandBuildingPlace within this function.
+ // TODO It would be better if the Buildable check could be fit with ExtendedBuildingPlacing within this function.
// TODO Similarly, it would be better if the following Adjacent & NoShroud check could be made within this function.
auto cell = pType->PlaceAnywhere ? baseCell : MapClass::Instance->NearByLocation(baseCell, speedType, -1, MovementZone::Normal, false,
width, height, false, false, false, false, CellStruct::Empty, false, buildable);
diff --git a/src/Ext/BuildingType/Body.h b/src/Ext/BuildingType/Body.h
index 7c27fc6647..d087bf8e05 100644
--- a/src/Ext/BuildingType/Body.h
+++ b/src/Ext/BuildingType/Body.h
@@ -64,7 +64,7 @@ class BuildingTypeExt
Valueable SellBuildupLength;
Valueable IsDestroyableObstacle;
- Valueable AutoBuilding;
+ Nullable AutoBuilding;
Valueable AutoBuilding_Gap;
Valueable LimboBuild;
Valueable LimboBuildID;
@@ -125,7 +125,7 @@ class BuildingTypeExt
, ConsideredVehicle {}
, ZShapePointMove_OnBuildup { false }
, SellBuildupLength { 23 }
- , AutoBuilding { false }
+ , AutoBuilding {}
, AutoBuilding_Gap { 1 }
, LimboBuild { false }
, LimboBuildID { -1 }
diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp
index 52040d4edb..5241783f74 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -168,7 +168,7 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
if (!Game::IsActive)
return CanExistHere;
- const auto expand = RulesExt::Global()->ExpandBuildingPlace.Get();
+ const auto expand = RulesExt::Global()->ExtendedBuildingPlacing.Get();
bool landFootOnly = false;
if (pBuildingType->LaserFence)
@@ -392,7 +392,7 @@ DEFINE_HOOK(0x47EEBC, CellClass_DrawPlaceGrid_RecordCell, 0x6)
if (!(pCell->AltFlags & AltCellFlags::ContainsBuilding))
{
- if (!RulesExt::Global()->ExpandBuildingPlace)
+ if (!RulesExt::Global()->ExtendedBuildingPlacing)
{
R->EDX(flags | (zero ? BlitterFlags::Zero : BlitterFlags::Nonzero));
return DrawVanillaAlt;
@@ -536,7 +536,7 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5)
return BuildSucceeded;
}
- if (RulesExt::Global()->ExpandBuildingPlace && !pBuildingType->PlaceAnywhere && !pBuildingType->PowersUpBuilding[0])
+ if (RulesExt::Global()->ExtendedBuildingPlacing && !pBuildingType->PlaceAnywhere && !pBuildingType->PowersUpBuilding[0])
{
const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
bool canBuild = true;
@@ -693,7 +693,7 @@ DEFINE_HOOK(0x4FB395, HouseClass_UnitFromFactory_SkipMouseReturn, 0x6)
{
enum { SkipGameCode = 0x4FB489 };
- if (!RulesExt::Global()->ExpandBuildingPlace)
+ if (!RulesExt::Global()->ExtendedBuildingPlacing)
return 0;
if (ProximityTemp::Mouse)
@@ -736,7 +736,7 @@ DEFINE_HOOK(0x4FB339, HouseClass_UnitFromFactory_SkipMouseClear, 0x6)
GET(TechnoClass* const, pTechno, ESI);
- if (RulesExt::Global()->ExpandBuildingPlace)
+ if (RulesExt::Global()->ExtendedBuildingPlacing)
{
if (const auto pBuilding = abstract_cast(pTechno))
{
@@ -758,7 +758,7 @@ DEFINE_HOOK(0x4FAB83, HouseClass_AbandonProductionOf_SkipMouseClear, 0x7)
GET(const int, index, EBX);
- if (RulesExt::Global()->ExpandBuildingPlace && index >= 0)
+ if (RulesExt::Global()->ExtendedBuildingPlacing && index >= 0)
{
if (const auto pCurrentBuildingType = abstract_cast(DisplayClass::Instance->CurrentBuildingType))
{
@@ -775,7 +775,7 @@ DEFINE_HOOK(0x4CA05B, FactoryClass_AbandonProduction_AbandonCurrentBuilding, 0x5
{
GET(FactoryClass*, pFactory, ESI);
- if (RulesExt::Global()->ExpandBuildingPlace)
+ if (RulesExt::Global()->ExtendedBuildingPlacing)
{
const auto pHouseExt = HouseExt::ExtMap.Find(pFactory->Owner);
@@ -843,7 +843,7 @@ DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
return BuildSucceeded;
}
- if (!RulesExt::Global()->ExpandBuildingPlace)
+ if (!RulesExt::Global()->ExtendedBuildingPlacing)
return 0;
if (topLeftCell != CellStruct::Empty && !pBuildingType->PlaceAnywhere)
@@ -1018,7 +1018,7 @@ DEFINE_HOOK(0x73946C, UnitClass_TryToDeploy_CleanUpDeploySpace, 0x6)
GET(UnitClass* const, pUnit, EBP);
GET(CellStruct, topLeftCell, ESI);
- if (!RulesExt::Global()->ExpandBuildingPlace)
+ if (!RulesExt::Global()->ExtendedBuildingPlacing)
return 0;
const auto pTechnoExt = TechnoExt::ExtMap.Find(pUnit);
@@ -1102,7 +1102,7 @@ DEFINE_HOOK(0x73946C, UnitClass_TryToDeploy_CleanUpDeploySpace, 0x6)
// Buildable-upon TechnoTypes Hook #9-2 -> sub_73FD50 - Push the owner house into deploy check
DEFINE_HOOK(0x73FF8F, UnitClass_MouseOverObject_ShowDeployCursor, 0x6)
{
- if (RulesExt::Global()->ExpandBuildingPlace) // This IF check is not so necessary
+ 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));
@@ -1115,7 +1115,7 @@ DEFINE_HOOK(0x73FF8F, UnitClass_MouseOverObject_ShowDeployCursor, 0x6)
// Buildable-upon TechnoTypes Hook #10 -> sub_4C6CB0 - Stop deploy when get stop command
DEFINE_HOOK(0x4C7665, EventClass_RespondToEvent_StopDeployInIdleEvent, 0x6)
{
- if (RulesExt::Global()->ExpandBuildingPlace) // This IF check is not so necessary
+ if (RulesExt::Global()->ExtendedBuildingPlacing) // This IF check is not so necessary
{
GET(const UnitClass* const, pUnit, ESI);
@@ -1168,7 +1168,7 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6)
}
}
- if (!RulesExt::Global()->ExpandBuildingPlace)
+ if (!RulesExt::Global()->ExtendedBuildingPlacing)
return 0;
if (const auto pHouseExt = HouseExt::ExtMap.Find(pHouse))
@@ -1255,7 +1255,7 @@ DEFINE_HOOK(0x4A946E, DisplayClass_PreparePassesProximityCheck_ReplaceBuildingTy
// Buildable-upon TechnoTypes Hook #12 -> sub_6D5030 - Draw the placing building preview
DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6)
{
- if (!RulesExt::Global()->ExpandBuildingPlace)
+ if (!RulesExt::Global()->ExtendedBuildingPlacing)
return 0;
const auto pPlayer = HouseClass::CurrentPlayer();
diff --git a/src/Ext/House/Hooks.cpp b/src/Ext/House/Hooks.cpp
index 7e677a1dfd..3e4fe9ce80 100644
--- a/src/Ext/House/Hooks.cpp
+++ b/src/Ext/House/Hooks.cpp
@@ -208,7 +208,7 @@ DEFINE_HOOK(0x7015C9, TechnoClass_Captured_UpdateTracking, 0x6)
pNewOwnerExt->AddToLimboTracking(pType);
}
- if (RulesExt::Global()->ExpandBuildingPlace && pThis->WhatAmI() == AbstractType::Unit && pType->DeploysInto)
+ if (RulesExt::Global()->ExtendedBuildingPlacing && pThis->WhatAmI() == AbstractType::Unit && pType->DeploysInto)
{
auto& vec = pOwnerExt->OwnedDeployingUnits;
vec.erase(std::remove(vec.begin(), vec.end(), pThis), vec.end());
diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp
index d8da3d228c..b6e8e24469 100644
--- a/src/Ext/Rules/Body.cpp
+++ b/src/Ext/Rules/Body.cpp
@@ -137,7 +137,9 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI)
this->HeightShadowScaling = false;
this->HeightShadowScaling_MinScale.Read(exINI, GameStrings::AudioVisual, "HeightShadowScaling.MinScale");
- this->ExpandBuildingPlace.Read(exINI, GameStrings::General, "ExpandBuildingPlace");
+ this->ExtendedBuildingPlacing.Read(exINI, GameStrings::General, "ExtendedBuildingPlacing");
+ this->AutoBuilding.Read(exINI, GameStrings::General, "AutoBuilding");
+
this->ExtendedAircraftMissions.Read(exINI, GameStrings::General, "ExtendedAircraftMissions");
this->AllowParallelAIQueues.Read(exINI, "GlobalControls", "AllowParallelAIQueues");
@@ -333,7 +335,8 @@ void RulesExt::ExtData::Serialize(T& Stm)
.Process(this->AirShadowBaseScale_log)
.Process(this->HeightShadowScaling)
.Process(this->HeightShadowScaling_MinScale)
- .Process(this->ExpandBuildingPlace)
+ .Process(this->ExtendedBuildingPlacing)
+ .Process(this->AutoBuilding)
.Process(this->ExtendedAircraftMissions)
.Process(this->AllowParallelAIQueues)
.Process(this->ForbidParallelAIQueues_Aircraft)
diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h
index cef958cded..ddc175b01d 100644
--- a/src/Ext/Rules/Body.h
+++ b/src/Ext/Rules/Body.h
@@ -93,7 +93,9 @@ class RulesExt
Valueable HeightShadowScaling_MinScale;
double AirShadowBaseScale_log;
- Valueable ExpandBuildingPlace;
+ Valueable ExtendedBuildingPlacing;
+ Valueable AutoBuilding;
+
Valueable ExtendedAircraftMissions;
Valueable AllowParallelAIQueues;
@@ -226,7 +228,9 @@ class RulesExt
, HeightShadowScaling_MinScale { 0.0 }
, AirShadowBaseScale_log { 0.693376137 }
- , ExpandBuildingPlace { false }
+ , ExtendedBuildingPlacing { false }
+ , AutoBuilding { false }
+
, ExtendedAircraftMissions { false }
, AllowParallelAIQueues { true }
diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp
index 43dc1d21e0..8c97ef968e 100644
--- a/src/Ext/Techno/Body.cpp
+++ b/src/Ext/Techno/Body.cpp
@@ -24,7 +24,7 @@ TechnoExt::ExtData::~ExtData()
vec.erase(std::remove(vec.begin(), vec.end(), this), vec.end());
}
- if (RulesExt::Global()->ExpandBuildingPlace && pThis->WhatAmI() == AbstractType::Unit && pType->DeploysInto)
+ if (RulesExt::Global()->ExtendedBuildingPlacing && pThis->WhatAmI() == AbstractType::Unit && pType->DeploysInto)
{
auto& vec = HouseExt::ExtMap.Find(pThis->Owner)->OwnedDeployingUnits;
vec.erase(std::remove(vec.begin(), vec.end(), pThis), vec.end());
From 00a202166db22e37af8215d6d48e1276234e3bdb Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Fri, 24 Jan 2025 15:09:51 +0800
Subject: [PATCH 14/58] New shortcut key
---
Phobos.vcxproj | 2 ++
docs/New-or-Enhanced-Logics.md | 1 +
docs/User-Interface.md | 5 +++++
src/Commands/AutoBuilding.cpp | 37 ++++++++++++++++++++++++++++++++++
src/Commands/AutoBuilding.h | 15 ++++++++++++++
src/Commands/Commands.cpp | 2 ++
src/Ext/BuildingType/Body.cpp | 3 +++
src/Phobos.INI.cpp | 1 +
src/Phobos.h | 1 +
9 files changed, 67 insertions(+)
create mode 100644 src/Commands/AutoBuilding.cpp
create mode 100644 src/Commands/AutoBuilding.h
diff --git a/Phobos.vcxproj b/Phobos.vcxproj
index 09275d7e6f..8d246470d0 100644
--- a/Phobos.vcxproj
+++ b/Phobos.vcxproj
@@ -31,6 +31,7 @@
+
@@ -197,6 +198,7 @@
+
diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md
index 3fbcf892d4..d053b0d3b4 100644
--- a/docs/New-or-Enhanced-Logics.md
+++ b/docs/New-or-Enhanced-Logics.md
@@ -586,6 +586,7 @@ SpyEffect.InfiltratorSuperWeapon= ; SuperWeaponType
- 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 non-player 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. Note that when the building has been recorded and is trying to place, unless the production queue has vanished (such as construction yard is no longer exist), it will continue to function normally until the conditions are not met.
- `AutoBuilding` controls whether building can be automatically placed. Default to `[General]->AutoBuilding`
- `AutoBuilding.Gap` controls the gap of automatically placed buildings.
+ - For shortcut keys, see [User Interface -> Auto Building](User-Interface.md#Auto-Building).
- `LimboBuild` controls whether building can be automatically placed like `LimboDelivery`.
- `LimboBuildID` defines the numeric ID of the building placed by `LimboBuild`.
- `PlaceBuilding.OnLand` controls building with `Naval=yes` will become which building when placed on land.
diff --git a/docs/User-Interface.md b/docs/User-Interface.md
index 709ddf798d..1083cb6112 100644
--- a/docs/User-Interface.md
+++ b/docs/User-Interface.md
@@ -317,6 +317,11 @@ SelectionFlashDuration=0 ; integer, number of frames
- Switches on/off [frame by frame mode](Miscellanous.md#frame-step-in).
- For localization add `TXT_FRAME_BY_FRAME` and `TXT_FRAME_BY_FRAME_DESC` into your `.csf` file.
+### `[ ]` Auto Building
+
+- Switches on/off [auto building mode](New-or-Enhanced-Logics.md#Building-placing-and-deploying-logic-enhancement).
+- For localization add `TXT_AUTO_BUILD` and `TXT_AUTO_BUILD_DESC` into your `.csf` file.
+
## Loading screen
- PCX files can now be used as loadscreen images.
diff --git a/src/Commands/AutoBuilding.cpp b/src/Commands/AutoBuilding.cpp
new file mode 100644
index 0000000000..17b18d5373
--- /dev/null
+++ b/src/Commands/AutoBuilding.cpp
@@ -0,0 +1,37 @@
+#include "AutoBuilding.h"
+
+#include
+#include
+#include
+
+const char* AutoBuildingCommandClass::GetName() const
+{
+ return "Auto Building";
+}
+
+const wchar_t* AutoBuildingCommandClass::GetUIName() const
+{
+ return GeneralUtils::LoadStringUnlessMissing("TXT_AUTO_BUILD", L"Toggle Auto Building");
+}
+
+const wchar_t* AutoBuildingCommandClass::GetUICategory() const
+{
+ return CATEGORY_CONTROL;
+}
+
+const wchar_t* AutoBuildingCommandClass::GetUIDescription() const
+{
+ return GeneralUtils::LoadStringUnlessMissing("TXT_AUTO_BUILD_DESC", L"Toggle on/off automatically place building");
+}
+
+void AutoBuildingCommandClass::Execute(WWKey eInput) const
+{
+ Phobos::Config::AutoBuilding_Enable = !Phobos::Config::AutoBuilding_Enable;
+
+ MessageListClass::Instance->PrintMessage(
+ Phobos::Config::AutoBuilding_Enable ? L"Auto Building Switch On." : L"Auto Building Switch Off.",
+ 150,
+ HouseClass::CurrentPlayer->ColorSchemeIndex,
+ true
+ );
+}
diff --git a/src/Commands/AutoBuilding.h b/src/Commands/AutoBuilding.h
new file mode 100644
index 0000000000..e5a9863c63
--- /dev/null
+++ b/src/Commands/AutoBuilding.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "Commands.h"
+
+// The select info display command class
+class AutoBuildingCommandClass : public CommandClass
+{
+public:
+ // CommandClass
+ virtual const char* GetName() const override;
+ virtual const wchar_t* GetUIName() const override;
+ virtual const wchar_t* GetUICategory() const override;
+ virtual const wchar_t* GetUIDescription() const override;
+ virtual void Execute(WWKey eInput) const override;
+};
diff --git a/src/Commands/Commands.cpp b/src/Commands/Commands.cpp
index 712a596467..0743328a9d 100644
--- a/src/Commands/Commands.cpp
+++ b/src/Commands/Commands.cpp
@@ -10,6 +10,7 @@
#include "ToggleDigitalDisplay.h"
#include "ToggleDesignatorRange.h"
#include "SaveVariablesToFile.h"
+#include "AutoBuilding.h"
DEFINE_HOOK(0x533066, CommandClassCallback_Register, 0x6)
{
@@ -19,6 +20,7 @@ DEFINE_HOOK(0x533066, CommandClassCallback_Register, 0x6)
MakeCommand();
MakeCommand();
MakeCommand();
+ MakeCommand();
if (Phobos::Config::DevelopmentCommands)
{
diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp
index a520f53b27..148de45fe1 100644
--- a/src/Ext/BuildingType/Body.cpp
+++ b/src/Ext/BuildingType/Body.cpp
@@ -454,6 +454,9 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel
bool BuildingTypeExt::AutoPlaceBuilding(BuildingClass* pBuilding)
{
+ if (!Phobos::Config::AutoBuilding_Enable)
+ return false;
+
const auto pType = pBuilding->Type;
const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pType);
diff --git a/src/Phobos.INI.cpp b/src/Phobos.INI.cpp
index 1f5d20345d..532245a5d3 100644
--- a/src/Phobos.INI.cpp
+++ b/src/Phobos.INI.cpp
@@ -45,6 +45,7 @@ bool Phobos::Config::ShowPlacementPreview = false;
bool Phobos::Config::DigitalDisplay_Enable = false;
bool Phobos::Config::RealTimeTimers = false;
bool Phobos::Config::RealTimeTimers_Adaptive = false;
+bool Phobos::Config::AutoBuilding_Enable = true;
int Phobos::Config::CampaignDefaultGameSpeed = 2;
bool Phobos::Config::SkirmishUnlimitedColors = false;
bool Phobos::Config::ShowDesignatorRange = false;
diff --git a/src/Phobos.h b/src/Phobos.h
index 9ba1a63879..7b54f84874 100644
--- a/src/Phobos.h
+++ b/src/Phobos.h
@@ -79,6 +79,7 @@ class Phobos
static bool DigitalDisplay_Enable;
static bool RealTimeTimers;
static bool RealTimeTimers_Adaptive;
+ static bool AutoBuilding_Enable;
static int CampaignDefaultGameSpeed;
static bool SkirmishUnlimitedColors;
static bool ShowDesignatorRange;
From c5bbec24e8886a7dde1c951614a01ca80feeead7 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Fri, 24 Jan 2025 15:20:08 +0800
Subject: [PATCH 15/58] No need to skip set shortcut key
---
src/Ext/BuildingType/Hooks.Placing.cpp | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp
index 5241783f74..c9e119d499 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -1353,11 +1353,12 @@ DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6)
// Auto Build Hook -> sub_6A8B30 - Auto Build Buildings
DEFINE_HOOK(0x6A8E34, StripClass_Update_AutoBuildBuildings, 0x7)
{
- enum { SkipSetStripShortCut = 0x6A8E4D };
-
GET(BuildingClass* const, pBuilding, ESI);
- return (BuildingTypeExt::BuildLimboBuilding(pBuilding) || BuildingTypeExt::AutoPlaceBuilding(pBuilding)) ? SkipSetStripShortCut : 0;
+ BuildingTypeExt::BuildLimboBuilding(pBuilding);
+ BuildingTypeExt::AutoPlaceBuilding(pBuilding);
+
+ return 0;
}
// Limbo Build Hook -> sub_42EB50 - Check Base Node
From 191cf39b5090cee4ca42b4623378adc26c25e2c3 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Sun, 26 Jan 2025 00:37:04 +0800
Subject: [PATCH 16/58] Use correct key to check
---
docs/New-or-Enhanced-Logics.md | 4 ++--
src/Ext/BuildingType/Hooks.Placing.cpp | 5 +++--
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md
index d053b0d3b4..3f481a99e3 100644
--- a/docs/New-or-Enhanced-Logics.md
+++ b/docs/New-or-Enhanced-Logics.md
@@ -589,8 +589,8 @@ SpyEffect.InfiltratorSuperWeapon= ; SuperWeaponType
- For shortcut keys, see [User Interface -> Auto Building](User-Interface.md#Auto-Building).
- `LimboBuild` controls whether building can be automatically placed like `LimboDelivery`.
- `LimboBuildID` defines the numeric ID of the building placed by `LimboBuild`.
-- `PlaceBuilding.OnLand` controls building with `Naval=yes` will become which building when placed on land.
-- `PlaceBuilding.OnWater` controls building with `Naval=no` will become which building when placed on water.
+- `PlaceBuilding.OnLand` controls building with `WaterBound=yes` will become which building when placed on land.
+- `PlaceBuilding.OnWater` controls building with `WaterBound=no` will become which building when placed on water.
In `rulesmd.ini`:
```ini
diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp
index c9e119d499..7baa5704eb 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -418,8 +418,9 @@ static inline BuildingTypeClass* GetAnotherPlacingType(BuildingTypeClass* pType,
if (!pTypeExt->LimboBuild)
{
const auto onWater = MapClass::Instance->GetCellAt(checkCell)->LandType == LandType::Water;
+ const auto waterBound = pType->SpeedType == SpeedType::Float;
- if (const auto pAnotherType = (opposite ^ onWater) ? (pType->Naval ? nullptr : pTypeExt->PlaceBuilding_OnWater) : (pType->Naval ? pTypeExt->PlaceBuilding_OnLand : nullptr))
+ if (const auto pAnotherType = (opposite ^ onWater) ? (waterBound ? nullptr : pTypeExt->PlaceBuilding_OnWater) : (waterBound ? pTypeExt->PlaceBuilding_OnLand : nullptr))
{
if (pAnotherType->BuildCat == pType->BuildCat && !pAnotherType->PlaceAnywhere && !BuildingTypeExt::ExtMap.Find(pAnotherType)->LimboBuild)
return pAnotherType;
@@ -492,7 +493,7 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5)
}
// If the land occupation of the two buildings is different, the larger one will prevail, And the smaller one may not be placed on the shore.
- if ((MapClass::Instance->GetCellAt(checkCell)->LandType == LandType::Water) ^ pBuildingType->Naval)
+ if ((MapClass::Instance->GetCellAt(checkCell)->LandType == LandType::Water) ^ (pBuildingType->SpeedType == SpeedType::Float))
pBuildingType = pAnotherType;
}
From 8b1aec50c85c9d6079044f55d9ac3b899f2d5657 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Mon, 27 Jan 2025 19:37:49 +0800
Subject: [PATCH 17/58] Fix mouse restore, fix contribution doc, do scatter
allies and revert AI actions
---
docs/New-or-Enhanced-Logics.md | 2 +-
docs/Whats-New.md | 2 +-
src/Ext/BuildingType/Body.cpp | 4 ++--
src/Ext/BuildingType/Hooks.Placing.cpp | 23 ++++++++++-------------
4 files changed, 14 insertions(+), 17 deletions(-)
diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md
index 1693d53d93..217e956a6a 100644
--- a/docs/New-or-Enhanced-Logics.md
+++ b/docs/New-or-Enhanced-Logics.md
@@ -584,7 +584,7 @@ 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 non-player 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. Note that when the building has been recorded and is trying to place, unless the production queue has vanished (such as construction yard is no longer exist), it will continue to function normally until the conditions are not met.
+- 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. Note that when the building has been recorded and is trying to place, unless the production queue has vanished (such as construction yard is no longer exist), it will continue to function normally until the conditions are not met.
- `AutoBuilding` controls whether building can be automatically placed. Default to `[General]->AutoBuilding`
- `AutoBuilding.Gap` controls the gap of automatically placed buildings.
- For shortcut keys, see [User Interface -> Auto Building](User-Interface.md#Auto-Building).
diff --git a/docs/Whats-New.md b/docs/Whats-New.md
index 477b93c594..61879628b7 100644
--- a/docs/Whats-New.md
+++ b/docs/Whats-New.md
@@ -317,6 +317,7 @@ New:
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
- Custom exit cell for infantry factory (by Starkku)
- Option for vehicles to keep target when issued move command (by Starkku)
+- Building placing and deploying logic enhancement (by CrimRecya)
Vanilla fixes:
- Aircraft will now behave as expected according to it's `MovementZone` and `SpeedType` when moving onto different surfaces. In particular, this fixes erratic behavior when vanilla aircraft is ordered to move onto water surface and instead the movement order changes to a shore nearby (by CrimRecya)
@@ -463,7 +464,6 @@ New:
- Allow customizing extra tint intensity for Iron Curtain & Force Shield (by Starkku)
- Option to enable parsing 8-bit RGB values from `[ColorAdd]` instead of RGB565 (by Starkku)
- Customizing height and speed at which subterranean units travel (by Starkku)
-- Building placing and deploying logic enhancement (by CrimRecya)
- Option for Warhead damage to penetrate Iron Curtain or Force Shield (by Starkku)
- Option for Warhead to remove all shield types at once (by Starkku)
- Allow customizing voxel light source position (by Kerbiter, Morton, based on knowledge of thomassnedon)
diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp
index b058f61bc8..63d9d7d908 100644
--- a/src/Ext/BuildingType/Body.cpp
+++ b/src/Ext/BuildingType/Body.cpp
@@ -122,9 +122,9 @@ bool BuildingTypeExt::CheckOccupierCanLeave(HouseClass* pBuildingHouse, HouseCla
return false;
else if (pBuildingHouse == pOccupierHouse)
return true;
- else if (SessionClass::IsCampaign() && pOccupierHouse->IsInPlayerControl)
+ else if (pOccupierHouse->IsAlliedWith(pBuildingHouse))
return true;
- else if (!pOccupierHouse->IsControlledByHuman() && pOccupierHouse->IsAlliedWith(pBuildingHouse))
+ else if (SessionClass::IsCampaign() && pBuildingHouse->IsControlledByHuman() && pOccupierHouse->IsControlledByHuman())
return true;
return false;
diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp
index 7baa5704eb..6456b0057b 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -610,13 +610,15 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5)
if (pHouseExt->CurrentBuildingType == pBufferType)
{
+ const bool noRevert = pHouseExt->CurrentBuildingTimes != 30;
+
pHouseExt->CurrentBuildingType = nullptr;
pHouseExt->CurrentBuildingDrawType = nullptr;
pHouseExt->CurrentBuildingTimes = 0;
pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
pHouseExt->CurrentBuildingTimer.Stop();
- if (pHouseExt->CurrentBuildingTimes != 30)
+ if (noRevert)
return CanNotBuild;
}
@@ -692,7 +694,7 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5)
// Buildable-upon TechnoTypes Hook #4-1 -> sub_4FB0E0 - Check whether need to skip the replace command
DEFINE_HOOK(0x4FB395, HouseClass_UnitFromFactory_SkipMouseReturn, 0x6)
{
- enum { SkipGameCode = 0x4FB489 };
+ enum { SkipGameCode = 0x4FB489, CheckMouseCoords = 0x4FB3E3 };
if (!RulesExt::Global()->ExtendedBuildingPlacing)
return 0;
@@ -704,7 +706,12 @@ DEFINE_HOOK(0x4FB395, HouseClass_UnitFromFactory_SkipMouseReturn, 0x6)
}
R->EBX(0);
- return SkipGameCode;
+
+ if (!DisplayClass::Instance->CurrentBuildingTypeCopy)
+ return SkipGameCode;
+
+ R->ECX(DisplayClass::Instance->CurrentBuildingTypeCopy);
+ return CheckMouseCoords;
}
static inline bool IsSameBuildingType(BuildingTypeClass* pType1, BuildingTypeClass* pType2)
@@ -886,21 +893,11 @@ DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
{
pHouseExt->CurrentBuildingType = pBuildingType;
pHouseExt->CurrentBuildingDrawType = pBuildingType;
- pHouseExt->CurrentBuildingTimes = 30;
pHouseExt->CurrentBuildingTopLeft = topLeftCell;
}
- else if (pHouseExt->CurrentBuildingTimes <= 0)
- {
- // This will be different from what vanilla do, but the vanilla way will still be used if in campaign
- if (!SessionClass::IsCampaign())
- break; // Time out
-
- pHouseExt->CurrentBuildingTimes = 30;
- }
if (!pHouseExt->CurrentBuildingTimer.HasTimeLeft())
{
- pHouseExt->CurrentBuildingTimes -= 5;
pHouseExt->CurrentBuildingTimer.Start(40);
if (BuildingTypeExt::CleanUpBuildingSpace(pBuildingType, topLeftCell, pHouse))
From 613c379cc21825ad3bac1c2031904736a942677f Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Sun, 2 Feb 2025 13:33:55 +0800
Subject: [PATCH 18/58] Add a sanity check
---
src/Ext/BuildingType/Body.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp
index 63d9d7d908..6f0442e05d 100644
--- a/src/Ext/BuildingType/Body.cpp
+++ b/src/Ext/BuildingType/Body.cpp
@@ -118,7 +118,7 @@ int BuildingTypeExt::GetUpgradesAmount(BuildingTypeClass* pBuilding, HouseClass*
// Check whether can call the occupiers leave
bool BuildingTypeExt::CheckOccupierCanLeave(HouseClass* pBuildingHouse, HouseClass* pOccupierHouse)
{
- if (!pOccupierHouse)
+ if (!pOccupierHouse || !pBuildingHouse)
return false;
else if (pBuildingHouse == pOccupierHouse)
return true;
From cfa92c8a3f5365973f68acb17f3a7d7b5349ab4b Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Sun, 2 Feb 2025 13:34:39 +0800
Subject: [PATCH 19/58] Exchange check sequence
---
src/Ext/BuildingType/Body.cpp | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp
index 6f0442e05d..56e5e27aa6 100644
--- a/src/Ext/BuildingType/Body.cpp
+++ b/src/Ext/BuildingType/Body.cpp
@@ -560,16 +560,16 @@ bool BuildingTypeExt::AutoPlaceBuilding(BuildingClass* pBuilding)
return false;
}
- else if (pType->PlaceAnywhere)
+ else if (pType->PowersUpBuilding[0])
{
for (const auto& pOwned : pHouse->Buildings)
{
- if (!pOwned->Type->BaseNormal)
+ if (!reinterpret_cast(0x452670)(pOwned, pType, pHouse)) // CanUpgradeBuilding
continue;
const auto cell = getMapCell(pOwned);
- if (cell == CellStruct::Empty || !canBuildHere(cell))
+ if (cell == CellStruct::Empty || pOwned->CurrentMission == Mission::Selling || !canBuildHere(cell))
continue;
addPlaceEvent(cell);
@@ -578,16 +578,16 @@ bool BuildingTypeExt::AutoPlaceBuilding(BuildingClass* pBuilding)
return false;
}
- else if (pType->PowersUpBuilding[0])
+ else if (pType->PlaceAnywhere)
{
for (const auto& pOwned : pHouse->Buildings)
{
- if (!reinterpret_cast(0x452670)(pOwned, pType, pHouse)) // CanUpgradeBuilding
+ if (!pOwned->Type->BaseNormal)
continue;
const auto cell = getMapCell(pOwned);
- if (cell == CellStruct::Empty || pOwned->CurrentMission == Mission::Selling || !canBuildHere(cell))
+ if (cell == CellStruct::Empty || !canBuildHere(cell))
continue;
addPlaceEvent(cell);
From 32c90bded6f720ad15594d11a32c5f24e11b0fcd Mon Sep 17 00:00:00 2001
From: Noble Fish <89088785+DeathFishAtEase@users.noreply.github.com>
Date: Mon, 3 Feb 2025 04:12:50 +0800
Subject: [PATCH 20/58] ")"
---
docs/Fixed-or-Improved-Logics.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md
index d39932ba2b..5606546923 100644
--- a/docs/Fixed-or-Improved-Logics.md
+++ b/docs/Fixed-or-Improved-Logics.md
@@ -1124,7 +1124,7 @@ Palette= ; filename - excluding .pal extension and three-character the
- Instead of showing at 1 point of HP left, TerrainTypes switch to damaged frames once their health reaches `[AudioVisual]` -> `ConditionYellow.Terrain` percentage of their maximum health. Defaults to `ConditionYellow` if not set.
- In addition, TerrainTypes can now show 'crumbling' animation after their health has reached zero and before they are deleted from the map by setting `HasCrumblingFrames` to true.
- Crumbling frames start from first frame after both regular & damaged frames and ends at halfway point of the frames in TerrainType's image.
- - Note that the number of regular & damage frames considered for this depends on value of `HasDamagedFrames` and for `IsAnimated` TerrainTypes, `AnimationLength` (see [Animated TerrainTypes](#animated-terraintypes). Exercise caution and ensure there are correct amount of frames to display.
+ - Note that the number of regular & damage frames considered for this depends on value of `HasDamagedFrames` and for `IsAnimated` TerrainTypes, `AnimationLength` (see [Animated TerrainTypes](#animated-terraintypes)). Exercise caution and ensure there are correct amount of frames to display.
- Sound event from `CrumblingSound` (if set) is played when crumbling animation starts playing.
- [Destroy animation & sound](New-or-Enhanced-Logics.md#destroy-animation--sound) only play after crumbling animation has finished.
From 88a6c2016bb4c27e0b77e972483225d11d3af076 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Wed, 5 Feb 2025 17:19:13 +0800
Subject: [PATCH 21/58] Change displaying prompt and fit with #1477
---
src/Commands/AutoBuilding.cpp | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/Commands/AutoBuilding.cpp b/src/Commands/AutoBuilding.cpp
index 17b18d5373..ad0885b80a 100644
--- a/src/Commands/AutoBuilding.cpp
+++ b/src/Commands/AutoBuilding.cpp
@@ -27,11 +27,11 @@ const wchar_t* AutoBuildingCommandClass::GetUIDescription() const
void AutoBuildingCommandClass::Execute(WWKey eInput) const
{
Phobos::Config::AutoBuilding_Enable = !Phobos::Config::AutoBuilding_Enable;
+ const int tabIndex = SidebarClass::Instance->ActiveTabIndex;
- MessageListClass::Instance->PrintMessage(
- Phobos::Config::AutoBuilding_Enable ? L"Auto Building Switch On." : L"Auto Building Switch Off.",
- 150,
- HouseClass::CurrentPlayer->ColorSchemeIndex,
- true
- );
+ if (!tabIndex || tabIndex == 1)
+ {
+ SidebarClass::Instance->SidebarBackgroundNeedsRedraw = true;
+ SidebarClass::Instance->RepaintSidebar(tabIndex);
+ }
}
From 904588d113e311362d1f5ff6df3a39b7096714cb Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Thu, 6 Feb 2025 02:05:48 +0800
Subject: [PATCH 22/58] Small change
---
src/Ext/BuildingType/Body.cpp | 23 ++++++++++++++++++++++
src/Ext/BuildingType/Body.h | 1 +
src/Ext/BuildingType/Hooks.Placing.cpp | 27 ++------------------------
3 files changed, 26 insertions(+), 25 deletions(-)
diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp
index 0c17ea9ad1..d673238944 100644
--- a/src/Ext/BuildingType/Body.cpp
+++ b/src/Ext/BuildingType/Body.cpp
@@ -465,6 +465,29 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel
return false;
}
+bool BuildingTypeExt::IsSameBuildingType(BuildingTypeClass* pType1, BuildingTypeClass* pType2)
+{
+ if (pType1 == pType2)
+ return true;
+
+ if (pType1->BuildCat != pType2->BuildCat || pType1->PlaceAnywhere || pType2->PlaceAnywhere)
+ return false;
+
+ const auto pType1Ext = BuildingTypeExt::ExtMap.Find(pType1);
+ const auto pType2Ext = BuildingTypeExt::ExtMap.Find(pType2);
+
+ if (pType1Ext->LimboBuild || pType2Ext->LimboBuild)
+ return false;
+
+ if (pType1Ext->PlaceBuilding_OnLand == pType2 || pType1Ext->PlaceBuilding_OnWater == pType2)
+ return true;
+
+ if (pType2Ext->PlaceBuilding_OnLand == pType1 || pType2Ext->PlaceBuilding_OnWater == pType1)
+ return true;
+
+ return false;
+}
+
bool BuildingTypeExt::AutoPlaceBuilding(BuildingClass* pBuilding)
{
if (!Phobos::Config::AutoBuilding_Enable)
diff --git a/src/Ext/BuildingType/Body.h b/src/Ext/BuildingType/Body.h
index 3e56a4e0cc..4bbecf0a8c 100644
--- a/src/Ext/BuildingType/Body.h
+++ b/src/Ext/BuildingType/Body.h
@@ -189,6 +189,7 @@ class BuildingTypeExt
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 AutoPlaceBuilding(BuildingClass* pBuilding);
static bool BuildLimboBuilding(BuildingClass* pBuilding);
static void CreateLimboBuilding(BuildingClass* pBuilding, BuildingTypeClass* pType, HouseClass* pOwner, int ID);
diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp
index 6456b0057b..9b05099ee6 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -714,29 +714,6 @@ DEFINE_HOOK(0x4FB395, HouseClass_UnitFromFactory_SkipMouseReturn, 0x6)
return CheckMouseCoords;
}
-static inline bool IsSameBuildingType(BuildingTypeClass* pType1, BuildingTypeClass* pType2)
-{
- if (pType1 == pType2)
- return true;
-
- if (pType1->BuildCat != pType2->BuildCat || pType1->PlaceAnywhere || pType2->PlaceAnywhere)
- return false;
-
- const auto pType1Ext = BuildingTypeExt::ExtMap.Find(pType1);
- const auto pType2Ext = BuildingTypeExt::ExtMap.Find(pType2);
-
- if (pType1Ext->LimboBuild || pType2Ext->LimboBuild)
- return false;
-
- if (pType1Ext->PlaceBuilding_OnLand == pType2 || pType1Ext->PlaceBuilding_OnWater == pType2)
- return true;
-
- if (pType2Ext->PlaceBuilding_OnLand == pType1 || pType2Ext->PlaceBuilding_OnWater == pType1)
- return true;
-
- return false;
-}
-
// Buildable-upon TechnoTypes Hook #4-2 -> sub_4FB0E0 - Check whether need to skip the clear command
DEFINE_HOOK(0x4FB339, HouseClass_UnitFromFactory_SkipMouseClear, 0x6)
{
@@ -750,7 +727,7 @@ DEFINE_HOOK(0x4FB339, HouseClass_UnitFromFactory_SkipMouseClear, 0x6)
{
if (const auto pCurrentType = abstract_cast(DisplayClass::Instance->CurrentBuildingType))
{
- if (!IsSameBuildingType(pBuilding->Type, pCurrentType))
+ if (!BuildingTypeExt::IsSameBuildingType(pBuilding->Type, pCurrentType))
return SkipGameCode;
}
}
@@ -770,7 +747,7 @@ DEFINE_HOOK(0x4FAB83, HouseClass_AbandonProductionOf_SkipMouseClear, 0x7)
{
if (const auto pCurrentBuildingType = abstract_cast(DisplayClass::Instance->CurrentBuildingType))
{
- if (!IsSameBuildingType(BuildingTypeClass::Array->Items[index], pCurrentBuildingType))
+ if (!BuildingTypeExt::IsSameBuildingType(BuildingTypeClass::Array->Items[index], pCurrentBuildingType))
return SkipGameCode;
}
}
From ca926eab287115f1da1864dc8da37e79c8b4ba44 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Thu, 6 Feb 2025 02:23:47 +0800
Subject: [PATCH 23/58] Not remove buildings on the mouse in all cases
---
src/Ext/Sidebar/Body.cpp | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/src/Ext/Sidebar/Body.cpp b/src/Ext/Sidebar/Body.cpp
index 8e42763e09..8e93768d2c 100644
--- a/src/Ext/Sidebar/Body.cpp
+++ b/src/Ext/Sidebar/Body.cpp
@@ -4,6 +4,9 @@
#include
#include
+#include
+#include
+
std::unique_ptr SidebarExt::Data = nullptr;
SHPStruct* SidebarExt::TabProducingProgress[4];
@@ -56,11 +59,19 @@ bool __stdcall SidebarExt::AresTabCameo_RemoveCameo(BuildType* pItem)
if (pItem->ItemType == AbstractType::BuildingType || pItem->ItemType == AbstractType::Building)
{
+ // It is not necessary to remove buildings on the mouse in all cases here
+ const auto pBldType = static_cast(pTechnoType);
const auto pDisplay = DisplayClass::Instance();
- pDisplay->SetActiveFoundation(nullptr);
- pDisplay->CurrentBuilding = nullptr;
- pDisplay->CurrentBuildingType = nullptr;
- pDisplay->CurrentBuildingOwnerArrayIndex = -1;
+ const auto pCurType = abstract_cast(pDisplay->CurrentBuildingType);
+
+ if (!RulesExt::Global()->ExtendedBuildingPlacing || !pCurType
+ || BuildingTypeExt::IsSameBuildingType(pBldType, pCurType))
+ {
+ pDisplay->SetActiveFoundation(nullptr);
+ pDisplay->CurrentBuilding = nullptr;
+ pDisplay->CurrentBuildingType = nullptr;
+ pDisplay->CurrentBuildingOwnerArrayIndex = -1;
+ }
}
return true;
From ca1c913a68153f4a742fcacda9362c24dd353b77 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Thu, 13 Feb 2025 15:47:13 +0800
Subject: [PATCH 24/58] Fix the stuck issue
---
src/Ext/BuildingType/Body.cpp | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp
index d673238944..0d924d15ed 100644
--- a/src/Ext/BuildingType/Body.cpp
+++ b/src/Ext/BuildingType/Body.cpp
@@ -439,7 +439,6 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel
const auto pCheckedTechno = pThisOrder.techno;
const auto pDestinationCell = pThisOrder.destination;
const auto absType = pCheckedTechno->WhatAmI();
- pCheckedTechno->ForceMission(Mission::Guard);
if (absType == AbstractType::Infantry)
{
@@ -448,8 +447,7 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel
if (pInfantry->IsDeployed())
pInfantry->PlayAnim(Sequence::Undeploy, true);
- pInfantry->SetDestination(pDestinationCell, false);
- pInfantry->QueueMission(Mission::QMove, false); // To force every three infantries gather together, it should be QMove
+ pInfantry->SetDestination(pDestinationCell, true);
}
else if (absType == AbstractType::Unit)
{
@@ -458,7 +456,7 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel
if (pUnit->Deployed)
pUnit->Undeploy();
- pUnit->SetDestination(pDestinationCell, false);
+ pUnit->SetDestination(pDestinationCell, true);
}
}
From 5adfd88bec5457a665cdb21c95022e8048129426 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Thu, 13 Feb 2025 22:06:26 +0800
Subject: [PATCH 25/58] Fix typo
---
src/Ext/BuildingType/Body.cpp | 6 +++---
src/Ext/BuildingType/Hooks.Placing.cpp | 3 ++-
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp
index 0d924d15ed..701896abaa 100644
--- a/src/Ext/BuildingType/Body.cpp
+++ b/src/Ext/BuildingType/Body.cpp
@@ -144,7 +144,7 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel
{
auto currentCell = topLeftCell + *pFoundation;
- if (const auto pCell = MapClass::Instance->GetCellAt(currentCell))
+ if (const auto pCell = MapClass::Instance->TryGetCellAt(currentCell))
{
auto pObject = pCell->FirstObject;
@@ -189,7 +189,7 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel
{
auto searchCell = topLeftCell + *pFoundation;
- if (const auto pSearchCell = MapClass::Instance->GetCellAt(searchCell))
+ if (const auto pSearchCell = MapClass::Instance->TryGetCellAt(searchCell))
{
if (std::find(checkedCells.begin(), checkedCells.end(), pSearchCell) == checkedCells.end() // TODO If there is a cellflag (or CellExt) that can be used …
&& !pSearchCell->GetBuilding()
@@ -401,7 +401,7 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel
{
for (int j = 0; j < 2; ++j)
{
- if (const auto pSearchCell = MapClass::Instance->GetCellAt(searchCell))
+ 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()
diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp
index 9b05099ee6..a5c540b14e 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -902,8 +902,9 @@ DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
pHouseExt->CurrentBuildingTimer.Stop();
}
- else if (const auto pCell = MapClass::Instance->GetCellAt(topLeftCell))
+ else
{
+ const auto pCell = MapClass::Instance->GetCellAt(topLeftCell);
const auto pCellBuilding = pCell->GetBuilding();
if (!pCellBuilding || !reinterpret_cast(0x452670)(pCellBuilding, pBuildingType, pHouse)) // CanUpgradeBuilding
From 3a8762934cbf7e46109c08c5c13f3fe66134569a Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Thu, 13 Feb 2025 23:51:42 +0800
Subject: [PATCH 26/58] Fix moving issue
---
src/Ext/BuildingType/Hooks.Placing.cpp | 48 +++++++++++++++++++-------
1 file changed, 35 insertions(+), 13 deletions(-)
diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp
index a5c540b14e..b7ca2064e3 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -201,7 +201,7 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
}
else if (pBuildingType->LaserFencePost || pBuildingType->Gate)
{
- bool builtOnTechno = false;
+ bool builtOnCanBeBuiltOn = false;
auto pObject = pCell->FirstObject;
while (pObject)
@@ -214,7 +214,7 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pAircraft->Type);
if (pTypeExt && pTypeExt->CanBeBuiltOn)
- builtOnTechno = true;
+ builtOnCanBeBuiltOn = true;
else
return CanNotExistHere;
}
@@ -226,7 +226,7 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
if (pTypeExt && pTypeExt->CanBeBuiltOn)
{
- builtOnTechno = true;
+ builtOnCanBeBuiltOn = true;
}
else if (pOwner != pBuilding->Owner || !pType->LaserFence)
{
@@ -260,23 +260,34 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
}
else if (absType == AbstractType::Infantry || absType == AbstractType::Unit)
{
- const auto pTechno = static_cast(pObject);
+ const auto pTechno = static_cast(pObject);
const auto pTechnoType = pTechno->GetTechnoType();
const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pTechnoType);
if (pTypeExt && pTypeExt->CanBeBuiltOn)
- builtOnTechno = true;
+ {
+ if (pTechno->GetMapCoords() == pTechno->CurrentMapCoords)
+ builtOnCanBeBuiltOn = true;
+ else if (expand)
+ landFootOnly = true;
+ else
+ return CanNotExistHere;
+ }
else if (!expand || pTechnoType->Speed <= 0 || !BuildingTypeExt::CheckOccupierCanLeave(pOwner, pTechno->Owner))
+ {
return CanNotExistHere;
+ }
else
+ {
landFootOnly = true;
+ }
}
else if (const auto pTerrain = abstract_cast(pObject))
{
const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type);
if (pTypeExt && pTypeExt->CanBeBuiltOn)
- builtOnTechno = true;
+ builtOnCanBeBuiltOn = true;
else
return CanNotExistHere;
}
@@ -284,7 +295,7 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
pObject = pObject->NextObject;
}
- if (!landFootOnly && !builtOnTechno && (pCell->OccupationFlags & 0x3F))
+ if (!landFootOnly && !builtOnCanBeBuiltOn && (pCell->OccupationFlags & 0x3F))
{
if (expand)
landFootOnly = true;
@@ -320,7 +331,7 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
}
else
{
- bool builtOnTechno = false;
+ bool builtOnCanBeBuiltOn = false;
auto pObject = pCell->FirstObject;
while (pObject)
@@ -334,29 +345,40 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pTechnoType);
if (pTypeExt && pTypeExt->CanBeBuiltOn)
- builtOnTechno = true;
+ builtOnCanBeBuiltOn = true;
else
return CanNotExistHere;
}
else if (absType == AbstractType::Infantry || absType == AbstractType::Unit)
{
- const auto pTechno = static_cast(pObject);
+ const auto pTechno = static_cast(pObject);
const auto pTechnoType = pTechno->GetTechnoType();
const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pTechnoType);
if (pTypeExt && pTypeExt->CanBeBuiltOn)
- builtOnTechno = true;
+ {
+ if (pTechno->GetMapCoords() == pTechno->CurrentMapCoords)
+ builtOnCanBeBuiltOn = true;
+ else if (expand)
+ landFootOnly = true;
+ else
+ return CanNotExistHere;
+ }
else if (!expand || pTechnoType->Speed <= 0 || !BuildingTypeExt::CheckOccupierCanLeave(pOwner, pTechno->Owner))
+ {
return CanNotExistHere;
+ }
else
+ {
landFootOnly = true;
+ }
}
else if (const auto pTerrain = abstract_cast(pObject))
{
const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type);
if (pTypeExt && pTypeExt->CanBeBuiltOn)
- builtOnTechno = true;
+ builtOnCanBeBuiltOn = true;
else
return CanNotExistHere;
}
@@ -364,7 +386,7 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
pObject = pObject->NextObject;
}
- if (!landFootOnly && !builtOnTechno && (pCell->OccupationFlags & 0x3F))
+ if (!landFootOnly && !builtOnCanBeBuiltOn && (pCell->OccupationFlags & 0x3F))
{
if (expand)
landFootOnly = true;
From bb133bfa751d63c841e0ea770d5a9395f2e86d04 Mon Sep 17 00:00:00 2001
From: CrimRecya <335958461@qq.com>
Date: Fri, 14 Feb 2025 17:30:20 +0800
Subject: [PATCH 27/58] Optimize and separate tech and defense
---
src/Ext/BuildingType/Body.cpp | 20 +-
src/Ext/BuildingType/Hooks.Placing.cpp | 916 ++++++++++---------------
src/Ext/House/Body.cpp | 7 +-
src/Ext/House/Body.h | 23 +-
4 files changed, 395 insertions(+), 571 deletions(-)
diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp
index 701896abaa..cbde91b801 100644
--- a/src/Ext/BuildingType/Body.cpp
+++ b/src/Ext/BuildingType/Body.cpp
@@ -465,25 +465,7 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel
bool BuildingTypeExt::IsSameBuildingType(BuildingTypeClass* pType1, BuildingTypeClass* pType2)
{
- if (pType1 == pType2)
- return true;
-
- if (pType1->BuildCat != pType2->BuildCat || pType1->PlaceAnywhere || pType2->PlaceAnywhere)
- return false;
-
- const auto pType1Ext = BuildingTypeExt::ExtMap.Find(pType1);
- const auto pType2Ext = BuildingTypeExt::ExtMap.Find(pType2);
-
- if (pType1Ext->LimboBuild || pType2Ext->LimboBuild)
- return false;
-
- if (pType1Ext->PlaceBuilding_OnLand == pType2 || pType1Ext->PlaceBuilding_OnWater == pType2)
- return true;
-
- if (pType2Ext->PlaceBuilding_OnLand == pType1 || pType2Ext->PlaceBuilding_OnWater == pType1)
- return true;
-
- return false;
+ return ((pType1->BuildCat != BuildCat::Combat) == (pType2->BuildCat != BuildCat::Combat));
}
bool BuildingTypeExt::AutoPlaceBuilding(BuildingClass* pBuilding)
diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp
index b7ca2064e3..50814571e5 100644
--- a/src/Ext/BuildingType/Hooks.Placing.cpp
+++ b/src/Ext/BuildingType/Hooks.Placing.cpp
@@ -43,11 +43,8 @@ DEFINE_HOOK(0x5684B1, MapClass_PlaceDown_BuildableUponTypes, 0x6)
while (pCellObject)
{
- const auto absType = pCellObject->WhatAmI();
-
- if (absType == AbstractType::Infantry || absType == AbstractType::Unit || absType == AbstractType::Aircraft || absType == AbstractType::Building)
+ if (const auto pTechno = abstract_cast(pCellObject))
{
- const auto pTechno = static_cast(pCellObject);
const auto pType = pTechno->GetTechnoType();
const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType);
@@ -127,7 +124,7 @@ DEFINE_HOOK(0x4A8F20, DisplayClass_BuildingProximityCheck_SetContext, 0x5)
return 0;
}
-// BaseNormal extra checking Hook #1-3 -> sub_4A8EB0 - Check allowed building
+// BaseNormal extra checking Hook #1-2 -> sub_4A8EB0 - Check allowed building
DEFINE_HOOK(0x4A8FD7, DisplayClass_BuildingProximityCheck_BuildArea, 0x6)
{
enum { SkipBuilding = 0x4A902C };
@@ -152,9 +149,70 @@ DEFINE_HOOK(0x4A8FD7, DisplayClass_BuildingProximityCheck_BuildArea, 0x6)
return 0;
}
+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)
+{
+/* if (pTechno == TechnoExt::Deployer) // TODO if merge #1525
+ {
+ skipFlag = true;
+ return false;
+ }*/
+
+ const auto pTechnoType = pTechno->GetTechnoType();
+ const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pTechnoType);
+
+ if (pTypeExt && pTypeExt->CanBeBuiltOn)
+ {
+ if (pTechno->GetMapCoords() == pTechno->CurrentMapCoords)
+ builtOnCanBeBuiltOn = true;
+ else if (expand)
+ landFootOnly = true;
+ else
+ return true;
+ }
+ else if (!expand || pTechnoType->Speed <= 0 || !BuildingTypeExt::CheckOccupierCanLeave(pOwner, pTechno->Owner))
+ {
+ return true;
+ }
+ else
+ {
+ landFootOnly = true;
+ }
+
+ return false;
+}
+
// Buildable-upon TerrainTypes Hook #1 -> sub_47C620 - Allow placing buildings on top of them
// Buildable-upon TechnoTypes Hook #1 -> sub_47C620 - Rewrite and check whether allow placing buildings on top of them
// Customized Laser Fence Hook #1 -> sub_47C620 - Forbid placing laser fence post on inappropriate laser fence
+// Fix DeploysInto Desync core Hook -> sub_47C620 - Exclude the specific unit who want to deploy
DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
{
enum { CanNotExistHere = 0x47C6D1, CanExistHere = 0x47C6A0 };
@@ -173,9 +231,7 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
if (pBuildingType->LaserFence)
{
- auto pObject = pCell->FirstObject;
-
- while (pObject)
+ for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject)
{
const auto absType = pObject->WhatAmI();
@@ -195,16 +251,15 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
if (!pTypeExt || !pTypeExt->CanBeBuiltOn)
return CanNotExistHere;
}
-
- pObject = pObject->NextObject;
}
}
else if (pBuildingType->LaserFencePost || pBuildingType->Gate)
{
+// bool skipFlag = TechnoExt::Deployer ? TechnoExt::Deployer->CurrentMapCoords == pCell->MapCoords : false; // TODO if merge #1525
+ bool skipFlag = false;
bool builtOnCanBeBuiltOn = false;
- auto pObject = pCell->FirstObject;
- while (pObject)
+ for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject)
{
const auto absType = pObject->WhatAmI();
@@ -225,62 +280,16 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType);
if (pTypeExt && pTypeExt->CanBeBuiltOn)
- {
builtOnCanBeBuiltOn = true;
- }
else if (pOwner != pBuilding->Owner || !pType->LaserFence)
- {
return CanNotExistHere;
- }
- else if (pBuildingType->LaserFencePost)
- {
- if (const auto pFenceType = BuildingTypeExt::ExtMap.Find(pBuildingType)->LaserFencePost_Fence.Get())
- {
- if (pFenceType != pType)
- return CanNotExistHere;
- }
- else // Vanilla search
- {
- 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 != pType)
- return CanNotExistHere;
- else
- break;
- }
- }
- }
- }
+ else if (pBuildingType->LaserFencePost && !IsSameFenceType(pBuildingType, pType))
+ return CanNotExistHere;
}
else if (absType == AbstractType::Infantry || absType == AbstractType::Unit)
{
- const auto pTechno = static_cast(pObject);
- const auto pTechnoType = pTechno->GetTechnoType();
- const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pTechnoType);
-
- if (pTypeExt && pTypeExt->CanBeBuiltOn)
- {
- if (pTechno->GetMapCoords() == pTechno->CurrentMapCoords)
- builtOnCanBeBuiltOn = true;
- else if (expand)
- landFootOnly = true;
- else
- return CanNotExistHere;
- }
- else if (!expand || pTechnoType->Speed <= 0 || !BuildingTypeExt::CheckOccupierCanLeave(pOwner, pTechno->Owner))
- {
+ if (CheckCanNotExistHere(static_cast(pObject), pOwner, expand, skipFlag, builtOnCanBeBuiltOn, landFootOnly))
return CanNotExistHere;
- }
- else
- {
- landFootOnly = true;
- }
}
else if (const auto pTerrain = abstract_cast(pObject))
{
@@ -291,11 +300,9 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
else
return CanNotExistHere;
}
-
- pObject = pObject->NextObject;
}
- if (!landFootOnly && !builtOnCanBeBuiltOn && (pCell->OccupationFlags & 0x3F))
+ if (!landFootOnly && !builtOnCanBeBuiltOn && (pCell->OccupationFlags & (skipFlag ? 0x1F : 0x3F)))
{
if (expand)
landFootOnly = true;
@@ -310,9 +317,7 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
if (isoTileTypeIndex >= 0 && isoTileTypeIndex < IsometricTileTypeClass::Array->Count && !IsometricTileTypeClass::Array->Items[isoTileTypeIndex]->Morphable)
return CanNotExistHere;
- auto pObject = pCell->FirstObject;
-
- while (pObject)
+ for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject)
{
const auto absType = pObject->WhatAmI();
@@ -325,16 +330,15 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
if (!pTypeExt || !pTypeExt->CanBeBuiltOn)
return CanNotExistHere;
}
-
- pObject = pObject->NextObject;
}
}
else
{
+// bool skipFlag = TechnoExt::Deployer ? TechnoExt::Deployer->CurrentMapCoords == pCell->MapCoords : false; // TODO if merge #1525
+ bool skipFlag = false;
bool builtOnCanBeBuiltOn = false;
- auto pObject = pCell->FirstObject;
- while (pObject)
+ for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject)
{
const auto absType = pObject->WhatAmI();
@@ -351,27 +355,8 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
}
else if (absType == AbstractType::Infantry || absType == AbstractType::Unit)
{
- const auto pTechno = static_cast(pObject);
- const auto pTechnoType = pTechno->GetTechnoType();
- const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pTechnoType);
-
- if (pTypeExt && pTypeExt->CanBeBuiltOn)
- {
- if (pTechno->GetMapCoords() == pTechno->CurrentMapCoords)
- builtOnCanBeBuiltOn = true;
- else if (expand)
- landFootOnly = true;
- else
- return CanNotExistHere;
- }
- else if (!expand || pTechnoType->Speed <= 0 || !BuildingTypeExt::CheckOccupierCanLeave(pOwner, pTechno->Owner))
- {
+ if (CheckCanNotExistHere(static_cast(pObject), pOwner, expand, skipFlag, builtOnCanBeBuiltOn, landFootOnly))
return CanNotExistHere;
- }
- else
- {
- landFootOnly = true;
- }
}
else if (const auto pTerrain = abstract_cast(pObject))
{
@@ -382,11 +367,9 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6)
else
return CanNotExistHere;
}
-
- pObject = pObject->NextObject;
}
- if (!landFootOnly && !builtOnCanBeBuiltOn && (pCell->OccupationFlags & 0x3F))
+ if (!landFootOnly && !builtOnCanBeBuiltOn && (pCell->OccupationFlags & (skipFlag ? 0x1F : 0x3F)))
{
if (expand)
landFootOnly = true;
@@ -431,36 +414,69 @@ DEFINE_HOOK(0x47EEBC, CellClass_DrawPlaceGrid_RecordCell, 0x6)
return DontDrawAlt;
}
-static inline BuildingTypeClass* GetAnotherPlacingType(BuildingTypeClass* pType, CellStruct checkCell, bool opposite)
+static inline void ClearPlacingBuildingData(PlacingBuildingStruct* const pPlace)
{
- if (!pType->PlaceAnywhere)
- {
- const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pType);
+ pPlace->Type = nullptr;
+ pPlace->DrawType = nullptr;
+ pPlace->Times = 0;
+ pPlace->TopLeft = CellStruct::Empty;
+ pPlace->Timer.Stop();
+}
- if (!pTypeExt->LimboBuild)
- {
- const auto onWater = MapClass::Instance->GetCellAt(checkCell)->LandType == LandType::Water;
- const auto waterBound = pType->SpeedType == SpeedType::Float;
+static inline void ClearCurrentBuildingData(DisplayClass* const pDisplay)
+{
+ pDisplay->SetActiveFoundation(nullptr);
+ pDisplay->CurrentBuilding = nullptr;
+ pDisplay->CurrentBuildingType = nullptr;
+ pDisplay->CurrentBuildingOwnerArrayIndexCopy = -1;
- if (const auto pAnotherType = (opposite ^ onWater) ? (waterBound ? nullptr : pTypeExt->PlaceBuilding_OnWater) : (waterBound ? pTypeExt->PlaceBuilding_OnLand : nullptr))
- {
- if (pAnotherType->BuildCat == pType->BuildCat && !pAnotherType->PlaceAnywhere && !BuildingTypeExt::ExtMap.Find(pAnotherType)->LimboBuild)
- return pAnotherType;
- }
- }
+ if (!Unsorted::ArmageddonMode)
+ {
+ reinterpret_cast(0x4A8D50)(pDisplay, nullptr); // Clear CurrentFoundationCopy_Data
+ pDisplay->CurrentBuildingCopy = nullptr;
+ pDisplay->CurrentBuildingTypeCopy = nullptr;
}
+}
- return 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 BuildingTypeClass* GetAnotherPlacingType(DisplayClass* pDisplay)
+static inline bool CheckBuildingFoundation(BuildingTypeClass* const pBuildingType, const CellStruct topLeftCell, HouseClass* const pHouse, bool& noOccupy)
{
- if (const auto pCurrentBuilding = abstract_cast(pDisplay->CurrentBuilding))
- return GetAnotherPlacingType(pCurrentBuilding->Type, pDisplay->CurrentFoundation_CenterCell, false);
+ for (auto pFoundation = pBuildingType->GetFoundationData(false); *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;
+ }
+ }
- return nullptr;
+ ProximityTemp::Exist = false;
+ return true;
}
+// Place Another Type Hook #1 -> sub_4FB0E0 - Replace the factory product
// Buildable-upon TechnoTypes Hook #3 -> sub_4FB0E0 - Hang up place event if there is only infantries and units on the cell
DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5)
{
@@ -472,244 +488,160 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5)
GET(BuildingClass* const, pFactory, EDI);
GET_STACK(const CellStruct, topLeftCell, STACK_OFFSET(0x3C, 0x10));
- if (pTechno->WhatAmI() == AbstractType::Building)
+ if (pTechno->WhatAmI() != AbstractType::Building)
{
- const auto pDisplay = DisplayClass::Instance();
- auto pBuilding = static_cast(pTechno);
- auto pBuildingType = pBuilding->Type;
- const auto pBufferBuilding = pBuilding;
- const auto pBufferType = pBuildingType;
+ pFactory->SendCommand(RadioCommand::RequestLink, pTechno);
- auto checkCell = topLeftCell;
+ if (pTechno->Unlimbo(CoordStruct{ (topLeftCell.X << 8) + 128, (topLeftCell.Y << 8) + 128, 0 }, DirType::North))
+ return CanBuild;
- if (pBuildingType->Gate)
- {
- if (pBuildingType->GetFoundationWidth() > 2)
- checkCell.X += 1;
- else if (pBuildingType->GetFoundationHeight(false) > 2)
- checkCell.Y += 1;
- }
- else if (pBuildingType->GetFoundationWidth() > 2 || pBuildingType->GetFoundationHeight(false) > 2)
- {
- checkCell += CellStruct { 1, 1 };
- }
+ ProximityTemp::Mouse = true;
+ return CanNotBuild;
+ }
- if (const auto pOtherType = GetAnotherPlacingType(pBuildingType, checkCell, false))
- {
- pBuildingType = pOtherType;
- }
- else if (const auto pAnotherType = GetAnotherPlacingType(pBuildingType, topLeftCell, true)) // Center cell may be different, so make assumptions
- {
- checkCell = topLeftCell;
+ 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 (pAnotherType->Gate)
- {
- if (pAnotherType->GetFoundationWidth() > 2)
- checkCell.X += 1;
- else if (pAnotherType->GetFoundationHeight(false) > 2)
- checkCell.Y += 1;
- }
- else if (pAnotherType->GetFoundationWidth() > 2 || pAnotherType->GetFoundationHeight(false) > 2)
- {
- checkCell += CellStruct { 1, 1 };
- }
+ if (pTypeExt->LimboBuild)
+ {
+ BuildingTypeExt::CreateLimboBuilding(pBuilding, pBuildingType, pHouse, pTypeExt->LimboBuildID);
- // If the land occupation of the two buildings is different, the larger one will prevail, And the smaller one may not be placed on the shore.
- if ((MapClass::Instance->GetCellAt(checkCell)->LandType == LandType::Water) ^ (pBuildingType->SpeedType == SpeedType::Float))
- pBuildingType = pAnotherType;
- }
+ if (pDisplay->CurrentBuilding == pBuilding && HouseClass::CurrentPlayer == pHouse)
+ ClearCurrentBuildingData(pDisplay);
- const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pBuildingType);
+ PlayConstructionYardAnim(pFactory);
+ return BuildSucceeded;
+ }
- if (pTypeExt->LimboBuild)
+ const bool expand = RulesExt::Global()->ExtendedBuildingPlacing && !pBuildingType->PlaceAnywhere && !pBuildingType->PowersUpBuilding[0];
+ auto tryGetAnotherType = [&]()
+ {
+ if (expand)
{
- BuildingTypeExt::CreateLimboBuilding(pBuilding, pBuildingType, pHouse, pTypeExt->LimboBuildID);
-
- if (pDisplay->CurrentBuilding == pBuilding && HouseClass::CurrentPlayer == pHouse)
- {
- pDisplay->SetActiveFoundation(nullptr);
- pDisplay->CurrentBuilding = nullptr;
- pDisplay->CurrentBuildingType = nullptr;
- pDisplay->CurrentBuildingOwnerArrayIndexCopy = -1;
+ const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
+ auto& place = pBufferType->BuildCat != BuildCat::Combat ? pHouseExt->Common : pHouseExt->Combat;
- if (!Unsorted::ArmageddonMode)
- {
- reinterpret_cast(0x4A8D50)(pDisplay, nullptr); // Clear CurrentFoundationCopy_Data
- pDisplay->CurrentBuildingCopy = nullptr;
- pDisplay->CurrentBuildingTypeCopy = nullptr;
- }
- }
+ if (place.DrawType && (place.DrawType == pTypeExt->PlaceBuilding_OnLand || place.DrawType == pTypeExt->PlaceBuilding_OnWater))
+ return place.DrawType;
+ }
- const auto pFactoryType = pFactory->Type;
+ const auto& pTypeCopy = pDisplay->CurrentBuildingTypeCopy;
- if (pFactoryType->ConstructionYard)
- {
- VocClass::PlayGlobal(RulesClass::Instance->BuildingSlam, 0x2000, 1.0);
+ if (pTypeCopy && (pTypeCopy == pTypeExt->PlaceBuilding_OnLand || pTypeCopy == pTypeExt->PlaceBuilding_OnWater))
+ return static_cast(pTypeCopy);
- pFactory->DestroyNthAnim(BuildingAnimSlot::PreProduction);
- pFactory->DestroyNthAnim(BuildingAnimSlot::Idle);
+ return pBufferType;
+ };
- const bool damaged = pFactory->GetHealthPercentage() <= RulesClass::Instance->ConditionYellow;
- const auto pAnimName = damaged ? pFactoryType->BuildingAnim[8].Damaged : pFactoryType->BuildingAnim[8].Anim;
+ if (pTypeExt->PlaceBuilding_OnWater || pTypeExt->PlaceBuilding_OnLand)
+ pBuildingType = tryGetAnotherType();
- if (pAnimName && *pAnimName)
- pFactory->PlayAnim(pAnimName, BuildingAnimSlot::Production, damaged, false);
- }
+ bool revert = false;
- return BuildSucceeded;
- }
+ 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;
- if (RulesExt::Global()->ExtendedBuildingPlacing && !pBuildingType->PlaceAnywhere && !pBuildingType->PowersUpBuilding[0])
+ do
{
- const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
- bool canBuild = true;
- bool noOccupy = true;
-
- for (auto pFoundation = pBuildingType->GetFoundationData(false); *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation)
+ if (canBuild)
{
- const auto currentCoord = topLeftCell + *pFoundation;
- const auto pCell = pDisplay->GetCellAt(currentCoord);
-
- if (!pCell->CanThisExistHere(pBuildingType->SpeedType, pBuildingType, pHouse))
- {
- canBuild = false;
- break;
- }
- else if (ProximityTemp::Exist)
- {
- noOccupy = false;
- }
- }
-
- ProximityTemp::Exist = false;
+ if (noOccupy)
+ break; // Can Build
- do
- {
- if (canBuild)
+ do
{
- if (noOccupy)
- break; // Can Build
-
- do
+ if (topLeftCell != place.TopLeft || pBufferType != place.Type) // New command
{
- if (topLeftCell != pHouseExt->CurrentBuildingTopLeft || pBufferType != pHouseExt->CurrentBuildingType) // New command
- {
- pHouseExt->CurrentBuildingType = pBufferType;
- pHouseExt->CurrentBuildingDrawType = pBuildingType;
- pHouseExt->CurrentBuildingTimes = 30;
- pHouseExt->CurrentBuildingTopLeft = topLeftCell;
- }
- else if (pHouseExt->CurrentBuildingTimes <= 0)
- {
- break; // Time out
- }
+ place.Type = pBufferType;
+ place.DrawType = pBuildingType;
+ place.Times = 30;
+ place.TopLeft = topLeftCell;
+ }
+ else if (place.Times <= 0)
+ {
+ break; // Time out
+ }
- if (!(pHouseExt->CurrentBuildingTimes % 5) && BuildingTypeExt::CleanUpBuildingSpace(pBuildingType, topLeftCell, pHouse))
- break; // No place for cleaning
+ if (!(place.Times % 5) && BuildingTypeExt::CleanUpBuildingSpace(pBuildingType, topLeftCell, pHouse))
+ break; // No place for cleaning
- if (pHouse == HouseClass::CurrentPlayer && pHouseExt->CurrentBuildingTimes == 30)
- {
- 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;
- }
- }
+ if (pHouse == HouseClass::CurrentPlayer && place.Times == 30)
+ ClearCurrentBuildingData(pDisplay);
- --pHouseExt->CurrentBuildingTimes;
- pHouseExt->CurrentBuildingTimer.Start(8);
+ --place.Times;
+ place.Timer.Start(8);
- return TemporarilyCanNotBuild;
- }
- while (false);
+ return TemporarilyCanNotBuild;
}
+ while (false);
+ }
- if (pHouseExt->CurrentBuildingType == pBufferType)
- {
- const bool noRevert = pHouseExt->CurrentBuildingTimes != 30;
-
- pHouseExt->CurrentBuildingType = nullptr;
- pHouseExt->CurrentBuildingDrawType = nullptr;
- pHouseExt->CurrentBuildingTimes = 0;
- pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
- pHouseExt->CurrentBuildingTimer.Stop();
-
- if (noRevert)
- return CanNotBuild;
- }
+ revert = place.Times == 30 || !place.Type;
+ ClearPlacingBuildingData(&place);
+ if (revert)
ProximityTemp::Mouse = true;
- return CanNotBuild;
- }
- while (false);
- if (pHouseExt->CurrentBuildingType == pBufferType)
- {
- pHouseExt->CurrentBuildingType = nullptr;
- pHouseExt->CurrentBuildingDrawType = nullptr;
- pHouseExt->CurrentBuildingTimes = 0;
- pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
- pHouseExt->CurrentBuildingTimer.Stop();
- }
+ return CanNotBuild;
}
+ while (false);
+
+ revert = !place.Type;
+ ClearPlacingBuildingData(&place);
+ }
- if (pBufferType != pBuildingType)
- pBuilding = static_cast(pBuildingType->CreateObject(pHouse));
+ if (pBufferType != pBuildingType)
+ pBuilding = static_cast(pBuildingType->CreateObject(pHouse));
- pFactory->SendCommand(RadioCommand::RequestLink, pBuilding);
+ pFactory->SendCommand(RadioCommand::RequestLink, pBuilding);
- if (pBuilding->Unlimbo(CoordStruct{ (topLeftCell.X << 8) + 128, (topLeftCell.Y << 8) + 128, 0 }, DirType::North))
+ if (pBuilding->Unlimbo(CoordStruct{ (topLeftCell.X << 8) + 128, (topLeftCell.Y << 8) + 128, 0 }, DirType::North))
+ {
+ if (pBufferBuilding != pBuilding)
{
- if (pBufferBuilding != pBuilding)
+ if (HouseClass::CurrentPlayer == pHouse)
{
- if (HouseClass::CurrentPlayer == pHouse)
- {
- if (pDisplay->CurrentBuilding == pBufferBuilding)
- pDisplay->CurrentBuilding = pBuilding;
+ if (pDisplay->CurrentBuilding == pBufferBuilding)
+ pDisplay->CurrentBuilding = pBuilding;
- if (pDisplay->CurrentBuildingType == pBufferType)
- pDisplay->CurrentBuildingType = pBuildingType;
+ if (pDisplay->CurrentBuildingType == pBufferType)
+ pDisplay->CurrentBuildingType = pBuildingType;
- if (pDisplay->CurrentBuildingCopy == pBufferBuilding)
- pDisplay->CurrentBuildingCopy = pBuilding;
+ if (pDisplay->CurrentBuildingCopy == pBufferBuilding)
+ pDisplay->CurrentBuildingCopy = pBuilding;
- if (pDisplay->CurrentBuildingTypeCopy == pBufferType)
- pDisplay->CurrentBuildingTypeCopy = pBuildingType;
+ if (pDisplay->CurrentBuildingTypeCopy == pBufferType)
+ pDisplay->CurrentBuildingTypeCopy = pBuildingType;
- if (Make_Global(0xB0FE5C) == pBufferBuilding) // Q
- Make_Global(0xB0FE5C) = pBuilding;
+ 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);
+ if (Make_Global(0xB0FE60) == pBufferBuilding) // W
+ Make_Global(0xB0FE60) = pBuilding;
}
- return CanBuild;
+ pBufferBuilding->UnInit();
+ pPrimary->Object = pBuilding;
+ R->ESI(pBuilding);
}
- if (pBufferBuilding != pBuilding)
- pBuilding->UnInit();
-
- ProximityTemp::Mouse = true;
- return CanNotBuild;
+ return CanBuild;
}
- pFactory->SendCommand(RadioCommand::RequestLink, pTechno);
+ if (pBufferBuilding != pBuilding)
+ pBuilding->UnInit();
- if (pTechno->Unlimbo(CoordStruct{ (topLeftCell.X << 8) + 128, (topLeftCell.Y << 8) + 128, 0 }, DirType::North))
- return CanBuild;
+ if (revert)
+ ProximityTemp::Mouse = true;
- ProximityTemp::Mouse = true;
return CanNotBuild;
}
@@ -784,21 +716,14 @@ DEFINE_HOOK(0x4CA05B, FactoryClass_AbandonProduction_AbandonCurrentBuilding, 0x5
if (RulesExt::Global()->ExtendedBuildingPlacing)
{
- const auto pHouseExt = HouseExt::ExtMap.Find(pFactory->Owner);
-
- if (!pHouseExt || !pHouseExt->CurrentBuildingType)
- return 0;
-
const auto pBuilding = abstract_cast(pFactory->Object);
- if (!pBuilding || (pFactory->Owner->IsControlledByHuman() && pHouseExt->CurrentBuildingType != pBuilding->Type))
+ if (!pBuilding)
return 0;
- pHouseExt->CurrentBuildingType = nullptr;
- pHouseExt->CurrentBuildingDrawType = nullptr;
- pHouseExt->CurrentBuildingTimes = 0;
- pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
- pHouseExt->CurrentBuildingTimer.Stop();
+ const auto pHouseExt = HouseExt::ExtMap.Find(pFactory->Owner);
+ auto& place = pBuilding->Type->BuildCat != BuildCat::Combat ? pHouseExt->Common : pHouseExt->Combat;
+ ClearPlacingBuildingData(&place);
}
return 0;
@@ -807,7 +732,7 @@ DEFINE_HOOK(0x4CA05B, FactoryClass_AbandonProduction_AbandonCurrentBuilding, 0x5
// Buildable-upon TechnoTypes Hook #6 -> sub_443C60 - Try to clean up the building space when AI is building
DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
{
- enum { CanBuild = 0x4452F0, TemporarilyCanNotBuild = 0x445237, CanNotBuild = 0x4454E6, BuildSucceeded = 0x4454D4 };
+ enum { CanBuild = 0x4452F0, TemporarilyCanNotBuild = 0x445237, CanNotBuild = 0x4454E6, BuildSucceeded = 0x4454D4, BuildFailed = 0x445696 };
GET(BaseNodeClass* const, pBaseNode, EBX);
GET(BuildingClass* const, pBuilding, EDI);
@@ -815,6 +740,18 @@ DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
GET(const CellStruct, topLeftCell, EDX);
const auto pBuildingType = pBuilding->Type;
+
+/* if (RulesExt::Global()->AIForbidConYard && pBuildingType->ConstructionYard) // TODO If merge #1470
+ {
+ if (pBaseNode)
+ {
+ pBaseNode->Placed = true;
+ pBaseNode->Attempts = 0;
+ }
+
+ return BuildFailed;
+ }*/
+
const auto pHouse = pFactory->Owner;
const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pBuildingType);
@@ -831,22 +768,7 @@ DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
pHouse->ProducingBuildingTypeIndex = -1;
}
- const auto pFactoryType = pFactory->Type;
-
- if (pFactoryType->ConstructionYard)
- {
- 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);
- }
-
+ PlayConstructionYardAnim(pFactory);
return BuildSucceeded;
}
@@ -857,27 +779,10 @@ DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
{
if (!pBuildingType->PowersUpBuilding[0])
{
- const auto pHouseExt = HouseExt::ExtMap.Find(pHouse);
- bool canBuild = true;
bool noOccupy = true;
-
- for (auto pFoundation = pBuildingType->GetFoundationData(false); *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation)
- {
- const auto currentCoord = topLeftCell + *pFoundation;
- const auto pCell = MapClass::Instance->GetCellAt(currentCoord);
-
- if (!pCell->CanThisExistHere(pBuildingType->SpeedType, pBuildingType, pHouse))
- {
- canBuild = false;
- break;
- }
- else if (ProximityTemp::Exist)
- {
- noOccupy = false;
- }
- }
-
- ProximityTemp::Exist = false;
+ 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
{
@@ -888,16 +793,16 @@ DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
do
{
- if (topLeftCell != pHouseExt->CurrentBuildingTopLeft || pBuildingType != pHouseExt->CurrentBuildingType) // New command
+ if (topLeftCell != place.TopLeft || pBuildingType != place.Type) // New command
{
- pHouseExt->CurrentBuildingType = pBuildingType;
- pHouseExt->CurrentBuildingDrawType = pBuildingType;
- pHouseExt->CurrentBuildingTopLeft = topLeftCell;
+ place.Type = pBuildingType;
+ place.DrawType = pBuildingType;
+ place.TopLeft = topLeftCell;
}
- if (!pHouseExt->CurrentBuildingTimer.HasTimeLeft())
+ if (!place.Timer.HasTimeLeft())
{
- pHouseExt->CurrentBuildingTimer.Start(40);
+ place.Timer.Start(40);
if (BuildingTypeExt::CleanUpBuildingSpace(pBuildingType, topLeftCell, pHouse))
break; // No place for cleaning
@@ -908,21 +813,12 @@ DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
while (false);
}
- pHouseExt->CurrentBuildingType = nullptr;
- pHouseExt->CurrentBuildingDrawType = nullptr;
- pHouseExt->CurrentBuildingTimes = 0;
- pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
- pHouseExt->CurrentBuildingTimer.Stop();
-
+ ClearPlacingBuildingData(&place);
return CanNotBuild;
}
while (false);
- pHouseExt->CurrentBuildingType = nullptr;
- pHouseExt->CurrentBuildingDrawType = nullptr;
- pHouseExt->CurrentBuildingTimes = 0;
- pHouseExt->CurrentBuildingTopLeft = CellStruct::Empty;
- pHouseExt->CurrentBuildingTimer.Stop();
+ ClearPlacingBuildingData(&place);
}
else
{
@@ -936,20 +832,7 @@ DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6)
if (pBuilding->Unlimbo(CoordStruct{ (topLeftCell.X << 8) + 128, (topLeftCell.Y << 8) + 128, 0 }, DirType::North))
{
- const auto pFactoryType = pFactory->Type;
-
- if (pFactoryType->ConstructionYard)
- {
- 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);
- }
-
+ PlayConstructionYardAnim(pFactory);
return CanBuild;
}
@@ -1031,26 +914,8 @@ DEFINE_HOOK(0x73946C, UnitClass_TryToDeploy_CleanUpDeploySpace, 0x6)
if (!pBuildingType->PlaceAnywhere)
{
- bool canBuild = true;
bool noOccupy = true;
-
- for (auto pFoundation = pBuildingType->GetFoundationData(false); *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation)
- {
- const auto currentCoord = topLeftCell + *pFoundation;
- const auto pCell = MapClass::Instance->GetCellAt(currentCoord);
-
- if (!pCell->CanThisExistHere(pBuildingType->SpeedType, pBuildingType, pUnit->Owner))
- {
- canBuild = false;
- break;
- }
- else if (ProximityTemp::Exist)
- {
- noOccupy = false;
- }
- }
-
- ProximityTemp::Exist = false;
+ bool canBuild = CheckBuildingFoundation(pBuildingType, topLeftCell, pUnit->Owner, noOccupy);
do
{
@@ -1173,21 +1038,25 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6)
{
if (pHouse == HouseClass::CurrentPlayer) // Prevent unexpected wrong event
{
- const auto pBuildingType = pHouseExt->CurrentBuildingType;
+ auto buildCurrent = [](const BuildingTypeClass* const pType, const int index, const CellStruct cell)
+ {
+ if (pType)
+ {
+ const EventClass event (index, EventType::Place, AbstractType::Building, pType->GetArrayIndex(), pType->Naval, cell);
+ EventClass::AddEvent(event);
+ }
+ };
+
+ if (pHouseExt->Common.Timer.Completed())
+ {
+ pHouseExt->Common.Timer.Stop();
+ buildCurrent(pHouseExt->Common.Type, pHouse->ArrayIndex, pHouseExt->Common.TopLeft);
+ }
- if (pHouseExt->CurrentBuildingTimer.Completed() && pBuildingType)
+ if (pHouseExt->Combat.Timer.Completed())
{
- pHouseExt->CurrentBuildingTimer.Stop();
- const EventClass event
- (
- pHouse->ArrayIndex,
- EventType::Place,
- AbstractType::Building,
- pBuildingType->GetArrayIndex(),
- pBuildingType->Naval,
- pHouseExt->CurrentBuildingTopLeft
- );
- EventClass::AddEvent(event);
+ pHouseExt->Combat.Timer.Stop();
+ buildCurrent(pHouseExt->Combat.Type, pHouse->ArrayIndex, pHouseExt->Combat.TopLeft);
}
}
@@ -1199,21 +1068,16 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6)
{
const auto pUnit = *it;
- if (!pUnit->InLimbo && pUnit->IsOnMap && !pUnit->IsSinking && pUnit->Owner == pHouse && !pUnit->Destination && pUnit->CurrentMission == Mission::Guard && !pUnit->ParasiteEatingMe && !pUnit->TemporalTargetingMe)
+ if (!pUnit->InLimbo && pUnit->IsOnMap && !pUnit->IsSinking && pUnit->Owner == pHouse && !pUnit->Destination && pUnit->CurrentMission == Mission::Guard
+ && !pUnit->ParasiteEatingMe && !pUnit->TemporalTargetingMe && pUnit->Type->DeploysInto)
{
- if (const auto pType = pUnit->Type)
+ if (const auto pExt = TechnoExt::ExtMap.Find(pUnit))
{
- if (pType->DeploysInto)
- {
- if (const auto pExt = TechnoExt::ExtMap.Find(pUnit))
- {
- if (!(pExt->UnitAutoDeployTimer.GetTimeLeft() % 8))
- pUnit->QueueMission(Mission::Unload, true);
-
- ++it;
- continue;
- }
- }
+ if (!(pExt->UnitAutoDeployTimer.GetTimeLeft() % 8))
+ pUnit->QueueMission(Mission::Unload, true);
+
+ ++it;
+ continue;
}
}
@@ -1225,6 +1089,34 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6)
return 0;
}
+static inline BuildingTypeClass* GetAnotherPlacingType(DisplayClass* pDisplay)
+{
+ if (const auto pCurrentBuilding = abstract_cast(pDisplay->CurrentBuilding))
+ {
+ const auto pType = pCurrentBuilding->Type;
+
+ if (!pType->PlaceAnywhere)
+ {
+ const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pType);
+
+ if (!pTypeExt->LimboBuild)
+ {
+ const auto onWater = MapClass::Instance->GetCellAt(pDisplay->CurrentFoundation_CenterCell)->LandType == LandType::Water;
+ const auto waterBound = pType->SpeedType == SpeedType::Float;
+
+ if (const auto pAnotherType = onWater ? (waterBound ? nullptr : pTypeExt->PlaceBuilding_OnWater) : (waterBound ? pTypeExt->PlaceBuilding_OnLand : nullptr))
+ {
+ if (pAnotherType->BuildCat == pType->BuildCat && !pAnotherType->PlaceAnywhere && !BuildingTypeExt::ExtMap.Find(pAnotherType)->LimboBuild)
+ return pAnotherType;
+ }
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+// Place Another Type Hook #2 -> sub_4AB9B0 - Replace current building type for check
DEFINE_HOOK_AGAIN(0x4ABA47, DisplayClass_PreparePassesProximityCheck_ReplaceBuildingType, 0x6)
DEFINE_HOOK(0x4A946E, DisplayClass_PreparePassesProximityCheck_ReplaceBuildingType, 0x6)
{
@@ -1259,88 +1151,67 @@ DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6)
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)
+ 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);
- const bool isPlayer = pHouse == pPlayer;
- {
- auto pType = pHouseExt->CurrentBuildingDrawType;
- if (pType || (isPlayer && (pType = abstract_cast