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(pDisplay->CurrentBuildingTypeCopy), pType))) - { - auto pCell = pDisplay->TryGetCellAt(pHouseExt->CurrentBuildingTopLeft); + if (const auto pType = abstract_cast(pDisplay->CurrentBuildingTypeCopy)) + drawImage(pType, pHouse, (pDisplay->CurrentFoundationCopy_TopLeftOffset + pDisplay->CurrentFoundationCopy_CenterCell)); - if (pCell || (isPlayer && (pCell = pDisplay->TryGetCellAt(pDisplay->CurrentFoundationCopy_TopLeftOffset + pDisplay->CurrentFoundationCopy_CenterCell), pCell))) - { - auto pImage = pType->LoadBuildup(); - int imageFrame = 0; + if (const auto pType = pHouseExt->Common.Type) + drawImage(pType, pHouse, pHouseExt->Common.TopLeft); - if (pImage) - imageFrame = ((pImage->Frames / 2) - 1); - else - pImage = pType->GetImage(); + if (const auto pType = pHouseExt->Combat.Type) + drawImage(pType, pHouse, pHouseExt->Combat.TopLeft); - 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; + if (pHouseExt->OwnedDeployingUnits.size() <= 0) + continue; - const auto ColorSchemeIndex = pHouse->ColorSchemeIndex; - const auto Palettes = pType->Palette; - const auto pColor = Palettes ? Palettes->Items[ColorSchemeIndex] : ColorScheme::Array->Items[ColorSchemeIndex]; + for (const auto& pUnit : pHouseExt->OwnedDeployingUnits) + { + const auto pType = pUnit->Type->DeploysInto; - DSurface::Temp->DrawSHP(pColor->LightConvert, pImage, imageFrame, &point, &rect, blitFlags, 0, 0, ZGradient::Ground, 1000, 0, nullptr, 0, 0, 0); - } - } - } - } + if (!pType) + continue; - 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); - } - } - } - } - } + auto displayCell = CellClass::Coord2Cell(pUnit->GetCoords()); // pUnit->GetMapCoords(); + + if (pType->GetFoundationWidth() > 2 || pType->GetFoundationHeight(false) > 2) + displayCell -= CellStruct { 1, 1 }; + + drawImage(pType, pHouse, displayCell); } } } @@ -1375,6 +1246,7 @@ DEFINE_HOOK(0x42EB8E, BaseClass_GetBaseNodeIndex_CheckValidBaseNode, 0x6) { const auto pType = BuildingTypeClass::Array->Items[index]; +// if ((pType->ConstructionYard && RulesExt::Global()->AIForbidConYard) || BuildingTypeExt::ExtMap.Find(pType)->LimboBuild) // TODO If merge #1470 if (BuildingTypeExt::ExtMap.Find(pType)->LimboBuild) return Invalid; } @@ -1414,33 +1286,21 @@ DEFINE_HOOK(0x440AE9, BuildingClass_Unlimbo_SkipUninitFence, 0x7) GET(BuildingClass* const, pFence, EDI); GET(BuildingClass* const, pThis, ESI); - if (const auto pExt = BuildingTypeExt::ExtMap.Find(pThis->Type)) + return IsSameFenceType(pThis->Type, pFence->Type) ? 0 : SkipGameCode; +} + +static inline bool IsMatchedPostType(const BuildingTypeClass* const pThisType, const BuildingTypeClass* const pPostType) +{ + if (const auto pThisTypeExt = BuildingTypeExt::ExtMap.Find(pThisType)) { - if (const auto pFenceType = pExt->LaserFencePost_Fence.Get()) - { - if (pFenceType != pFence->Type) - return SkipGameCode; - } - else // Vanilla search + if (const auto pPostTypeExt = BuildingTypeExt::ExtMap.Find(pPostType)) { - 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; - } - } + if (pThisTypeExt->LaserFencePost_Fence.Get() != pPostTypeExt->LaserFencePost_Fence.Get()) + return false; } } - return 0; + return true; } // Customized Laser Fence Hook #4 -> sub_452BB0 - Only accept specific fence post @@ -1451,16 +1311,7 @@ DEFINE_HOOK(0x452CB4, BuildingClass_FindLaserFencePost_CheckLaserFencePost, 0x7) 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; + return IsMatchedPostType(pThis->Type, pPost->Type) ? 0 : SkipGameCode; } // Customized Laser Fence Hook #5 -> sub_6D5730 - Break draw inappropriate laser fence grids @@ -1471,14 +1322,5 @@ DEFINE_HOOK(0x6D5815, TacticalClass_DrawLaserFenceGrid_SkipDrawLaserFence, 0x6) 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; + return IsMatchedPostType(static_cast(DisplayClass::Instance->CurrentBuilding)->Type, pPostType) ? 0 : SkipGameCode; } diff --git a/src/Ext/House/Body.cpp b/src/Ext/House/Body.cpp index 0ceed0c6e2..eff5b684a1 100644 --- a/src/Ext/House/Body.cpp +++ b/src/Ext/House/Body.cpp @@ -608,11 +608,8 @@ void HouseExt::ExtData::Serialize(T& Stm) .Process(this->PowerPlantEnhancers) .Process(this->OwnedLimboDeliveredBuildings) .Process(this->OwnedDeployingUnits) - .Process(this->CurrentBuildingType) - .Process(this->CurrentBuildingDrawType) - .Process(this->CurrentBuildingTopLeft) - .Process(this->CurrentBuildingTimer) - .Process(this->CurrentBuildingTimes) + .Process(this->Common) + .Process(this->Combat) .Process(this->LimboAircraft) .Process(this->LimboBuildings) .Process(this->LimboInfantry) diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h index 1f851e4349..707df4a320 100644 --- a/src/Ext/House/Body.h +++ b/src/Ext/House/Body.h @@ -9,6 +9,15 @@ #include +struct PlacingBuildingStruct +{ + BuildingTypeClass* Type; + BuildingTypeClass* DrawType; + int Times; + CDTimerClass Timer; + CellStruct TopLeft; +}; + class HouseExt { public: @@ -25,11 +34,8 @@ class HouseExt std::vector OwnedLimboDeliveredBuildings; std::vector OwnedDeployingUnits; - BuildingTypeClass* CurrentBuildingType; - BuildingTypeClass* CurrentBuildingDrawType; - CellStruct CurrentBuildingTopLeft; - CDTimerClass CurrentBuildingTimer; - int CurrentBuildingTimes; + PlacingBuildingStruct Common; + PlacingBuildingStruct Combat; CounterClass LimboAircraft; // Currently owned aircraft in limbo CounterClass LimboBuildings; // Currently owned buildings in limbo @@ -74,11 +80,8 @@ class HouseExt , PowerPlantEnhancers {} , OwnedLimboDeliveredBuildings {} , OwnedDeployingUnits {} - , CurrentBuildingType { nullptr } - , CurrentBuildingDrawType { nullptr } - , CurrentBuildingTopLeft {} - , CurrentBuildingTimer {} - , CurrentBuildingTimes { 0 } + , Common { nullptr, nullptr, 0 } + , Combat { nullptr, nullptr, 0 } , LimboAircraft {} , LimboBuildings {} , LimboInfantry {} From 1def8f7f8edc74f8b9d3da5c6da71125e2fd89c5 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Fri, 14 Feb 2025 20:16:10 +0800 Subject: [PATCH 28/58] Check grey cameo --- src/Ext/BuildingType/Hooks.Placing.cpp | 75 ++++++++++++++------------ 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index 50814571e5..820f04e13c 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -1005,7 +1005,7 @@ DEFINE_HOOK(0x4C7665, EventClass_RespondToEvent_StopDeployInIdleEvent, 0x6) // 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); + GET(HouseClass* const, pHouse, ESI); if (!pHouse->IsControlledByHuman()) return 0; @@ -1034,55 +1034,62 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6) if (!RulesExt::Global()->ExtendedBuildingPlacing) return 0; - if (const auto pHouseExt = HouseExt::ExtMap.Find(pHouse)) + const auto pHouseExt = HouseExt::ExtMap.Find(pHouse); + + if (pHouse == HouseClass::CurrentPlayer) // Prevent unexpected wrong event { - if (pHouse == HouseClass::CurrentPlayer) // Prevent unexpected wrong event + auto buildCurrent = [&pHouse, &pHouseExt](BuildingTypeClass* pType, CellStruct cell) { - 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 (!pType) + return; - if (pHouseExt->Common.Timer.Completed()) + if (reinterpret_cast(0x50B370)(pHouse, pType)) // ShouldDisableCameo { - pHouseExt->Common.Timer.Stop(); - buildCurrent(pHouseExt->Common.Type, pHouse->ArrayIndex, pHouseExt->Common.TopLeft); + auto& place = pType->BuildCat != BuildCat::Combat ? pHouseExt->Common : pHouseExt->Combat; + ClearPlacingBuildingData(&place); } - - if (pHouseExt->Combat.Timer.Completed()) + else { - pHouseExt->Combat.Timer.Stop(); - buildCurrent(pHouseExt->Combat.Type, pHouse->ArrayIndex, pHouseExt->Combat.TopLeft); + const EventClass event (pHouse->ArrayIndex, 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, pHouseExt->Common.TopLeft); } - if (pHouseExt->OwnedDeployingUnits.size() > 0) + if (pHouseExt->Combat.Timer.Completed()) { - auto& vec = pHouseExt->OwnedDeployingUnits; + pHouseExt->Combat.Timer.Stop(); + buildCurrent(pHouseExt->Combat.Type, pHouseExt->Combat.TopLeft); + } + } - for (auto it = vec.begin(); it != vec.end(); ) - { - const auto pUnit = *it; + if (pHouseExt->OwnedDeployingUnits.size() > 0) + { + auto& vec = pHouseExt->OwnedDeployingUnits; - if (!pUnit->InLimbo && pUnit->IsOnMap && !pUnit->IsSinking && pUnit->Owner == pHouse && !pUnit->Destination && pUnit->CurrentMission == Mission::Guard - && !pUnit->ParasiteEatingMe && !pUnit->TemporalTargetingMe && pUnit->Type->DeploysInto) + 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 && pUnit->Type->DeploysInto) + { + if (const auto pExt = TechnoExt::ExtMap.Find(pUnit)) { - if (const auto pExt = TechnoExt::ExtMap.Find(pUnit)) - { - if (!(pExt->UnitAutoDeployTimer.GetTimeLeft() % 8)) - pUnit->QueueMission(Mission::Unload, true); + if (!(pExt->UnitAutoDeployTimer.GetTimeLeft() % 8)) + pUnit->QueueMission(Mission::Unload, true); - ++it; - continue; - } + ++it; + continue; } - - it = vec.erase(it); } + + it = vec.erase(it); } } From b47832a37d94381cbc593a99745cea32c7f63af2 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Fri, 14 Feb 2025 20:18:45 +0800 Subject: [PATCH 29/58] Update doc --- docs/New-or-Enhanced-Logics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 190398381c..ec4f9c1ec7 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 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. - `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). From db4049ff56af1ca91ee1a3036bba0aaaae130e17 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Fri, 14 Feb 2025 20:31:24 +0800 Subject: [PATCH 30/58] CannotDeployHere --- 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 820f04e13c..079bd22ccf 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -1045,8 +1045,8 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6) if (reinterpret_cast(0x50B370)(pHouse, pType)) // ShouldDisableCameo { - auto& place = pType->BuildCat != BuildCat::Combat ? pHouseExt->Common : pHouseExt->Combat; - ClearPlacingBuildingData(&place); + ClearPlacingBuildingData(pType->BuildCat != BuildCat::Combat ? &pHouseExt->Common : &pHouseExt->Combat); + VoxClass::Play(GameStrings::EVA_CannotDeployHere); } else { From fed2a8ff041f889e6c7184d52838e4cd4b7d6d73 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Fri, 14 Feb 2025 21:35:49 +0800 Subject: [PATCH 31/58] Fix RE --- src/Ext/BuildingType/Hooks.Placing.cpp | 123 +++++++++++++++++-------- 1 file changed, 83 insertions(+), 40 deletions(-) diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index 079bd22ccf..25df253d93 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -476,6 +476,36 @@ static inline bool CheckBuildingFoundation(BuildingTypeClass* const pBuildingTyp return true; } +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; + const auto waterBound = pType->SpeedType == SpeedType::Float; + + 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; + } + } + } + + 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; +} + // 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) @@ -518,27 +548,67 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5) } const bool expand = RulesExt::Global()->ExtendedBuildingPlacing && !pBuildingType->PlaceAnywhere && !pBuildingType->PowersUpBuilding[0]; - auto tryGetAnotherType = [&]() + + if (pTypeExt->PlaceBuilding_OnWater || pTypeExt->PlaceBuilding_OnLand) { - if (expand) + if (!SessionClass::IsMultiplayer()) { - const auto pHouseExt = HouseExt::ExtMap.Find(pHouse); - auto& place = pBufferType->BuildCat != BuildCat::Combat ? pHouseExt->Common : pHouseExt->Combat; + if (expand) + { + const auto pHouseExt = HouseExt::ExtMap.Find(pHouse); + auto& place = pBufferType->BuildCat != BuildCat::Combat ? pHouseExt->Common : pHouseExt->Combat; + + if (place.DrawType && (place.DrawType == pTypeExt->PlaceBuilding_OnLand || place.DrawType == pTypeExt->PlaceBuilding_OnWater)) + pBuildingType = place.DrawType; + } + + const auto& pTypeCopy = pDisplay->CurrentBuildingTypeCopy; - if (place.DrawType && (place.DrawType == pTypeExt->PlaceBuilding_OnLand || place.DrawType == pTypeExt->PlaceBuilding_OnWater)) - return place.DrawType; + if (pTypeCopy && (pTypeCopy == pTypeExt->PlaceBuilding_OnLand || pTypeCopy == pTypeExt->PlaceBuilding_OnWater)) + pBuildingType = static_cast(pTypeCopy); } + else // When playing online, this can not rely on locally stored replicas, and must made speculation based on the event + { + auto checkCell = topLeftCell; - const auto& pTypeCopy = pDisplay->CurrentBuildingTypeCopy; + 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 (pTypeCopy && (pTypeCopy == pTypeExt->PlaceBuilding_OnLand || pTypeCopy == pTypeExt->PlaceBuilding_OnWater)) - return static_cast(pTypeCopy); + 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; - return pBufferType; - }; + 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->PlaceBuilding_OnWater || pTypeExt->PlaceBuilding_OnLand) - pBuildingType = tryGetAnotherType(); + // 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; + } + } + } bool revert = false; @@ -1096,33 +1166,6 @@ 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) From c86ce53bf41c203b0678207c60369bc105b0cc97 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Fri, 14 Feb 2025 22:02:24 +0800 Subject: [PATCH 32/58] Optimize --- src/Ext/BuildingType/Hooks.Placing.cpp | 31 +++++++++++++------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index 25df253d93..35427c4f78 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -476,22 +476,17 @@ static inline bool CheckBuildingFoundation(BuildingTypeClass* const pBuildingTyp return true; } -static inline BuildingTypeClass* GetAnotherPlacingType(BuildingTypeClass* pType, CellStruct checkCell, bool opposite) +static inline BuildingTypeClass* GetAnotherPlacingType(BuildingTypeClass* pType, BuildingTypeExt::ExtData* pTypeExt, CellStruct checkCell, bool opposite) { - if (!pType->PlaceAnywhere) + if (!pType->PlaceAnywhere && !pTypeExt->LimboBuild) { - const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pType); + const auto onWater = MapClass::Instance->GetCellAt(checkCell)->LandType == LandType::Water; + const auto waterBound = pType->SpeedType == SpeedType::Float; - if (!pTypeExt->LimboBuild) + if (const auto pAnotherType = (opposite ^ onWater) ? (waterBound ? nullptr : pTypeExt->PlaceBuilding_OnWater) : (waterBound ? pTypeExt->PlaceBuilding_OnLand : nullptr)) { - const auto onWater = MapClass::Instance->GetCellAt(checkCell)->LandType == LandType::Water; - const auto waterBound = pType->SpeedType == SpeedType::Float; - - 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 (pAnotherType->BuildCat == pType->BuildCat && !pAnotherType->PlaceAnywhere && !BuildingTypeExt::ExtMap.Find(pAnotherType)->LimboBuild) + return pAnotherType; } } @@ -501,7 +496,13 @@ static inline BuildingTypeClass* GetAnotherPlacingType(BuildingTypeClass* pType, static inline BuildingTypeClass* GetAnotherPlacingType(DisplayClass* pDisplay) { if (const auto pCurrentBuilding = abstract_cast(pDisplay->CurrentBuilding)) - return GetAnotherPlacingType(pCurrentBuilding->Type, pDisplay->CurrentFoundation_CenterCell, false); + { + const auto pType = pCurrentBuilding->Type; + const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pType); + + if (pTypeExt->PlaceBuilding_OnLand || pTypeExt->PlaceBuilding_OnWater) + return GetAnotherPlacingType(pType, pTypeExt, pDisplay->CurrentFoundation_CenterCell, false); + } return nullptr; } @@ -583,11 +584,11 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5) checkCell += CellStruct { 1, 1 }; } - if (const auto pOtherType = GetAnotherPlacingType(pBuildingType, checkCell, false)) + if (const auto pOtherType = GetAnotherPlacingType(pBuildingType, pTypeExt, checkCell, false)) { pBuildingType = pOtherType; } - else if (const auto pAnotherType = GetAnotherPlacingType(pBuildingType, topLeftCell, true)) // Center cell may be different, so make assumptions + else if (const auto pAnotherType = GetAnotherPlacingType(pBuildingType, pTypeExt, topLeftCell, true)) // Center cell may be different, so make assumptions { checkCell = topLeftCell; From bfc628c4d3a18078a185a804f2b6267708ebb5cb Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Sun, 16 Feb 2025 17:36:34 +0800 Subject: [PATCH 33/58] Fix draw --- src/Ext/BuildingType/Hooks.Placing.cpp | 46 +++++++++++++------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index 35427c4f78..63b7fe4705 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -1107,36 +1107,36 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6) const auto pHouseExt = HouseExt::ExtMap.Find(pHouse); - if (pHouse == HouseClass::CurrentPlayer) // Prevent unexpected wrong event + const auto pHouseExt = HouseExt::ExtMap.Find(pHouse); + auto buildCurrent = [&pHouse, &pHouseExt](BuildingTypeClass* pType, CellStruct cell) { - auto buildCurrent = [&pHouse, &pHouseExt](BuildingTypeClass* pType, CellStruct cell) + if (!pType) + return; + + if (reinterpret_cast(0x50B370)(pHouse, pType)) // ShouldDisableCameo { - if (!pType) - return; + ClearPlacingBuildingData(pType->BuildCat != BuildCat::Combat ? &pHouseExt->Common : &pHouseExt->Combat); - if (reinterpret_cast(0x50B370)(pHouse, pType)) // ShouldDisableCameo - { - ClearPlacingBuildingData(pType->BuildCat != BuildCat::Combat ? &pHouseExt->Common : &pHouseExt->Combat); + if (pHouse == HouseClass::CurrentPlayer) VoxClass::Play(GameStrings::EVA_CannotDeployHere); - } - else - { - const EventClass event (pHouse->ArrayIndex, 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, pHouseExt->Common.TopLeft); } - - if (pHouseExt->Combat.Timer.Completed()) + else if (pHouse == HouseClass::CurrentPlayer) // Prevent unexpected wrong event { - pHouseExt->Combat.Timer.Stop(); - buildCurrent(pHouseExt->Combat.Type, pHouseExt->Combat.TopLeft); + const EventClass event (pHouse->ArrayIndex, 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, pHouseExt->Common.TopLeft); + } + + if (pHouseExt->Combat.Timer.Completed()) + { + pHouseExt->Combat.Timer.Stop(); + buildCurrent(pHouseExt->Combat.Type, pHouseExt->Combat.TopLeft); } if (pHouseExt->OwnedDeployingUnits.size() > 0) From 2d5e835ad583ae5910ea9374a52ed15b152f60c6 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Sun, 16 Feb 2025 17:44:45 +0800 Subject: [PATCH 34/58] Fix typo --- src/Ext/BuildingType/Hooks.Placing.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index 63b7fe4705..eafa619483 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -1105,8 +1105,6 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6) if (!RulesExt::Global()->ExtendedBuildingPlacing) return 0; - const auto pHouseExt = HouseExt::ExtMap.Find(pHouse); - const auto pHouseExt = HouseExt::ExtMap.Find(pHouse); auto buildCurrent = [&pHouse, &pHouseExt](BuildingTypeClass* pType, CellStruct cell) { From 5a5e6b292151f6a10a35203a92621ebe284f080c Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Sat, 22 Feb 2025 23:47:42 +0800 Subject: [PATCH 35/58] Remove `AutoBuilding` --- Phobos.vcxproj | 2 - docs/New-or-Enhanced-Logics.md | 7 - src/Commands/AutoBuilding.cpp | 37 ----- src/Commands/AutoBuilding.h | 15 -- src/Commands/Commands.cpp | 2 - src/Ext/BuildingType/Body.cpp | 182 ------------------------- src/Ext/BuildingType/Body.h | 5 - src/Ext/BuildingType/Hooks.Placing.cpp | 27 +--- src/Ext/Rules/Body.cpp | 2 - src/Ext/Rules/Body.h | 2 - src/Phobos.INI.cpp | 1 - src/Phobos.h | 1 - 12 files changed, 1 insertion(+), 282 deletions(-) delete mode 100644 src/Commands/AutoBuilding.cpp delete mode 100644 src/Commands/AutoBuilding.h diff --git a/Phobos.vcxproj b/Phobos.vcxproj index 131b44018c..5555056215 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -31,7 +31,6 @@ - @@ -199,7 +198,6 @@ - diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index ec4f9c1ec7..1227aec1a0 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -585,9 +585,6 @@ SpyEffect.InfiltratorSuperWeapon= ; SuperWeaponType ### Building placing and deploying logic enhancement - In vanilla games, buildings are always cannot placing or deploying on the cells that other infantries or units on. Now this can be changed by setting `ExtendedBuildingPlacing` to true, when you try to place the building on these cells, it will check whether the occupiers can be scatter by yourself (include your own technos and allies technos) and whether there are enough spaces to scatter. If can, it will record which building you are placing and show a preview to you and your allies, then start a timer to record this placement and order the occupiers to leave this building area. When the area is cleared, the building will be truly place down and the production queue will be restored to original state. But when the timer expires or an unexpected situation has occurred which make the building impossible be constructed here anymore, it will stop the action and play "cannot deploy here", then you should re-place or re-deploy the building in a valid space. -- `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 `WaterBound=yes` will become which building when placed on land. @@ -597,11 +594,8 @@ In `rulesmd.ini`: ```ini [General] ExtendedBuildingPlacing=false ; boolean -AutoBuilding=false ; boolean [SOMEBUILDING] ; BuildingType -AutoBuilding= ; boolean -AutoBuilding.Gap=0 ; integer LimboBuild=false ; boolean LimboBuildID=-1 ; integer PlaceBuilding.OnLand= ; BuildingType @@ -609,7 +603,6 @@ 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. ``` diff --git a/src/Commands/AutoBuilding.cpp b/src/Commands/AutoBuilding.cpp deleted file mode 100644 index ad0885b80a..0000000000 --- a/src/Commands/AutoBuilding.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#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; - const int tabIndex = SidebarClass::Instance->ActiveTabIndex; - - if (!tabIndex || tabIndex == 1) - { - SidebarClass::Instance->SidebarBackgroundNeedsRedraw = true; - SidebarClass::Instance->RepaintSidebar(tabIndex); - } -} diff --git a/src/Commands/AutoBuilding.h b/src/Commands/AutoBuilding.h deleted file mode 100644 index e5a9863c63..0000000000 --- a/src/Commands/AutoBuilding.h +++ /dev/null @@ -1,15 +0,0 @@ -#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 0743328a9d..712a596467 100644 --- a/src/Commands/Commands.cpp +++ b/src/Commands/Commands.cpp @@ -10,7 +10,6 @@ #include "ToggleDigitalDisplay.h" #include "ToggleDesignatorRange.h" #include "SaveVariablesToFile.h" -#include "AutoBuilding.h" DEFINE_HOOK(0x533066, CommandClassCallback_Register, 0x6) { @@ -20,7 +19,6 @@ 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 cbde91b801..f72df5b3fe 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -468,184 +468,6 @@ bool BuildingTypeExt::IsSameBuildingType(BuildingTypeClass* pType1, BuildingType return ((pType1->BuildCat != BuildCat::Combat) == (pType2->BuildCat != BuildCat::Combat)); } -bool BuildingTypeExt::AutoPlaceBuilding(BuildingClass* pBuilding) -{ - if (!Phobos::Config::AutoBuilding_Enable) - return false; - - const auto pType = pBuilding->Type; - const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pType); - - if (!pTypeExt->AutoBuilding.Get(RulesExt::Global()->AutoBuilding) || pType->LaserFence || pType->Gate || pType->ToTile) - return false; - - const auto pHouse = pBuilding->Owner; - - 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); - - 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) - { - const auto pOwnedType = pOwned->Type; - - if (!pOwnedType->ProtectWithWall) - continue; - - const auto baseCell = getMapCell(pOwned); - - if (baseCell == CellStruct::Empty) - continue; - - 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 if (pType->PowersUpBuilding[0]) - { - for (const auto& pOwned : pHouse->Buildings) - { - if (!reinterpret_cast(0x452670)(pOwned, pType, pHouse)) // CanUpgradeBuilding - continue; - - const auto cell = getMapCell(pOwned); - - if (cell == CellStruct::Empty || pOwned->CurrentMission == Mission::Selling || !canBuildHere(cell)) - continue; - - addPlaceEvent(cell); - return true; - } - - return false; - } - else if (pType->PlaceAnywhere) - { - 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; - } - - 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 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); - - if (cell == CellStruct::Empty) - return false; - - cell += CellStruct { buildGap, buildGap }; - - if (!canBuildHere(cell)) - continue; - - addPlaceEvent(cell); - return true; - } - - return false; - }; - - if (pHouse->ConYards.Count > 0 && tryBuildAt(pHouse->ConYards, false)) - return true; - - return tryBuildAt(pHouse->Buildings, true); -} - bool BuildingTypeExt::BuildLimboBuilding(BuildingClass* pBuilding) { const auto pBuildingType = pBuilding->Type; @@ -816,8 +638,6 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->SellBuildupLength.Read(exINI, pSection, "SellBuildupLength"); this->IsDestroyableObstacle.Read(exINI, pSection, "IsDestroyableObstacle"); - 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"); @@ -950,8 +770,6 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm) .Process(this->ConsideredVehicle) .Process(this->ZShapePointMove_OnBuildup) .Process(this->SellBuildupLength) - .Process(this->AutoBuilding) - .Process(this->AutoBuilding_Gap) .Process(this->LimboBuild) .Process(this->LimboBuildID) .Process(this->LaserFencePost_Fence) diff --git a/src/Ext/BuildingType/Body.h b/src/Ext/BuildingType/Body.h index 4bbecf0a8c..a9c4af8746 100644 --- a/src/Ext/BuildingType/Body.h +++ b/src/Ext/BuildingType/Body.h @@ -64,8 +64,6 @@ class BuildingTypeExt Valueable SellBuildupLength; Valueable IsDestroyableObstacle; - Nullable AutoBuilding; - Valueable AutoBuilding_Gap; Valueable LimboBuild; Valueable LimboBuildID; Valueable LaserFencePost_Fence; @@ -127,8 +125,6 @@ class BuildingTypeExt , ConsideredVehicle {} , ZShapePointMove_OnBuildup { false } , SellBuildupLength { 23 } - , AutoBuilding {} - , AutoBuilding_Gap { 1 } , LimboBuild { false } , LimboBuildID { -1 } , LaserFencePost_Fence {} @@ -190,7 +186,6 @@ class BuildingTypeExt 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); static bool DeleteLimboBuilding(BuildingClass* pBuilding, int ID); diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index eafa619483..c55ce8c233 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -1078,31 +1078,7 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6) { GET(HouseClass* const, pHouse, ESI); - if (!pHouse->IsControlledByHuman()) - return 0; - - if (pHouse == HouseClass::CurrentPlayer && (pHouse->RecheckTechTree || !(Unsorted::CurrentFrame() % 15))) - { - if (const auto pFactory = pHouse->Primary_ForBuildings) - { - 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 pBuilding = abstract_cast(pFactory->Object)) - BuildingTypeExt::AutoPlaceBuilding(pBuilding); - } - } - } - - if (!RulesExt::Global()->ExtendedBuildingPlacing) + if (!pHouse->IsControlledByHuman() || !RulesExt::Global()->ExtendedBuildingPlacing) return 0; const auto pHouseExt = HouseExt::ExtMap.Find(pHouse); @@ -1274,7 +1250,6 @@ DEFINE_HOOK(0x6A8E34, StripClass_Update_AutoBuildBuildings, 0x7) GET(BuildingClass* const, pBuilding, ESI); BuildingTypeExt::BuildLimboBuilding(pBuilding); - BuildingTypeExt::AutoPlaceBuilding(pBuilding); return 0; } diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index cee9dc78dc..1284fbac6f 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -138,7 +138,6 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->HeightShadowScaling_MinScale.Read(exINI, GameStrings::AudioVisual, "HeightShadowScaling.MinScale"); this->ExtendedBuildingPlacing.Read(exINI, GameStrings::General, "ExtendedBuildingPlacing"); - this->AutoBuilding.Read(exINI, GameStrings::General, "AutoBuilding"); this->ExtendedAircraftMissions.Read(exINI, GameStrings::General, "ExtendedAircraftMissions"); @@ -354,7 +353,6 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->HeightShadowScaling) .Process(this->HeightShadowScaling_MinScale) .Process(this->ExtendedBuildingPlacing) - .Process(this->AutoBuilding) .Process(this->ExtendedAircraftMissions) .Process(this->BuildingProductionQueue) .Process(this->AllowParallelAIQueues) diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index 48059cf668..75d56ead73 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -94,7 +94,6 @@ class RulesExt double AirShadowBaseScale_log; Valueable ExtendedBuildingPlacing; - Valueable AutoBuilding; Valueable ExtendedAircraftMissions; @@ -247,7 +246,6 @@ class RulesExt , AirShadowBaseScale_log { 0.693376137 } , ExtendedBuildingPlacing { false } - , AutoBuilding { false } , ExtendedAircraftMissions { false } diff --git a/src/Phobos.INI.cpp b/src/Phobos.INI.cpp index 29213d3469..247eb46fee 100644 --- a/src/Phobos.INI.cpp +++ b/src/Phobos.INI.cpp @@ -45,7 +45,6 @@ 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 d9a06e46d1..da85fd883b 100644 --- a/src/Phobos.h +++ b/src/Phobos.h @@ -79,7 +79,6 @@ 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 08b5272fd462c99122fd6c63a4477e89bc7dfed1 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Sat, 22 Feb 2025 23:48:37 +0800 Subject: [PATCH 36/58] Fix doc --- docs/User-Interface.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/User-Interface.md b/docs/User-Interface.md index 038f270854..1c4df94243 100644 --- a/docs/User-Interface.md +++ b/docs/User-Interface.md @@ -345,11 +345,6 @@ ShowFlashOnSelecting=false ; boolean - 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. From 97581cc22bc1447c4f1e402aac692ca6c2e2782d Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Wed, 26 Feb 2025 20:48:26 +0800 Subject: [PATCH 37/58] Fix deployed unit --- src/Ext/BuildingType/Body.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index f72df5b3fe..90318707d0 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -453,8 +453,8 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel { const auto pUnit = static_cast(pCheckedTechno); - if (pUnit->Deployed) - pUnit->Undeploy(); + if (pUnit->Deployed && !(pUnit->Deploying || pUnit->Undeploying)) + pUnit->QueueMission(Mission::Unload, false); pUnit->SetDestination(pDestinationCell, true); } From 9dcc5ef2a855abcf0d24944bdc45aa550c46f958 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Tue, 25 Mar 2025 21:59:21 +0800 Subject: [PATCH 38/58] Fix merge --- src/Ext/BuildingType/Body.cpp | 6 ++-- src/Ext/BuildingType/Hooks.Placing.cpp | 46 +++++++++++++------------- src/Ext/BuildingType/Hooks.cpp | 4 +-- src/Ext/Sidebar/Body.cpp | 2 +- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index 950016c858..93fa8e1d4a 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->TryGetCellAt(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->TryGetCellAt(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->TryGetCellAt(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 c55ce8c233..8100072a3e 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -158,11 +158,11 @@ static inline bool IsSameFenceType(const BuildingTypeClass* const pPostType, con } else { - const auto count = BuildingTypeClass::Array->Count; + const auto count = BuildingTypeClass::Array.Count; for (int i = 0; i < count; ++i) { - const auto pSearchType = BuildingTypeClass::Array->Items[i]; + const auto pSearchType = BuildingTypeClass::Array.Items[i]; if (pSearchType->LaserFence) { @@ -314,7 +314,7 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6) { const auto isoTileTypeIndex = pCell->IsoTileTypeIndex; - if (isoTileTypeIndex >= 0 && isoTileTypeIndex < IsometricTileTypeClass::Array->Count && !IsometricTileTypeClass::Array->Items[isoTileTypeIndex]->Morphable) + if (isoTileTypeIndex >= 0 && isoTileTypeIndex < IsometricTileTypeClass::Array.Count && !IsometricTileTypeClass::Array.Items[isoTileTypeIndex]->Morphable) return CanNotExistHere; for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) @@ -402,7 +402,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(DisplayClass::Instance->CurrentBuildingTypeCopy)) + 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); @@ -463,7 +463,7 @@ static inline bool CheckBuildingFoundation(BuildingTypeClass* const pBuildingTyp { for (auto pFoundation = pBuildingType->GetFoundationData(false); *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation) { - if (const auto pCell = MapClass::Instance->TryGetCellAt(topLeftCell + *pFoundation)) + if (const auto pCell = MapClass::Instance.TryGetCellAt(topLeftCell + *pFoundation)) { if (!pCell->CanThisExistHere(pBuildingType->SpeedType, pBuildingType, pHouse)) return false; @@ -480,7 +480,7 @@ static inline BuildingTypeClass* GetAnotherPlacingType(BuildingTypeClass* pType, { if (!pType->PlaceAnywhere && !pTypeExt->LimboBuild) { - const auto onWater = MapClass::Instance->GetCellAt(checkCell)->LandType == LandType::Water; + const auto onWater = MapClass::Instance.GetCellAt(checkCell)->LandType == LandType::Water; const auto waterBound = pType->SpeedType == SpeedType::Float; if (const auto pAnotherType = (opposite ^ onWater) ? (waterBound ? nullptr : pTypeExt->PlaceBuilding_OnWater) : (waterBound ? pTypeExt->PlaceBuilding_OnLand : nullptr)) @@ -530,7 +530,7 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5) return CanNotBuild; } - const auto pDisplay = DisplayClass::Instance(); + const auto pDisplay = &DisplayClass::Instance; auto pBuilding = static_cast(pTechno); auto pBuildingType = pBuilding->Type; const auto pBufferBuilding = pBuilding; @@ -605,7 +605,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->SpeedType == SpeedType::Float)) + if ((MapClass::Instance.GetCellAt(checkCell)->LandType == LandType::Water) ^ (pBuildingType->SpeedType == SpeedType::Float)) pBuildingType = pAnotherType; } } @@ -732,10 +732,10 @@ DEFINE_HOOK(0x4FB395, HouseClass_UnitFromFactory_SkipMouseReturn, 0x6) R->EBX(0); - if (!DisplayClass::Instance->CurrentBuildingTypeCopy) + if (!DisplayClass::Instance.CurrentBuildingTypeCopy) return SkipGameCode; - R->ECX(DisplayClass::Instance->CurrentBuildingTypeCopy); + R->ECX(DisplayClass::Instance.CurrentBuildingTypeCopy); return CheckMouseCoords; } @@ -750,7 +750,7 @@ DEFINE_HOOK(0x4FB339, HouseClass_UnitFromFactory_SkipMouseClear, 0x6) { if (const auto pBuilding = abstract_cast(pTechno)) { - if (const auto pCurrentType = abstract_cast(DisplayClass::Instance->CurrentBuildingType)) + if (const auto pCurrentType = abstract_cast(DisplayClass::Instance.CurrentBuildingType)) { if (!BuildingTypeExt::IsSameBuildingType(pBuilding->Type, pCurrentType)) return SkipGameCode; @@ -770,9 +770,9 @@ DEFINE_HOOK(0x4FAB83, HouseClass_AbandonProductionOf_SkipMouseClear, 0x7) if (RulesExt::Global()->ExtendedBuildingPlacing && index >= 0) { - if (const auto pCurrentBuildingType = abstract_cast(DisplayClass::Instance->CurrentBuildingType)) + if (const auto pCurrentBuildingType = abstract_cast(DisplayClass::Instance.CurrentBuildingType)) { - if (!BuildingTypeExt::IsSameBuildingType(BuildingTypeClass::Array->Items[index], pCurrentBuildingType)) + if (!BuildingTypeExt::IsSameBuildingType(BuildingTypeClass::Array.Items[index], pCurrentBuildingType)) return SkipGameCode; } } @@ -893,7 +893,7 @@ DEFINE_HOOK(0x4451F8, BuildingClass_KickOutUnit_CleanUpAIBuildingSpace, 0x6) } else { - const auto pCell = MapClass::Instance->GetCellAt(topLeftCell); + const auto pCell = MapClass::Instance.GetCellAt(topLeftCell); const auto pCellBuilding = pCell->GetBuilding(); if (!pCellBuilding || !reinterpret_cast(0x452670)(pCellBuilding, pBuildingType, pHouse)) // CanUpgradeBuilding @@ -1145,7 +1145,7 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6) DEFINE_HOOK_AGAIN(0x4ABA47, DisplayClass_PreparePassesProximityCheck_ReplaceBuildingType, 0x6) DEFINE_HOOK(0x4A946E, DisplayClass_PreparePassesProximityCheck_ReplaceBuildingType, 0x6) { - const auto pDisplay = DisplayClass::Instance(); + const auto pDisplay = &DisplayClass::Instance; if (const auto pAnotherType = GetAnotherPlacingType(pDisplay)) { @@ -1173,8 +1173,8 @@ DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6) if (!RulesExt::Global()->ExtendedBuildingPlacing) return 0; - const auto pPlayer = HouseClass::CurrentPlayer(); - const auto pDisplay = DisplayClass::Instance(); + const auto pPlayer = HouseClass::CurrentPlayer; + const auto pDisplay = &DisplayClass::Instance; auto drawImage = [&pDisplay](BuildingTypeClass* const pType, HouseClass* const pHouse, const CellStruct cell) { @@ -1202,11 +1202,11 @@ DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6) const auto ColorSchemeIndex = pHouse->ColorSchemeIndex; const auto Palettes = pType->Palette; - const auto pColor = Palettes ? Palettes->Items[ColorSchemeIndex] : ColorScheme::Array->Items[ColorSchemeIndex]; + 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) + for (const auto& pHouse : HouseClass::Array) { if (pPlayer->IsObserver() || pHouse->IsAlliedWith(pPlayer)) { @@ -1266,9 +1266,9 @@ DEFINE_HOOK(0x42EB8E, BaseClass_GetBaseNodeIndex_CheckValidBaseNode, 0x6) { const auto index = pBaseNode->BuildingTypeIndex; - if (index >= 0 && index < BuildingTypeClass::Array->Count) + if (index >= 0 && index < BuildingTypeClass::Array.Count) { - const auto pType = BuildingTypeClass::Array->Items[index]; + const auto pType = BuildingTypeClass::Array.Items[index]; // if ((pType->ConstructionYard && RulesExt::Global()->AIForbidConYard) || BuildingTypeExt::ExtMap.Find(pType)->LimboBuild) // TODO If merge #1470 if (BuildingTypeExt::ExtMap.Find(pType)->LimboBuild) @@ -1293,7 +1293,7 @@ DEFINE_HOOK(0x452E2C, BuildingClass_CreateLaserFence_FindSpecificIndex, 0x5) if (pFenceType->LaserFence) { R->EBP(pFenceType->ArrayIndex); - R->EAX(BuildingTypeClass::Array->Count); + R->EAX(BuildingTypeClass::Array.Count); return SkipGameCode; } } @@ -1346,5 +1346,5 @@ DEFINE_HOOK(0x6D5815, TacticalClass_DrawLaserFenceGrid_SkipDrawLaserFence, 0x6) GET(BuildingTypeClass* const, pPostType, ECX); // Have used CurrentBuilding->Type yet, so simply use static_cast - return IsMatchedPostType(static_cast(DisplayClass::Instance->CurrentBuilding)->Type, pPostType) ? 0 : SkipGameCode; + return IsMatchedPostType(static_cast(DisplayClass::Instance.CurrentBuilding)->Type, pPostType) ? 0 : SkipGameCode; } diff --git a/src/Ext/BuildingType/Hooks.cpp b/src/Ext/BuildingType/Hooks.cpp index 8bc11b3912..3cf9774657 100644 --- a/src/Ext/BuildingType/Hooks.cpp +++ b/src/Ext/BuildingType/Hooks.cpp @@ -72,8 +72,8 @@ DEFINE_HOOK(0x6D528A, TacticalClass_DrawPlacement_PlacementPreview, 0x6) if (!pRules->PlacementPreview || !Phobos::Config::ShowPlacementPreview) return 0; - auto pBuilding = specific_cast(DisplayClass::Instance->CurrentBuilding); - auto pType = specific_cast(DisplayClass::Instance->CurrentBuildingType); + auto pBuilding = specific_cast(DisplayClass::Instance.CurrentBuilding); + auto pType = specific_cast(DisplayClass::Instance.CurrentBuildingType); auto pTypeExt = pType ? BuildingTypeExt::ExtMap.Find(pType) : nullptr; if (pBuilding && pTypeExt && pTypeExt->PlacementPreview) diff --git a/src/Ext/Sidebar/Body.cpp b/src/Ext/Sidebar/Body.cpp index b44676956b..8434d4371f 100644 --- a/src/Ext/Sidebar/Body.cpp +++ b/src/Ext/Sidebar/Body.cpp @@ -51,7 +51,7 @@ bool __stdcall SidebarExt::AresTabCameo_RemoveCameo(BuildType* pItem) // It is not necessary to remove buildings on the mouse in all cases here const auto pBldType = static_cast(pTechnoType); buildCat = pBldType->BuildCat; - const auto pDisplay = DisplayClass::Instance(); + const auto pDisplay = &DisplayClass::Instance; const auto pCurType = abstract_cast(pDisplay->CurrentBuildingType); if (!RulesExt::Global()->ExtendedBuildingPlacing || !pCurType From dab30ccebd12bf303075c7cdb521b294e369ae81 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Tue, 25 Mar 2025 22:00:04 +0800 Subject: [PATCH 39/58] Optimize --- src/Ext/BuildingType/Body.cpp | 217 +++++++++++++------------ src/Ext/BuildingType/Hooks.Placing.cpp | 22 +-- 2 files changed, 118 insertions(+), 121 deletions(-) diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index 93fa8e1d4a..c0877a6a73 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -146,9 +146,7 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel if (const auto pCell = MapClass::Instance.TryGetCellAt(currentCell)) { - auto pObject = pCell->FirstObject; - - while (pObject) + for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) { const auto absType = pObject->WhatAmI(); @@ -170,8 +168,6 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel } } } - - pObject = pObject->NextObject; } checkedCells.push_back(pCell); @@ -185,17 +181,31 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel std::vector optionalCells; optionalCells.reserve(24); - for (auto pFoundation = pBuildingType->FoundationOutside; *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation) +// for (auto pFoundation = pBuildingType->FoundationOutside; *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation) + // Sometimes, FoundationOutside may be wrong (like 2*5 , 4*3 or 4*4) + for (const auto& pCheckedCell : checkedCells) { - auto searchCell = topLeftCell + *pFoundation; + auto searchCell = pCheckedCell->MapCoords - CellStruct { 1, 1 }; - if (const auto pSearchCell = MapClass::Instance.TryGetCellAt(searchCell)) + for (int i = 0; i < 4; ++i) { - 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)) + for (int j = 0; j < 2; ++j) { - optionalCells.push_back(pSearchCell); + if (const auto pSearchCell = MapClass::Instance.TryGetCellAt(searchCell)) + { + if (std::find(checkedCells.begin(), checkedCells.end(), pSearchCell) == checkedCells.end() + && std::find(optionalCells.begin(), optionalCells.end(), pSearchCell) == optionalCells.end() + && !(pSearchCell->OccupationFlags & 0x80) + && pSearchCell->IsClearToMove(SpeedType::Amphibious, true, true, -1, MovementZone::Amphibious, -1, false)) + { + optionalCells.push_back(pSearchCell); + } + } + + if (i % 2) + searchCell.Y += static_cast((i / 2) ? -1 : 1); + else + searchCell.X += static_cast((i / 2) ? -1 : 1); } } } @@ -211,21 +221,26 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel for (const auto& pOptionalCell : optionalCells) // If there are many valid cells at start, it means most of occupiers will near to the edge { - if (minA > 65536) // If distance squared is lower or equal to 256^2, then no need to calculate any more because it is on the edge + if (minA <= 65536) // If distance squared is lower or equal to 256^2, then no need to calculate any more because it is on the edge + { + if (minB <= 65536) + break; + } + else { auto curA = static_cast(pTechnoA->GetMapCoords().DistanceFromSquared(pOptionalCell->MapCoords)); if (curA < minA) minA = curA; + + if (minB <= 65536) + continue; } - if (minB > 65536) - { - auto curB = static_cast(pTechnoB->GetMapCoords().DistanceFromSquared(pOptionalCell->MapCoords)); + auto curB = static_cast(pTechnoB->GetMapCoords().DistanceFromSquared(pOptionalCell->MapCoords)); - if (curB < minB) - minB = curB; - } + if (curB < minB) + minB = curB; } return minA > minB; @@ -267,15 +282,12 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel // 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 auto pCheckedType = pCheckedTechno->GetTechnoType(); + const bool isInfantry = pCheckedTechno->WhatAmI() == AbstractType::Infantry; + auto tryGetInfantryDestinationCell = [&]() -> CellClass* { - 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))) @@ -286,67 +298,46 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel 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)) + if (infantryCell.count < 3 && infantryCell.position->IsClearToMove(pCheckedType->SpeedType, true, true, -1, pCheckedType->MovementZone, -1, false)) { - pDestinationCell = infantryCell.position; ++infantryCell.count; - - break; + return infantryCell.position; } } - - if (pDestinationCell) - break; // Complete } } + return nullptr; + }; + auto pDestinationCell = tryGetInfantryDestinationCell(); + + if (!pDestinationCell) + { std::sort(&optionalCells[0], &optionalCells[optionalCells.size()],[location](CellClass* pCellA, CellClass* pCellB){ return pCellA->MapCoords.DistanceFromSquared(location) < pCellB->MapCoords.DistanceFromSquared(location); }); - const auto minDistanceSquared = optionalCells[0]->MapCoords.DistanceFromSquared(location); + const auto minDistanceSquaredFactor = optionalCells[0]->MapCoords.DistanceFromSquared(location); + std::vector deleteCells; + deleteCells.reserve(4); - for (const auto& pOptionalCell : optionalCells) // Prioritize selecting empty cells + for (const auto& pOptionalCell : optionalCells) { - if (!pOptionalCell->FirstObject && pOptionalCell->IsClearToMove(pCheckedType->SpeedType, true, true, -1, pCheckedType->MovementZone, -1, false)) + if (!pDestinationCell) // First find a feasible destination { - 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); + auto pObject = pOptionalCell->FirstObject; bool valid = true; - while (pCurObject) + for (; pObject; pObject = pObject->NextObject) { - const auto absType = pCurObject->WhatAmI(); + const auto absType = pObject->WhatAmI(); if (absType == AbstractType::Infantry || absType == AbstractType::Unit) { - const auto pCurTechno = static_cast(pCurObject); + const auto pCurTechno = static_cast(pObject); - if (!BuildingTypeExt::CheckOccupierCanLeave(pHouse, pCurTechno->Owner)) // Means invalid for all + if (!BuildingTypeExt::CheckOccupierCanLeave(pHouse, pCurTechno->Owner)) { deleteCells.push_back(pOptionalCell); valid = false; @@ -355,20 +346,36 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel optionalTechnos.push_back(pCurTechno); } - - pCurObject = pCurObject->NextObject; + // Other types will be checked by IsClearToMove } if (valid && pOptionalCell->IsClearToMove(pCheckedType->SpeedType, true, true, -1, pCheckedType->MovementZone, -1, false)) { + // Record the foots on the destination cell, they also need to be evacuated for (const auto& pOptionalTechno : optionalTechnos) - { reCheckedTechnos.push_back(pOptionalTechno); + + if (isInfantry) // Not need to remove it now + { + infantryCells.emplace_back(InfantryCountInCell{ pOptionalCell, 1 }); + ++infantryCount.Y; } + pDestinationCell = pOptionalCell; + + // Prioritize selecting empty cells + if (!pObject || pOptionalCell->MapCoords.DistanceFromSquared(location) > minDistanceSquaredFactor) + break; + } + } + else if (pOptionalCell->MapCoords.DistanceFromSquared(location) <= minDistanceSquaredFactor) // Not too far + { + // Only check empty cell + if (!pOptionalCell->FirstObject && pOptionalCell->IsClearToMove(pCheckedType->SpeedType, true, true, -1, pCheckedType->MovementZone, -1, false)) + { if (isInfantry) // Not need to remove it now { - infantryCells.push_back(InfantryCountInCell{ pOptionalCell, 1 }); + infantryCells.emplace_back(InfantryCountInCell{ pOptionalCell, 1 }); ++infantryCount.Y; } @@ -376,57 +383,55 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel break; } } - - for (const auto& pDeleteCell : deleteCells) // Mark the invalid cells + else // End immediately if the distance is longer { - checkedCells.push_back(pDeleteCell); - optionalCells.erase(std::remove(optionalCells.begin(), optionalCells.end(), pDeleteCell), optionalCells.end()); + break; } } + + if (!pDestinationCell) // Can not build + return true; + + for (const auto& pDeleteCell : deleteCells) // Mark the invalid cells + { + checkedCells.push_back(pDeleteCell); + optionalCells.erase(std::remove(optionalCells.begin(), optionalCells.end(), pDeleteCell), optionalCells.end()); + } } - 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()) { - if (std::find(checkedCells.begin(), checkedCells.end(), pDestinationCell) == checkedCells.end()) - checkedCells.push_back(pDestinationCell); + optionalCells.erase(std::remove(optionalCells.begin(), optionalCells.end(), pDestinationCell), optionalCells.end()); + auto searchCell = pDestinationCell->MapCoords - CellStruct { 1, 1 }; - if (std::find(optionalCells.begin(), optionalCells.end(), pDestinationCell) != optionalCells.end()) + for (int i = 0; i < 4; ++i) { - 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) { - for (int j = 0; j < 2; ++j) + if (const auto pSearchCell = MapClass::Instance.TryGetCellAt(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() + && !(pSearchCell->OccupationFlags & 0x80) + && pSearchCell->IsClearToMove(SpeedType::Amphibious, true, true, -1, MovementZone::Amphibious, -1, false)) { - 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); - } + optionalCells.push_back(pSearchCell); } - - if (i % 2) - searchCell.Y += static_cast((i / 2) ? -1 : 1); - else - searchCell.X += static_cast((i / 2) ? -1 : 1); } + + 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; } + + finalOrder.emplace_back(TechnoWithDestination { pCheckedTechno, pDestinationCell }); } checkedTechnos.clear(); @@ -434,10 +439,10 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel while (reCheckedTechnos.size()); // Step 5: Confirm command execution. - for (const auto& pThisOrder : finalOrder) + for (const auto& thisOrder : finalOrder) { - const auto pCheckedTechno = pThisOrder.techno; - const auto pDestinationCell = pThisOrder.destination; + const auto pCheckedTechno = thisOrder.techno; + const auto pDestinationCell = thisOrder.destination; const auto absType = pCheckedTechno->WhatAmI(); if (absType == AbstractType::Infantry) diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index 8100072a3e..de38586b8f 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -34,16 +34,14 @@ DEFINE_HOOK(0x6D57C1, TacticalClass_DrawLaserFencePlacement_BuildableTerrain, 0x // 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(ObjectClass*, pPlaceObject, EDI); GET(CellClass*, pCell, EAX); - if (pObject->WhatAmI() == AbstractType::Building) + if (pPlaceObject->WhatAmI() == AbstractType::Building) { - auto pCellObject = pCell->FirstObject; - - while (pCellObject) + for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) { - if (const auto pTechno = abstract_cast(pCellObject)) + if (const auto pTechno = abstract_cast(pObject)) { const auto pType = pTechno->GetTechnoType(); const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType); @@ -56,7 +54,7 @@ DEFINE_HOOK(0x5684B1, MapClass_PlaceDown_BuildableUponTypes, 0x6) pTechno->UnInit(); } } - else if (const auto pTerrain = abstract_cast(pCellObject)) + else if (const auto pTerrain = abstract_cast(pObject)) { const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type); @@ -66,8 +64,6 @@ DEFINE_HOOK(0x5684B1, MapClass_PlaceDown_BuildableUponTypes, 0x6) TerrainTypeExt::Remove(pTerrain); } } - - pCellObject = pCellObject->NextObject; } } @@ -84,11 +80,9 @@ DEFINE_HOOK(0x5FD2B6, OverlayClass_Unlimbo_SkipTerrainCheck, 0x9) if (!Game::IsActive) return Unlimbo; - auto pCellObject = pCell->FirstObject; - - while (pCellObject) + for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) { - if (const auto pTerrain = abstract_cast(pCellObject)) + if (const auto pTerrain = abstract_cast(pObject)) { const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type); @@ -98,8 +92,6 @@ DEFINE_HOOK(0x5FD2B6, OverlayClass_Unlimbo_SkipTerrainCheck, 0x9) pCell->RemoveContent(pTerrain, false); TerrainTypeExt::Remove(pTerrain); } - - pCellObject = pCellObject->NextObject; } return Unlimbo; From bd7be2f9b4bc38d4453f16f5f52c6dd3b7c52527 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Sat, 12 Apr 2025 17:12:42 +0800 Subject: [PATCH 40/58] Fix merge --- src/Ext/BuildingType/Hooks.Placing.cpp | 15 ++-- src/Ext/Unit/Hooks.DeploysInto.cpp | 106 ------------------------- 2 files changed, 8 insertions(+), 113 deletions(-) diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index de38586b8f..d8e29c6e8a 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -171,11 +171,11 @@ static inline bool IsSameFenceType(const BuildingTypeClass* const pPostType, con 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 + if (pTechno == TechnoExt::Deployer) { skipFlag = true; return false; - }*/ + } const auto pTechnoType = pTechno->GetTechnoType(); const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pTechnoType); @@ -247,8 +247,7 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6) } else if (pBuildingType->LaserFencePost || pBuildingType->Gate) { -// bool skipFlag = TechnoExt::Deployer ? TechnoExt::Deployer->CurrentMapCoords == pCell->MapCoords : false; // TODO if merge #1525 - bool skipFlag = false; + bool skipFlag = TechnoExt::Deployer ? TechnoExt::Deployer->CurrentMapCoords == pCell->MapCoords : false; bool builtOnCanBeBuiltOn = false; for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) @@ -326,8 +325,7 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6) } else { -// bool skipFlag = TechnoExt::Deployer ? TechnoExt::Deployer->CurrentMapCoords == pCell->MapCoords : false; // TODO if merge #1525 - bool skipFlag = false; + bool skipFlag = TechnoExt::Deployer ? TechnoExt::Deployer->CurrentMapCoords == pCell->MapCoords : false; bool builtOnCanBeBuiltOn = false; for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) @@ -1070,7 +1068,10 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6) { GET(HouseClass* const, pHouse, ESI); - if (!pHouse->IsControlledByHuman() || !RulesExt::Global()->ExtendedBuildingPlacing) + if (!pHouse->IsControlledByHuman()) + return 0; + + if (!RulesExt::Global()->ExtendedBuildingPlacing) return 0; const auto pHouseExt = HouseExt::ExtMap.Find(pHouse); diff --git a/src/Ext/Unit/Hooks.DeploysInto.cpp b/src/Ext/Unit/Hooks.DeploysInto.cpp index cf71442ecc..24b9d4ba29 100644 --- a/src/Ext/Unit/Hooks.DeploysInto.cpp +++ b/src/Ext/Unit/Hooks.DeploysInto.cpp @@ -142,110 +142,4 @@ DEFINE_HOOK(0x73FEC1, UnitClass_WhatAction_DeploysIntoDesyncFix, 0x6) return SkipGameCode; } -// Exclude the specific unit who want to deploy -// Allow placing buildings on top of TerrainType with CanBeBuiltOn -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)); - - if (!Game::IsActive) - return CanExistHere; - - if (pBuildingType->LaserFence) - { - for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) - { - if (pObject->WhatAmI() == AbstractType::Building) - { - return CanNotExistHere; - } - else if (const auto pTerrain = abstract_cast(pObject)) - { - const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type); - - if (!pTypeExt || !pTypeExt->CanBeBuiltOn) - return CanNotExistHere; - } - } - } - else if (pBuildingType->LaserFencePost || pBuildingType->Gate) - { - bool skipFlag = TechnoExt::Deployer ? TechnoExt::Deployer->CurrentMapCoords == pCell->MapCoords : false; - - for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) - { - if (const auto pTerrain = abstract_cast(pObject)) - { - const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type); - - if (!pTypeExt || !pTypeExt->CanBeBuiltOn) - return CanNotExistHere; - } - else if (pObject->AbstractFlags & AbstractFlags::Techno) - { - if (pObject == TechnoExt::Deployer) - { - skipFlag = true; - } - else if (pObject->WhatAmI() != AbstractType::Building - || pOwner != static_cast(pObject)->Owner - || !static_cast(pObject)->Type->LaserFence) - { - return CanNotExistHere; - } - } - } - - if (pCell->OccupationFlags & (skipFlag ? 0x1F : 0x3F)) - return CanNotExistHere; - } - else if (pBuildingType->ToTile) - { - const auto isoTileTypeIndex = pCell->IsoTileTypeIndex; - - if (isoTileTypeIndex >= 0 && isoTileTypeIndex < IsometricTileTypeClass::Array.Count - && !IsometricTileTypeClass::Array.Items[isoTileTypeIndex]->Morphable) - { - return CanNotExistHere; - } - - for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) - { - if (pObject->WhatAmI() == AbstractType::Building) - return CanNotExistHere; - } - } - else - { - bool skipFlag = TechnoExt::Deployer ? TechnoExt::Deployer->CurrentMapCoords == pCell->MapCoords : false; - - for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) - { - if (pObject->AbstractFlags & AbstractFlags::Techno) - { - if (pObject == TechnoExt::Deployer) - skipFlag = true; - else - return CanNotExistHere; - } - else if (const auto pTerrain = abstract_cast(pObject)) - { - const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type); - - if (!pTypeExt || !pTypeExt->CanBeBuiltOn) - return CanNotExistHere; - } - } - - if (pCell->OccupationFlags & (skipFlag ? 0x1F : 0x3F)) - return CanNotExistHere; - } - - return CanExistHere; // Continue check the overlays .etc -} - #pragma endregion From 9d81e457ac4c9d9839e7fdfab59a44f232b8e9de Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Mon, 21 Apr 2025 00:14:56 +0800 Subject: [PATCH 41/58] Update --- YRpp | 2 +- src/Ext/BuildingType/Body.cpp | 9 +- src/Ext/BuildingType/Hooks.Placing.cpp | 229 ++++++++++++++----------- 3 files changed, 130 insertions(+), 110 deletions(-) diff --git a/YRpp b/YRpp index 10678e3374..e388594b05 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit 10678e3374484ccfafbf71830858daeb7eb60393 +Subproject commit e388594b055c71e158115ac81802eb441119ba29 diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index 05c0e7ff89..52a169d632 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -152,19 +152,16 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel if (absType == AbstractType::Infantry || absType == AbstractType::Unit) { - const auto pCellTechno = static_cast(pObject); - const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pCellTechno->GetTechnoType()); + const auto pFoot = static_cast(pObject); - if ((!pTypeExt || !pTypeExt->CanBeBuiltOn) && pCellTechno != pExceptTechno) // No need to check house + if (!TechnoTypeExt::ExtMap.Find(pFoot->GetTechnoType())->CanBeBuiltOn && pFoot != pExceptTechno) // No need to check house { - const auto pFoot = static_cast(pCellTechno); - if (pFoot->GetCurrentSpeed() <= 0 || !pFoot->Locomotor->Is_Moving()) { if (absType == AbstractType::Infantry) ++infantryCount.X; - checkedTechnos.push_back(pCellTechno); + checkedTechnos.push_back(pFoot); } } } diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index d8e29c6e8a..ff837360f6 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -18,14 +18,7 @@ DEFINE_HOOK(0x6D57C1, TacticalClass_DrawLaserFencePlacement_BuildableTerrain, 0x 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 TerrainTypeExt::ExtMap.Find(pTerrain->Type)->CanBeBuiltOn ? ContinueChecks : DontDraw; return ContinueChecks; } @@ -41,12 +34,9 @@ DEFINE_HOOK(0x5684B1, MapClass_PlaceDown_BuildableUponTypes, 0x6) { for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) { - if (const auto pTechno = abstract_cast(pObject)) + if (const auto pTechno = real_abstract_cast(pObject)) { - const auto pType = pTechno->GetTechnoType(); - const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType); - - if (pTypeExt && pTypeExt->CanBeBuiltOn) + if (TechnoTypeExt::ExtMap.Find(pTechno->GetTechnoType())->CanBeBuiltOn) { pTechno->KillPassengers(nullptr); pTechno->Stun(); @@ -54,11 +44,9 @@ DEFINE_HOOK(0x5684B1, MapClass_PlaceDown_BuildableUponTypes, 0x6) pTechno->UnInit(); } } - else if (const auto pTerrain = abstract_cast(pObject)) + else if (const auto pTerrain = real_abstract_cast(pObject)) { - const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type); - - if (pTypeExt && pTypeExt->CanBeBuiltOn) + if (TerrainTypeExt::ExtMap.Find(pTerrain->Type)->CanBeBuiltOn) { pCell->RemoveContent(pTerrain, false); TerrainTypeExt::Remove(pTerrain); @@ -82,11 +70,9 @@ DEFINE_HOOK(0x5FD2B6, OverlayClass_Unlimbo_SkipTerrainCheck, 0x9) for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) { - if (const auto pTerrain = abstract_cast(pObject)) + if (const auto pTerrain = real_abstract_cast(pObject)) { - const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type); - - if (!pTypeExt || !pTypeExt->CanBeBuiltOn) + if (!TerrainTypeExt::ExtMap.Find(pTerrain->Type)->CanBeBuiltOn) return NoUnlimbo; pCell->RemoveContent(pTerrain, false); @@ -123,9 +109,7 @@ DEFINE_HOOK(0x4A8FD7, DisplayClass_BuildingProximityCheck_BuildArea, 0x6) GET(BuildingClass*, pCellBuilding, ESI); - auto const pTypeExt = BuildingTypeExt::ExtMap.Find(pCellBuilding->Type); - - if (pTypeExt->NoBuildAreaOnBuildup && pCellBuilding->CurrentMission == Mission::Construction) + if (BuildingTypeExt::ExtMap.Find(pCellBuilding->Type)->NoBuildAreaOnBuildup && pCellBuilding->CurrentMission == Mission::Construction) return SkipBuilding; auto const& pBuildingsAllowed = BuildingTypeExt::ExtMap.Find(ProximityTemp::BuildType)->Adjacent_Allowed; @@ -178,9 +162,8 @@ static inline bool CheckCanNotExistHere(FootClass* const pTechno, HouseClass* co } const auto pTechnoType = pTechno->GetTechnoType(); - const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pTechnoType); - if (pTypeExt && pTypeExt->CanBeBuiltOn) + if (TechnoTypeExt::ExtMap.Find(pTechnoType)->CanBeBuiltOn) { if (pTechno->GetMapCoords() == pTechno->CurrentMapCoords) builtOnCanBeBuiltOn = true; @@ -225,23 +208,28 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6) { for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) { - const auto absType = pObject->WhatAmI(); - - if (absType == AbstractType::Building) + switch (pObject->WhatAmI()) { - const auto pBuilding = static_cast(pObject); - const auto pType = pBuilding->Type; - const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType); + case AbstractType::Building: + { + if (!TechnoTypeExt::ExtMap.Find(static_cast(pObject)->Type)->CanBeBuiltOn) + return CanNotExistHere; - if (!pTypeExt || !pTypeExt->CanBeBuiltOn) - return CanNotExistHere; - } - else if (const auto pTerrain = abstract_cast(pObject)) - { - const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type); + break; + } - if (!pTypeExt || !pTypeExt->CanBeBuiltOn) - return CanNotExistHere; + case AbstractType::Terrain: + { + if (!TerrainTypeExt::ExtMap.Find(static_cast(pObject)->Type)->CanBeBuiltOn) + return CanNotExistHere; + + break; + } + + default: + { + break; + } } } } @@ -252,44 +240,54 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6) for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) { - const auto absType = pObject->WhatAmI(); - - if (absType == AbstractType::Aircraft) + switch (pObject->WhatAmI()) { - const auto pAircraft = static_cast(pObject); - const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pAircraft->Type); + case AbstractType::Aircraft: + { + if (!TechnoTypeExt::ExtMap.Find(static_cast(pObject)->Type)->CanBeBuiltOn) + return CanNotExistHere; - if (pTypeExt && pTypeExt->CanBeBuiltOn) builtOnCanBeBuiltOn = 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); + break; + } - if (pTypeExt && pTypeExt->CanBeBuiltOn) - builtOnCanBeBuiltOn = true; - else if (pOwner != pBuilding->Owner || !pType->LaserFence) - return CanNotExistHere; - else if (pBuildingType->LaserFencePost && !IsSameFenceType(pBuildingType, pType)) - return CanNotExistHere; - } - else if (absType == AbstractType::Infantry || absType == AbstractType::Unit) - { - if (CheckCanNotExistHere(static_cast(pObject), pOwner, expand, skipFlag, builtOnCanBeBuiltOn, landFootOnly)) - return CanNotExistHere; - } - else if (const auto pTerrain = abstract_cast(pObject)) - { - const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type); + case AbstractType::Building: + { + const auto pBuilding = static_cast(pObject); + const auto pType = pBuilding->Type; + + if (TechnoTypeExt::ExtMap.Find(pType)->CanBeBuiltOn) + builtOnCanBeBuiltOn = true; + else if (pOwner != pBuilding->Owner || !pType->LaserFence) + return CanNotExistHere; + else if (pBuildingType->LaserFencePost && !IsSameFenceType(pBuildingType, pType)) + return CanNotExistHere; + + break; + } + + case AbstractType::Infantry: + case AbstractType::Unit: + { + if (CheckCanNotExistHere(static_cast(pObject), pOwner, expand, skipFlag, builtOnCanBeBuiltOn, landFootOnly)) + return CanNotExistHere; + + break; + } + + case AbstractType::Terrain: + { + if (!TerrainTypeExt::ExtMap.Find(static_cast(pObject)->Type)->CanBeBuiltOn) + return CanNotExistHere; - if (pTypeExt && pTypeExt->CanBeBuiltOn) builtOnCanBeBuiltOn = true; - else - return CanNotExistHere; + break; + } + + default: + { + break; + } } } @@ -310,15 +308,9 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6) for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) { - const auto absType = pObject->WhatAmI(); - - if (absType == AbstractType::Building) + if (const auto pBuilding = real_abstract_cast(pObject)) { - const auto pBuilding = static_cast(pObject); - const auto pType = pBuilding->Type; - const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType); - - if (!pTypeExt || !pTypeExt->CanBeBuiltOn) + if (!TechnoTypeExt::ExtMap.Find(pBuilding->Type)->CanBeBuiltOn) return CanNotExistHere; } } @@ -330,32 +322,40 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6) for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) { - const auto absType = pObject->WhatAmI(); - - if (absType == AbstractType::Aircraft || absType == AbstractType::Building) + switch (pObject->WhatAmI()) { - const auto pTechno = static_cast(pObject); - const auto pTechnoType = pTechno->GetTechnoType(); - const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pTechnoType); + case AbstractType::Aircraft: + case AbstractType::Building: + { + if (!TechnoTypeExt::ExtMap.Find(pObject->GetTechnoType())->CanBeBuiltOn) + return CanNotExistHere; - if (pTypeExt && pTypeExt->CanBeBuiltOn) builtOnCanBeBuiltOn = true; - else - return CanNotExistHere; - } - else if (absType == AbstractType::Infantry || absType == AbstractType::Unit) - { - if (CheckCanNotExistHere(static_cast(pObject), pOwner, expand, skipFlag, builtOnCanBeBuiltOn, landFootOnly)) - return CanNotExistHere; - } - else if (const auto pTerrain = abstract_cast(pObject)) - { - const auto pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type); + break; + } + + case AbstractType::Infantry: + case AbstractType::Unit: + { + if (CheckCanNotExistHere(static_cast(pObject), pOwner, expand, skipFlag, builtOnCanBeBuiltOn, landFootOnly)) + return CanNotExistHere; + + break; + } + + case AbstractType::Terrain: + { + if (!TerrainTypeExt::ExtMap.Find(static_cast(pObject)->Type)->CanBeBuiltOn) + return CanNotExistHere; - if (pTypeExt && pTypeExt->CanBeBuiltOn) builtOnCanBeBuiltOn = true; - else - return CanNotExistHere; + break; + } + + default: + { + break; + } } } @@ -738,7 +738,7 @@ DEFINE_HOOK(0x4FB339, HouseClass_UnitFromFactory_SkipMouseClear, 0x6) if (RulesExt::Global()->ExtendedBuildingPlacing) { - if (const auto pBuilding = abstract_cast(pTechno)) + if (const auto pBuilding = real_abstract_cast(pTechno)) { if (const auto pCurrentType = abstract_cast(DisplayClass::Instance.CurrentBuildingType)) { @@ -1080,7 +1080,30 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6) if (!pType) return; - if (reinterpret_cast(0x50B370)(pHouse, pType)) // ShouldDisableCameo + auto currentCanBuild = [&pHouse, &pType]() -> const bool + { + auto const bitsOwners = pType->GetOwners(); + + for(auto const& pConYard : pHouse->ConYards) + { + if (pConYard->InLimbo || !pConYard->HasPower) + continue; + + if (pConYard->CurrentMission == Mission::Selling || pConYard->QueuedMission == Mission::Selling) + continue; + + auto const pType = pConYard->Type; + + if (pType->Factory != AbstractType::Building || !pType->InOwners(bitsOwners)) + continue; + + return true; + } + + return false; + }; + + if (currentCanBuild()) { ClearPlacingBuildingData(pType->BuildCat != BuildCat::Combat ? &pHouseExt->Common : &pHouseExt->Combat); From 861de719cc4dc1eb28f3503c1a61622e720520e0 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Mon, 21 Apr 2025 16:13:42 +0800 Subject: [PATCH 42/58] Fix incorrect check --- src/Ext/BuildingType/Hooks.Placing.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index ff837360f6..fc566bbd1a 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -1086,15 +1086,15 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6) for(auto const& pConYard : pHouse->ConYards) { - if (pConYard->InLimbo || !pConYard->HasPower) + if (pConYard->InLimbo || !pConYard->HasPower || pConYard->Deactivated) continue; if (pConYard->CurrentMission == Mission::Selling || pConYard->QueuedMission == Mission::Selling) continue; - auto const pType = pConYard->Type; + const auto pConYardType = pConYard->Type; - if (pType->Factory != AbstractType::Building || !pType->InOwners(bitsOwners)) + if (pConYardType->Factory != AbstractType::BuildingType || !pConYardType->InOwners(bitsOwners)) continue; return true; @@ -1103,7 +1103,7 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6) return false; }; - if (currentCanBuild()) + if (!currentCanBuild()) { ClearPlacingBuildingData(pType->BuildCat != BuildCat::Combat ? &pHouseExt->Common : &pHouseExt->Combat); From a2388f73e82e410b0dbf2dfb5a516f3159380d92 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Mon, 21 Apr 2025 23:55:18 +0800 Subject: [PATCH 43/58] Update YRpp --- YRpp | 2 +- src/Ext/BuildingType/Hooks.Placing.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/YRpp b/YRpp index e388594b05..c2133762cb 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit e388594b055c71e158115ac81802eb441119ba29 +Subproject commit c2133762cbea0be1be37eae38108d0e65ad33fd4 diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index fc566bbd1a..cf622b9af8 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -34,7 +34,7 @@ DEFINE_HOOK(0x5684B1, MapClass_PlaceDown_BuildableUponTypes, 0x6) { for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) { - if (const auto pTechno = real_abstract_cast(pObject)) + if (const auto pTechno = abstract_cast(pObject)) { if (TechnoTypeExt::ExtMap.Find(pTechno->GetTechnoType())->CanBeBuiltOn) { @@ -44,7 +44,7 @@ DEFINE_HOOK(0x5684B1, MapClass_PlaceDown_BuildableUponTypes, 0x6) pTechno->UnInit(); } } - else if (const auto pTerrain = real_abstract_cast(pObject)) + else if (const auto pTerrain = abstract_cast(pObject)) { if (TerrainTypeExt::ExtMap.Find(pTerrain->Type)->CanBeBuiltOn) { @@ -70,7 +70,7 @@ DEFINE_HOOK(0x5FD2B6, OverlayClass_Unlimbo_SkipTerrainCheck, 0x9) for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) { - if (const auto pTerrain = real_abstract_cast(pObject)) + if (const auto pTerrain = abstract_cast(pObject)) { if (!TerrainTypeExt::ExtMap.Find(pTerrain->Type)->CanBeBuiltOn) return NoUnlimbo; @@ -308,7 +308,7 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6) for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) { - if (const auto pBuilding = real_abstract_cast(pObject)) + if (const auto pBuilding = abstract_cast(pObject)) { if (!TechnoTypeExt::ExtMap.Find(pBuilding->Type)->CanBeBuiltOn) return CanNotExistHere; @@ -738,7 +738,7 @@ DEFINE_HOOK(0x4FB339, HouseClass_UnitFromFactory_SkipMouseClear, 0x6) if (RulesExt::Global()->ExtendedBuildingPlacing) { - if (const auto pBuilding = real_abstract_cast(pTechno)) + if (const auto pBuilding = abstract_cast(pTechno)) { if (const auto pCurrentType = abstract_cast(DisplayClass::Instance.CurrentBuildingType)) { From 4e9364cfea3a9886f83e289b9ba30e62185829a2 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Thu, 24 Apr 2025 10:34:33 +0800 Subject: [PATCH 44/58] Simplify --- src/Ext/BuildingType/Hooks.Placing.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index cf622b9af8..3b4895ed89 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -68,16 +68,13 @@ DEFINE_HOOK(0x5FD2B6, OverlayClass_Unlimbo_SkipTerrainCheck, 0x9) if (!Game::IsActive) return Unlimbo; - for (auto pObject = pCell->FirstObject; pObject; pObject = pObject->NextObject) + if (auto const pTerrain = pCell->GetTerrain(false)) { - if (const auto pTerrain = abstract_cast(pObject)) - { - if (!TerrainTypeExt::ExtMap.Find(pTerrain->Type)->CanBeBuiltOn) - return NoUnlimbo; + if (!TerrainTypeExt::ExtMap.Find(pTerrain->Type)->CanBeBuiltOn) + return NoUnlimbo; - pCell->RemoveContent(pTerrain, false); - TerrainTypeExt::Remove(pTerrain); - } + pCell->RemoveContent(pTerrain, false); + TerrainTypeExt::Remove(pTerrain); } return Unlimbo; From c808fc3824b78f55b8f46d4567d7a8d35d9d1c5a Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Tue, 8 Jul 2025 18:24:39 +0800 Subject: [PATCH 45/58] Skip set shortcut key for limbo building --- src/Ext/BuildingType/Hooks.Placing.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index 3b4895ed89..3ce3a80d28 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -1260,9 +1260,12 @@ DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6) // Auto Build Hook -> sub_6A8B30 - Auto Build Buildings DEFINE_HOOK(0x6A8E34, StripClass_Update_AutoBuildBuildings, 0x7) { + enum { SkipGameCode = 0x6A8E1E }; + GET(BuildingClass* const, pBuilding, ESI); - BuildingTypeExt::BuildLimboBuilding(pBuilding); + if (BuildingTypeExt::BuildLimboBuilding(pBuilding)) + return SkipGameCode; return 0; } From b34db0aa3ad3913a228724229e931e45ccf9d078 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Wed, 9 Jul 2025 16:07:10 +0800 Subject: [PATCH 46/58] Fix merge --- src/Ext/BuildingType/Body.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index d7768a11f0..eebc3b675e 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -514,20 +514,18 @@ void BuildingTypeExt::CreateLimboBuilding(BuildingClass* pBuilding, BuildingType 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. + // Jun 3, 2023 - Starkku: For reasons beyond my comprehension, the discovery logic is checked for certain logics like power drain/output in campaign only. // Normally on unlimbo the buildings are revealed to current player if unshrouded or if game is a campaign and to non-player houses always. // Because of the unique nature of LimboDelivered buildings, this has been adjusted to always reveal to the current player in singleplayer - // and to the owner of the building regardless, removing the shroud check from the equation since they don't physically exist - Starkku + // and to the owner of the building regardless, removing the shroud check from the equation since they don't physically exist if (SessionClass::IsCampaign()) pBuilding->DiscoveredBy(HouseClass::CurrentPlayer); pBuilding->DiscoveredBy(pOwner); pOwner->RegisterGain(pBuilding, false); - pOwner->UpdatePower(); pOwner->RecheckTechTree = true; pOwner->RecheckPower = true; - pOwner->RecheckRadar = true; pOwner->Buildings.AddItem(pBuilding); // Different types of building logics @@ -704,7 +702,7 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) } this->Refinery_UseNormalActiveAnim.Read(exArtINI, pArtSection, "Refinery.UseNormalActiveAnim"); - + // Ares tag this->SpyEffect_Custom.Read(exINI, pSection, "SpyEffect.Custom"); if (SuperWeaponTypeClass::Array.Count > 0) From eb8fabefb00930992e46aa37dd55b0e98a6861f0 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Wed, 9 Jul 2025 16:08:07 +0800 Subject: [PATCH 47/58] Update --- src/Ext/BuildingType/Hooks.Placing.cpp | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index 3ce3a80d28..e3cfbe542b 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -161,22 +161,11 @@ static inline bool CheckCanNotExistHere(FootClass* const pTechno, HouseClass* co const auto pTechnoType = pTechno->GetTechnoType(); if (TechnoTypeExt::ExtMap.Find(pTechnoType)->CanBeBuiltOn) - { - if (pTechno->GetMapCoords() == pTechno->CurrentMapCoords) - builtOnCanBeBuiltOn = true; - else if (expand) - landFootOnly = true; - else - return true; - } + builtOnCanBeBuiltOn = true; else if (!expand || pTechnoType->Speed <= 0 || !BuildingTypeExt::CheckOccupierCanLeave(pOwner, pTechno->Owner)) - { return true; - } else - { landFootOnly = true; - } return false; } From 0cc5ecb3a6b8b0664aa6c4161d7b97f1a7a5fd5d Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Sat, 12 Jul 2025 16:21:15 +0800 Subject: [PATCH 48/58] New function --- docs/New-or-Enhanced-Logics.md | 14 +- src/Ext/BuildingType/Body.cpp | 32 ++ src/Ext/BuildingType/Body.h | 12 +- src/Ext/BuildingType/Hooks.Placing.cpp | 389 +++++++++++++++---------- src/Ext/BuildingType/Hooks.cpp | 88 ++++-- src/Ext/House/Body.h | 1 + src/Ext/Scenario/Body.cpp | 1 + src/Ext/Scenario/Body.h | 3 + 8 files changed, 355 insertions(+), 185 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 3b170a3f84..3ab200c553 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -648,8 +648,10 @@ 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 technos) and whether there are enough spaces to scatter. If can, it will record which building you are placing and show a preview to you and your allies, then start a timer to record this placement and order the occupiers to leave this building area. When the area is cleared, the building will be truly place down and the production queue will be restored to original state. But when the timer expires or an unexpected situation has occurred which make the building impossible be constructed here anymore, it will stop the action and play "cannot deploy here", then you should re-place or re-deploy the building in a valid space. - `LimboBuild` controls whether building can be automatically placed like `LimboDelivery`. - `LimboBuildID` defines the numeric ID of the building placed by `LimboBuild`. -- `PlaceBuilding.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. +- `PlaceBuilding.Extra` controls whether the actual placement type of the building can be changed by holding the left mouse button and changing the mouse position when placing. + - `PlaceBuilding.OnLand` controls buildings can be replaced when placed on land. + - `PlaceBuilding.OnWater` controls buildings can be replaced when placed on water. + - `PlaceBuilding.DirectionShape` and `PlaceBuilding.DirectionPalette` controls what additional directional guidance shape looks like when placing `PlaceBuilding.Extra=true` buildings. In `rulesmd.ini`: ```ini @@ -659,12 +661,16 @@ ExtendedBuildingPlacing=false ; boolean [SOMEBUILDING] ; BuildingType LimboBuild=false ; boolean LimboBuildID=-1 ; integer -PlaceBuilding.OnLand= ; BuildingType -PlaceBuilding.OnWater= ; BuildingType +PlaceBuilding.Extra=false ; boolean +PlaceBuilding.OnLand= ; List of BuildingTypes +PlaceBuilding.OnWater= ; List of BuildingTypes +PlaceBuilding.DirectionShape= ; filename with .shp extension +PlaceBuilding.DirectionPalette= ; filename with .pal extension ``` ```{note} - `PlaceBuilding.OnLand` and `PlaceBuilding.OnWater` are only work for players. +- The replacement building and the original building must have the same `BuildCat`, and neither can have `LimboBuild` or `PlaceAnywhere`. ``` ## Infantry diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index eebc3b675e..55c04f81cc 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -127,6 +127,32 @@ int BuildingTypeExt::GetUpgradesAmount(BuildingTypeClass* pBuilding, HouseClass* return isUpgrade ? result : -1; } +BuildingTypeClass* BuildingTypeExt::ExtData::GetAnotherPlacingType(size_t direction, bool onWater) +{ + const auto pType = this->OwnerObject(); + + if (pType->PlaceAnywhere || this->LimboBuild) + return nullptr; + + const auto& types = onWater ? this->PlaceBuilding_OnWater : this->PlaceBuilding_OnLand; + const size_t size = types.size(); + + if (!size) + return nullptr; + + direction = (direction + (16u / size)) & 0x1Fu; + const auto pAnotherType = types[static_cast(direction * size / 32u)]; + + if (pAnotherType->BuildCat != pType->BuildCat + || pAnotherType->PlaceAnywhere + || BuildingTypeExt::ExtMap.Find(pAnotherType)->LimboBuild) + { + return nullptr; + } + + return pAnotherType; +} + // Check whether can call the occupiers leave bool BuildingTypeExt::CheckOccupierCanLeave(HouseClass* pBuildingHouse, HouseClass* pOccupierHouse) { @@ -651,6 +677,9 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->LaserFencePost_Fence.Read(exINI, pSection, "LaserFencePost.Fence"); this->PlaceBuilding_OnLand.Read(exINI, pSection, "PlaceBuilding.OnLand"); this->PlaceBuilding_OnWater.Read(exINI, pSection, "PlaceBuilding.OnWater"); + this->PlaceBuilding_DirectionShape.Read(exINI, pSection, "PlaceBuilding.DirectionShape"); + this->PlaceBuilding_DirectionPalette.LoadFromINI(pINI, pSection, "PlaceBuilding.DirectionPalette"); + this->PlaceBuilding_Extra.Read(exINI, pSection, "PlaceBuilding.Extra"); this->FactoryPlant_AllowTypes.Read(exINI, pSection, "FactoryPlant.AllowTypes"); this->FactoryPlant_DisallowTypes.Read(exINI, pSection, "FactoryPlant.DisallowTypes"); @@ -798,6 +827,9 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm) .Process(this->LaserFencePost_Fence) .Process(this->PlaceBuilding_OnLand) .Process(this->PlaceBuilding_OnWater) + .Process(this->PlaceBuilding_DirectionShape) + .Process(this->PlaceBuilding_DirectionPalette) + .Process(this->PlaceBuilding_Extra) .Process(this->AircraftDockingDirs) .Process(this->FactoryPlant_AllowTypes) .Process(this->FactoryPlant_DisallowTypes) diff --git a/src/Ext/BuildingType/Body.h b/src/Ext/BuildingType/Body.h index f3c621d3bd..e0dea044ff 100644 --- a/src/Ext/BuildingType/Body.h +++ b/src/Ext/BuildingType/Body.h @@ -67,8 +67,11 @@ class BuildingTypeExt Valueable LimboBuild; Valueable LimboBuildID; Valueable LaserFencePost_Fence; - Valueable PlaceBuilding_OnLand; - Valueable PlaceBuilding_OnWater; + ValueableVector PlaceBuilding_OnLand; + ValueableVector PlaceBuilding_OnWater; + Valueable PlaceBuilding_DirectionShape; + CustomPalette PlaceBuilding_DirectionPalette; + Valueable PlaceBuilding_Extra; Valueable IsAnimDelayedBurst; @@ -145,6 +148,9 @@ class BuildingTypeExt , LaserFencePost_Fence {} , PlaceBuilding_OnLand {} , PlaceBuilding_OnWater {} + , PlaceBuilding_DirectionShape { nullptr } + , PlaceBuilding_DirectionPalette {} + , PlaceBuilding_Extra { false } , AircraftDockingDirs {} , FactoryPlant_AllowTypes {} , FactoryPlant_DisallowTypes {} @@ -171,6 +177,8 @@ class BuildingTypeExt , Refinery_UseNormalActiveAnim { false } { } + BuildingTypeClass* GetAnotherPlacingType(size_t direction, bool onWater); + // Ares 0.A functions int GetSuperWeaponCount() const; int GetSuperWeaponIndex(int index, HouseClass* pHouse) const; diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index e3cfbe542b..473180e1d6 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -122,6 +123,10 @@ DEFINE_HOOK(0x4A8FD7, DisplayClass_BuildingProximityCheck_BuildArea, 0x6) return 0; } +// Let the game do the proximity and shroud check when the cell which mouse is pointing at has not changed +DEFINE_JUMP(LJMP, 0x4AACD9, 0x4AACF5); +DEFINE_JUMP(LJMP, 0x4A9361, 0x4A9371); + static inline bool IsSameFenceType(const BuildingTypeClass* const pPostType, const BuildingTypeClass* const pFenceType) { if (const auto pSpecificType = BuildingTypeExt::ExtMap.Find(pPostType)->LaserFencePost_Fence.Get()) @@ -395,8 +400,9 @@ static inline void ClearPlacingBuildingData(PlacingBuildingStruct* const pPlace) pPlace->Type = nullptr; pPlace->DrawType = nullptr; pPlace->Times = 0; - pPlace->TopLeft = CellStruct::Empty; pPlace->Timer.Stop(); + pPlace->TopLeft = CellStruct::Empty; + pPlace->PlaceType = 0; } static inline void ClearCurrentBuildingData(DisplayClass* const pDisplay) @@ -452,35 +458,10 @@ static inline bool CheckBuildingFoundation(BuildingTypeClass* const pBuildingTyp return true; } -static inline BuildingTypeClass* GetAnotherPlacingType(BuildingTypeClass* pType, BuildingTypeExt::ExtData* pTypeExt, CellStruct checkCell, bool opposite) +// Place Another Type Helper +namespace PlaceTypeTemp { - if (!pType->PlaceAnywhere && !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) ? (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; -} - -static inline BuildingTypeClass* GetAnotherPlacingType(DisplayClass* pDisplay) -{ - if (const auto pCurrentBuilding = abstract_cast(pDisplay->CurrentBuilding)) - { - const auto pType = pCurrentBuilding->Type; - const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pType); - - if (pTypeExt->PlaceBuilding_OnLand || pTypeExt->PlaceBuilding_OnWater) - return GetAnotherPlacingType(pType, pTypeExt, pDisplay->CurrentFoundation_CenterCell, false); - } - - return nullptr; + size_t PlaceType = 0; } // Place Another Type Hook #1 -> sub_4FB0E0 - Replace the factory product @@ -524,70 +505,17 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5) return BuildSucceeded; } - const bool expand = RulesExt::Global()->ExtendedBuildingPlacing && !pBuildingType->PlaceAnywhere && !pBuildingType->PowersUpBuilding[0]; + const bool upgrade = pBuildingType->PowersUpBuilding[0]; + const bool expand = RulesExt::Global()->ExtendedBuildingPlacing && !pBuildingType->PlaceAnywhere && !upgrade; + const size_t placeType = PlaceTypeTemp::PlaceType; - if (pTypeExt->PlaceBuilding_OnWater || pTypeExt->PlaceBuilding_OnLand) + if (pTypeExt->PlaceBuilding_Extra) { - if (!SessionClass::IsMultiplayer()) - { - if (expand) - { - const auto pHouseExt = HouseExt::ExtMap.Find(pHouse); - auto& place = pBufferType->BuildCat != BuildCat::Combat ? pHouseExt->Common : pHouseExt->Combat; - - if (place.DrawType && (place.DrawType == pTypeExt->PlaceBuilding_OnLand || place.DrawType == pTypeExt->PlaceBuilding_OnWater)) - pBuildingType = place.DrawType; - } - - const auto& pTypeCopy = pDisplay->CurrentBuildingTypeCopy; - - if (pTypeCopy && (pTypeCopy == pTypeExt->PlaceBuilding_OnLand || pTypeCopy == pTypeExt->PlaceBuilding_OnWater)) - pBuildingType = static_cast(pTypeCopy); - } - else // When playing online, this can not rely on locally stored replicas, and must made speculation based on the event - { - 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, pTypeExt, checkCell, false)) - { - pBuildingType = pOtherType; - } - else if (const auto pAnotherType = GetAnotherPlacingType(pBuildingType, pTypeExt, 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->SpeedType == SpeedType::Float)) - pBuildingType = pAnotherType; - } - } + if (const auto pSelectType = pTypeExt->GetAnotherPlacingType(((placeType >> 1u) & 0x1Fu), (placeType & 1u))) + pBuildingType = pSelectType; } - bool revert = false; + bool revert = upgrade; if (expand) { @@ -611,6 +539,7 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5) place.DrawType = pBuildingType; place.Times = 30; place.TopLeft = topLeftCell; + place.PlaceType = placeType; } else if (place.Times <= 0) { @@ -692,6 +621,174 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5) return CanNotBuild; } +// Place Another Type Hook #2 -> sub_4A91B0 - Replace current building type for check +DEFINE_HOOK(0x4A937D, DisplayClass_CallBuildingPlaceCheck_ReplaceBuildingType, 0x8) +{ + enum { SkipGameCode = 0x4A943A }; + + GET(const CellStruct, cell, EBP); + + const auto pDisplay = &DisplayClass::Instance; + + auto updateCurrentFoundation = [pDisplay, cell]() + { + if (pDisplay->CurrentFoundation_CenterCell != cell) + { + auto oldCell = pDisplay->CurrentFoundation_CenterCell; + + if (oldCell != CellStruct::Empty) + { + oldCell += pDisplay->CurrentFoundation_TopLeftOffset; + pDisplay->MarkFoundation(&oldCell, false); + } + + auto newCell = cell; + + if (newCell != CellStruct::Empty) + { + newCell += pDisplay->CurrentFoundation_TopLeftOffset; + pDisplay->MarkFoundation(&newCell, true); + } + + pDisplay->CurrentFoundation_CenterCell = cell; + } + }; + + const auto pCurrentBuilding = abstract_cast(pDisplay->CurrentBuilding); + const auto pTypeExt = pCurrentBuilding ? BuildingTypeExt::ExtMap.Find(pCurrentBuilding->Type) : nullptr; + + if (pTypeExt && pTypeExt->PlaceBuilding_Extra) + { + if (!ScrollClass::Instance.unknown_byte_554A) // 555A: AnyMouseButtonDown + updateCurrentFoundation(); + else // bp + R->EBP(pDisplay->CurrentFoundation_CenterCell.X); + + const auto delta = cell - pDisplay->CurrentFoundation_CenterCell; + ScenarioExt::Global()->PlacingDirection = DirStruct(Math::atan2(-delta.Y, delta.X)).GetFacing<32>(); + + auto getAnotherPlacingType = [pDisplay, pTypeExt]() -> BuildingTypeClass* + { + if (pTypeExt) + { + const auto pCenterCell = MapClass::Instance.GetCellAt(pDisplay->CurrentFoundation_CenterCell); + const bool onWater = pCenterCell->LandType == LandType::Water; + return pTypeExt->GetAnotherPlacingType(ScenarioExt::Global()->PlacingDirection, onWater); + } + + return nullptr; + }; + + if (const auto pAnotherType = getAnotherPlacingType()) + { + if (pDisplay->CurrentBuildingType && pDisplay->CurrentBuildingType != pAnotherType) + { + pDisplay->CurrentBuildingType = pAnotherType; + pDisplay->SetActiveFoundation(pAnotherType->GetFoundationData(true)); + } + } + else if (pCurrentBuilding) + { + if (pDisplay->CurrentBuildingType && pDisplay->CurrentBuildingType != pCurrentBuilding->Type) + { + pDisplay->CurrentBuildingType = pCurrentBuilding->Type; + pDisplay->SetActiveFoundation(pCurrentBuilding->Type->GetFoundationData(true)); + } + } + } + else + { + updateCurrentFoundation(); + } + + return SkipGameCode; +} + +// Place Another Type Hook #3 -> sub_4AB9B0 - Keep current foundation center cell +DEFINE_HOOK(0x4AB9FF, DisplayClass_LeftMouseButtonUp_MaintainCell, 0x6) +{ + enum { ContinueCheckUpgrade = 0x4ABA84, SkipCheckUpgrade = 0x4ABAA4 }; + + const auto pCellStruct = &DisplayClass::Instance.CurrentFoundation_CenterCell; + + R->EBX(pCellStruct); + + const auto pType = DisplayClass::Instance.CurrentBuildingType; + + if (pType->WhatAmI() != AbstractType::BuildingType || !static_cast(pType)->PowersUpBuilding[0]) + return SkipCheckUpgrade; + + const auto pCellBuilding = MapClass::Instance.GetCellAt(*pCellStruct)->GetBuilding(); + + if (!pCellBuilding) + return SkipCheckUpgrade; + else + R->ESI(pCellBuilding); + + return ContinueCheckUpgrade; +} + +// Place Another Type Hook #4 -> sub_4AB9B0 - Replace event +DEFINE_HOOK(0x4ABAC0, DisplayClass_LeftMouseButtonUp_ReplaceBuildingType, 0x6) +{ + enum { SkipGameCode = 0x4ABBB3 }; + + const auto pDisplay = &DisplayClass::Instance; + + const auto centerCell = pDisplay->CurrentFoundation_CenterCell; + const auto placeCell = centerCell + pDisplay->CurrentFoundation_TopLeftOffset; + + const auto pType = pDisplay->CurrentBuildingType; + const auto pBuildingType = abstract_cast(pType); + + int placeType = 0; + + if (pBuildingType && BuildingTypeExt::ExtMap.Find(pBuildingType)->PlaceBuilding_Extra) + { + const auto pCenterCell = MapClass::Instance.GetCellAt(centerCell); + placeType |= pCenterCell->LandType == LandType::Water; + placeType |= (ScenarioExt::Global()->PlacingDirection << 1); + } + else + { + const auto pTechnoType = TechnoTypeExt::GetTechnoType(pType); + placeType |= (pTechnoType && pTechnoType->Naval); + } + + const int arrayIndex = pType->GetArrayIndex(); + const auto absType = pDisplay->CurrentBuilding->WhatAmI(); + + const EventClass event (HouseClass::CurrentPlayer->ArrayIndex, EventType::Place, absType, arrayIndex, placeType, placeCell); + EventClass::AddEvent(event); + + return SkipGameCode; +} + +// Place Another Type Hook #5 -> sub_4C6CB0 - Replace placing action +DEFINE_HOOK(0x4C70E1, EventClass_RespondToEvent_SetPlaceType, 0x8) +{ + enum { SkipGameCode = 0x4C7110 }; + + GET(EventClass* const, pThis, ESI); + GET(HouseClass* const, pHouse, EDI); + + const auto cell = pThis->Place.Location; + const int flags = pThis->Place.IsNaval; + const bool isNaval = flags & 1; + const int arrayIndex = pThis->Place.HeapID; + const auto absType = pThis->Place.RTTIType; + + if (absType == AbstractType::Building || absType == AbstractType::BuildingType) + PlaceTypeTemp::PlaceType = static_cast(flags); + + reinterpret_cast + (0x4FB0E0)(pHouse, absType, arrayIndex, isNaval, &cell); // UnitFromFactory + + PlaceTypeTemp::PlaceType = 0; + + return SkipGameCode; +} + // Buildable-upon TechnoTypes Hook #4-1 -> sub_4FB0E0 - Check whether need to skip the replace command DEFINE_HOOK(0x4FB395, HouseClass_UnitFromFactory_SkipMouseReturn, 0x6) { @@ -1061,58 +1158,64 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6) return 0; const auto pHouseExt = HouseExt::ExtMap.Find(pHouse); - auto buildCurrent = [&pHouse, &pHouseExt](BuildingTypeClass* pType, CellStruct cell) - { - if (!pType) - return; - - auto currentCanBuild = [&pHouse, &pType]() -> const bool + auto buildCurrent = [&pHouse, &pHouseExt](BuildingTypeClass* pType, CellStruct cell, size_t placeType) { - auto const bitsOwners = pType->GetOwners(); + if (!pType) + return; - for(auto const& pConYard : pHouse->ConYards) - { - if (pConYard->InLimbo || !pConYard->HasPower || pConYard->Deactivated) - continue; + auto currentCanBuild = [&pHouse, &pType]() -> const bool + { + auto const bitsOwners = pType->GetOwners(); - if (pConYard->CurrentMission == Mission::Selling || pConYard->QueuedMission == Mission::Selling) - continue; + for(auto const& pConYard : pHouse->ConYards) + { + if (pConYard->InLimbo || !pConYard->HasPower || pConYard->Deactivated) + continue; - const auto pConYardType = pConYard->Type; + if (pConYard->CurrentMission == Mission::Selling || pConYard->QueuedMission == Mission::Selling) + continue; - if (pConYardType->Factory != AbstractType::BuildingType || !pConYardType->InOwners(bitsOwners)) - continue; + const auto pConYardType = pConYard->Type; - return true; - } + if (pConYardType->Factory != AbstractType::BuildingType || !pConYardType->InOwners(bitsOwners)) + continue; - return false; - }; + return true; + } - if (!currentCanBuild()) - { - ClearPlacingBuildingData(pType->BuildCat != BuildCat::Combat ? &pHouseExt->Common : &pHouseExt->Combat); + return false; + }; - if (pHouse == HouseClass::CurrentPlayer) - VoxClass::Play(GameStrings::EVA_CannotDeployHere); - } - else if (pHouse == HouseClass::CurrentPlayer) // Prevent unexpected wrong event - { - const EventClass event (pHouse->ArrayIndex, EventType::Place, AbstractType::Building, pType->GetArrayIndex(), pType->Naval, cell); - EventClass::AddEvent(event); - } - }; + if (!currentCanBuild()) + { + ClearPlacingBuildingData(pType->BuildCat != BuildCat::Combat ? &pHouseExt->Common : &pHouseExt->Combat); + + if (pHouse == HouseClass::CurrentPlayer) + VoxClass::Play(GameStrings::EVA_CannotDeployHere); + } + else if (pHouse == HouseClass::CurrentPlayer) // Prevent unexpected wrong event + { + const int place = static_cast(placeType); + const auto arrayIndex = pType->GetArrayIndex(); + const EventClass event (pHouse->ArrayIndex, EventType::Place, AbstractType::Building, arrayIndex, place, cell); + EventClass::AddEvent(event); + } + }; - if (pHouseExt->Common.Timer.Completed()) + auto& commonPlace = pHouseExt->Common; + + if (commonPlace.Timer.Completed()) { - pHouseExt->Common.Timer.Stop(); - buildCurrent(pHouseExt->Common.Type, pHouseExt->Common.TopLeft); + commonPlace.Timer.Stop(); + buildCurrent(commonPlace.Type, commonPlace.TopLeft, commonPlace.PlaceType); } - if (pHouseExt->Combat.Timer.Completed()) + auto& combatPlace = pHouseExt->Combat; + + if (combatPlace.Timer.Completed()) { - pHouseExt->Combat.Timer.Stop(); - buildCurrent(pHouseExt->Combat.Type, pHouseExt->Combat.TopLeft); + combatPlace.Timer.Stop(); + buildCurrent(combatPlace.Type, combatPlace.TopLeft, combatPlace.PlaceType); } if (pHouseExt->OwnedDeployingUnits.size() > 0) @@ -1143,32 +1246,6 @@ DEFINE_HOOK(0x4F8DB1, HouseClass_Update_CheckHangUpBuilding, 0x6) return 0; } -// 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) -{ - 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) { @@ -1182,7 +1259,7 @@ DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6) { const auto pCell = pDisplay->TryGetCellAt(cell); - if (!pCell || cell == CellStruct::Empty) + if (!pCell || cell == CellStruct::Empty || pType->PowersUpBuilding[0]) return; auto pImage = pType->LoadBuildup(); @@ -1217,10 +1294,10 @@ DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6) if (const auto pType = abstract_cast(pDisplay->CurrentBuildingTypeCopy)) drawImage(pType, pHouse, (pDisplay->CurrentFoundationCopy_TopLeftOffset + pDisplay->CurrentFoundationCopy_CenterCell)); - if (const auto pType = pHouseExt->Common.Type) + if (const auto pType = pHouseExt->Common.DrawType) drawImage(pType, pHouse, pHouseExt->Common.TopLeft); - if (const auto pType = pHouseExt->Combat.Type) + if (const auto pType = pHouseExt->Combat.DrawType) drawImage(pType, pHouse, pHouseExt->Combat.TopLeft); if (pHouseExt->OwnedDeployingUnits.size() <= 0) diff --git a/src/Ext/BuildingType/Hooks.cpp b/src/Ext/BuildingType/Hooks.cpp index f6cda1fe97..1a31f74262 100644 --- a/src/Ext/BuildingType/Hooks.cpp +++ b/src/Ext/BuildingType/Hooks.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -70,31 +71,72 @@ DEFINE_HOOK(0x458623, BuildingClass_KillOccupiers_Replace_MuzzleFix, 0x7) DEFINE_HOOK(0x6D528A, TacticalClass_DrawPlacement_PlacementPreview, 0x6) { - if (!RulesExt::Global()->PlacementPreview || !Phobos::Config::ShowPlacementPreview) + const auto pDisplay = &DisplayClass::Instance; + const auto pBuilding = abstract_cast(pDisplay->CurrentBuilding); + + if (!pBuilding) return 0; - auto pBuilding = specific_cast(DisplayClass::Instance.CurrentBuilding); - auto pType = specific_cast(DisplayClass::Instance.CurrentBuildingType); - auto pTypeExt = pType ? BuildingTypeExt::ExtMap.Find(pType) : nullptr; + const auto pRulesExt = RulesExt::Global(); + const auto pTactical = TacticalClass::Instance; - if (pBuilding && pTypeExt && pTypeExt->PlacementPreview) + do { - CellClass* pCell = nullptr; - { - const CellStruct nDisplayCell = Make_Global(0x88095C); - const CellStruct nDisplayCell_Offset = Make_Global(0x880960); + const auto pType = pBuilding->Type; + const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pType); - pCell = MapClass::Instance.TryGetCellAt(nDisplayCell + nDisplayCell_Offset); - if (!pCell) - return 0; - } + if (!pTypeExt->PlaceBuilding_Extra) + break; + + const auto pShape = pTypeExt->PlaceBuilding_DirectionShape.Get(); + + if (!pShape || pShape->Frames <= 0) + break; + + const auto pCell = MapClass::Instance.GetCellAt(pDisplay->CurrentFoundation_CenterCell); + const auto& types = pCell->LandType == LandType::Water ? pTypeExt->PlaceBuilding_OnWater : pTypeExt->PlaceBuilding_OnLand; + const size_t size = types.size(); + + if (!size) + break; + + constexpr BlitterFlags blit = BlitterFlags::Centered | BlitterFlags::TransLucent50 | BlitterFlags::bf_400 | BlitterFlags::Zero; + const auto pPalette = pTypeExt->PlaceBuilding_DirectionPalette.GetOrDefaultConvert(FileSystem::PALETTE_PAL); + + const auto location = CoordStruct { (pCell->MapCoords.X << 8), (pCell->MapCoords.Y << 8), 0 }; + const int height = pCell->Level * 15; + const int zAdjust = -height - (pCell->SlopeIndex ? 12 : 2); + const auto position = pTactical->CoordsToScreen(location) - pTactical->TacticalPos - Point2D { 0, (1 + height) }; + + const auto direction = (ScenarioExt::Global()->PlacingDirection + (16u / size)) & 0x1Fu; + const int frameIndex = Math::min(static_cast(pShape->Frames - 1), static_cast(direction * size / 32u)); + + DSurface::Temp->DrawSHP(pPalette, pShape, frameIndex, &position, + &DSurface::ViewBounds, blit, 0, zAdjust, ZGradient::Ground, 1000, 0, 0, 0, 0, 0); + } + while (false); + + if (!pRulesExt->PlacementPreview || !Phobos::Config::ShowPlacementPreview) + return 0; + + // DrawType + const auto pType = abstract_cast(pDisplay->CurrentBuildingType); + const auto pTypeExt = pType ? BuildingTypeExt::ExtMap.Find(pType) : nullptr; + + if (pTypeExt && pTypeExt->PlacementPreview) + { + const auto pCell = MapClass::Instance.TryGetCellAt(pDisplay->CurrentFoundation_CenterCell + pDisplay->CurrentFoundation_TopLeftOffset); + + if (!pCell) + return 0; int nImageFrame = 0; - SHPStruct* pImage = pTypeExt->PlacementPreview_Shape.GetSHP(); + auto pImage = pTypeExt->PlacementPreview_Shape.GetSHP(); { if (!pImage) { pImage = pType->LoadBuildup(); + if (pImage) nImageFrame = ((pImage->Frames / 2) - 1); else @@ -111,26 +153,26 @@ DEFINE_HOOK(0x6D528A, TacticalClass_DrawPlacement_PlacementPreview, 0x6) Point2D point; { const CoordStruct offset = pTypeExt->PlacementPreview_Offset; - const int nHeight = offset.Z + pCell->GetFloorHeight({ 0, 0 }); - const CoordStruct coords = CellClass::Cell2Coord(pCell->MapCoords, nHeight); + const int height = offset.Z + pCell->GetFloorHeight({ 0, 0 }); + const CoordStruct coords = CellClass::Cell2Coord(pCell->MapCoords, height); - point = TacticalClass::Instance->CoordsToClient(coords).first; + point = pTactical->CoordsToClient(coords).first; point.X += offset.X; point.Y += offset.Y; } - const BlitterFlags blitFlags = pTypeExt->PlacementPreview_Translucency.Get(RulesExt::Global()->PlacementPreview_Translucency) | - BlitterFlags::Centered | BlitterFlags::Nonzero | BlitterFlags::MultiPass; + const BlitterFlags blitFlags = pTypeExt->PlacementPreview_Translucency.Get(pRulesExt->PlacementPreview_Translucency) + | BlitterFlags::Centered | BlitterFlags::Nonzero | BlitterFlags::MultiPass; - ConvertClass* pPalette = pTypeExt->PlacementPreview_Remap.Get() + const auto pPalette = pTypeExt->PlacementPreview_Remap.Get() ? pBuilding->GetDrawer() : pTypeExt->PlacementPreview_Palette.GetOrDefaultConvert(FileSystem::UNITx_PAL); - DSurface* pSurface = DSurface::Temp; - RectangleStruct rect = pSurface->GetRect(); + const auto pSurface = DSurface::Temp; + auto rect = pSurface->GetRect(); rect.Height -= 32; // account for bottom bar - CC_Draw_Shape(pSurface, pPalette, pImage, nImageFrame, &point, &rect, blitFlags, + pSurface->DrawSHP(pPalette, pImage, nImageFrame, &point, &rect, blitFlags, 0, 0, ZGradient::Ground, 1000, 0, nullptr, 0, 0, 0); } diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h index 6d0199fa4b..eb333ed9d9 100644 --- a/src/Ext/House/Body.h +++ b/src/Ext/House/Body.h @@ -16,6 +16,7 @@ struct PlacingBuildingStruct int Times; CDTimerClass Timer; CellStruct TopLeft; + size_t PlaceType; }; class HouseExt diff --git a/src/Ext/Scenario/Body.cpp b/src/Ext/Scenario/Body.cpp index 060c6dac55..c1cc6ced9f 100644 --- a/src/Ext/Scenario/Body.cpp +++ b/src/Ext/Scenario/Body.cpp @@ -163,6 +163,7 @@ void ScenarioExt::ExtData::Serialize(T& Stm) .Process(this->TransportReloaders) .Process(this->SWSidebar_Enable) .Process(this->SWSidebar_Indices) + .Process(this->PlacingDirection) // .Process(this->NewMessageList); // Should not S/L ; } diff --git a/src/Ext/Scenario/Body.h b/src/Ext/Scenario/Body.h index ce09cd2a3e..c9a78393c6 100644 --- a/src/Ext/Scenario/Body.h +++ b/src/Ext/Scenario/Body.h @@ -40,6 +40,8 @@ class ScenarioExt bool SWSidebar_Enable; std::vector SWSidebar_Indices; + size_t PlacingDirection; + std::unique_ptr NewMessageList; ExtData(ScenarioClass* OwnerObject) : Extension(OwnerObject) @@ -51,6 +53,7 @@ class ScenarioExt , TransportReloaders {} , SWSidebar_Enable { true } , SWSidebar_Indices {} + , PlacingDirection { 0 } , NewMessageList {} { } From f354fa2f79da9b42009e9e1027ee7809fa238ff1 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Sun, 13 Jul 2025 16:39:18 +0800 Subject: [PATCH 49/58] Fix event --- src/Ext/BuildingType/Hooks.Placing.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index 473180e1d6..6a9e7bb861 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -738,7 +738,8 @@ DEFINE_HOOK(0x4ABAC0, DisplayClass_LeftMouseButtonUp_ReplaceBuildingType, 0x6) const auto centerCell = pDisplay->CurrentFoundation_CenterCell; const auto placeCell = centerCell + pDisplay->CurrentFoundation_TopLeftOffset; - const auto pType = pDisplay->CurrentBuildingType; + const auto pPlace = pDisplay->CurrentBuilding; + const auto pType = pPlace->GetType(); // Should not use CurrentBuildingType const auto pBuildingType = abstract_cast(pType); int placeType = 0; @@ -756,7 +757,7 @@ DEFINE_HOOK(0x4ABAC0, DisplayClass_LeftMouseButtonUp_ReplaceBuildingType, 0x6) } const int arrayIndex = pType->GetArrayIndex(); - const auto absType = pDisplay->CurrentBuilding->WhatAmI(); + const auto absType = pPlace->WhatAmI(); const EventClass event (HouseClass::CurrentPlayer->ArrayIndex, EventType::Place, absType, arrayIndex, placeType, placeCell); EventClass::AddEvent(event); From cf11f5dd7f939dc40a4b9d1e9a931695cece6548 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Sun, 13 Jul 2025 16:44:29 +0800 Subject: [PATCH 50/58] Fix return address mistake --- 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 6a9e7bb861..10832929ce 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -1327,7 +1327,7 @@ DEFINE_HOOK(0x6D504C, TacticalClass_DrawPlacement_DrawPlacingPreview, 0x6) // Auto Build Hook -> sub_6A8B30 - Auto Build Buildings DEFINE_HOOK(0x6A8E34, StripClass_Update_AutoBuildBuildings, 0x7) { - enum { SkipGameCode = 0x6A8E1E }; + enum { SkipGameCode = 0x6A8F49 }; GET(BuildingClass* const, pBuilding, ESI); From cd5db2d2ca4098b65b1befd5d51d35cd4dd43b41 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Sat, 19 Jul 2025 19:34:35 +0800 Subject: [PATCH 51/58] New `CanBuildUnderUnits` --- docs/Fixed-or-Improved-Logics.md | 4 ++++ src/Ext/BuildingType/Body.cpp | 2 ++ src/Ext/BuildingType/Body.h | 2 ++ src/Ext/BuildingType/Hooks.Placing.cpp | 23 +++++++++++++++++------ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 602b2bc40b..1b53c96705 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -1461,11 +1461,15 @@ LaserFencePost.Fence= ; BuildingType ### Buildable-upon TechnoTypes - Now technos have `CanBeBuiltOn=true` can simply removed when building is placed on them. +- Buildings like land mines can be placed in positions already occupied by infantry, vehicles, or aircraft after setting `CanBuildUnderUnits` to true. In `rulesmd.ini`: ```ini [SOMETECHNO] ; TechnoType CanBeBuiltOn=false ; boolean + +[SOMEBUILDING] ; BuildingType +CanBuildUnderUnits=false ; boolean ``` ## Terrains diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index 55c04f81cc..2e5acb75d6 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -680,6 +680,7 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->PlaceBuilding_DirectionShape.Read(exINI, pSection, "PlaceBuilding.DirectionShape"); this->PlaceBuilding_DirectionPalette.LoadFromINI(pINI, pSection, "PlaceBuilding.DirectionPalette"); this->PlaceBuilding_Extra.Read(exINI, pSection, "PlaceBuilding.Extra"); + this->CanBuildUnderUnits.Read(exINI, pSection, "CanBuildUnderUnits"); this->FactoryPlant_AllowTypes.Read(exINI, pSection, "FactoryPlant.AllowTypes"); this->FactoryPlant_DisallowTypes.Read(exINI, pSection, "FactoryPlant.DisallowTypes"); @@ -830,6 +831,7 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm) .Process(this->PlaceBuilding_DirectionShape) .Process(this->PlaceBuilding_DirectionPalette) .Process(this->PlaceBuilding_Extra) + .Process(this->CanBuildUnderUnits) .Process(this->AircraftDockingDirs) .Process(this->FactoryPlant_AllowTypes) .Process(this->FactoryPlant_DisallowTypes) diff --git a/src/Ext/BuildingType/Body.h b/src/Ext/BuildingType/Body.h index e0dea044ff..f3c27a74f5 100644 --- a/src/Ext/BuildingType/Body.h +++ b/src/Ext/BuildingType/Body.h @@ -72,6 +72,7 @@ class BuildingTypeExt Valueable PlaceBuilding_DirectionShape; CustomPalette PlaceBuilding_DirectionPalette; Valueable PlaceBuilding_Extra; + Valueable CanBuildUnderUnits; Valueable IsAnimDelayedBurst; @@ -151,6 +152,7 @@ class BuildingTypeExt , PlaceBuilding_DirectionShape { nullptr } , PlaceBuilding_DirectionPalette {} , PlaceBuilding_Extra { false } + , CanBuildUnderUnits { false } , AircraftDockingDirs {} , FactoryPlant_AllowTypes {} , FactoryPlant_DisallowTypes {} diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index 10832929ce..9dc6d1057f 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -155,7 +155,8 @@ static inline bool IsSameFenceType(const BuildingTypeClass* const pPostType, con return true; } -static inline bool CheckCanNotExistHere(FootClass* const pTechno, HouseClass* const pOwner, bool expand, bool& skipFlag, bool& builtOnCanBeBuiltOn, bool& landFootOnly) +static inline bool CheckCanNotExistHere(FootClass* const pTechno, HouseClass* const pOwner, + bool expand, bool& skipFlag, bool& builtOnCanBeBuiltOn, bool& landFootOnly, bool canBuildUnderUnits) { if (pTechno == TechnoExt::Deployer) { @@ -165,7 +166,7 @@ static inline bool CheckCanNotExistHere(FootClass* const pTechno, HouseClass* co const auto pTechnoType = pTechno->GetTechnoType(); - if (TechnoTypeExt::ExtMap.Find(pTechnoType)->CanBeBuiltOn) + if (canBuildUnderUnits || TechnoTypeExt::ExtMap.Find(pTechnoType)->CanBeBuiltOn) builtOnCanBeBuiltOn = true; else if (!expand || pTechnoType->Speed <= 0 || !BuildingTypeExt::CheckOccupierCanLeave(pOwner, pTechno->Owner)) return true; @@ -192,7 +193,8 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6) if (!Game::IsActive) return CanExistHere; - const auto expand = RulesExt::Global()->ExtendedBuildingPlacing.Get(); + const bool expand = RulesExt::Global()->ExtendedBuildingPlacing.Get(); + const bool canBuildUnderUnits = BuildingTypeExt::ExtMap.Find(pBuildingType)->CanBuildUnderUnits.Get(); bool landFootOnly = false; if (pBuildingType->LaserFence) @@ -235,7 +237,7 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6) { case AbstractType::Aircraft: { - if (!TechnoTypeExt::ExtMap.Find(static_cast(pObject)->Type)->CanBeBuiltOn) + if (!canBuildUnderUnits && !TechnoTypeExt::ExtMap.Find(static_cast(pObject)->Type)->CanBeBuiltOn) return CanNotExistHere; builtOnCanBeBuiltOn = true; @@ -260,7 +262,7 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6) case AbstractType::Infantry: case AbstractType::Unit: { - if (CheckCanNotExistHere(static_cast(pObject), pOwner, expand, skipFlag, builtOnCanBeBuiltOn, landFootOnly)) + if (CheckCanNotExistHere(static_cast(pObject), pOwner, expand, skipFlag, builtOnCanBeBuiltOn, landFootOnly, canBuildUnderUnits)) return CanNotExistHere; break; @@ -316,6 +318,15 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6) switch (pObject->WhatAmI()) { case AbstractType::Aircraft: + { + if (canBuildUnderUnits) + { + builtOnCanBeBuiltOn = true; + break; + } + + // No break + } case AbstractType::Building: { if (!TechnoTypeExt::ExtMap.Find(pObject->GetTechnoType())->CanBeBuiltOn) @@ -328,7 +339,7 @@ DEFINE_HOOK(0x47C640, CellClass_CanThisExistHere_IgnoreSomething, 0x6) case AbstractType::Infantry: case AbstractType::Unit: { - if (CheckCanNotExistHere(static_cast(pObject), pOwner, expand, skipFlag, builtOnCanBeBuiltOn, landFootOnly)) + if (CheckCanNotExistHere(static_cast(pObject), pOwner, expand, skipFlag, builtOnCanBeBuiltOn, landFootOnly, canBuildUnderUnits)) return CanNotExistHere; break; From 2458fa34177b365cab82a3180f37da5a5fe650c0 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Thu, 14 Aug 2025 18:38:23 +0800 Subject: [PATCH 52/58] Default direction --- docs/New-or-Enhanced-Logics.md | 7 +++++++ src/Ext/BuildingType/Hooks.Placing.cpp | 24 ++++++++++++++++++++---- src/Ext/BuildingType/Hooks.cpp | 2 +- src/Ext/Scenario/Body.cpp | 1 - src/Ext/Scenario/Body.h | 3 --- src/Phobos.INI.cpp | 4 ++++ src/Phobos.h | 2 ++ 7 files changed, 34 insertions(+), 9 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index b6bd8e34fe..a9bc3048f5 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -667,6 +667,7 @@ SpyEffect.InfiltratorSuperWeapon= ; SuperWeaponType - `LimboBuild` controls whether building can be automatically placed like `LimboDelivery`. - `LimboBuildID` defines the numeric ID of the building placed by `LimboBuild`. - `PlaceBuilding.Extra` controls whether the actual placement type of the building can be changed by holding the left mouse button and changing the mouse position when placing. + - `DefaultPlacingDirection` controls the default placing direction at the beginning of the game and every time after placing the building. - `PlaceBuilding.OnLand` controls buildings can be replaced when placed on land. - `PlaceBuilding.OnWater` controls buildings can be replaced when placed on water. - `PlaceBuilding.DirectionShape` and `PlaceBuilding.DirectionPalette` controls what additional directional guidance shape looks like when placing `PlaceBuilding.Extra=true` buildings. @@ -686,6 +687,12 @@ PlaceBuilding.DirectionShape= ; filename with .shp extension PlaceBuilding.DirectionPalette= ; filename with .pal extension ``` +In `ra2md.ini`: +```ini +[Phobos] +DefaultPlacingDirection=0 ; integer, 0-31 +``` + ```{note} - `PlaceBuilding.OnLand` and `PlaceBuilding.OnWater` are only work for players. - The replacement building and the original building must have the same `BuildCat`, and neither can have `LimboBuild` or `PlaceAnywhere`. diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index 9dc6d1057f..3c9bd06388 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -596,9 +596,17 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5) { if (HouseClass::CurrentPlayer == pHouse) { - if (pDisplay->CurrentBuilding == pBufferBuilding) + if (!pDisplay->CurrentBuilding) + { + Phobos::Config::CurrentPlacingDirection = Phobos::Config::DefaultPlacingDirection; + } + else if (pDisplay->CurrentBuilding == pBufferBuilding) + { pDisplay->CurrentBuilding = pBuilding; + Phobos::Config::CurrentPlacingDirection = Phobos::Config::DefaultPlacingDirection; + } + if (pDisplay->CurrentBuildingType == pBufferType) pDisplay->CurrentBuildingType = pBuildingType; @@ -619,6 +627,12 @@ DEFINE_HOOK(0x4FB1EA, HouseClass_UnitFromFactory_HangUpPlaceEvent, 0x5) pPrimary->Object = pBuilding; R->ESI(pBuilding); } + else if (HouseClass::CurrentPlayer == pHouse + && (!pDisplay->CurrentBuilding + || pDisplay->CurrentBuilding == pBuilding)) + { + Phobos::Config::CurrentPlacingDirection = Phobos::Config::DefaultPlacingDirection; + } return CanBuild; } @@ -676,7 +690,9 @@ DEFINE_HOOK(0x4A937D, DisplayClass_CallBuildingPlaceCheck_ReplaceBuildingType, 0 R->EBP(pDisplay->CurrentFoundation_CenterCell.X); const auto delta = cell - pDisplay->CurrentFoundation_CenterCell; - ScenarioExt::Global()->PlacingDirection = DirStruct(Math::atan2(-delta.Y, delta.X)).GetFacing<32>(); + + if (delta.Y || delta.X) + Phobos::Config::CurrentPlacingDirection = DirStruct(Math::atan2(-delta.Y, delta.X)).GetFacing<32>(); auto getAnotherPlacingType = [pDisplay, pTypeExt]() -> BuildingTypeClass* { @@ -684,7 +700,7 @@ DEFINE_HOOK(0x4A937D, DisplayClass_CallBuildingPlaceCheck_ReplaceBuildingType, 0 { const auto pCenterCell = MapClass::Instance.GetCellAt(pDisplay->CurrentFoundation_CenterCell); const bool onWater = pCenterCell->LandType == LandType::Water; - return pTypeExt->GetAnotherPlacingType(ScenarioExt::Global()->PlacingDirection, onWater); + return pTypeExt->GetAnotherPlacingType(Phobos::Config::CurrentPlacingDirection, onWater); } return nullptr; @@ -759,7 +775,7 @@ DEFINE_HOOK(0x4ABAC0, DisplayClass_LeftMouseButtonUp_ReplaceBuildingType, 0x6) { const auto pCenterCell = MapClass::Instance.GetCellAt(centerCell); placeType |= pCenterCell->LandType == LandType::Water; - placeType |= (ScenarioExt::Global()->PlacingDirection << 1); + placeType |= (Phobos::Config::CurrentPlacingDirection << 1); } else { diff --git a/src/Ext/BuildingType/Hooks.cpp b/src/Ext/BuildingType/Hooks.cpp index 1a31f74262..99b0a80ca3 100644 --- a/src/Ext/BuildingType/Hooks.cpp +++ b/src/Ext/BuildingType/Hooks.cpp @@ -108,7 +108,7 @@ DEFINE_HOOK(0x6D528A, TacticalClass_DrawPlacement_PlacementPreview, 0x6) const int zAdjust = -height - (pCell->SlopeIndex ? 12 : 2); const auto position = pTactical->CoordsToScreen(location) - pTactical->TacticalPos - Point2D { 0, (1 + height) }; - const auto direction = (ScenarioExt::Global()->PlacingDirection + (16u / size)) & 0x1Fu; + const auto direction = (Phobos::Config::CurrentPlacingDirection + (16u / size)) & 0x1Fu; const int frameIndex = Math::min(static_cast(pShape->Frames - 1), static_cast(direction * size / 32u)); DSurface::Temp->DrawSHP(pPalette, pShape, frameIndex, &position, diff --git a/src/Ext/Scenario/Body.cpp b/src/Ext/Scenario/Body.cpp index c1cc6ced9f..060c6dac55 100644 --- a/src/Ext/Scenario/Body.cpp +++ b/src/Ext/Scenario/Body.cpp @@ -163,7 +163,6 @@ void ScenarioExt::ExtData::Serialize(T& Stm) .Process(this->TransportReloaders) .Process(this->SWSidebar_Enable) .Process(this->SWSidebar_Indices) - .Process(this->PlacingDirection) // .Process(this->NewMessageList); // Should not S/L ; } diff --git a/src/Ext/Scenario/Body.h b/src/Ext/Scenario/Body.h index c9a78393c6..ce09cd2a3e 100644 --- a/src/Ext/Scenario/Body.h +++ b/src/Ext/Scenario/Body.h @@ -40,8 +40,6 @@ class ScenarioExt bool SWSidebar_Enable; std::vector SWSidebar_Indices; - size_t PlacingDirection; - std::unique_ptr NewMessageList; ExtData(ScenarioClass* OwnerObject) : Extension(OwnerObject) @@ -53,7 +51,6 @@ class ScenarioExt , TransportReloaders {} , SWSidebar_Enable { true } , SWSidebar_Indices {} - , PlacingDirection { 0 } , NewMessageList {} { } diff --git a/src/Phobos.INI.cpp b/src/Phobos.INI.cpp index 5b458a3089..dd1b7f990d 100644 --- a/src/Phobos.INI.cpp +++ b/src/Phobos.INI.cpp @@ -55,6 +55,8 @@ bool Phobos::Config::EnableSelectBox = false; bool Phobos::Config::DigitalDisplay_Enable = false; bool Phobos::Config::MessageApplyHoverState = false; bool Phobos::Config::MessageDisplayInCenter = false; +size_t Phobos::Config::DefaultPlacingDirection = 0; +size_t Phobos::Config::CurrentPlacingDirection = 0; bool Phobos::Config::RealTimeTimers = false; bool Phobos::Config::RealTimeTimers_Adaptive = false; int Phobos::Config::CampaignDefaultGameSpeed = 2; @@ -98,6 +100,8 @@ DEFINE_HOOK(0x5FACDF, OptionsClass_LoadSettings_LoadPhobosSettings, 0x5) Phobos::Config::HideLightFlashEffects = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "HideLightFlashEffects", false); Phobos::Config::ShowFlashOnSelecting = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "ShowFlashOnSelecting", false); Phobos::Config::SuperWeaponSidebar_RequiredSignificance = CCINIClass::INI_RA2MD.ReadInteger(phobosSection, "SuperWeaponSidebar.RequiredSignificance", 0); + Phobos::Config::DefaultPlacingDirection = static_cast(CCINIClass::INI_RA2MD.ReadInteger(phobosSection, "DefaultPlacingDirection", 0)) & 0x1Fu; + Phobos::Config::CurrentPlacingDirection = Phobos::Config::DefaultPlacingDirection; // Custom game speeds, 6 - i so that GS6 is index 0, just like in the engine Phobos::Config::CampaignDefaultGameSpeed = 6 - CCINIClass::INI_RA2MD.ReadInteger(phobosSection, "CampaignDefaultGameSpeed", 4); diff --git a/src/Phobos.h b/src/Phobos.h index fa1ac7d1d1..098d03a5d0 100644 --- a/src/Phobos.h +++ b/src/Phobos.h @@ -89,6 +89,8 @@ class Phobos static bool DigitalDisplay_Enable; static bool MessageApplyHoverState; static bool MessageDisplayInCenter; + static size_t DefaultPlacingDirection; + static size_t CurrentPlacingDirection; static bool RealTimeTimers; static bool RealTimeTimers_Adaptive; static int CampaignDefaultGameSpeed; From 1343c756b6fc06f3d602fa0c9dd49e61e7c685ba Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Thu, 14 Aug 2025 18:40:57 +0800 Subject: [PATCH 53/58] Update merge --- src/Ext/BuildingType/Body.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index 735896d92c..0a479c4867 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -582,9 +582,10 @@ void BuildingTypeExt::CreateLimboBuilding(BuildingClass* pBuilding, BuildingType // Add building to list of owned limbo buildings pOwnerExt->OwnedLimboDeliveredBuildings.push_back(pBuilding); + auto const pBldType = pBuilding->Type; - if (!pBuilding->Type->Insignificant && !pBuilding->Type->DontScore) - pOwnerExt->AddToLimboTracking(pBuilding->Type); + if (!pBldType->Insignificant && !pBldType->DontScore) + pOwnerExt->AddToLimboTracking(pBldType); auto const pTechnoExt = TechnoExt::ExtMap.Find(pBuilding); auto const pTechnoTypeExt = pTechnoExt->TypeExtData; From 334bf96b04742649039b7db33ead67896a24ca35 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Wed, 20 Aug 2025 02:14:39 +0800 Subject: [PATCH 54/58] Make deploying unit do turn when waiting --- src/Ext/BuildingType/Hooks.Placing.cpp | 56 ++++++++++++++++++-------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index 3c9bd06388..eaaf7509f9 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -1068,15 +1068,15 @@ DEFINE_HOOK(0x73946C, UnitClass_TryToDeploy_CleanUpDeploySpace, 0x6) { enum { CanDeploy = 0x73958A, TemporarilyCanNotDeploy = 0x73953B, CanNotDeploy = 0x7394E0 }; - GET(UnitClass* const, pUnit, EBP); + GET(UnitClass* const, pThis, EBP); GET(CellStruct, topLeftCell, ESI); if (!RulesExt::Global()->ExtendedBuildingPlacing) return 0; - const auto pTechnoExt = TechnoExt::ExtMap.Find(pUnit); - const auto pBuildingType = pUnit->Type->DeploysInto; - const auto pHouseExt = HouseExt::ExtMap.Find(pUnit->Owner); + const auto pTechnoExt = TechnoExt::ExtMap.Find(pThis); + const auto pBuildingType = pThis->Type->DeploysInto; + const auto pHouseExt = HouseExt::ExtMap.Find(pThis->Owner); auto& vec = pHouseExt->OwnedDeployingUnits; if (pBuildingType->GetFoundationWidth() > 2 || pBuildingType->GetFoundationHeight(false) > 2) @@ -1087,7 +1087,7 @@ DEFINE_HOOK(0x73946C, UnitClass_TryToDeploy_CleanUpDeploySpace, 0x6) if (!pBuildingType->PlaceAnywhere) { bool noOccupy = true; - bool canBuild = CheckBuildingFoundation(pBuildingType, topLeftCell, pUnit->Owner, noOccupy); + bool canBuild = CheckBuildingFoundation(pBuildingType, topLeftCell, pThis->Owner, noOccupy); do { @@ -1100,22 +1100,30 @@ DEFINE_HOOK(0x73946C, UnitClass_TryToDeploy_CleanUpDeploySpace, 0x6) { if (pTechnoExt && !pTechnoExt->UnitAutoDeployTimer.InProgress()) { - if (BuildingTypeExt::CleanUpBuildingSpace(pBuildingType, topLeftCell, pUnit->Owner, pUnit)) + if (BuildingTypeExt::CleanUpBuildingSpace(pBuildingType, topLeftCell, pThis->Owner, pThis)) break; // No place for cleaning - if (vec.size() == 0 || std::find(vec.begin(), vec.end(), pUnit) == vec.end()) - vec.push_back(pUnit); + if (vec.size() == 0 || std::find(vec.begin(), vec.end(), pThis) == vec.end()) + vec.push_back(pThis); pTechnoExt->UnitAutoDeployTimer.Start(40); } + if (pThis->PrimaryFacing.Current().GetFacing<256>() != static_cast(pBuildingType->DeployFacing)) + { + const auto pLoco = pThis->Locomotor; + + if (!pLoco->Is_Moving_Now()) + pLoco->Do_Turn(DirStruct(static_cast(pBuildingType->DeployFacing))); + } + return TemporarilyCanNotDeploy; } while (false); } if (vec.size() > 0) - vec.erase(std::remove(vec.begin(), vec.end(), pUnit), vec.end()); + vec.erase(std::remove(vec.begin(), vec.end(), pThis), vec.end()); if (pTechnoExt) pTechnoExt->UnitAutoDeployTimer.Stop(); @@ -1126,7 +1134,7 @@ DEFINE_HOOK(0x73946C, UnitClass_TryToDeploy_CleanUpDeploySpace, 0x6) } if (vec.size() > 0) - vec.erase(std::remove(vec.begin(), vec.end(), pUnit), vec.end()); + vec.erase(std::remove(vec.begin(), vec.end(), pThis), vec.end()); if (pTechnoExt) pTechnoExt->UnitAutoDeployTimer.Stop(); @@ -1254,17 +1262,33 @@ 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 && pUnit->Type->DeploysInto) + if (!pUnit->InLimbo + && pUnit->IsOnMap + && !pUnit->IsSinking + && pUnit->Owner == pHouse + && !pUnit->Destination + && !pUnit->ParasiteEatingMe + && !pUnit->TemporalTargetingMe + && pUnit->Type->DeploysInto) { - if (const auto pExt = TechnoExt::ExtMap.Find(pUnit)) - { - if (!(pExt->UnitAutoDeployTimer.GetTimeLeft() % 8)) - pUnit->QueueMission(Mission::Unload, true); + const auto mission = pUnit->CurrentMission; + if (mission == Mission::Unload) + { ++it; continue; } + else if (mission == Mission::Guard) + { + if (const auto pExt = TechnoExt::ExtMap.Find(pUnit)) + { + if (!(pExt->UnitAutoDeployTimer.GetTimeLeft() % 8)) + pUnit->QueueMission(Mission::Unload, true); + + ++it; + continue; + } + } } it = vec.erase(it); From b08da6aadc9e232c554ffa31a0cb0c8326875aee Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Wed, 20 Aug 2025 02:29:59 +0800 Subject: [PATCH 55/58] Fix merge --- src/Ext/BuildingType/Hooks.Placing.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index 3af9a34b5a..2c00ad2117 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -11,11 +11,6 @@ #include #include #include -#include "Body.h" - -#include - -#include "Ext/Rules/Body.h" /* In sub_740810 @@ -268,13 +263,26 @@ DEFINE_HOOK(0x5FD2B6, OverlayClass_Unlimbo_SkipTerrainCheck, 0x9) // Buildable Proximity Helper namespace ProximityTemp { - bool Build = false; bool Exist = false; bool Mouse = false; CellClass* CurrentCell = nullptr; BuildingTypeClass* BuildType = nullptr; } +/* + In sub_4A8EB0 + + - BaseNormal extra checking Hook #1-1 -> Set context and clear up data +*/ +DEFINE_HOOK(0x4A8F20, DisplayClass_BuildingProximityCheck_SetContext, 0x5) +{ + GET(BuildingTypeClass*, pType, ESI); + + ProximityTemp::BuildType = pType; + + return 0; +} + /* In sub_4A8EB0 From 1615a0f216da8343cfe116df59da770c294b4648 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Wed, 27 Aug 2025 21:48:22 +0800 Subject: [PATCH 56/58] Fix a typo --- 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 2c00ad2117..9ad2825e8a 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -1767,7 +1767,7 @@ static inline bool IsMatchedPostType(const BuildingTypeClass* const pThisType, c const auto pThisTypeExt = BuildingTypeExt::ExtMap.Find(pThisType); const auto pPostTypeExt = BuildingTypeExt::ExtMap.Find(pPostType); - return pThisTypeExt->LaserFencePost_Fence.Get() != pPostTypeExt->LaserFencePost_Fence.Get(); + return pThisTypeExt->LaserFencePost_Fence.Get() == pPostTypeExt->LaserFencePost_Fence.Get(); } /* From fd89bf291fa3daec2ec29134b7628147a85ab438 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Wed, 27 Aug 2025 21:53:17 +0800 Subject: [PATCH 57/58] Doc --- CREDITS.md | 1 + docs/Fixed-or-Improved-Logics.md | 2 +- docs/Whats-New.md | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CREDITS.md b/CREDITS.md index fdc56e34b9..fb20cee01a 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -542,6 +542,7 @@ This page lists all the individual contributions to the project by their author. - Jumpjet Climbing Logic Enhancement - Fix for pathfinding crashes on big maps due to too small pathfinding node buffer - Building placing and deploying logic enhancement + - Customized laser fence - **Ollerus**: - Build limit group enhancement - Customizable rocker amplitude diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 12bfa71673..2bf8f2d872 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -1545,7 +1545,7 @@ HeightShadowScaling.MinScale=0.0 ; floating point value ShadowSizeCharacteristicHeight= ; integer, height in leptons ``` -### Custom laser fence +### Customized laser fence - Now `LaserFence` can be customized by setting `LaserFencePost.Fence` on `LaserFencePost=true` buildings. - `LaserFencePost.Fence` defines which kind of laser fence can connect this kind of laser fence post. If they have different `LaserFencePost.Fence`, they will not be connected. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 747a585f9e..ec767c896c 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -437,6 +437,7 @@ New: - [Customize hardcoded projectile initial facing behavior](Fixed-or-Improved-Logics.md#customizing-initial-facing-behavior) (by Starkku) - Health bar permanently displayed (by FlyStar) - Building placing and deploying logic enhancement (by CrimRecya) +- Customized laser fence (by CrimRecya) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) From 078ddcbb9e02cda6bf99a9c47028ae6afc69c324 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Sat, 30 Aug 2025 13:10:51 +0800 Subject: [PATCH 58/58] Fix incorrect FoundationOutside --- src/Ext/BuildingType/Body.cpp | 27 ++------ src/Ext/BuildingType/Hooks.Placing.cpp | 86 ++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 21 deletions(-) diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index 0a479c4867..11c9cb88bc 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -213,31 +213,16 @@ bool BuildingTypeExt::CleanUpBuildingSpace(BuildingTypeClass* pBuildingType, Cel std::vector optionalCells; optionalCells.reserve(24); -// for (auto pFoundation = pBuildingType->FoundationOutside; *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation) - // Sometimes, FoundationOutside may be wrong (like 2*5 , 4*3 or 4*4) - for (const auto& pCheckedCell : checkedCells) + for (auto pFoundation = pBuildingType->FoundationOutside; *pFoundation != CellStruct { 0x7FFF, 0x7FFF }; ++pFoundation) { - auto searchCell = pCheckedCell->MapCoords - CellStruct { 1, 1 }; + auto searchCell = topLeftCell + *pFoundation; - for (int i = 0; i < 4; ++i) + if (const auto pSearchCell = MapClass::Instance.TryGetCellAt(searchCell)) { - for (int j = 0; j < 2; ++j) + if (!(pSearchCell->OccupationFlags & 0x80) + && pSearchCell->IsClearToMove(SpeedType::Amphibious, true, true, -1, MovementZone::Amphibious, -1, false)) { - if (const auto pSearchCell = MapClass::Instance.TryGetCellAt(searchCell)) - { - if (std::find(checkedCells.begin(), checkedCells.end(), pSearchCell) == checkedCells.end() - && std::find(optionalCells.begin(), optionalCells.end(), pSearchCell) == optionalCells.end() - && !(pSearchCell->OccupationFlags & 0x80) - && pSearchCell->IsClearToMove(SpeedType::Amphibious, true, true, -1, MovementZone::Amphibious, -1, false)) - { - optionalCells.push_back(pSearchCell); - } - } - - if (i % 2) - searchCell.Y += static_cast((i / 2) ? -1 : 1); - else - searchCell.X += static_cast((i / 2) ? -1 : 1); + optionalCells.push_back(pSearchCell); } } } diff --git a/src/Ext/BuildingType/Hooks.Placing.cpp b/src/Ext/BuildingType/Hooks.Placing.cpp index 9ad2825e8a..a3691ba2a4 100644 --- a/src/Ext/BuildingType/Hooks.Placing.cpp +++ b/src/Ext/BuildingType/Hooks.Placing.cpp @@ -1799,3 +1799,89 @@ DEFINE_HOOK(0x6D5815, TacticalClass_DrawLaserFenceGrid_SkipDrawLaserFence, 0x6) // Have used CurrentBuilding->Type yet, so simply use static_cast return IsMatchedPostType(static_cast(DisplayClass::Instance.CurrentBuilding)->Type, pPostType) ? 0 : SkipGameCode; } + +/* + In sub_45C300 + + - Fix foundation outside Hook -> Fix incorrect building type's FoundationOutside +*/ +DEFINE_HOOK(0x45DD04, BuildingTypeClass_InitFoundationOutsides, 0x5) +{ + auto& foundationOutsides = Make_Global(0x89D368); + constexpr auto CellStruct_OutsideEnd = CellStruct { 0x7FFF, 0x7FFF }; + + // 4x3 remove incorrect (3,2), (3,1), (3,0) and add missing (4,3), (4,2), (4,1), (4,0), (4,-1) + { + auto& outside4x3 = foundationOutsides[12]; + outside4x3[6] = CellStruct { 4, 2 }; + outside4x3[8] = CellStruct { 4, 1 }; + outside4x3[10] = CellStruct { 4, 0 }; + outside4x3[16] = CellStruct { 4, -1 }; + outside4x3[17] = CellStruct { 4, 3 }; + outside4x3[18] = CellStruct_OutsideEnd; + } + + // 2x6 add missing (-1,4) and (2,4) + { + auto& outside2x6 = foundationOutsides[15]; + outside2x6[18] = CellStruct { -1, 4 }; + outside2x6[19] = CellStruct { 2, 4 }; + outside2x6[20] = CellStruct_OutsideEnd; + } + + // 2x5 add missing (-1,4) and (2,4) + { + auto& outside2x5 = foundationOutsides[16]; + outside2x5[16] = CellStruct { -1, 4 }; + outside2x5[17] = CellStruct { 2, 4 }; + outside2x5[18] = CellStruct_OutsideEnd; + } + + // 4x4 remove duplicate (-1,4) and (4,4) + { + auto& outside4x4 = foundationOutsides[18]; + outside4x4[16] = CellStruct { 4, 3 }; + outside4x4[20] = CellStruct_OutsideEnd; + outside4x4[21] = CellStruct::Empty; + outside4x4[22] = CellStruct::Empty; + } + + // 3x4 add missing (-1,3) and (3,3) + { + auto& outside3x4 = foundationOutsides[19]; + outside3x4[16] = CellStruct { -1, 3 }; + outside3x4[17] = CellStruct { 3, 3 }; + outside3x4[18] = CellStruct_OutsideEnd; + } + + // 6x4 fix the entire error without (2,-1) + { + auto& outside6x4 = foundationOutsides[20]; + outside6x4[1] = CellStruct { -1, -1 }; + outside6x4[2] = CellStruct { 0, -1 }; + outside6x4[3] = CellStruct { 1, -1 }; + outside6x4[4] = CellStruct { 3, -1 }; + outside6x4[5] = CellStruct { 4, -1 }; + outside6x4[6] = CellStruct { 5, -1 }; + outside6x4[7] = CellStruct { 6, -1 }; + outside6x4[8] = CellStruct { -1, 0 }; + outside6x4[9] = CellStruct { 6, 0 }; + outside6x4[10] = CellStruct { -1, 1 }; + outside6x4[11] = CellStruct { 6, 1 }; + outside6x4[12] = CellStruct { -1, 2 }; + outside6x4[13] = CellStruct { 6, 2 }; + outside6x4[14] = CellStruct { -1, 3 }; + outside6x4[15] = CellStruct { 6, 3 }; + outside6x4[16] = CellStruct { -1, 4 }; + outside6x4[17] = CellStruct { 0, 4 }; + outside6x4[18] = CellStruct { 1, 4 }; + outside6x4[19] = CellStruct { 2, 4 }; + outside6x4[20] = CellStruct { 3, 4 }; + outside6x4[21] = CellStruct { 4, 4 }; + outside6x4[22] = CellStruct { 5, 4 }; + outside6x4[23] = CellStruct { 6, 4 }; + outside6x4[24] = CellStruct_OutsideEnd; + } + + return 0; +}