diff --git a/CREDITS.md b/CREDITS.md index 0e2786a9ee..498a936437 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -545,6 +545,7 @@ This page lists all the individual contributions to the project by their author. - Fix for pathfinding crashes on big maps due to too small pathfinding node buffer - Fix an issue that units' `LaserTrails` will always lags behind by one frame - Fix an issue that the currently hovered planning node not update up-to-date, such as using hotkeys to select technos + - Allow the aircraft to enter area guard mission and not crash immediately without any airport - **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 f29d41a73a..e3500a07bc 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -325,24 +325,29 @@ FiringForceScatter=true ; boolean ### Extended Aircraft Missions - Aircraft will now be able to use waypoints. -- When a `guard` command (`[G]` by default) is issued, the aircraft will search for targets around the current location and return immediately when target is not found, target is destroyed or ammos are depleted. - - If the target is destroyed but ammos are not depleted yet, it will also return because the aircraft's command is one-time. +- When a `guard` command (`[G]` by default) or a `area guard` command (`[Ctrl]+[Alt]`) is issued, the aircraft will search for targets around the position (for `guard` is the current location, for `area guard` is the target position) and return immediately when ammos are depleted. + - If the target is not found, or if there is still ammo when the target is destroyed, it will continue to hover over the guarded area. - When an `attack move` command (`[Ctrl]+[Shift]`) is issued, the aircraft will move towards the destination and search for nearby targets on the route for attack. Once ammo is depleted or the destination is reached, it will return. - If the automatically selected target is destroyed but ammo is not depleted yet during the process, the aircraft will continue flying to the destination. - In addition, the actions of aircraft are also changed. - `ExtendedAircraftMissions.SmoothMoving` controls whether the aircraft will return to the airport when the distance to the destination is less than half of `SlowdownDistance` or its turning radius. - `ExtendedAircraftMissions.EarlyDescend` controls whether the aircraft not have to fly directly above the airport before starting to descend when the distance between the aircraft and the landing point is less than `SlowdownDistance` (also work for aircraft spawned by aircraft carriers). - `ExtendedAircraftMissions.RearApproach` controls whether the aircraft should start landing at the airport from the opposite direction of `LandingDir`. + - `ExtendedAircraftMissions.FastScramble` controls whether the aircraft can scramble when its airport has been destroyed. + - `ExtendedAircraftMissions.UnlandDamage` controls the damage suffered by the aircraft every 4 frames when there is no airport for the aircraft to land. If the value is negative, it will crash immediately. Not recommended to use when `ExtendedAircraftMissions` is not enabled. In `rulesmd.ini`: ```ini [General] -ExtendedAircraftMissions=false ; boolean - -[SOMEAIRCRAFT] ; AircraftType -ExtendedAircraftMissions.SmoothMoving= ; boolean, default to [General] -> ExtendedAircraftMissions -ExtendedAircraftMissions.EarlyDescend= ; boolean, default to [General] -> ExtendedAircraftMissions -ExtendedAircraftMissions.RearApproach= ; boolean, default to [General] -> ExtendedAircraftMissions +ExtendedAircraftMissions=false ; boolean +ExtendedAircraftMissions.UnlandDamage=-1 ; integer + +[SOMEAIRCRAFT] ; AircraftType +ExtendedAircraftMissions.SmoothMoving= ; boolean, default to [General] -> ExtendedAircraftMissions +ExtendedAircraftMissions.EarlyDescend= ; boolean, default to [General] -> ExtendedAircraftMissions +ExtendedAircraftMissions.RearApproach= ; boolean, default to [General] -> ExtendedAircraftMissions +ExtendedAircraftMissions.FastScramble= ; boolean, default to [General] -> ExtendedAircraftMissions +ExtendedAircraftMissions.UnlandDamage= ; integer, default to [General] -> ExtendedAircraftMissions.UnlandDamage ``` ### Fixed spawn distance & spawn height for airstrike / SpyPlane aircraft diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 9626ad39c9..3708c58421 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -10,7 +10,7 @@ You can use the migration utility (can be found on [Phobos supplementaries repo] ### From vanilla -- Map trigger action `125 Build At...` now plays buildup by default if available, this can be toggled off using the third parameter (values other than 0). See [required changes for `fadata.ini`](#for-map-editor-final-alert-2) on how to enable the parameter in map editor. +- Map trigger action `125 Build At...` now plays buildup by default if available, this can be toggled off using the third parameter (values other than 0). See [required changes for `fadata.ini`](#for-map-editor-final-alert-2) on how to enable the parameter in map editor. - `IsSimpleDeployer` units now obey deploying facing constraint even without deploying animation. To disable this, set `DeployDir` (defaults to `[AudioVisual] -> DeployDir`) to -1. - `Vertical=true` projectiles now default to completely downwards initial trajectory/facing regardless of if their projectile image has `Voxel=true` or not. This behavior can be reverted by setting `VerticalInitialFacing=false` on projectile in `rulesmd.ini`. - `Vertical=true` projectiles no longer move horizontally if fired by aircraft by default. To re-enable this behaviour set `Vertical.AircraftFix=false` on the projectile. @@ -446,6 +446,7 @@ New: - Randomized anims for several behaviors (by Ollerus) - Shield respawn animation and weapon (by Ollerus) - Customize the chained damage of the wall (by TaranDahl) +- Allow the aircraft to enter area guard mission and not crash immediately without any airport (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/Aircraft/Hooks.cpp b/src/Ext/Aircraft/Hooks.cpp index 2c920daf66..9ca656d3ef 100644 --- a/src/Ext/Aircraft/Hooks.cpp +++ b/src/Ext/Aircraft/Hooks.cpp @@ -483,6 +483,15 @@ bool __fastcall AircraftTypeClass_CanUseWaypoint(AircraftTypeClass* pThis) } DEFINE_FUNCTION_JUMP(VTABLE, 0x7E2908, AircraftTypeClass_CanUseWaypoint) +static inline int GetTurningRadius(AircraftClass* pThis) +{ + constexpr double epsilon = 1e-10; + constexpr double raw2Radian = Math::TwoPi / 65536; + // GetRadian<65536>() is an incorrect method + const double rotRadian = std::abs(static_cast(pThis->PrimaryFacing.ROT.Raw) * raw2Radian); + return rotRadian > epsilon ? static_cast(static_cast(pThis->Type->Speed) / rotRadian) : 0; +} + // Move: smooth the planning paths and returning route DEFINE_HOOK_AGAIN(0x4168C7, AircraftClass_Mission_Move_SmoothMoving, 0x5) DEFINE_HOOK(0x416A0A, AircraftClass_Mission_Move_SmoothMoving, 0x5) @@ -500,7 +509,7 @@ DEFINE_HOOK(0x416A0A, AircraftClass_Mission_Move_SmoothMoving, 0x5) if (!pType->AirportBound) return 0; - const auto extendedMissions = RulesExt::Global()->ExtendedAircraftMissions; + const bool extendedMissions = RulesExt::Global()->ExtendedAircraftMissions; if (!TechnoTypeExt::ExtMap.Find(pType)->ExtendedAircraftMissions_SmoothMoving.Get(extendedMissions)) return 0; @@ -509,14 +518,13 @@ DEFINE_HOOK(0x416A0A, AircraftClass_Mission_Move_SmoothMoving, 0x5) // When the horizontal distance between the aircraft and its destination is greater than half of its deceleration distance // or its turning radius, continue to move forward, otherwise return to airbase or execute the next planning waypoint - const auto rotRadian = std::abs(pThis->PrimaryFacing.ROT.Raw * (Math::TwoPi / 65536)); // GetRadian<65536>() is an incorrect method - const auto turningRadius = rotRadian > 1e-10 ? static_cast(pType->Speed / rotRadian) : 0; + const int turningRadius = GetTurningRadius(pThis); if (distance > std::max((pType->SlowdownDistance / 2), turningRadius)) return (R->Origin() == 0x4168C7 ? ContinueMoving1 : ContinueMoving2); // Try next planning waypoint first, then return to air base if it does not exist or cannot be taken - if (!extendedMissions || !pThis->TryNextPlanningTokenNode()) + if (!extendedMissions || (!pThis->TryNextPlanningTokenNode() && pThis->QueuedMission != Mission::Area_Guard)) pThis->EnterIdleMode(false, true); return EnterIdleAndReturn; @@ -539,6 +547,30 @@ DEFINE_HOOK(0x4DDD66, FootClass_IsLandZoneClear_ReplaceHardcode, 0x6) // To avoi // Skip duplicated aircraft check DEFINE_PATCH(0x4CF033, 0x8B, 0x06, 0xEB, 0x18); // mov eax, [esi] ; jmp short loc_4CF04F ; +DEFINE_JUMP(LJMP, 0x4179E2, 0x417B44); + +// Fix enter mission +DEFINE_HOOK(0x419EF6, AircraftClass_Mission_Enter_FixNotCarryall, 0x7) +{ + enum { SkipGameCode = 0x419EFD }; + + GET(AircraftClass* const, pThis, ESI); + + return pThis->Type->Carryall ? 0 : SkipGameCode; +} + +// Skip set chaotic ArchiveTarget +void __fastcall AircraftClass_SetArchiveTarget_Wrapper(AircraftClass* pThis, void* _, AbstractClass* pTarget) +{ + if (!RulesExt::Global()->ExtendedAircraftMissions) + pThis->SetArchiveTarget(pTarget); +} +DEFINE_FUNCTION_JUMP(CALL, 0x41AB09, AircraftClass_SetArchiveTarget_Wrapper); +DEFINE_FUNCTION_JUMP(CALL, 0x41AC0A, AircraftClass_SetArchiveTarget_Wrapper); +DEFINE_FUNCTION_JUMP(CALL, 0x41AC2E, AircraftClass_SetArchiveTarget_Wrapper); +DEFINE_FUNCTION_JUMP(CALL, 0x41AC45, AircraftClass_SetArchiveTarget_Wrapper); +DEFINE_FUNCTION_JUMP(CALL, 0x41AC68, AircraftClass_SetArchiveTarget_Wrapper); +DEFINE_FUNCTION_JUMP(CALL, 0x41ACB9, AircraftClass_SetArchiveTarget_Wrapper); DEFINE_HOOK(0x4CF190, FlyLocomotionClass_FlightUpdate_SetPrimaryFacing, 0x6) // Make aircraft not to fly directly to the airport before starting to land { @@ -553,10 +585,10 @@ DEFINE_HOOK(0x4CF190, FlyLocomotionClass_FlightUpdate_SetPrimaryFacing, 0x6) // REF_STACK(CoordStruct, destination, STACK_OFFSET(0x48, 0x8)); auto horizontalDistance = [&destination](const CoordStruct& location) - { - const auto delta = Point2D { location.X, location.Y } - Point2D { destination.X, destination.Y }; - return static_cast(delta.Magnitude()); - }; + { + const auto delta = Point2D { location.X, location.Y } - Point2D { destination.X, destination.Y }; + return static_cast(delta.Magnitude()); + }; const auto pFoot = *pFootPtr; const auto pAircraft = abstract_cast(pFoot); @@ -584,17 +616,16 @@ DEFINE_HOOK(0x4CF190, FlyLocomotionClass_FlightUpdate_SetPrimaryFacing, 0x6) // const auto pType = pAircraft->Type; // Like smooth moving - const auto rotRadian = std::abs(pAircraft->PrimaryFacing.ROT.Raw * (Math::TwoPi / 65536)); - const auto turningRadius = rotRadian > 1e-10 ? static_cast(pType->Speed / rotRadian) : 0; + const int turningRadius = GetTurningRadius(pAircraft); // diameter = 2 * radius - const auto cellCounts = Math::max((pType->SlowdownDistance / Unsorted::LeptonsPerCell), (turningRadius / 128)); + const int cellCounts = Math::max((pType->SlowdownDistance / Unsorted::LeptonsPerCell), (turningRadius / 128)); // The direction of the airport const auto currentDir = DirStruct(Math::atan2(footCoords.Y - destination.Y, destination.X - footCoords.X)); // Included angle's raw - const auto difference = static_cast(static_cast(currentDir.Raw) - static_cast(landingDir.Raw)); + const short difference = static_cast(static_cast(currentDir.Raw) - static_cast(landingDir.Raw)); // Land from this direction of the airport const auto landingFace = landingDir.GetFacing<8>(4); @@ -636,7 +667,9 @@ DEFINE_HOOK(0x4CF3D0, FlyLocomotionClass_FlightUpdate_SetFlightLevel, 0x7) // Ma if (pType->HunterSeeker) return 0; - if (!TechnoTypeExt::ExtMap.Find(pType)->ExtendedAircraftMissions_EarlyDescend.Get(RulesExt::Global()->ExtendedAircraftMissions)) + const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType); + + if (!pTypeExt->ExtendedAircraftMissions_EarlyDescend.Get(RulesExt::Global()->ExtendedAircraftMissions)) return 0; enum { SkipGameCode = 0x4CF4D2 }; @@ -665,7 +698,9 @@ DEFINE_HOOK(0x4CF3D0, FlyLocomotionClass_FlightUpdate_SetFlightLevel, 0x7) // Ma // Check returning actions if (distance < pType->SlowdownDistance && pAircraft->Destination - && (pAircraft->DockNowHeadingTo == pAircraft->Destination || pAircraft->SpawnOwner == pAircraft->Destination)) + && (pAircraft->DockNowHeadingTo == pAircraft->Destination || pAircraft->SpawnOwner == pAircraft->Destination) + && (!pTypeExt->ExtendedAircraftMissions_RearApproach.Get(RulesExt::Global()->ExtendedAircraftMissions) + || std::abs(static_cast(static_cast(DirStruct(AircraftExt::GetLandingDir(pAircraft)).Raw) - static_cast(pAircraft->PrimaryFacing.Current().Raw))) < 16384)) { // Slow descent const auto floorHeight = MapClass::Instance.GetCellFloorHeight(pThis->MovingDestination); @@ -681,27 +716,181 @@ DEFINE_HOOK(0x4CF3D0, FlyLocomotionClass_FlightUpdate_SetFlightLevel, 0x7) // Ma return SkipGameCode; } -// AreaGuard: return when no ammo or first target died +DEFINE_HOOK(0x4CE42A, FlyLocomotionClass_StateUpdate_NoLanding, 0x6) // Prevent aircraft from hovering due to cyclic enter Guard and AreaGuard missions when above buildings +{ + enum { SkipGameCode = 0x4CE441 }; + + GET(FootClass* const, pLinkTo, EAX); + + if (!RulesExt::Global()->ExtendedAircraftMissions) + return 0; + + const auto pAircraft = abstract_cast(pLinkTo); + + if (!pAircraft || pAircraft->Airstrike || pAircraft->Spawned || pAircraft->GetCurrentMission() == Mission::Enter) + return 0; + + return SkipGameCode; +} + +DEFINE_HOOK(0x414DA8, AircraftClass_Update_UnlandableDamage, 0x6) // After FootClass_Update +{ + GET(AircraftClass* const, pThis, ESI); + + if (pThis->IsAlive && pThis->Type->AirportBound && !pThis->Airstrike && !pThis->Spawned) + { + const bool extendedMissions = RulesExt::Global()->ExtendedAircraftMissions; + + // Check dock building + if (extendedMissions) + pThis->TryNearestDockBuilding(&pThis->Type->Dock, 0, 0); + + if (pThis->DockNowHeadingTo) + { + // Exit the aimless hovering state and return to the new airport + if (pThis->GetCurrentMission() == Mission::Area_Guard && pThis->MissionStatus) + { + pThis->SetArchiveTarget(nullptr); + pThis->EnterIdleMode(false, true); + } + } + else if (pThis->IsInAir()) + { + int damage = TechnoTypeExt::ExtMap.Find(pThis->Type)->ExtendedAircraftMissions_UnlandDamage.Get(RulesExt::Global()->ExtendedAircraftMissions_UnlandDamage); + + if (damage > 0) + { + if (!extendedMissions && !pThis->unknown_bool_6B3 && pThis->TryNearestDockBuilding(&pThis->Type->Dock, 0, 0)) + return 0; + + // Injury every four frames + if (!((Unsorted::CurrentFrame - pThis->LastFireBulletFrame + pThis->UniqueID) & 0x3)) + pThis->ReceiveDamage(&damage, 0, RulesClass::Instance->C4Warhead, nullptr, true, false, nullptr); + } + else if (damage < 0) + { + // Avoid using circular movement paths to prevent the aircraft from crashing + if (extendedMissions) + pThis->Crash(nullptr); + } + } + } + + return 0; +} + +// Guard: restart area guard +DEFINE_HOOK(0x41A5C7, AircraftClass_Mission_Guard_StartAreaGuard, 0x6) +{ + enum { SkipGameCode = 0x41A6AC }; + + GET(AircraftClass* const, pThis, ESI); + + if (!RulesExt::Global()->ExtendedAircraftMissions || pThis->Team || !pThis->IsArmed() || pThis->Airstrike || pThis->Spawned) + return 0; + + const auto pArchive = pThis->ArchiveTarget; + + if (!pArchive || !pThis->Ammo) + return 0; + + pThis->SetDestination(pArchive, true); + pThis->QueueMission(Mission::Area_Guard, false); + return SkipGameCode; +} + +// AreaGuard: Hover over to guard, or return when no ammo DEFINE_HOOK_AGAIN(0x41A982, AircraftClass_Mission_AreaGuard, 0x6) DEFINE_HOOK(0x41A96C, AircraftClass_Mission_AreaGuard, 0x6) { - enum { SkipGameCode = 0x41A97A }; + enum { SkipGameCode = 0x41A9DA }; GET(AircraftClass* const, pThis, ESI); - if (RulesExt::Global()->ExtendedAircraftMissions && !pThis->Team && pThis->Ammo && pThis->IsArmed()) + if (!RulesExt::Global()->ExtendedAircraftMissions || pThis->Team || !pThis->IsArmed() || pThis->Airstrike || pThis->Spawned) + return 0; + + auto enterIdleMode = [pThis]() -> bool { - auto coords = pThis->GetCoords(); + // Avoid duplicate checks in Update + if (pThis->MissionStatus) + return false; - if (pThis->TargetAndEstimateDamage(coords, ThreatType::Area)) - pThis->QueueMission(Mission::Attack, false); - else if (pThis->Destination && pThis->Destination != pThis->DockNowHeadingTo) - pThis->EnterIdleMode(false, true); + pThis->EnterIdleMode(false, true); - return SkipGameCode; + if (pThis->DockNowHeadingTo) + return true; + + // Hovering state without any airport + pThis->MissionStatus = 1; + return false; + }; + auto hoverOverArchive = [pThis](const CoordStruct& coords, AbstractClass* pDest) + { + const auto& location = pThis->Location; + const int turningRadius = GetTurningRadius(pThis); + const double distance = Math::max(1.0, Point2D { coords.X, coords.Y }.DistanceFrom(Point2D { location.X, location.Y })); + + // Random hovering direction + const double ratio = (((pThis->LastFireBulletFrame + pThis->UniqueID) & 1) ? turningRadius : -turningRadius) / distance; + + // Fly sideways towards the target, and extend the distance to ensure no deceleration + const CoordStruct destination + { + (static_cast(coords.X - ratio * (location.Y - coords.Y)) - location.X) * 4 + location.X, + (static_cast(coords.Y + ratio * (location.X - coords.X)) - location.Y) * 4 + location.Y, + coords.Z + }; + + pThis->Locomotor->Move_To(destination); + pThis->IsLocked = distance < turningRadius; + }; + + if (const auto pArchive = pThis->ArchiveTarget) + { + if (pThis->Ammo) + { + auto coords = pArchive->GetCoords(); + + if (!pThis->TargetingTimer.HasTimeLeft() && pThis->TargetAndEstimateDamage(coords, ThreatType::Area)) + { + // Without an airport, there is no need to record the previous location + if (pThis->MissionStatus) + pThis->SetArchiveTarget(nullptr); + + pThis->QueueMission(Mission::Attack, false); + } + else + { + // Check dock building + if (!pThis->MissionStatus && !pThis->TryNearestDockBuilding(&pThis->Type->Dock, 0, 0)) + pThis->MissionStatus = 1; + + hoverOverArchive(coords, pArchive); + } + } + else if (!enterIdleMode() && pThis->IsAlive) + { + // continue circling + hoverOverArchive(pArchive->GetCoords(), pArchive); + } + } + else if (!pThis->Destination) + { + enterIdleMode(); } - return 0; + R->EAX(1); + return SkipGameCode; +} + +DEFINE_HOOK(0x7001B0, TechnoClass_MouseOverObject_EnableGuardObject, 0x7) +{ + enum { SkipCheckCanGuard = 0x7001E9, ContinueCheckCanGuard = 0x7001BC }; + + GET(TechnoClass* const, pThis, ESI); + + return pThis->WhatAmI() == AbstractType::Aircraft && !RulesExt::Global()->ExtendedAircraftMissions ? SkipCheckCanGuard : ContinueCheckCanGuard; } // Sleep: return to airbase if in incorrect sleep status @@ -766,12 +955,25 @@ DEFINE_HOOK(0x418CD1, AircraftClass_Mission_Attack_ContinueFlyToDestination, 0x6 if (!pThis->Target) { - if (!RulesExt::Global()->ExtendedAircraftMissions || !pThis->MegaMissionIsAttackMove() || !pThis->MegaDestination) + if (!RulesExt::Global()->ExtendedAircraftMissions || pThis->Airstrike || pThis->Spawned) return Continue; - pThis->SetDestination(pThis->MegaDestination, false); - pThis->QueueMission(Mission::Move, true); - pThis->HaveAttackMoveTarget = false; + if (pThis->MegaMissionIsAttackMove() && pThis->MegaDestination) + { + pThis->SetArchiveTarget(nullptr); + pThis->SetDestination(pThis->MegaDestination, false); + pThis->QueueMission(Mission::Move, false); + pThis->HaveAttackMoveTarget = false; + } + else if (!pThis->Team && pThis->ArchiveTarget) + { + pThis->SetDestination(pThis->ArchiveTarget, true); + pThis->QueueMission(Mission::Area_Guard, false); + } + else + { + return Continue; + } } else { @@ -805,9 +1007,58 @@ DEFINE_HOOK(0x414D4D, AircraftClass_Update_ClearTargetIfNoAmmo, 0x6) return 0; }*/ +// Idle: should not crash immediately +DEFINE_HOOK_AGAIN(0x417B82, AircraftClass_EnterIdleMode_NoCrash, 0x6) +DEFINE_HOOK(0x4179F7, AircraftClass_EnterIdleMode_NoCrash, 0x6) +{ + enum { SkipGameCode = 0x417B69 }; + + GET(AircraftClass* const, pThis, ESI); + + if (pThis->Airstrike || pThis->Spawned) + return 0; + + if (TechnoTypeExt::ExtMap.Find(pThis->Type)->ExtendedAircraftMissions_UnlandDamage.Get(RulesExt::Global()->ExtendedAircraftMissions_UnlandDamage) < 0) + return 0; + + if (!pThis->Team && (pThis->CurrentMission != Mission::Area_Guard || !pThis->ArchiveTarget)) + { + const auto pCell = reinterpret_cast(0x41A160)(pThis); + pThis->SetDestination(pCell, true); + pThis->SetArchiveTarget(pCell); + pThis->QueueMission(Mission::Area_Guard, true); + } + else if (!pThis->Destination) + { + const auto pCell = reinterpret_cast(0x41A160)(pThis); + pThis->SetDestination(pCell, true); + } + + return SkipGameCode; +} + // Stop: clear the mega mission and return to airbase immediately // (StopEventFix's DEFINE_HOOK(0x4C75DA, EventClass_RespondToEvent_Stop, 0x6) in Hooks.BugFixes.cpp) +// TakeOff: Emergency takeoff when the airport is destroyed +DEFINE_HOOK(0x4425B6, BuildingClass_ReceiveDamage_NoDestroyLink, 0xA) +{ + enum { LetTakeOff = 0x44259D }; + + GET(BuildingClass* const, pThis, ESI); + GET(TechnoClass* const, pTechno, EDI); + + if (!pThis->Type->Helipad) + return 0; + + const auto pAircraft = abstract_cast(pTechno); + + if (!pAircraft || !TechnoTypeExt::ExtMap.Find(pAircraft->Type)->ExtendedAircraftMissions_FastScramble.Get(RulesExt::Global()->ExtendedAircraftMissions)) + return 0; + + return LetTakeOff; +} + // GreatestThreat: for all the mission that should let the aircraft auto select a target AbstractClass* __fastcall AircraftClass_GreatestThreat(AircraftClass* pThis, void* _, ThreatType threatType, CoordStruct* pSelectCoords, bool onlyTargetHouseEnemy) { @@ -831,8 +1082,9 @@ DEFINE_HOOK(0x4C7403, EventClass_Execute_AircraftAreaGuard, 0x6) GET(TechnoClass* const, pTechno, EDI); - if (RulesExt::Global()->ExtendedAircraftMissions - && pTechno->WhatAmI() == AbstractType::Aircraft) + if (pTechno->WhatAmI() == AbstractType::Aircraft + && RulesExt::Global()->ExtendedAircraftMissions + && !pTechno->Ammo) { // Skip assigning destination / target here. return SkipGameCode; @@ -842,17 +1094,17 @@ DEFINE_HOOK(0x4C7403, EventClass_Execute_AircraftAreaGuard, 0x6) } // Do not untether aircraft when assigning area guard mission by default. -DEFINE_HOOK(0x4C72F2, EventClass_Execute_AircraftAreaGuard_Untether, 0x6) +DEFINE_HOOK(0x4C72F2, EventClass_Execute_AircraftAreaGuard_Unlink, 0x6) { enum { SkipGameCode = 0x4C7349 }; GET(EventClass* const, pThis, ESI); GET(TechnoClass* const, pTechno, EDI); - if (RulesExt::Global()->ExtendedAircraftMissions - && pTechno->WhatAmI() == AbstractType::Aircraft + if (pTechno->WhatAmI() == AbstractType::Aircraft + && RulesExt::Global()->ExtendedAircraftMissions && pThis->MegaMission.Mission == (char)Mission::Area_Guard - && (pTechno->CurrentMission != Mission::Sleep || !pTechno->Ammo)) + && !pTechno->Ammo) { // If we're on dock reloading but have ammo, untether from dock and try to scan for targets. return SkipGameCode; diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index 8f6ae9523a..da9557129c 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -158,6 +158,7 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->HeightShadowScaling_MinScale.Read(exINI, GameStrings::AudioVisual, "HeightShadowScaling.MinScale"); this->ExtendedAircraftMissions.Read(exINI, GameStrings::General, "ExtendedAircraftMissions"); + this->ExtendedAircraftMissions_UnlandDamage.Read(exINI, GameStrings::General, "ExtendedAircraftMissions.UnlandDamage"); this->AmphibiousEnter.Read(exINI, GameStrings::General, "AmphibiousEnter"); this->AmphibiousUnload.Read(exINI, GameStrings::General, "AmphibiousUnload"); this->NoQueueUpToEnter.Read(exINI, GameStrings::General, "NoQueueUpToEnter"); @@ -451,6 +452,7 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->HeightShadowScaling) .Process(this->HeightShadowScaling_MinScale) .Process(this->ExtendedAircraftMissions) + .Process(this->ExtendedAircraftMissions_UnlandDamage) .Process(this->AmphibiousEnter) .Process(this->AmphibiousUnload) .Process(this->NoQueueUpToEnter) diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index 715b844efc..4889f08a42 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -99,6 +99,7 @@ class RulesExt double AirShadowBaseScale_log; Valueable ExtendedAircraftMissions; + Valueable ExtendedAircraftMissions_UnlandDamage; Valueable AmphibiousEnter; Valueable AmphibiousUnload; Valueable NoQueueUpToEnter; @@ -330,6 +331,7 @@ class RulesExt , AirShadowBaseScale_log { 0.693376137 } , ExtendedAircraftMissions { false } + , ExtendedAircraftMissions_UnlandDamage { -1 } , AmphibiousEnter { false } , AmphibiousUnload { false } , NoQueueUpToEnter { false } diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index c474fa23ab..0b9b3895fe 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -942,6 +942,8 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->ExtendedAircraftMissions_SmoothMoving.Read(exINI, pSection, "ExtendedAircraftMissions.SmoothMoving"); this->ExtendedAircraftMissions_EarlyDescend.Read(exINI, pSection, "ExtendedAircraftMissions.EarlyDescend"); this->ExtendedAircraftMissions_RearApproach.Read(exINI, pSection, "ExtendedAircraftMissions.RearApproach"); + this->ExtendedAircraftMissions_FastScramble.Read(exINI, pSection, "ExtendedAircraftMissions.FastScramble"); + this->ExtendedAircraftMissions_UnlandDamage.Read(exINI, pSection, "ExtendedAircraftMissions.UnlandDamage"); this->FallingDownDamage.Read(exINI, pSection, "FallingDownDamage"); this->FallingDownDamage_Water.Read(exINI, pSection, "FallingDownDamage.Water"); @@ -1575,6 +1577,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->ExtendedAircraftMissions_SmoothMoving) .Process(this->ExtendedAircraftMissions_EarlyDescend) .Process(this->ExtendedAircraftMissions_RearApproach) + .Process(this->ExtendedAircraftMissions_FastScramble) + .Process(this->ExtendedAircraftMissions_UnlandDamage) .Process(this->FallingDownDamage) .Process(this->FallingDownDamage_Water) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 087db441e4..277021f05d 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -395,6 +395,8 @@ class TechnoTypeExt Nullable ExtendedAircraftMissions_SmoothMoving; Nullable ExtendedAircraftMissions_EarlyDescend; Nullable ExtendedAircraftMissions_RearApproach; + Nullable ExtendedAircraftMissions_FastScramble; + Nullable ExtendedAircraftMissions_UnlandDamage; Valueable FallingDownDamage; Nullable FallingDownDamage_Water; @@ -775,6 +777,8 @@ class TechnoTypeExt , ExtendedAircraftMissions_SmoothMoving {} , ExtendedAircraftMissions_EarlyDescend {} , ExtendedAircraftMissions_RearApproach {} + , ExtendedAircraftMissions_FastScramble {} + , ExtendedAircraftMissions_UnlandDamage {} , FallingDownDamage { 1.0 } , FallingDownDamage_Water {} diff --git a/src/Misc/Hooks.BugFixes.cpp b/src/Misc/Hooks.BugFixes.cpp index d7aa74d88a..fd8f87f3e0 100644 --- a/src/Misc/Hooks.BugFixes.cpp +++ b/src/Misc/Hooks.BugFixes.cpp @@ -1256,6 +1256,8 @@ DEFINE_HOOK(0x4C75DA, EventClass_RespondToEvent_Stop, 0x6) if (commonAircraft) { + pAircraft->SetArchiveTarget(nullptr); + if (pAircraft->Type->AirportBound) { // To avoid `AirportBound=yes` aircraft with ammo at low altitudes cannot correctly receive stop command and queue Mission::Guard with a `Destination`. @@ -1282,9 +1284,9 @@ DEFINE_HOOK(0x4C75DA, EventClass_RespondToEvent_Stop, 0x6) { const auto pFoot = abstract_cast(pTechno); - // Clear archive target for infantries and vehicles like receive a mega mission - if (pFoot && !pAircraft) - pTechno->SetArchiveTarget(nullptr); + // Clear archive target for foots like receive a mega mission + if (pFoot) + pFoot->SetArchiveTarget(nullptr); // Only stop when it is not under the bridge (meeting the original conditions which has been skipped) if (!pTechno->vt_entry_2B0() || pTechno->OnBridge || pTechno->IsInAir() || pTechno->GetCell()->SlopeIndex)