From bddafbc6821917275a1c04719fb0800497c8bf58 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Thu, 14 Aug 2025 22:59:33 +0800 Subject: [PATCH 1/5] Core --- CREDITS.md | 1 + docs/New-or-Enhanced-Logics.md | 23 +++ docs/Whats-New.md | 1 + src/Ext/Building/Body.cpp | 53 +++++- src/Ext/BuildingType/Body.cpp | 10 + src/Ext/BuildingType/Body.h | 7 + src/Ext/BuildingType/Hooks.cpp | 334 ++++++++++++++++++++++++++++++++- src/Ext/Rules/Body.cpp | 3 + src/Ext/Rules/Body.h | 9 +- 9 files changed, 435 insertions(+), 6 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index bdb498d967..8fe0aaa156 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -530,6 +530,7 @@ This page lists all the individual contributions to the project by their author. - Burst without delay - Fix an issue that if the garrison unload occupants when there is no open space around it would result in the disappearance of the occupants - Fix an issue where Ares' `Convert.Deploy` triggers repeatedly when the unit is turning or moving + - Customized bib & impassable rows & weapons factory direction - **Ollerus**: - Build limit group enhancement - Customizable rocker amplitude diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 0ed79088c9..9a079be50e 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -586,6 +586,29 @@ Adjacent.Disallowed= ; List of BuildingTypes NoBuildAreaOnBuildup=false ; boolean ``` +### Customized bib & impassable rows & weapons factory direction + +- Now you can use `ExtendedWeaponsFactory` to remove the hard coding of `WeaponsFactory` position and direction, and adjust the position of the generated unit and the direction of the unit's exit separately through the original `ExitCoord` and the newly added `WeaponsFactory.Dir`. Similarly, the directions of `Bib` and `NumberImpassableRows` can also be modified through `Bib.Dir` and `NumberImpassableRows.Dir` respectively. + +In `rulesmd.ini`: +```ini +[General] +ExtendedWeaponsFactory=false ; boolean + +[SOMEBUILDING] ; BuildingType +Bib.Dir=2 ; integer +NumberImpassableRows.Dir=2 ; integer +WeaponsFactory.Dir=2 ; integer +``` + +```{note} +- The available directions are: + - 0 - North (top right) + - 2 - East (bottom right) + - 4 - South (bottom left) + - 6 - West (top left) +``` + ### Destroyable pathfinding obstacles - It is possible to make buildings be considered pathfinding obstacles that can be destroyed by setting `IsDestroyableBlockage` to true. What this does is make the building be considered impassable and impenetrable pathfinding obstacle to every unit that is not flying or have appropriate `MovementZone` (ones that allow destroyable obstacles to be overcome, e.g `(Infantry|Amphibious)Destroyer`) akin to wall overlays and TerrainTypes. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index e6dee4bdbe..19a88672fc 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -428,6 +428,7 @@ New: - [Units can customize the attack voice that plays when using more weapons](New-or-Enhanced-Logics.md#multi-voiceattack) (by FlyStar) - Customize squid grapple animation (by NetsuNegi) - [Auto deploy for GI-like infantry](Fixed-or-Improved-Logics.md#auto-deploy-for-gi-like-infantry) (by TaranDahl) +- Customized bib & impassable rows & weapons factory direction (by CrimRecya) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) diff --git a/src/Ext/Building/Body.cpp b/src/Ext/Building/Body.cpp index 7348eab758..84bb77e52c 100644 --- a/src/Ext/Building/Body.cpp +++ b/src/Ext/Building/Body.cpp @@ -373,9 +373,56 @@ void BuildingExt::KickOutStuckUnits(BuildingClass* pThis) auto cell = CellClass::Coord2Cell(buffer); + bool upward = false; + short* pCur = nullptr; + short start = 0; // door + const auto pType = pThis->Type; - const short start = static_cast(pThis->Location.X / Unsorted::LeptonsPerCell + pType->GetFoundationWidth() - 2); // door - const short end = cell.X; // exit + + switch (RulesExt::Global()->ExtendedWeaponsFactory ? BuildingTypeExt::ExtMap.Find(pType)->WeaponsFactory_Dir.Get() : 2) + { + + case 0: // North -> left+down/++Y + { + upward = false; + pCur = &cell.Y; + start = static_cast(pThis->Location.Y / Unsorted::LeptonsPerCell + 1); + break; + } + + case 2: // East -> left+up/--X + { + upward = true; + pCur = &cell.X; + start = static_cast(pThis->Location.X / Unsorted::LeptonsPerCell + pType->GetFoundationWidth() - 2); + break; + } + + case 4: // South -> right+up/--Y + { + upward = true; + pCur = &cell.Y; + start = static_cast(pThis->Location.Y / Unsorted::LeptonsPerCell + pType->GetFoundationHeight(false) - 2); + break; + } + + case 6: // West -> right+down/++X + { + upward = false; + pCur = &cell.X; + start = static_cast(pThis->Location.X / Unsorted::LeptonsPerCell + 1); + break; + } + + default: // Invalid direction + { + return; + } + + } + + const short end = *pCur; // exit + *pCur = start; auto pCell = MapClass::Instance.GetCellAt(cell); while (true) @@ -401,7 +448,7 @@ void BuildingExt::KickOutStuckUnits(BuildingClass* pThis) } } - if (--cell.X < end) + if (upward ? (--(*pCur) < end) : (++(*pCur) > end)) return; // no stuck pCell = MapClass::Instance.GetCellAt(cell); diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index acad4d12e9..24fdfe6d4a 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -219,6 +219,13 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Refinery_UseNormalActiveAnim.Read(exArtINI, pArtSection, "Refinery.UseNormalActiveAnim"); + this->Bib_Dir.Read(exINI, pSection, "Bib.Dir"); + this->Bib_Dir = Math::max(0, this->Bib_Dir) & 6; // Only accept 0,2,4,6 + this->NumberImpassableRows_Dir.Read(exINI, pSection, "NumberImpassableRows.Dir"); + this->NumberImpassableRows_Dir = Math::max(0, this->NumberImpassableRows_Dir) & 6; // Only accept 0,2,4,6 + this->WeaponsFactory_Dir.Read(exINI, pSection, "WeaponsFactory.Dir"); + this->WeaponsFactory_Dir = Math::max(0, this->WeaponsFactory_Dir) & 6; // Only accept 0,2,4,6 + // Ares tag this->SpyEffect_Custom.Read(exINI, pSection, "SpyEffect.Custom"); if (SuperWeaponTypeClass::Array.Count > 0) @@ -334,6 +341,9 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm) .Process(this->BuildingRepairedSound) .Process(this->Refinery_UseNormalActiveAnim) .Process(this->HasPowerUpAnim) + .Process(this->Bib_Dir) + .Process(this->NumberImpassableRows_Dir) + .Process(this->WeaponsFactory_Dir) ; } diff --git a/src/Ext/BuildingType/Body.h b/src/Ext/BuildingType/Body.h index 9cf41a7af4..384bc46250 100644 --- a/src/Ext/BuildingType/Body.h +++ b/src/Ext/BuildingType/Body.h @@ -99,6 +99,10 @@ class BuildingTypeExt ValueableVector HasPowerUpAnim; + Valueable Bib_Dir; + Valueable NumberImpassableRows_Dir; + Valueable WeaponsFactory_Dir; + ExtData(BuildingTypeClass* OwnerObject) : Extension(OwnerObject) , PowersUp_Owner { AffectedHouse::Owner } , PowersUp_Buildings {} @@ -161,6 +165,9 @@ class BuildingTypeExt , BuildingRepairedSound {} , Refinery_UseNormalActiveAnim { false } , HasPowerUpAnim {} + , Bib_Dir { 2 } + , NumberImpassableRows_Dir { 2 } + , WeaponsFactory_Dir { 2 } { } // Ares 0.A functions diff --git a/src/Ext/BuildingType/Hooks.cpp b/src/Ext/BuildingType/Hooks.cpp index 3a61cdbf04..02bdb381d0 100644 --- a/src/Ext/BuildingType/Hooks.cpp +++ b/src/Ext/BuildingType/Hooks.cpp @@ -425,7 +425,339 @@ DEFINE_HOOK(0x73F5A7, UnitClass_IsCellOccupied_UnlimboDirection, 0x8) GET(CellClass* const, pCell, EDI); - return pCell->MapCoords.Y == pType->FoundationOutside[10].Y ? NextObject : ContinueCheck; + if (!RulesExt::Global()->ExtendedWeaponsFactory) + return pCell->MapCoords.Y == pType->FoundationOutside[10].Y ? NextObject : ContinueCheck; + + auto buffer = CoordStruct::Empty; + pBuilding->GetExitCoords(&buffer, 0); + const auto cell = CellClass::Coord2Cell(buffer); + const bool pathX = (BuildingTypeExt::ExtMap.Find(pType)->WeaponsFactory_Dir.Get() & 2) != 0; // 2,6/0,4 + const bool onPath = pathX ? pCell->MapCoords.Y == cell.Y : pCell->MapCoords.X == cell.X; + + return onPath ? NextObject : ContinueCheck; +} + +#pragma endregion + +#pragma region WeaponFactoryDirection + +DEFINE_HOOK(0x44457B, BuildingClass_KickOutUnit_UnlimboDirection, 0x5) +{ + if (!RulesExt::Global()->ExtendedWeaponsFactory) + return 0; + + GET(BuildingClass* const, pThis, ESI); + REF_STACK(DirType, dir, STACK_OFFSET(0x144, -0x144)); + + dir = static_cast(BuildingTypeExt::ExtMap.Find(pThis->Type)->WeaponsFactory_Dir.Get() << 5); + + return 0; +} + +static inline CellStruct GetWeaponFactoryDoor(BuildingClass* pThis) +{ + auto cell = pThis->GetMapCoords(); + auto buffer = CoordStruct::Empty; + pThis->GetExitCoords(&buffer, 0); + const auto pType = pThis->Type; + + switch (RulesExt::Global()->ExtendedWeaponsFactory ? BuildingTypeExt::ExtMap.Find(pType)->WeaponsFactory_Dir.Get() : 2) + { + + case 0: + { + cell.X = static_cast(buffer.X / Unsorted::LeptonsPerCell); + break; + } + + case 2: + { + cell.X += static_cast(pType->GetFoundationWidth() - 1); + cell.Y = static_cast(buffer.Y / Unsorted::LeptonsPerCell); + break; + } + + case 4: + { + cell.X = static_cast(buffer.X / Unsorted::LeptonsPerCell); + cell.Y += static_cast(pType->GetFoundationHeight(false) - 1); + break; + } + + case 6: + { + cell.Y = static_cast(buffer.Y / Unsorted::LeptonsPerCell); + break; + } + + default: + { + break; + } + + } + + return cell; +} + +DEFINE_HOOK(0x44955D, BuildingClass_WeaponFactoryOutsideBusy_WeaponFactoryCell, 0x6) +{ + if (!RulesExt::Global()->ExtendedWeaponsFactory) + return 0; + + enum { SkipGameCode = 0x4495DF }; + + GET(BuildingClass* const, pThis, ESI); + REF_STACK(CoordStruct, coords, STACK_OFFSET(0x30, -0xC)); + + const auto cell = GetWeaponFactoryDoor(pThis); + coords = CellClass::Cell2Coord(cell); + + R->EAX(MapClass::Instance.GetCellAt(cell)); + + return SkipGameCode; +} + +DEFINE_JUMP(LJMP, 0x44DCC7, 0x44DD3C); + +DEFINE_HOOK(0x44E131, BuildingClass_Mission_Unload_WeaponFactoryFix1, 0x5) +{ + enum { SkipGameCode = 0x44E191 }; + + GET(BuildingClass* const, pThis, EBP); + GET(FootClass* const, pLink, EDI); +// REF_STACK(const CoordStruct, coords, STACK_OFFSET(0x50, -0x1C)); + + const auto cell = GetWeaponFactoryDoor(pThis); + const auto coords = CellClass::Cell2Coord(cell); + + if (RulesExt::Global()->ExtendedWeaponsFactory) + { +// const auto pType = pLink->GetTechnoType(); +// const bool isSubterranean = pType->IsSubterranean; +// pType->IsSubterranean = false; + pLink->SetDestination(MapClass::Instance.GetCellAt(cell), true); +// pType->IsSubterranean = isSubterranean; + } + else + { + pLink->Locomotor->Force_Track(66, coords); + } + + return SkipGameCode; +} + +DEFINE_HOOK(0x44DF72, BuildingClass_Mission_Unload_WeaponFactoryFix2, 0x5) +{ + enum { SkipGameCode = 0x44E1AD }; + + GET(BuildingClass* const, pThis, EBP); + GET_STACK(FootClass* const, pLink, STACK_OFFSET(0x50, -0x30)); +// REF_STACK(const CoordStruct, coords, STACK_OFFSET(0x50, -0x1C)); + + const auto cell = GetWeaponFactoryDoor(pThis); + const auto coords = CellClass::Cell2Coord(cell); + + if (RulesExt::Global()->ExtendedWeaponsFactory) + pLink->SetDestination(MapClass::Instance.GetCellAt(cell), true); + else + pLink->Locomotor->Force_Track(66, coords); + + R->EDI(pLink); + + return SkipGameCode; +} + +DEFINE_HOOK(0x44DF1C, BuildingClass_Mission_Unload_WeaponFactoryFix3, 0x7) +{ + if (!RulesExt::Global()->ExtendedWeaponsFactory) + return 0; + + enum { SkipGameCode = 0x44DF47 }; + + GET(BuildingClass* const, pThis, EBP); + GET_STACK(FootClass* const, pLink, STACK_OFFSET(0x50, -0x30)); + REF_STACK(CellStruct, cell, STACK_OFFSET(0x50, -0x34)); +// REF_STACK(const CoordStruct, coords, STACK_OFFSET(0x50, -0x1C)); + + cell = GetWeaponFactoryDoor(pThis); + + R->ESI(pLink); + + return SkipGameCode; +} + +DEFINE_HOOK(0x742D98, UnitClass_SetDestination_WeaponFactoryCell, 0x6) +{ + if (!RulesExt::Global()->ExtendedWeaponsFactory) + return 0; + + enum { SkipGameCode = 0x742DFB }; + + GET(BuildingClass* const, pLink, ESI); + + const auto cell = GetWeaponFactoryDoor(pLink); + + R->EAX(MapClass::Instance.GetCellAt(cell)); + + return SkipGameCode; +} + +DEFINE_HOOK(0x516D3C, HoverLocomotionClass_IsIonSensitive_WeaponFactoryCell, 0x5) +{ + if (!RulesExt::Global()->ExtendedWeaponsFactory) + return 0; + + enum { Right = 0x516DFF, IsNot = 0x516DF6 }; + + GET(BuildingClass* const, pBuilding, EAX); + GET(ILocomotion* const, iLoco, ESI); + + const auto location = CellClass::Coord2Cell(static_cast(iLoco)->LinkedTo->Location); + bool notIon = false; + auto buffer = CoordStruct::Empty; + pBuilding->GetExitCoords(&buffer, 0); + const auto cell = CellClass::Coord2Cell(buffer); + const auto pType = pBuilding->Type; + + switch (BuildingTypeExt::ExtMap.Find(pType)->WeaponsFactory_Dir.Get()) + { + + case 0: + { + notIon |= (cell.X == location.X + && cell.Y != location.Y + && (pBuilding->Location.Y / Unsorted::LeptonsPerCell) != location.Y); + + break; + } + + case 2: + { + notIon |= (cell.Y == location.Y + && cell.X != location.X + && (pBuilding->Location.X / Unsorted::LeptonsPerCell + pType->GetFoundationWidth() - 1) != location.X); + + break; + } + + case 4: + { + notIon |= (cell.X == location.X + && cell.Y != location.Y + && (pBuilding->Location.Y / Unsorted::LeptonsPerCell + pType->GetFoundationHeight(false) - 1) != location.Y); + + break; + } + + case 6: + { + notIon |= (cell.Y == location.Y + && cell.X != location.X + && (pBuilding->Location.X / Unsorted::LeptonsPerCell) != location.X); + + break; + } + + default: + { + break; + } + + } + + return notIon ? IsNot : Right; +} + +DEFINE_HOOK(0x7443D9, UnitClass_ReadyToNextMission_WeaponFactoryCell, 0x5) +{ + enum { SkipGameCode = 0x744463 }; + return RulesExt::Global()->ExtendedWeaponsFactory ? SkipGameCode : 0; +} + +#pragma endregion + +#pragma region ImpassableRowsDirection + +DEFINE_HOOK(0x458A00, BuildingClass_IsCellNotPassable_ImpassableRowsDirection, 0x6) +{ + enum { SkipGameCode = 0x458A76 }; + + GET(BuildingClass* const, pThis, ECX); + GET_STACK(CellClass* const, pCell, STACK_OFFSET(0x0, 0x4)); + + auto isCellNotPassable = [pThis, pCell]() -> bool + { + if (pCell->GetBuilding() != pThis) + return false; + + const auto pType = pThis->Type; + + if (pType->NumberImpassableRows == -1) + return true; + + if (pType->Bunker && pThis->BunkerLinkedItem) + return true; + + switch (BuildingTypeExt::ExtMap.Find(pType)->NumberImpassableRows_Dir.Get()) + { + + case 0: + { + const int y = pThis->Location.Y / Unsorted::LeptonsPerCell; + const int maxPassableY = y + pType->GetFoundationHeight(false) - 1 - pType->NumberImpassableRows; + return pCell->MapCoords.Y > maxPassableY; + } + + case 2: + { + const int x = pThis->Location.X / Unsorted::LeptonsPerCell; + const int minPassableX = x + pType->NumberImpassableRows; + return pCell->MapCoords.X < minPassableX; + } + + case 4: + { + const int y = pThis->Location.Y / Unsorted::LeptonsPerCell; + const int minPassableY = y + pType->NumberImpassableRows; + return pCell->MapCoords.Y < minPassableY; + } + + case 6: + { + const int x = pThis->Location.X / Unsorted::LeptonsPerCell; + const int maxPassableX = x + pType->GetFoundationWidth() - 1 - pType->NumberImpassableRows; + return pCell->MapCoords.X > maxPassableX; + } + + default: + { + return true; + } + + } + }; + + R->EAX(isCellNotPassable()); + + return SkipGameCode; +} + +#pragma endregion + +#pragma region BibDirection + +// The input parameter of GetFoundationHeight is incorrect, and there is no call to input the incorrect parameter, so no need to consider it +DEFINE_HOOK(0x73F7DD, BuildingClass_IsCellNotPassable_BibDirection, 0x8) +{ + enum { SkipGameCode = 0x73F816 }; + + GET(CellClass* const, pCell, EDI); + GET(BuildingTypeClass* const, pType, EAX); + + R->ECX(MapClass::Instance.GetCellAt(Unsorted::AdjacentCell[BuildingTypeExt::ExtMap.Find(pType)->Bib_Dir.Get()] + pCell->MapCoords)); + + return SkipGameCode; } #pragma endregion diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index 6ce4e6d426..abed35c48b 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -301,6 +301,8 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->InfantryAutoDeploy.Read(exINI, GameStrings::General, "InfantryAutoDeploy"); + this->ExtendedWeaponsFactory.Read(exINI, GameStrings::General, "ExtendedWeaponsFactory"); + // Section AITargetTypes int itemsCount = pINI->GetKeyCount("AITargetTypes"); for (int i = 0; i < itemsCount; ++i) @@ -554,6 +556,7 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->AttackMove_StopWhenTargetAcquired) .Process(this->Parasite_GrappleAnim) .Process(this->InfantryAutoDeploy) + .Process(this->ExtendedWeaponsFactory) ; } diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index bef203036e..3bd5a341a4 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -247,13 +247,15 @@ class RulesExt NullableIdx Parasite_GrappleAnim; + Valueable InfantryAutoDeploy; + + Valueable ExtendedWeaponsFactory; + // cache tint color int TintColorIronCurtain; int TintColorForceShield; int TintColorBerserk; - Valueable InfantryAutoDeploy; - ExtData(RulesClass* OwnerObject) : Extension(OwnerObject) , Storage_TiberiumIndex { -1 } , HarvesterDumpAmount { 0.0f } @@ -449,7 +451,10 @@ class RulesExt , AttackMove_StopWhenTargetAcquired { } , Parasite_GrappleAnim {} + , InfantryAutoDeploy { false } + + , ExtendedWeaponsFactory { false } { } virtual ~ExtData() = default; From 44a4cf1b0e4096d356a89693ee98b206908dfd80 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Fri, 15 Aug 2025 01:57:54 +0800 Subject: [PATCH 2/5] Fix stuck --- src/Ext/Building/Body.cpp | 16 --------- src/Ext/Building/Hooks.cpp | 34 ++++++++++-------- src/Ext/BuildingType/Body.cpp | 46 ++++++++++++++++++++++++ src/Ext/BuildingType/Body.h | 2 +- src/Ext/BuildingType/Hooks.cpp | 65 ++++++++-------------------------- 5 files changed, 82 insertions(+), 81 deletions(-) diff --git a/src/Ext/Building/Body.cpp b/src/Ext/Building/Body.cpp index 84bb77e52c..ea072e5fea 100644 --- a/src/Ext/Building/Body.cpp +++ b/src/Ext/Building/Body.cpp @@ -355,19 +355,6 @@ bool BuildingExt::ExtData::HandleInfiltrate(HouseClass* pInfiltratorHouse, int m // For unit's weapons factory only void BuildingExt::KickOutStuckUnits(BuildingClass* pThis) { - if (const auto pUnit = abstract_cast(pThis->GetNthLink())) - { - if (pUnit->Locomotor->Destination() == CoordStruct::Empty) - { - if (const auto pTeam = pUnit->Team) - pTeam->LiberateMember(pUnit); - - pThis->SendCommand(RadioCommand::NotifyUnlink, pUnit); - pUnit->QueueMission(Mission::Guard, false); - return; // one after another - } - } - auto buffer = CoordStruct::Empty; pThis->GetExitCoords(&buffer, 0); @@ -439,9 +426,6 @@ void BuildingExt::KickOutStuckUnits(BuildingClass* pThis) if (height < 0 || height > Unsorted::CellHeight) continue; - if (const auto pTeam = pUnit->Team) - pTeam->LiberateMember(pUnit); - pThis->SendCommand(RadioCommand::RequestLink, pUnit); pThis->QueueMission(Mission::Unload, false); return; // one after another diff --git a/src/Ext/Building/Hooks.cpp b/src/Ext/Building/Hooks.cpp index 23d9755f03..b6e6c9d51c 100644 --- a/src/Ext/Building/Hooks.cpp +++ b/src/Ext/Building/Hooks.cpp @@ -204,27 +204,33 @@ DEFINE_HOOK(0x44D455, BuildingClass_Mission_Missile_EMPulseBulletWeapon, 0x8) #pragma region KickOutStuckUnits -// Kick out stuck units when the factory building is not busy, only factory buildings can enter this hook -DEFINE_HOOK(0x450248, BuildingClass_UpdateFactory_KickOutStuckUnits, 0x6) +DEFINE_HOOK(0x44E202, BuildingClass_Mission_Unload_CheckStuck, 0x6) { - GET(BuildingClass*, pThis, ESI); + enum { Waiting = 0x44E267, NextStatus = 0x44E20C}; - // This is not a solution to the problem at its root - // Currently the root cause of the problem is not located - // So the idle weapon factory is asked to search every second for any units that are stuck - if (!(Unsorted::CurrentFrame % 15)) // Check every 15 frames for factories - { - const auto pType = pThis->Type; + GET(BuildingClass*, pThis, EBP); - if (pType->Factory == AbstractType::UnitType && pType->WeaponsFactory && !pType->Naval && pThis->QueuedMission != Mission::Unload) - { - const auto mission = pThis->CurrentMission; + if (!pThis->IsTether) + return NextStatus; - if (mission == Mission::Guard && !pThis->InLimbo || mission == Mission::Unload && pThis->MissionStatus == 3) // Unloading but stuck - BuildingExt::KickOutStuckUnits(pThis); + if (const auto pUnit = abstract_cast(pThis->GetNthLink())) + { + if (pUnit->Locomotor->Destination() == CoordStruct::Empty) + { + reinterpret_cast(0x449540)(pThis); + pUnit->SetDestination(MapClass::Instance.GetCellAt(BuildingTypeExt::GetWeaponFactoryDoor(pThis)), true); } } + return Waiting; +} + +DEFINE_HOOK(0x44E260, BuildingClass_Mission_Unload_KickOutStuckUnits, 0x7) +{ + GET(BuildingClass*, pThis, EBP); + + BuildingExt::KickOutStuckUnits(pThis); + return 0; } diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index 24fdfe6d4a..c085417d08 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -73,6 +73,52 @@ void BuildingTypeExt::PlayBunkerSound(BuildingClass const* pThis, bool buildUp) VocClass::PlayAt(nSound, pThis->Location); } +CellStruct BuildingTypeExt::GetWeaponFactoryDoor(BuildingClass* pThis) +{ + auto cell = pThis->GetMapCoords(); + auto buffer = CoordStruct::Empty; + pThis->GetExitCoords(&buffer, 0); + const auto pType = pThis->Type; + + switch (RulesExt::Global()->ExtendedWeaponsFactory ? BuildingTypeExt::ExtMap.Find(pType)->WeaponsFactory_Dir.Get() : 2) + { + + case 0: + { + cell.X = static_cast(buffer.X / Unsorted::LeptonsPerCell); + break; + } + + case 2: + { + cell.X += static_cast(pType->GetFoundationWidth() - 1); + cell.Y = static_cast(buffer.Y / Unsorted::LeptonsPerCell); + break; + } + + case 4: + { + cell.X = static_cast(buffer.X / Unsorted::LeptonsPerCell); + cell.Y += static_cast(pType->GetFoundationHeight(false) - 1); + break; + } + + case 6: + { + cell.Y = static_cast(buffer.Y / Unsorted::LeptonsPerCell); + break; + } + + default: + { + break; + } + + } + + return cell; +} + int BuildingTypeExt::CountOwnedNowWithDeployOrUpgrade(BuildingTypeClass* pType, HouseClass* pHouse) { const auto upgrades = BuildingTypeExt::GetUpgradesAmount(pType, pHouse); diff --git a/src/Ext/BuildingType/Body.h b/src/Ext/BuildingType/Body.h index 384bc46250..36a5794a2a 100644 --- a/src/Ext/BuildingType/Body.h +++ b/src/Ext/BuildingType/Body.h @@ -205,7 +205,7 @@ class BuildingTypeExt static bool SaveGlobals(PhobosStreamWriter& Stm); static void PlayBunkerSound(BuildingClass const* pThis, bool buildUp = false); - + static CellStruct GetWeaponFactoryDoor(BuildingClass* pThis); static int GetEnhancedPower(BuildingClass* pBuilding, HouseClass* pHouse); static bool CanUpgrade(BuildingClass* pBuilding, BuildingTypeClass* pUpgradeType, HouseClass* pUpgradeOwner); static int CountOwnedNowWithDeployOrUpgrade(BuildingTypeClass* pBuilding, HouseClass* pHouse); diff --git a/src/Ext/BuildingType/Hooks.cpp b/src/Ext/BuildingType/Hooks.cpp index 02bdb381d0..e19a179f59 100644 --- a/src/Ext/BuildingType/Hooks.cpp +++ b/src/Ext/BuildingType/Hooks.cpp @@ -454,68 +454,33 @@ DEFINE_HOOK(0x44457B, BuildingClass_KickOutUnit_UnlimboDirection, 0x5) return 0; } -static inline CellStruct GetWeaponFactoryDoor(BuildingClass* pThis) +DEFINE_HOOK(0x44955D, BuildingClass_WeaponFactoryOutsideBusy_WeaponFactoryCell, 0x6) { - auto cell = pThis->GetMapCoords(); - auto buffer = CoordStruct::Empty; - pThis->GetExitCoords(&buffer, 0); - const auto pType = pThis->Type; - - switch (RulesExt::Global()->ExtendedWeaponsFactory ? BuildingTypeExt::ExtMap.Find(pType)->WeaponsFactory_Dir.Get() : 2) - { - - case 0: - { - cell.X = static_cast(buffer.X / Unsorted::LeptonsPerCell); - break; - } + enum { StartCheck = 0x4495DF, NotBusy = 0x44969B }; - case 2: - { - cell.X += static_cast(pType->GetFoundationWidth() - 1); - cell.Y = static_cast(buffer.Y / Unsorted::LeptonsPerCell); - break; - } - - case 4: - { - cell.X = static_cast(buffer.X / Unsorted::LeptonsPerCell); - cell.Y += static_cast(pType->GetFoundationHeight(false) - 1); - break; - } + GET(BuildingClass* const, pThis, ESI); - case 6: - { - cell.Y = static_cast(buffer.Y / Unsorted::LeptonsPerCell); - break; - } + const auto pLink = pThis->GetNthLink(); - default: - { - break; - } + if (!pLink) + return NotBusy; - } + const auto pLinkType = pLink->GetTechnoType(); - return cell; -} + if (pLinkType->JumpJet && pLinkType->BalloonHover) + return NotBusy; -DEFINE_HOOK(0x44955D, BuildingClass_WeaponFactoryOutsideBusy_WeaponFactoryCell, 0x6) -{ if (!RulesExt::Global()->ExtendedWeaponsFactory) return 0; - enum { SkipGameCode = 0x4495DF }; - - GET(BuildingClass* const, pThis, ESI); REF_STACK(CoordStruct, coords, STACK_OFFSET(0x30, -0xC)); - const auto cell = GetWeaponFactoryDoor(pThis); + const auto cell = BuildingTypeExt::GetWeaponFactoryDoor(pThis); coords = CellClass::Cell2Coord(cell); R->EAX(MapClass::Instance.GetCellAt(cell)); - return SkipGameCode; + return StartCheck; } DEFINE_JUMP(LJMP, 0x44DCC7, 0x44DD3C); @@ -528,7 +493,7 @@ DEFINE_HOOK(0x44E131, BuildingClass_Mission_Unload_WeaponFactoryFix1, 0x5) GET(FootClass* const, pLink, EDI); // REF_STACK(const CoordStruct, coords, STACK_OFFSET(0x50, -0x1C)); - const auto cell = GetWeaponFactoryDoor(pThis); + const auto cell = BuildingTypeExt::GetWeaponFactoryDoor(pThis); const auto coords = CellClass::Cell2Coord(cell); if (RulesExt::Global()->ExtendedWeaponsFactory) @@ -555,7 +520,7 @@ DEFINE_HOOK(0x44DF72, BuildingClass_Mission_Unload_WeaponFactoryFix2, 0x5) GET_STACK(FootClass* const, pLink, STACK_OFFSET(0x50, -0x30)); // REF_STACK(const CoordStruct, coords, STACK_OFFSET(0x50, -0x1C)); - const auto cell = GetWeaponFactoryDoor(pThis); + const auto cell = BuildingTypeExt::GetWeaponFactoryDoor(pThis); const auto coords = CellClass::Cell2Coord(cell); if (RulesExt::Global()->ExtendedWeaponsFactory) @@ -580,7 +545,7 @@ DEFINE_HOOK(0x44DF1C, BuildingClass_Mission_Unload_WeaponFactoryFix3, 0x7) REF_STACK(CellStruct, cell, STACK_OFFSET(0x50, -0x34)); // REF_STACK(const CoordStruct, coords, STACK_OFFSET(0x50, -0x1C)); - cell = GetWeaponFactoryDoor(pThis); + cell = BuildingTypeExt::GetWeaponFactoryDoor(pThis); R->ESI(pLink); @@ -596,7 +561,7 @@ DEFINE_HOOK(0x742D98, UnitClass_SetDestination_WeaponFactoryCell, 0x6) GET(BuildingClass* const, pLink, ESI); - const auto cell = GetWeaponFactoryDoor(pLink); + const auto cell = BuildingTypeExt::GetWeaponFactoryDoor(pLink); R->EAX(MapClass::Instance.GetCellAt(cell)); From 7ff8eabe68a83fc34b73590178cd0f5f069ec722 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Fri, 15 Aug 2025 10:38:15 +0800 Subject: [PATCH 3/5] More hover units fix --- src/Ext/Building/Hooks.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Ext/Building/Hooks.cpp b/src/Ext/Building/Hooks.cpp index b6e6c9d51c..e8b1ae7714 100644 --- a/src/Ext/Building/Hooks.cpp +++ b/src/Ext/Building/Hooks.cpp @@ -218,7 +218,15 @@ DEFINE_HOOK(0x44E202, BuildingClass_Mission_Unload_CheckStuck, 0x6) if (pUnit->Locomotor->Destination() == CoordStruct::Empty) { reinterpret_cast(0x449540)(pThis); - pUnit->SetDestination(MapClass::Instance.GetCellAt(BuildingTypeExt::GetWeaponFactoryDoor(pThis)), true); + const auto pDest = MapClass::Instance.GetCellAt(BuildingTypeExt::GetWeaponFactoryDoor(pThis)); + + auto getAdjCell = [pThis, pDest]() + { + const int dir = RulesExt::Global()->ExtendedWeaponsFactory ? BuildingTypeExt::ExtMap.Find(pThis->Type)->WeaponsFactory_Dir.Get() : 2; + return pDest->GetNeighbourCell(static_cast(dir)); + }; + + pUnit->SetDestination((pUnit->Destination != pDest ? pDest : getAdjCell()), true); } } From 9b3705caa2531eabc17ed6e166d4b52768b09e0f Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Fri, 15 Aug 2025 11:44:47 +0800 Subject: [PATCH 4/5] Fix typo --- src/Ext/BuildingType/Hooks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ext/BuildingType/Hooks.cpp b/src/Ext/BuildingType/Hooks.cpp index e19a179f59..2d8d520e0d 100644 --- a/src/Ext/BuildingType/Hooks.cpp +++ b/src/Ext/BuildingType/Hooks.cpp @@ -426,7 +426,7 @@ DEFINE_HOOK(0x73F5A7, UnitClass_IsCellOccupied_UnlimboDirection, 0x8) GET(CellClass* const, pCell, EDI); if (!RulesExt::Global()->ExtendedWeaponsFactory) - return pCell->MapCoords.Y == pType->FoundationOutside[10].Y ? NextObject : ContinueCheck; + return pCell->MapCoords.Y == pBuilding->Location.Y / Unsorted::LeptonsPerCell + pType->FoundationOutside[10].Y ? NextObject : ContinueCheck; auto buffer = CoordStruct::Empty; pBuilding->GetExitCoords(&buffer, 0); From f381fb5165a41021ec19bc6ef7cc2874dd046db7 Mon Sep 17 00:00:00 2001 From: CrimRecya <335958461@qq.com> Date: Wed, 3 Sep 2025 22:05:41 +0800 Subject: [PATCH 5/5] More fix --- src/Ext/Building/Body.cpp | 7 ++++++- src/Ext/Building/Hooks.cpp | 29 +++++++++++++++++++---------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/Ext/Building/Body.cpp b/src/Ext/Building/Body.cpp index ea072e5fea..f705dccf20 100644 --- a/src/Ext/Building/Body.cpp +++ b/src/Ext/Building/Body.cpp @@ -418,7 +418,12 @@ void BuildingExt::KickOutStuckUnits(BuildingClass* pThis) { if (const auto pUnit = abstract_cast(pObject)) { - if (pThis->Owner != pUnit->Owner || pUnit->Locomotor->Destination() != CoordStruct::Empty) + if (pThis->Owner != pUnit->Owner) + continue; + + const auto pLocoDest = pUnit->Locomotor->Destination(); + + if (pLocoDest != CoordStruct::Empty && pLocoDest != pUnit->Location) continue; const auto height = pUnit->GetHeight(); diff --git a/src/Ext/Building/Hooks.cpp b/src/Ext/Building/Hooks.cpp index b3a9ac0dee..23ac37ab75 100644 --- a/src/Ext/Building/Hooks.cpp +++ b/src/Ext/Building/Hooks.cpp @@ -217,20 +217,20 @@ DEFINE_HOOK(0x44E202, BuildingClass_Mission_Unload_CheckStuck, 0x6) if (const auto pUnit = abstract_cast(pThis->GetNthLink())) { // Detecting movement status - if (pUnit->Locomotor->Destination() == CoordStruct::Empty) + const auto pLocoDest = pUnit->Locomotor->Destination(); + + if (pLocoDest == CoordStruct::Empty || pLocoDest == pUnit->Location) { // Evacuate the congestion at the entrance reinterpret_cast(0x449540)(pThis); - const auto pDest = MapClass::Instance.GetCellAt(BuildingTypeExt::GetWeaponFactoryDoor(pThis)); - - auto getAdjCell = [pThis, pDest]() - { - const int dir = RulesExt::Global()->ExtendedWeaponsFactory ? BuildingTypeExt::ExtMap.Find(pThis->Type)->WeaponsFactory_Dir.Get() : 2; - return pDest->GetNeighbourCell(static_cast(dir)); - }; + const auto cell = BuildingTypeExt::GetWeaponFactoryDoor(pThis); + const auto pDest = MapClass::Instance.GetCellAt(cell); - // Hover units may stop one cell behind their destination, should forcing them to advance one more cell - pUnit->SetDestination((pUnit->Destination != pDest ? pDest : getAdjCell()), true); + // Hover units may stop one cell behind their destination + if (pUnit->Destination != pDest) + pUnit->SetDestination(pDest, true); + else + pUnit->Locomotor->Move_To(CellClass::Cell2Coord(cell, Unsorted::LevelHeight * pDest->Level)); } } @@ -242,6 +242,15 @@ DEFINE_HOOK(0x44E260, BuildingClass_Mission_Unload_KickOutStuckUnits, 0x7) { GET(BuildingClass*, pThis, EBP); + if (!pThis->IsTether) + { + if (const auto pLink = pThis->GetNthLink()) + { + pThis->SendCommand(RadioCommand::NotifyUnlink, pLink); + pLink->Scatter(pThis->GetCoords(), true, true); + } + } + BuildingExt::KickOutStuckUnits(pThis); return 0;