Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion YRpp
Submodule YRpp updated 1 files
+9 −2 HouseClass.h
27 changes: 16 additions & 11 deletions docs/Fixed-or-Improved-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -845,27 +845,32 @@ 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.
- `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
LandingDir= ; Direction type (integers from 0-255). Accepts negative values as a special case.
[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
Expand Down
2 changes: 1 addition & 1 deletion docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
65 changes: 56 additions & 9 deletions src/Ext/Aircraft/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -76,6 +81,48 @@ 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);
auto const edgeMode = !isOnRetreat ? pTypeExt->SpawnFromEdge : pTypeExt->RetreatToEdge;
auto spawnEdge = edge;
auto refCell = CellStruct::Empty;

switch (edgeMode)
{
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<short>(ScenarioClass::Instance->Random.RandomRanged(-randomRange, randomRange));
short const randomY = static_cast<short>(ScenarioClass::Instance->Random.RandomRanged(-randomRange, randomRange));
refCell += CellStruct { randomX, randomY };
}
break;
}
case EdgeType::Random:
{
int const min = static_cast<int>(Edge::North);
int const max = static_cast<int>(Edge::West);
spawnEdge = static_cast<Edge>(ScenarioClass::Instance->Random.RandomRanged(min, max));
break;
}
default:
{
break;
}
}

return MapClass::Instance.PickCellOnEdge(spawnEdge, refCell, CellStruct::Empty, SpeedType::Winged, true, MovementZone::Normal);
}

DirType AircraftExt::GetLandingDir(AircraftClass* pThis, BuildingClass* pDock)
{
auto const poseDir = static_cast<DirType>(RulesClass::Instance->PoseDir);
Expand Down
3 changes: 2 additions & 1 deletion src/Ext/Aircraft/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
90 changes: 87 additions & 3 deletions src/Ext/House/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,30 +291,114 @@ 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 };

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)
{
Expand Down
4 changes: 4 additions & 0 deletions src/Ext/TechnoType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,8 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->SpawnsPipSize.Read(exINI, pSection, "SpawnsPipSize");
this->SpawnsPipOffset.Read(exINI, pSection, "SpawnsPipOffset");

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");
Expand Down Expand Up @@ -1751,6 +1753,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm)
.Process(this->SpawnsPipSize)
.Process(this->SpawnsPipOffset)

.Process(this->SpawnFromEdge)
.Process(this->RetreatToEdge)
.Process(this->SpawnDistanceFromTarget)
.Process(this->SpawnHeight)
.Process(this->LandingDir)
Expand Down
4 changes: 4 additions & 0 deletions src/Ext/TechnoType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ class TechnoTypeExt
Nullable<Point2D> SpawnsPipSize;
Valueable<Point2D> SpawnsPipOffset;

Valueable<EdgeType> SpawnFromEdge;
Valueable<EdgeType> RetreatToEdge;
Nullable<Leptons> SpawnDistanceFromTarget;
Nullable<int> SpawnHeight;
Nullable<int> LandingDir;
Expand Down Expand Up @@ -777,6 +779,8 @@ class TechnoTypeExt
, SpawnsPipSize {}
, SpawnsPipOffset { { 0,0 } }

, SpawnFromEdge { EdgeType::Owner }
, RetreatToEdge { EdgeType::Owner }
, SpawnDistanceFromTarget {}
, SpawnHeight {}
, LandingDir {}
Expand Down
48 changes: 48 additions & 0 deletions src/Misc/Hooks.Ares.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <Utilities/AresFunctions.h>
#include <Utilities/Helpers.Alex.h>

#include <Ext/Aircraft/Body.h>
#include <Ext/Building/Body.h>
#include <Ext/Sidebar/Body.h>
#include <Ext/EBolt/Body.h>
Expand Down Expand Up @@ -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<TechnoTypeClass*> Types, Iterator<int> 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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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);
}
Loading
Loading