From 42ddd72cc154172255600dd7b7c88a4b30a4156b Mon Sep 17 00:00:00 2001 From: Starkku Date: Tue, 7 Jul 2026 22:49:58 +0300 Subject: [PATCH 1/2] Improvements & additions for reinforcement aircraft spawn logic --- CREDITS.md | 2 +- YRpp | 2 +- docs/Fixed-or-Improved-Logics.md | 28 ++++++---- docs/Whats-New.md | 2 +- src/Ext/Aircraft/Body.cpp | 48 +++++++++++++---- src/Ext/Aircraft/Body.h | 3 +- src/Ext/House/Hooks.cpp | 90 +++++++++++++++++++++++++++++-- src/Ext/TechnoType/Body.cpp | 4 ++ src/Ext/TechnoType/Body.h | 4 ++ src/Misc/Hooks.Ares.cpp | 48 +++++++++++++++++ src/Utilities/AresAddressInit.cpp | 5 ++ src/Utilities/AresFunctions.h | 2 + src/Utilities/GeneralUtils.cpp | 18 +++++-- src/Utilities/GeneralUtils.h | 3 +- 14 files changed, 226 insertions(+), 33 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index 13ec35cbe6..5a0f916c7f 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -241,7 +241,7 @@ This page lists all the individual contributions to the project by their author. - Warhead animation improvements - Reloading ammo in transports - Straight projectile trajectory additions - - Airstrike & spy plane fixed spawn distance & height + - Reinforcement plane (paradrop, spy plane, airstrike) spawn customizations - Negative damage `Verses/PercentAtMax` toggle - Misc. singleplayer mission improvements - Weapon effect obstacle interaction fix diff --git a/YRpp b/YRpp index 85b5b5e185..919053e232 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit 85b5b5e1857a6e06e1341dc87d01c38b1104272e +Subproject commit 919053e2321bb67e816b78cdf270afe2e91eaa50 diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 883378ccec..8a413c0d8f 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -845,27 +845,33 @@ ExtendedAircraftMissions.UnlandDamage= ; integer, default to [General] -> Ext And now when `ExtendedAircraftMissions` is enabled, aircraft that can land at the airport will check at any time to see if they have a dock. Therefore, if there are aircraft in your mission that require dock and you have not provided enough or not disabled the feature, they will crash immediately ``` -### Fixed spawn distance & spawn height for airstrike / SpyPlane aircraft +### Landing direction -- It is now possible to have aircraft spawned from `(Elite)AirstrikeTeamType` or `Type=SpyPlane` superweapons to be created at fixed distance from their intended target/destination instead of from edge of the map by setting `SpawnDistanceFromTarget`. -- `SpawnHeight` can also be used to override the initial height of the aircraft, which defaults to `FlightLevel`, or if not set then `[General] -> FlightLevel`. +- By default aircraft land facing the direction specified by `[AudioVisual] -> PoseDir`. This can now be customized per AircraftType via `LandingDir`, defaults to `[AudioVisual] -> PoseDir`. If the building the aircraft is docking to has [aircraft docking direction](#aircraft-docking-direction) set, that setting takes priority over this. + - Negative values are allowed as a special case for `AirportBound=false` aircraft which makes them land facing their current direction. In `rulesmd.ini`: ```ini -[SOMEAIRCRAFT] ; AircraftType -SpawnDistanceFromTarget= ; floating point value, distance in cells -SpawnHeight= ; integer, height in leptons +[SOMEAIRCRAFT] ; AircraftType +LandingDir= ; Direction type (integers from 0-255). Accepts negative values as a special case. ``` -### Landing direction +### Reinforcement aircraft spawn settings -- By default aircraft land facing the direction specified by `[AudioVisual] -> PoseDir`. This can now be customized per AircraftType via `LandingDir`, defaults to `[AudioVisual] -> PoseDir`. If the building the aircraft is docking to has [aircraft docking direction](#aircraft-docking-direction) set, that setting takes priority over this. - - Negative values are allowed as a special case for `AirportBound=false` aircraft which makes them land facing their current direction. +- A number of new settings are available for AircraftTypes spawned from `(Elite)AirstrikeTeamType` or `Type=SpyPlane/Paradrop/AmerParadrop` superweapons. + - If `SpawnFromClosestEdge` is set to true, closest edge to target is picked instead of one determined based on the target position and owner's base location etc. + - `RetreatToClosestEdge` does same but for the return destination of the aircraft. + - `SpawnDistanceFromTarget` can be used to set fixed spawn distance from target instead of at edge of map. The approach direction is still determined by the edge where it would've otherwise spawned. Negative values will invert the direction. + - `SpawnHeight` can be used to override the initial height of the aircraft, which defaults to `FlightLevel`, or if not set then `[General] -> FlightLevel`. + - Additionally, these aircraft now spawn facing the target's direction instead of always facing north. In `rulesmd.ini`: ```ini -[SOMEAIRCRAFT] ; AircraftType -LandingDir= ; Direction type (integers from 0-255). Accepts negative values as a special case. +[SOMEAIRCRAFT] ; AircraftType +SpawnFromClosestEdge=false ; boolean +RetreatToClosestEdge=false ; boolean +SpawnDistanceFromTarget= ; floating point value, distance in cells +SpawnHeight= ; integer, height in leptons ``` ## Animations diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 057e57276d..2130e341ac 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -607,6 +607,7 @@ HideShakeEffects=false ; boolean - [Customize the step limit of the credits indicator](User-Interface.md#customize-the-step-limit-of-the-credits-indicator) (by Noble_Fish) - [Disable the credits indicator smooth transition](User-Interface.md#disable-the-credits-indicator-smooth-transition) (by Noble_Fish) - Add `selling`, `undeploying` and `harvesting` conditions to `DiscardOn` (by Noble_Fish) +- [Additional reinforcement aircraft (airstrike, paradrop and spy plane) spawn customizations](Fixed-or-Improved-Logics.md#reinforcement-aircraft-spawn-settings) (by Starkku) #### Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) @@ -757,7 +758,6 @@ HideShakeEffects=false ; boolean - [Export interface for accessing scenario local/global variables](Interoperability.md#scenarioext) (by Chang_zhi) - Allowed infantry to use `Convert.Deploy` without requiring `IsSimpleDeployer=true` (by Noble_Fish) - Added the scenario where `Missile.Raise` can be applied by custom missiles (by Noble_Fish) - ``` ### 0.4.0.3 diff --git a/src/Ext/Aircraft/Body.cpp b/src/Ext/Aircraft/Body.cpp index bc0d7976fb..3f9bdbc2a8 100644 --- a/src/Ext/Aircraft/Body.cpp +++ b/src/Ext/Aircraft/Body.cpp @@ -47,25 +47,30 @@ void AircraftExt::FireWeapon(AircraftClass* pThis, AbstractClass* pTarget) } } -// Spy plane, airstrike etc. -bool AircraftExt::PlaceReinforcementAircraft(AircraftClass* pThis, CellStruct edgeCell) +// Paradrop, spy plane, airstrike. +bool AircraftExt::PlaceReinforcementAircraft(AircraftClass* pThis, CoordStruct edgeCoords) { auto const pType = pThis->Type; auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pType); - auto coords = CellClass::Cell2Coord(edgeCell); + auto dir = DirType::North; + auto coords = edgeCoords; coords.Z = 0; - AbstractClass* pTarget = nullptr; + AbstractClass* pTarget = pThis->Target ? pThis->Target : pThis->Destination; - if (pTypeExt->SpawnDistanceFromTarget.isset()) + if (pTarget) { - pTarget = pThis->Target ? pThis->Target : pThis->Destination; + auto const pTargetCoords = pTarget->GetCoords(); + + if (pTypeExt->SpawnDistanceFromTarget.isset()) + coords = GeneralUtils::CalculateCoordsFromDistance(edgeCoords, pTargetCoords, pTypeExt->SpawnDistanceFromTarget.Get()); - if (pTarget) - coords = GeneralUtils::CalculateCoordsFromDistance(CellClass::Cell2Coord(edgeCell), pTarget->GetCoords(), pTypeExt->SpawnDistanceFromTarget.Get()); + dir = GeneralUtils::GetDirectionBetweenCoords(coords, pTargetCoords).GetDir(); } + bool result = false; + ++Unsorted::ScenarioInit; - const bool result = pThis->Unlimbo(coords, DirType::North); + result = pThis->Unlimbo(coords, dir); --Unsorted::ScenarioInit; pThis->SetHeight(pTypeExt->SpawnHeight.isset() ? pTypeExt->SpawnHeight.Get() : pType->GetFlightLevel()); @@ -76,6 +81,31 @@ bool AircraftExt::PlaceReinforcementAircraft(AircraftClass* pThis, CellStruct ed return result; } +CellStruct AircraftExt::PickEdgeCellForPlane(AircraftTypeClass* pPlaneType, CellStruct destCell, Edge edge, bool isOnRetreat) +{ + auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pPlaneType); + bool const useClosestEdge = !isOnRetreat ? pTypeExt->SpawnFromClosestEdge : pTypeExt->RetreatToClosestEdge; + auto useEdge = edge; + auto refCell = CellStruct::Empty; + + if (useClosestEdge && destCell != CellStruct::Empty) + { + useEdge = Edge::None; + refCell = destCell; + + // Scatter the coords a bit to randomize spawn cell a little - otherwise multiple planes sent at same target + // from same source might end up overlapping - still a possibility, just less likely. + int randomRange = 5; + short randomX = static_cast(ScenarioClass::Instance->Random.RandomRanged(-randomRange, randomRange)); + short randomY = static_cast(ScenarioClass::Instance->Random.RandomRanged(-randomRange, randomRange)); + refCell += CellStruct { randomX, randomY }; + } + + auto cell = MapClass::Instance.PickCellOnEdge(useEdge, refCell, CellStruct::Empty, SpeedType::Winged, true, MovementZone::Normal); + + return cell; +} + DirType AircraftExt::GetLandingDir(AircraftClass* pThis, BuildingClass* pDock) { auto const poseDir = static_cast(RulesClass::Instance->PoseDir); diff --git a/src/Ext/Aircraft/Body.h b/src/Ext/Aircraft/Body.h index 5fecdfc443..fc8f644f92 100644 --- a/src/Ext/Aircraft/Body.h +++ b/src/Ext/Aircraft/Body.h @@ -7,6 +7,7 @@ class AircraftExt { public: static void FireWeapon(AircraftClass* pThis, AbstractClass* pTarget); - static bool PlaceReinforcementAircraft(AircraftClass* pThis, CellStruct edgeCell); + static bool PlaceReinforcementAircraft(AircraftClass* pThis, CoordStruct edgeCoords); + static CellStruct PickEdgeCellForPlane(AircraftTypeClass* pPlaneType, CellStruct destCell, Edge edge, bool isOnRetreat = false); static DirType GetLandingDir(AircraftClass* pThis, BuildingClass* pDock = nullptr); }; diff --git a/src/Ext/House/Hooks.cpp b/src/Ext/House/Hooks.cpp index bb1286f267..15f2a4e92b 100644 --- a/src/Ext/House/Hooks.cpp +++ b/src/Ext/House/Hooks.cpp @@ -291,18 +291,56 @@ DEFINE_HOOK(0x7015C9, TechnoClass_Captured_UpdateTracking, 0x6) #pragma endregion -DEFINE_HOOK(0x65EB8D, HouseClass_SendSpyPlanes_PlaceAircraft, 0x6) +#pragma region Reinforcement_Planes + +DEFINE_HOOK(0x65EB2D, HouseClass_SendSpyPlane_PickEdgeCell, 0x6) +{ + enum { SkipGameCode = 0x65EB4D }; + + GET(AircraftClass* const, pAircraft, ESI); + GET(Edge const, edge, EAX); + GET(AbstractClass* const, navCom, EBX); + GET_STACK(AbstractClass* const, tarCom, STACK_OFFSET(0x28, 0xC)); + REF_STACK(CellStruct, edgeCell, STACK_OFFSET(0x28, -0x10)); + + const auto pTarget = tarCom ? tarCom : navCom; + const auto targetCell = pTarget ? CellClass::Coord2Cell(pTarget->GetCoords()) : CellStruct::Empty; + edgeCell = AircraftExt::PickEdgeCellForPlane(pAircraft->Type, targetCell, edge); + + R->EAX(&edgeCell); + return SkipGameCode; +} + +DEFINE_HOOK(0x65EB8D, HouseClass_SendSpyPlane_PlaceAircraft, 0x6) { enum { SkipGameCode = 0x65EBE5, SkipGameCodeNoSuccess = 0x65EC12 }; GET(AircraftClass* const, pAircraft, ESI); GET(CellStruct const, edgeCell, EDI); - const bool result = AircraftExt::PlaceReinforcementAircraft(pAircraft, edgeCell); + const bool result = AircraftExt::PlaceReinforcementAircraft(pAircraft, CellClass::Cell2Coord(edgeCell)); return result ? SkipGameCode : SkipGameCodeNoSuccess; } +DEFINE_HOOK(0x65E881, HouseClass_SendAirstrike_PickEdgeCell, 0x5) +{ + enum { SkipGameCode = 0x65E8A0 }; + + GET(AircraftTypeClass* const, pAircraftType, EBP); + GET(Edge const, edge, EAX); + GET_STACK(AbstractClass* const, navCom, STACK_OFFSET(0x38, 0x10)); + GET_STACK(AbstractClass* const, tarCom, STACK_OFFSET(0x38, 0xC)); + REF_STACK(CellStruct, edgeCell, STACK_OFFSET(0x38, -0x1C)); + + const auto pTarget = tarCom ? tarCom : navCom; + const auto targetCell = pTarget ? CellClass::Coord2Cell(pTarget->GetCoords()) : CellStruct::Empty; + edgeCell = AircraftExt::PickEdgeCellForPlane(pAircraftType, targetCell, edge); + + R->EAX(&edgeCell); + return SkipGameCode; +} + DEFINE_HOOK(0x65E997, HouseClass_SendAirstrike_PlaceAircraft, 0x6) { enum { SkipGameCode = 0x65E9EE, SkipGameCodeNoSuccess = 0x65EA8B }; @@ -310,11 +348,57 @@ DEFINE_HOOK(0x65E997, HouseClass_SendAirstrike_PlaceAircraft, 0x6) GET(AircraftClass* const, pAircraft, ESI); GET(CellStruct const, edgeCell, EDI); - const bool result = AircraftExt::PlaceReinforcementAircraft(pAircraft, edgeCell); + const bool result = AircraftExt::PlaceReinforcementAircraft(pAircraft, CellClass::Cell2Coord(edgeCell)); + + return result ? SkipGameCode : SkipGameCodeNoSuccess; +} + +DEFINE_HOOK(0x65E6DB, HouseClass_SendParadrop_PickEdgeCell, 0x6) +{ + enum { SkipGameCode = 0x65E6FB }; + + GET(AircraftClass* const, pAircraft, ESI); + GET(Edge const, edge, EAX); + GET_STACK(AbstractClass* const, navCom, STACK_OFFSET(0x30, 0x10)); + GET_STACK(AbstractClass* const, tarCom, STACK_OFFSET(0x30, 0xC)); + REF_STACK(CellStruct, edgeCell, STACK_OFFSET(0x30, -0x10)); + + const auto pTarget = tarCom ? tarCom : navCom; + const auto targetCell = pTarget ? CellClass::Coord2Cell(pTarget->GetCoords()) : CellStruct::Empty; + edgeCell = AircraftExt::PickEdgeCellForPlane(pAircraft->Type, targetCell, edge); + + R->EAX(&edgeCell); + return SkipGameCode; +} + +DEFINE_HOOK(0x65E73A, HouseClass_SendParadrop_PlaceAircraft, 0x5) +{ + enum { SkipGameCode = 0x65E79B, SkipGameCodeNoSuccess = 0x65E82C }; + + GET(AircraftClass* const, pAircraft, ESI); + GET(CellStruct const, edgeCell, EDI); + + const bool result = AircraftExt::PlaceReinforcementAircraft(pAircraft, CellClass::Cell2Coord(edgeCell)); return result ? SkipGameCode : SkipGameCodeNoSuccess; } +DEFINE_HOOK(0x415A7A, AircraftClass_Mission_Retreat_PickEdgeCell, 0x6) +{ + enum { SkipGameCode = 0x415A9A }; + + GET(AircraftClass* const, pThis, ESI); + GET(Edge const, edge, EAX); + REF_STACK(CellStruct, edgeCell, STACK_OFFSET(0xC, -0x4)); + + edgeCell = AircraftExt::PickEdgeCellForPlane(pThis->Type, pThis->GetMapCoords(), edge, true); + + R->EAX(&edgeCell); + return SkipGameCode; +} + +#pragma endregion + // Vanilla and Ares all only hardcoded to find factory with BuildCat::DontCare... static inline bool CheckShouldDisableDefensesCameo(HouseClass* pHouse, TechnoTypeClass* pType) { diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index e539e3114b..59a2e5c7c5 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -1011,6 +1011,8 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->SpawnsPipSize.Read(exINI, pSection, "SpawnsPipSize"); this->SpawnsPipOffset.Read(exINI, pSection, "SpawnsPipOffset"); + this->SpawnFromClosestEdge.Read(exINI, pSection, "SpawnFromClosestEdge"); + this->RetreatToClosestEdge.Read(exINI, pSection, "RetreatToClosestEdge"); this->SpawnDistanceFromTarget.Read(exINI, pSection, "SpawnDistanceFromTarget"); this->SpawnHeight.Read(exINI, pSection, "SpawnHeight"); this->LandingDir.Read(exINI, pSection, "LandingDir"); @@ -1751,6 +1753,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->SpawnsPipSize) .Process(this->SpawnsPipOffset) + .Process(this->SpawnFromClosestEdge) + .Process(this->RetreatToClosestEdge) .Process(this->SpawnDistanceFromTarget) .Process(this->SpawnHeight) .Process(this->LandingDir) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 13acf8a56d..a4ba8469a4 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -282,6 +282,8 @@ class TechnoTypeExt Nullable SpawnsPipSize; Valueable SpawnsPipOffset; + Valueable SpawnFromClosestEdge; + Valueable RetreatToClosestEdge; Nullable SpawnDistanceFromTarget; Nullable SpawnHeight; Nullable LandingDir; @@ -777,6 +779,8 @@ class TechnoTypeExt , SpawnsPipSize {} , SpawnsPipOffset { { 0,0 } } + , SpawnFromClosestEdge { false } + , RetreatToClosestEdge { false } , SpawnDistanceFromTarget {} , SpawnHeight {} , LandingDir {} diff --git a/src/Misc/Hooks.Ares.cpp b/src/Misc/Hooks.Ares.cpp index a6fd263813..177818a83c 100644 --- a/src/Misc/Hooks.Ares.cpp +++ b/src/Misc/Hooks.Ares.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -121,6 +122,35 @@ static void __fastcall UnitDeliveryStateMachine_Update_Wrapper(void* pThis) UnitDeliveryTemp::Placing = false; } +#pragma region AresParadrop + +namespace ParadropTemp +{ + AircraftTypeClass* pPlaneType = nullptr; + CellClass* pDestination = nullptr; +} + +static void SendPDPlane(HouseClass* pOwner, CellClass* pDestination, AircraftTypeClass* pPlaneType, Iterator Types, Iterator Nums) +{ + ParadropTemp::pPlaneType = pPlaneType; + ParadropTemp::pDestination = pDestination; + AresFunctions::SendPDPlane(pOwner, pDestination, pPlaneType, Types, Nums); +} + +static CellStruct* __fastcall ParadropPickCellOnEdge(MapClass* pThis, void* _, CellStruct& buffer, Edge edge, + const CellStruct& waypointCell, const CellStruct& fallbackCell, SpeedType speedType, bool validate, MovementZone mZone) +{ + buffer = AircraftExt::PickEdgeCellForPlane(ParadropTemp::pPlaneType, ParadropTemp::pDestination->MapCoords, edge); + return &buffer; +} + +static bool __fastcall ParadropPlaneUnlimbo(AircraftClass* pThis, void* _, const CoordStruct& coords, DirType direction) +{ + return AircraftExt::PlaceReinforcementAircraft(pThis, coords); +} + +#pragma endregion + DEFINE_HOOK(0x440580, BuildingClass_Unlimbo_UnitDeliveryFix, 0x5) { if (UnitDeliveryTemp::Placing) @@ -217,6 +247,15 @@ void Apply_Ares3_0_Patches() // Fix building direction of Ares's UnitDelivery Patch::Apply_VTABLE(AresHelper::AresBaseAddress + 0xA8D94, &UnitDeliveryStateMachine_Update_Wrapper); + + // Replace Ares paradrop plane send function call with our wrapper. + Patch::Apply_CALL(AresHelper::AresBaseAddress + 0x745B8, &SendPDPlane); + + // Replace Ares paradrop plane edge cell picker with our wrapper. + Patch::Apply_CALL(AresHelper::AresBaseAddress + 0x74242, &ParadropPickCellOnEdge); + + // Replace Ares paradrop plane Unlimbo call with our wrapper. + Patch::Apply_CALL6(AresHelper::AresBaseAddress + 0x742AC, &ParadropPlaneUnlimbo); } void Apply_Ares3_0p1_Patches() @@ -307,4 +346,13 @@ void Apply_Ares3_0p1_Patches() // Fix building direction of Ares's UnitDelivery Patch::Apply_VTABLE(AresHelper::AresBaseAddress + 0xA9F28, &UnitDeliveryStateMachine_Update_Wrapper); + + // Replace Ares paradrop plane send function call with our wrapper. + Patch::Apply_CALL(AresHelper::AresBaseAddress + 0x75668, &SendPDPlane); + + // Replace Ares paradrop plane edge cell picker with our wrapper. + Patch::Apply_CALL(AresHelper::AresBaseAddress + 0x752F2, &ParadropPickCellOnEdge); + + // Replace Ares paradrop plane Unlimbo call with our wrapper. + Patch::Apply_CALL6(AresHelper::AresBaseAddress + 0x7535C, &ParadropPlaneUnlimbo); } diff --git a/src/Utilities/AresAddressInit.cpp b/src/Utilities/AresAddressInit.cpp index 47aff979a0..bf6d4d6c4b 100644 --- a/src/Utilities/AresAddressInit.cpp +++ b/src/Utilities/AresAddressInit.cpp @@ -12,6 +12,7 @@ decltype(AresFunctions::IsTargetConstraintsEligible) AresFunctions::IsTargetCons decltype(AresFunctions::UnitDeliveryStateMachine_Update) AresFunctions::UnitDeliveryStateMachine_Update = nullptr; decltype(AresFunctions::ApplyPermaMC) AresFunctions::ApplyPermaMC = nullptr; decltype(AresFunctions::DetailsCurrentlyEnabled) AresFunctions::DetailsCurrentlyEnabled = nullptr; +decltype(AresFunctions::SendPDPlane) AresFunctions::SendPDPlane = nullptr; std::function AresFunctions::SWTypeExtMap_Find; PhobosMap* AresFunctions::AlphaExtMap = nullptr; @@ -53,6 +54,8 @@ void AresFunctions::InitAres3_0() NOTE_ARES_FUN(DetailsCurrentlyEnabled, 0x02A6C0); + NOTE_ARES_FUN(SendPDPlane, 0x0741A0); + NOTE_ARES_FUN(_SWTypeExtMapFind, 0x57C70); NOTE_ARES_FUN(_SWTypeExtMap, 0xC1C54); SWTypeExtMap_Find = [](SuperWeaponTypeClass* swt) { return _SWTypeExtMapFind(_SWTypeExtMap, swt); }; @@ -98,6 +101,8 @@ void AresFunctions::InitAres3_0p1() NOTE_ARES_FUN(DetailsCurrentlyEnabled, 0x02B1C0); + NOTE_ARES_FUN(SendPDPlane, 0x075250); + NOTE_ARES_FUN(_SWTypeExtMapFind, 0x58900); NOTE_ARES_FUN(_SWTypeExtMap, 0xC2C50); SWTypeExtMap_Find = [](SuperWeaponTypeClass* swt) { return _SWTypeExtMapFind(_SWTypeExtMap, swt); }; diff --git a/src/Utilities/AresFunctions.h b/src/Utilities/AresFunctions.h index 128b5e1ab6..000ec07dd9 100644 --- a/src/Utilities/AresFunctions.h +++ b/src/Utilities/AresFunctions.h @@ -44,6 +44,8 @@ class AresFunctions static bool (*DetailsCurrentlyEnabled)(); + static void(*SendPDPlane)(HouseClass* pOwner, CellClass* pDestination, AircraftTypeClass* pPlaneType, Iterator Types, Iterator Nums); + static std::function SWTypeExtMap_Find; static PhobosMap* AlphaExtMap; diff --git a/src/Utilities/GeneralUtils.cpp b/src/Utilities/GeneralUtils.cpp index d809ab497d..ffdbf9f554 100644 --- a/src/Utilities/GeneralUtils.cpp +++ b/src/Utilities/GeneralUtils.cpp @@ -199,14 +199,22 @@ int GeneralUtils::CountDigitsInNumber(int number) return digits; } -// Calculates a new coordinates based on current & target coordinates within specified distance (can be negative to switch the direction) in leptons. -CoordStruct GeneralUtils::CalculateCoordsFromDistance(CoordStruct currentCoords, CoordStruct targetCoords, int distance) +// Calculates direction between two coordinates. +DirStruct GeneralUtils::GetDirectionBetweenCoords(const CoordStruct& currentCoords, const CoordStruct& targetCoords) { - const int deltaX = currentCoords.X - targetCoords.X; - const int deltaY = targetCoords.Y - currentCoords.Y; - + const int deltaX = targetCoords.X - currentCoords.X; + const int deltaY = currentCoords.Y - targetCoords.Y; const double atan = Math::atan2(deltaY, deltaX); const double radians = (((atan - Math::HalfPi) * (1.0 / Math::GameDegreesToRadiansCoefficient)) - Math::GameDegrees90) * Math::GameDegreesToRadiansCoefficient; + DirStruct dir {}; + dir.SetRadian<65536>(radians); + return dir; +} + +// Calculates a new coordinates based on current & target coordinates within specified distance (can be negative to switch the direction) in leptons. +CoordStruct GeneralUtils::CalculateCoordsFromDistance(const CoordStruct& currentCoords, const CoordStruct& targetCoords, int distance) +{ + const double radians = GeneralUtils::GetDirectionBetweenCoords(currentCoords, targetCoords).GetRadian<65536>() + Math::Pi; const int x = static_cast(targetCoords.X + Math::cos(radians) * distance); const int y = static_cast(targetCoords.Y - Math::sin(radians) * distance); diff --git a/src/Utilities/GeneralUtils.h b/src/Utilities/GeneralUtils.h index d97ec3bb6b..b86a4ec21b 100644 --- a/src/Utilities/GeneralUtils.h +++ b/src/Utilities/GeneralUtils.h @@ -36,7 +36,8 @@ class GeneralUtils static bool ApplyTheaterSuffixToString(char* str); static std::string IntToDigits(int num); static int CountDigitsInNumber(int number); - static CoordStruct CalculateCoordsFromDistance(CoordStruct currentCoords, CoordStruct targetCoords, int distance); + static DirStruct GetDirectionBetweenCoords(const CoordStruct& currentCoords, const CoordStruct& targetCoords); + static CoordStruct CalculateCoordsFromDistance(const CoordStruct& currentCoords, const CoordStruct& targetCoords, int distance); static void DisplayDamageNumberString(int damage, DamageDisplayType type, CoordStruct coords, int& offset); static int GetColorFromColorAdd(int colorIndex); static int SafeMultiply(int value, int mult); From a10b332460092f0f14f8674041578569063da3e5 Mon Sep 17 00:00:00 2001 From: Starkku Date: Wed, 8 Jul 2026 13:49:22 +0300 Subject: [PATCH 2/2] Make spawn/retreat edge keys enum type --- docs/Fixed-or-Improved-Logics.md | 13 ++++----- src/Ext/Aircraft/Body.cpp | 47 ++++++++++++++++++++++---------- src/Ext/TechnoType/Body.cpp | 8 +++--- src/Ext/TechnoType/Body.h | 8 +++--- src/Utilities/Enum.h | 7 +++++ src/Utilities/TemplateDef.h | 28 +++++++++++++++++++ 6 files changed, 81 insertions(+), 30 deletions(-) diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 8a413c0d8f..34db74a144 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -859,19 +859,18 @@ LandingDir= ; Direction type (integers from 0-255). Accepts negative values ### Reinforcement aircraft spawn settings - A number of new settings are available for AircraftTypes spawned from `(Elite)AirstrikeTeamType` or `Type=SpyPlane/Paradrop/AmerParadrop` superweapons. - - If `SpawnFromClosestEdge` is set to true, closest edge to target is picked instead of one determined based on the target position and owner's base location etc. - - `RetreatToClosestEdge` does same but for the return destination of the aircraft. + - `SpawnFromEdge` and `RetreatToEdge` can be used to customize the edge of map from where the aircraft spawns and where it retreats to, respectively. Defaults to the owner's edge of the map which is determined based on base location and other factors. - `SpawnDistanceFromTarget` can be used to set fixed spawn distance from target instead of at edge of map. The approach direction is still determined by the edge where it would've otherwise spawned. Negative values will invert the direction. - `SpawnHeight` can be used to override the initial height of the aircraft, which defaults to `FlightLevel`, or if not set then `[General] -> FlightLevel`. - Additionally, these aircraft now spawn facing the target's direction instead of always facing north. In `rulesmd.ini`: ```ini -[SOMEAIRCRAFT] ; AircraftType -SpawnFromClosestEdge=false ; boolean -RetreatToClosestEdge=false ; boolean -SpawnDistanceFromTarget= ; floating point value, distance in cells -SpawnHeight= ; integer, height in leptons +[SOMEAIRCRAFT] ; AircraftType +SpawnFromEdge=owner ; Edge type enumeration (owner|closest|random) +RetreatToEdge=owner ; Edge type enumeration (owner|closest|random) +SpawnDistanceFromTarget= ; floating point value, distance in cells +SpawnHeight= ; integer, height in leptons ``` ## Animations diff --git a/src/Ext/Aircraft/Body.cpp b/src/Ext/Aircraft/Body.cpp index 3f9bdbc2a8..cbd4205f8c 100644 --- a/src/Ext/Aircraft/Body.cpp +++ b/src/Ext/Aircraft/Body.cpp @@ -84,26 +84,43 @@ bool AircraftExt::PlaceReinforcementAircraft(AircraftClass* pThis, CoordStruct e CellStruct AircraftExt::PickEdgeCellForPlane(AircraftTypeClass* pPlaneType, CellStruct destCell, Edge edge, bool isOnRetreat) { auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pPlaneType); - bool const useClosestEdge = !isOnRetreat ? pTypeExt->SpawnFromClosestEdge : pTypeExt->RetreatToClosestEdge; - auto useEdge = edge; + auto const edgeMode = !isOnRetreat ? pTypeExt->SpawnFromEdge : pTypeExt->RetreatToEdge; + auto spawnEdge = edge; auto refCell = CellStruct::Empty; - if (useClosestEdge && destCell != CellStruct::Empty) + switch (edgeMode) { - useEdge = Edge::None; - refCell = destCell; - - // Scatter the coords a bit to randomize spawn cell a little - otherwise multiple planes sent at same target - // from same source might end up overlapping - still a possibility, just less likely. - int randomRange = 5; - short randomX = static_cast(ScenarioClass::Instance->Random.RandomRanged(-randomRange, randomRange)); - short randomY = static_cast(ScenarioClass::Instance->Random.RandomRanged(-randomRange, randomRange)); - refCell += CellStruct { randomX, randomY }; + case EdgeType::Closest: + { + if (destCell != CellStruct::Empty) + { + spawnEdge = Edge::None; + refCell = destCell; + + // Scatter the coords a bit to randomize spawn cell a little - otherwise multiple planes sent at same target + // from same source might end up overlapping - still a possibility, just less likely. + // The edge cell picking function itself will do no randomization on Edge::None + waypoint cell set mode. + int const randomRange = 5; + short const randomX = static_cast(ScenarioClass::Instance->Random.RandomRanged(-randomRange, randomRange)); + short const randomY = static_cast(ScenarioClass::Instance->Random.RandomRanged(-randomRange, randomRange)); + refCell += CellStruct { randomX, randomY }; + } + break; + } + case EdgeType::Random: + { + int const min = static_cast(Edge::North); + int const max = static_cast(Edge::West); + spawnEdge = static_cast(ScenarioClass::Instance->Random.RandomRanged(min, max)); + break; + } + default: + { + break; + } } - auto cell = MapClass::Instance.PickCellOnEdge(useEdge, refCell, CellStruct::Empty, SpeedType::Winged, true, MovementZone::Normal); - - return cell; + return MapClass::Instance.PickCellOnEdge(spawnEdge, refCell, CellStruct::Empty, SpeedType::Winged, true, MovementZone::Normal); } DirType AircraftExt::GetLandingDir(AircraftClass* pThis, BuildingClass* pDock) diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 59a2e5c7c5..41c5d7f211 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -1011,8 +1011,8 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->SpawnsPipSize.Read(exINI, pSection, "SpawnsPipSize"); this->SpawnsPipOffset.Read(exINI, pSection, "SpawnsPipOffset"); - this->SpawnFromClosestEdge.Read(exINI, pSection, "SpawnFromClosestEdge"); - this->RetreatToClosestEdge.Read(exINI, pSection, "RetreatToClosestEdge"); + this->SpawnFromEdge.Read(exINI, pSection, "SpawnFromEdge"); + this->RetreatToEdge.Read(exINI, pSection, "RetreatToEdge"); this->SpawnDistanceFromTarget.Read(exINI, pSection, "SpawnDistanceFromTarget"); this->SpawnHeight.Read(exINI, pSection, "SpawnHeight"); this->LandingDir.Read(exINI, pSection, "LandingDir"); @@ -1753,8 +1753,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->SpawnsPipSize) .Process(this->SpawnsPipOffset) - .Process(this->SpawnFromClosestEdge) - .Process(this->RetreatToClosestEdge) + .Process(this->SpawnFromEdge) + .Process(this->RetreatToEdge) .Process(this->SpawnDistanceFromTarget) .Process(this->SpawnHeight) .Process(this->LandingDir) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index a4ba8469a4..ceef220788 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -282,8 +282,8 @@ class TechnoTypeExt Nullable SpawnsPipSize; Valueable SpawnsPipOffset; - Valueable SpawnFromClosestEdge; - Valueable RetreatToClosestEdge; + Valueable SpawnFromEdge; + Valueable RetreatToEdge; Nullable SpawnDistanceFromTarget; Nullable SpawnHeight; Nullable LandingDir; @@ -779,8 +779,8 @@ class TechnoTypeExt , SpawnsPipSize {} , SpawnsPipOffset { { 0,0 } } - , SpawnFromClosestEdge { false } - , RetreatToClosestEdge { false } + , SpawnFromEdge { EdgeType::Owner } + , RetreatToEdge { EdgeType::Owner } , SpawnDistanceFromTarget {} , SpawnHeight {} , LandingDir {} diff --git a/src/Utilities/Enum.h b/src/Utilities/Enum.h index 2e9d9645f4..c726b6c356 100644 --- a/src/Utilities/Enum.h +++ b/src/Utilities/Enum.h @@ -411,3 +411,10 @@ enum class InterpolationMode : BYTE None = 0, Linear = 1 }; + +enum class EdgeType : BYTE +{ + Owner = 0, + Closest = 1, + Random = 2 +}; diff --git a/src/Utilities/TemplateDef.h b/src/Utilities/TemplateDef.h index dcac3f3534..8348b2d167 100644 --- a/src/Utilities/TemplateDef.h +++ b/src/Utilities/TemplateDef.h @@ -1271,6 +1271,34 @@ if(_strcmpi(parser.value(), #name) == 0){ value = __uuidof(name ## LocomotionCla return false; } + template <> + inline bool read(EdgeType& value, INI_EX& parser, const char* pSection, const char* pKey) + { + if (parser.ReadString(pSection, pKey)) + { + auto str = parser.value(); + if (_strcmpi(str, "owner") == 0) + { + value = EdgeType::Owner; + } + else if (_strcmpi(str, "closest") == 0) + { + value = EdgeType::Closest; + } + else if (_strcmpi(str, "random") == 0) + { + value = EdgeType::Random; + } + else + { + Debug::INIParseFailed(pSection, pKey, str, "Expected an edge type"); + return false; + } + return true; + } + return false; + } + template <> inline bool read(HorizontalPosition& value, INI_EX& parser, const char* pSection, const char* pKey) {