diff --git a/CREDITS.md b/CREDITS.md index 4c788fe55f..e11bea1769 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -372,6 +372,8 @@ 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 + - Allow using waypoints, area guard and attack move with aircraft + - Fix `Stop` command not working so well in some cases - **Ollerus** - Build limit group enhancement - Customizable rocker amplitude diff --git a/YRpp b/YRpp index 0b7b61a49c..0cc38feea7 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit 0b7b61a49c2a5890fee918edcdc58008814f79f7 +Subproject commit 0cc38feea7590cf0478e2f194b766eea80b9a26d diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 6d56a1369f..96292a2692 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -176,6 +176,12 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - `` can now be used as owner for pre-placed objects on skirmish and multiplayer maps. - Follower vehicle index for preplaced vehicles in maps is now explicitly constrained to `[Units]` list in map files and is no longer thrown off by vehicles that could not be created or created vehicles having other vehicles as initial passengers. - Drive/Jumpjet/Ship/Teleport locomotor did not power on when it is un-piggybacked bugfix +- Stop command (`[S]` by default) behavior is now more correct: + - Jumpjets no longer fall into a state of standing by idly. + - Technos are no longer unable to stop the attack move mission. + - Aircraft no longer find airport twice and overlap. + - Aircraft no longer briefly pause in the air before returning. + - Aircraft with `AirportBound=no` continue moving forward. - Unit `Speed` setting now accepts floating-point values. Internally parsed values are clamped down to maximum of 100, multiplied by 256 and divided by 100, the result (which at this point is converted to an integer) then clamped down to maximum of 255 giving effective internal speed value range of 0 to 255, e.g leptons traveled per game frame. - Subterranean movement now benefits from speed multipliers from all sources such as veterancy, AttachEffect etc. @@ -228,6 +234,20 @@ In `rulesmd.ini`: LandingDir= ; Direction type (integers from 0-255). Accepts negative values as a special case. ``` +### 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 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 `rulesmd.ini`: +```ini +[General] +ExtendedAircraftMissions=false ; boolean +``` + ## Animations ### Animation weapon and damage settings diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 37c9f39e2f..e0bd0fdf9d 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -445,6 +445,7 @@ New: - 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) - Option to fix voxel light source being offset and incorrectly tilting on slopes (by Kerbiter) +- Allow using waypoints, area guard and attack move with aircraft (by CrimRecya) - AI superweapon delay timer customization (by Starkku) - Disabling `MultipleFactory` bonus from specific BuildingType (by Starkku) - Customizable ChronoSphere teleport delays for units (by Starkku) @@ -552,6 +553,7 @@ Vanilla fixes: - Fixed objects with ally target and `AttackFriendlies=true` having their target reset every frame, particularly AI-owned buildings (by Starkku) - Follower vehicle index for preplaced vehicles in maps is now explicitly constrained to `[Units]` list in map files and is no longer thrown off by vehicles that could not be created or created vehicles having other vehicles as initial passengers (by Starkku) - Drive/Jumpjet/Ship/Teleport locomotor did not power on when it is un-piggybacked bugfix (by tyuah8) +- Fix `Stop` command not working so well in some cases (by CrimRecya) - Subterranean movement now benefits from speed multipliers from all sources such as veterancy, AttachEffect etc. (by Starkku) Phobos fixes: diff --git a/src/Ext/Aircraft/Hooks.cpp b/src/Ext/Aircraft/Hooks.cpp index 1c9f10bd7b..a6ec8dc5da 100644 --- a/src/Ext/Aircraft/Hooks.cpp +++ b/src/Ext/Aircraft/Hooks.cpp @@ -318,6 +318,179 @@ DEFINE_HOOK(0x415EEE, AircraftClass_Fire_KickOutPassengers, 0x6) return SkipKickOutPassengers; } +// Aircraft mission hard code are all disposable that no ammo, target died or arrived destination all will call the aircraft return airbase +#pragma region ExtendedAircraftMissions + +// Waypoint: enable and smooth moving action +bool __fastcall AircraftTypeClass_CanUseWaypoint(AircraftTypeClass* pThis) +{ + return RulesExt::Global()->ExtendedAircraftMissions.Get(); +} +DEFINE_JUMP(VTABLE, 0x7E2908, GET_OFFSET(AircraftTypeClass_CanUseWaypoint)) + +// KickOut: skip useless tether +DEFINE_JUMP(LJMP, 0x444021, 0x44402E) + +// 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) +{ + enum { EnterIdleAndReturn = 0x416AC0, ContinueMoving1 = 0x416908, ContinueMoving2 = 0x416A47 }; + + GET(AircraftClass* const, pThis, ESI); + GET(CoordStruct* const, pCoords, EAX); + + if (!RulesExt::Global()->ExtendedAircraftMissions) + return 0; + + const auto pType = pThis->Type; + + if (!pType->AirportBound || pThis->Team || pThis->Airstrike || pThis->Spawned) + return 0; + + const int distance = Game::F2I(Point2D { pCoords->X, pCoords->Y }.DistanceFrom(Point2D { pThis->Location.X, pThis->Location.Y })); + + // 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 path + if (distance > std::max((pType->SlowdownDistance >> 1), (2048 / pType->ROT))) + return (R->Origin() == 0x4168C7 ? ContinueMoving1 : ContinueMoving2); + + pThis->EnterIdleMode(false, true); + + return EnterIdleAndReturn; +} + +// AreaGuard: return when no ammo or first target died +DEFINE_HOOK_AGAIN(0x41A982, AircraftClass_Mission_AreaGuard, 0x6) +DEFINE_HOOK(0x41A96C, AircraftClass_Mission_AreaGuard, 0x6) +{ + enum { SkipGameCode = 0x41A97A }; + + GET(AircraftClass* const, pThis, ESI); + + if (RulesExt::Global()->ExtendedAircraftMissions && !pThis->Team && pThis->Ammo && pThis->IsArmed()) + { + auto coords = pThis->GetCoords(); + + if (pThis->TargetAndEstimateDamage(coords, ThreatType::Area)) + { + pThis->QueueMission(Mission::Attack, false); + return SkipGameCode; + } + } + + return 0; +} + +// AttackMove: return when no ammo or arrived destination +bool __fastcall AircraftTypeClass_CanAttackMove(AircraftTypeClass* pThis) +{ + return RulesExt::Global()->ExtendedAircraftMissions.Get(); +} +DEFINE_JUMP(VTABLE, 0x7E290C, GET_OFFSET(AircraftTypeClass_CanAttackMove)) + +DEFINE_HOOK(0x6FA68B, TechnoClass_Update_AttackMovePaused, 0xA) // To make aircrafts not search for targets while resting at the airport, this is designed to adapt to loop waypoint +{ + enum { SkipGameCode = 0x6FA6F5 }; + + GET(TechnoClass* const, pThis, ESI); + + const bool skip = RulesExt::Global()->ExtendedAircraftMissions + && pThis->WhatAmI() == AbstractType::Aircraft + && (!pThis->Ammo || !pThis->IsInAir()); + + return skip ? SkipGameCode : 0; +} + +DEFINE_HOOK(0x4DF3BA, FootClass_UpdateAttackMove_AircraftHoldAttackMoveTarget1, 0x6) // When it have MegaDestination +{ + enum { LoseTarget = 0x4DF3D3, HoldTarget = 0x4DF4AB }; + + GET(FootClass* const, pThis, ESI); + + // The aircraft is constantly moving, which may cause its target to constantly enter and leave its range, so it is fixed to hold the target. + if (RulesExt::Global()->ExtendedAircraftMissions && pThis->WhatAmI() == AbstractType::Aircraft) + return HoldTarget; + + return pThis->InAuxiliarySearchRange(pThis->Target) ? HoldTarget : LoseTarget; +} + +DEFINE_HOOK(0x4DF42A, FootClass_UpdateAttackMove_AircraftHoldAttackMoveTarget2, 0x6) // When it have MegaTarget +{ + enum { ContinueCheck = 0x4DF462, HoldTarget = 0x4DF4AB }; + + GET(FootClass* const, pThis, ESI); + + // Although if the target selected by CS is an object rather than cell. + return (RulesExt::Global()->ExtendedAircraftMissions && pThis->WhatAmI() == AbstractType::Aircraft) ? HoldTarget : ContinueCheck; +} + +DEFINE_HOOK(0x418CD1, AircraftClass_Mission_Attack_ContinueFlyToDestination, 0x6) +{ + enum { Continue = 0x418C43, Return = 0x418CE8 }; + + GET(AircraftClass* const, pThis, ESI); + + if (!pThis->Target) + { + if (!RulesExt::Global()->ExtendedAircraftMissions || !pThis->MegaMissionIsAttackMove() || !pThis->MegaDestination) + return Continue; + + pThis->SetDestination(pThis->MegaDestination, false); + pThis->QueueMission(Mission::Move, true); + pThis->HaveAttackMoveTarget = false; + } + else + { + pThis->MissionStatus = 1; + } + + R->EAX(1); + return Return; +} + +// Idle: clear the target if no ammo +DEFINE_HOOK(0x414D4D, AircraftClass_Update_ClearTargetIfNoAmmo, 0x6) +{ + enum { ClearTarget = 0x414D3F }; + + GET(AircraftClass* const, pThis, ESI); + + if (RulesExt::Global()->ExtendedAircraftMissions && !pThis->Ammo && !pThis->Airstrike && !pThis->Spawned) + { + if (!SessionClass::IsCampaign()) // To avoid AI's aircrafts team repeatedly attempting to attack the target when no ammo + { + if (const auto pTeam = pThis->Team) + pTeam->LiberateMember(pThis); + } + + return ClearTarget; + } + + return 0; +} + +// Stop: clear the mega mission and return to airbase immediately +// (StopEventFix's DEFINE_HOOK(0x4C75DA, EventClass_RespondToEvent_Stop, 0x6) in Hooks.BugFixes.cpp) + +// 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) +{ + if (RulesExt::Global()->ExtendedAircraftMissions) + { + if (const auto pPrimaryWeapon = pThis->GetWeapon(0)->WeaponType) + threatType |= pPrimaryWeapon->AllowedThreats(); + + if (const auto pSecondaryWeapon = pThis->GetWeapon(1)->WeaponType) + threatType |= pSecondaryWeapon->AllowedThreats(); + } + + return pThis->FootClass::GreatestThreat(threatType, pSelectCoords, onlyTargetHouseEnemy); +} +DEFINE_JUMP(VTABLE, 0x7E2668, GET_OFFSET(AircraftClass_GreatestThreat)) + +#pragma endregion + static __forceinline bool CheckSpyPlaneCameraCount(AircraftClass* pThis) { auto const pExt = TechnoExt::ExtMap.Find(pThis); diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index bc72f69570..2cfe87a114 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->ExtendedAircraftMissions.Read(exINI, GameStrings::General, "ExtendedAircraftMissions"); + 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->ExtendedAircraftMissions) .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..34be37d8bd 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 ExtendedAircraftMissions; + Valueable AllowParallelAIQueues; Valueable ForbidParallelAIQueues_Aircraft; Valueable ForbidParallelAIQueues_Building; @@ -223,6 +225,8 @@ class RulesExt , HeightShadowScaling_MinScale { 0.0 } , AirShadowBaseScale_log { 0.693376137 } + , ExtendedAircraftMissions { false } + , AllowParallelAIQueues { true } , ForbidParallelAIQueues_Aircraft { false } , ForbidParallelAIQueues_Building { false } diff --git a/src/Misc/Hooks.BugFixes.cpp b/src/Misc/Hooks.BugFixes.cpp index 984c546dc4..b46ed47a0c 100644 --- a/src/Misc/Hooks.BugFixes.cpp +++ b/src/Misc/Hooks.BugFixes.cpp @@ -1064,6 +1064,78 @@ DEFINE_HOOK(0x743664, UnitClass_ReadFromINI_Follower3, 0x6) #pragma endregion +#pragma region StopEventFix + +DEFINE_HOOK(0x4C75DA, EventClass_RespondToEvent_Stop, 0x6) +{ + enum { SkipGameCode = 0x4C762A }; + + GET(TechnoClass* const, pTechno, ESI); + + // Check aircraft + const auto pAircraft = abstract_cast(pTechno); + const bool commonAircraft = pAircraft && !pAircraft->Airstrike && !pAircraft->Spawned; + const auto mission = pTechno->CurrentMission; + + // To avoid aircraft overlap by keep link if is returning or is in airport now. + if (!commonAircraft || (mission != Mission::Sleep && mission != Mission::Guard && mission != Mission::Enter) + || !pAircraft->DockNowHeadingTo || (pAircraft->DockNowHeadingTo != pAircraft->GetNthLink())) + { + pTechno->SendToEachLink(RadioCommand::NotifyUnlink); + } + + // To avoid technos being unable to stop in attack move mega mission + if (pTechno->MegaMissionIsAttackMove()) + pTechno->ClearMegaMissionData(); + + // Clearing the current target should still be necessary for all technos + pTechno->SetTarget(nullptr); + + if (commonAircraft) + { + 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`. + if (pAircraft->Ammo) + pTechno->SetDestination(nullptr, true); + + // To avoid `AirportBound=yes` aircraft pausing in the air and let they returning to air base immediately. + if (!pAircraft->DockNowHeadingTo || (pAircraft->DockNowHeadingTo != pAircraft->GetNthLink())) // If the aircraft have no valid dock, try to find a new one + pAircraft->EnterIdleMode(false, true); + } + else if (pAircraft->Ammo) + { + // To avoid `AirportBound=no` aircraft ignoring the stop task or directly return to the airport. + if (pAircraft->Destination && static_cast(CellClass::Coord2Cell(pAircraft->Destination->GetCoords()).DistanceFromSquared(pAircraft->GetMapCoords())) > 2) // If the aircraft is moving, find the forward cell then stop in it + pAircraft->SetDestination(pAircraft->GetCell()->GetNeighbourCell(static_cast(pAircraft->PrimaryFacing.Current().GetValue<3>())), true); + } + else if (!pAircraft->DockNowHeadingTo || (pAircraft->DockNowHeadingTo != pAircraft->GetNthLink())) + { + pAircraft->EnterIdleMode(false, true); + } + // Otherwise landing or idling normally without answering the stop command + } + else + { + // Check Jumpjets + const auto pFoot = abstract_cast(pTechno); + const auto pJumpjetLoco = pFoot ? locomotion_cast(pFoot->Locomotor) : nullptr; + + // To avoid jumpjets falling into a state of standing idly by + if (!pJumpjetLoco) // If is not jumpjet, clear the destination is enough + pTechno->SetDestination(nullptr, true); + else if (!pFoot->Destination) // When in attack move and have had a target, the destination will be cleaned up, enter the guard mission can prevent the jumpjets stuck in a status of standing idly by + pTechno->QueueMission(Mission::Guard, true); + else if (static_cast(CellClass::Coord2Cell(pFoot->Destination->GetCoords()).DistanceFromSquared(pTechno->GetMapCoords())) > 2) // If the jumpjet is moving, find the forward cell then stop in it + pTechno->SetDestination(pTechno->GetCell()->GetNeighbourCell(static_cast(pJumpjetLoco->LocomotionFacing.Current().GetValue<3>())), true); + // Otherwise landing or idling normally without answering the stop command + } + + return SkipGameCode; +} + +#pragma endregion + // This shouldn't be here // Author: tyuah8 DEFINE_HOOK_AGAIN(0x4AF94D, EndPiggyback_PowerOn, 0x7) // Drive