Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
881e5ce
Extract non-template AbstractClassExtension base
ZivDero Jul 15, 2026
f9a962c
Move single-extension classes to the unified 0x18 slot
ZivDero Jul 15, 2026
5284f7d
Merge BuildingClass extension into the TechnoClass hierarchy
ZivDero Jul 15, 2026
83bbf85
Merge BuildingTypeClass extension into the TechnoTypeClass hierarchy
ZivDero Jul 15, 2026
26b5623
Add top-level ClassExtension names for the Techno/Building hierarchy
ZivDero Jul 15, 2026
42addd1
Consolidate trigger extensions on the 0x18 slot and drop redundant se…
ZivDero Jul 15, 2026
ff991fc
Add top-level ClassExtension names for the remaining extensions
ZivDero Jul 15, 2026
d654490
Add intermediate extension bases and reparent Techno/TechnoType onto …
ZivDero Jul 15, 2026
16f1ef4
Instantiate concrete Unit/Infantry/Aircraft leaves instead of the Tec…
ZivDero Jul 15, 2026
9a59cbe
Fix leaf extension ctor hooks to run after the base ctor chain
ZivDero Jul 16, 2026
739ed0e
Rename extension root class to AbstractExt
ZivDero Jul 16, 2026
1314db4
Reparent remaining extensions onto the game mirror hierarchy
ZivDero Jul 16, 2026
1432241
Replace pointer invalidation broadcast with typed detach listener reg…
ZivDero Jul 16, 2026
98fa43d
Centralize extension save/load through per-container streams and post…
ZivDero Jul 16, 2026
8c1273e
Flatten extension data classes onto the mirror hierarchy and adopt Fe…
ZivDero Jul 16, 2026
ca85550
Update project structure documentation for the new extension system
ZivDero Jul 16, 2026
f635444
Track extensions in one container per concrete leaf class
ZivDero Jul 16, 2026
d5fd64e
Destroy extensions from concrete leaf destructor hooks
ZivDero Jul 16, 2026
e5b82d5
Expose slot-read Fetch on every hierarchy level and unify the slot co…
ZivDero Jul 16, 2026
b266495
Hook the twin type destructor bodies the vtables point at
ZivDero Jul 16, 2026
bf483a4
Enable /MP
ZivDero Jul 16, 2026
3d7e46e
Allocate the building extension before the in-constructor Init call
ZivDero Jul 16, 2026
5df72cf
Replace the building TypeExtData shadow member with a typed accessor
ZivDero Jul 16, 2026
4b9685b
Resolve extension data classes in the container instead of per-class …
ZivDero Jul 16, 2026
b7ad2d4
Remove extensions late in the leaf destructor bodies before the base …
ZivDero Jul 16, 2026
a99acc1
Size the destructor hooks to cover whole instructions
ZivDero Jul 16, 2026
66fa4f1
Cover the standalone type destructor tails via the alignment padding
ZivDero Jul 16, 2026
d0ba272
Add the missing ParticleTypeClass destructor hook
ZivDero Jul 16, 2026
4d3ec3c
Clean up extension system comments
ZivDero Jul 16, 2026
3c4511c
Handle in-place cell reconstruction and the static working cell
ZivDero Jul 16, 2026
04a1c92
Tolerate cells absent from the savegame on save and load
ZivDero Jul 16, 2026
8036a68
Rebuild cell extension slots after SetMapDimensions copies cells around
ZivDero Jul 17, 2026
e1fde19
Clear the extension slot when an object is loaded from a savegame
ZivDero Jul 17, 2026
c1fa685
Persist cell extensions inline with their cells
ZivDero Jul 17, 2026
f6c12cc
Polish comments and documentation for the extension rework
ZivDero Jul 17, 2026
471967f
Add changelog and credits entries
ZivDero Jul 17, 2026
830f6ae
Move changelog entries to the end of the lists
ZivDero Jul 17, 2026
1bdefa3
Merge remote-tracking branch 'origin/develop' into inherited-extensio…
ZivDero Jul 17, 2026
be1438b
Merge branch 'develop' into inherited-extensions-2
ZivDero Jul 17, 2026
d1f2524
Add typed checked Fetch/TryFetch accessors, deprecate container lookups
ZivDero Jul 17, 2026
802bf9e
Route all extension lookups through Fetch/TryFetch, shim TechnoExt/Te…
ZivDero Jul 17, 2026
48af0a3
Document the extension lookup API
ZivDero Jul 17, 2026
cfb95b8
Move container hooks to the end of extension Body.cpp files
ZivDero Jul 18, 2026
f4d1695
Standardize container sections in the new leaf extension files
ZivDero Jul 18, 2026
c1ce7a0
Merge remote-tracking branch 'origin/develop' into inherited-extensio…
ZivDero Jul 18, 2026
57d93d3
Remove old TODO
ZivDero Jul 18, 2026
e691df7
Add deprecated ExtData self-aliases for source compatibility
ZivDero Jul 18, 2026
4d5aeee
Use TryFetch where extension lookups are null-checked
ZivDero Jul 18, 2026
1753d26
Make pointer invalidation handlers drop references even without the e…
ZivDero Jul 18, 2026
9c3ffc6
Stack the ParticleTypeClass destructor hook on Ares' address to avoid…
ZivDero Jul 18, 2026
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
4 changes: 3 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,12 @@ scripts/ Build and setup scripts
### Extending a vanilla game class

Each extension lives in `src/Ext/<ClassName>/` with:
- **`Body.h`**: Declares `<Name>Ext` with `ExtData` (inherits `Extension<T>`) and `ExtContainer`/`ExtMap`.
- **`Body.h`**: Declares `<Name>Ext`, part of an inheritance hierarchy mirroring the game's own class tree rooted at `AbstractExt` (`Container.h`); concrete leaf classes also declare an `ExtContainer`/`ExtMap` (inherits `Container<T>`).
- **`Body.cpp`**: Implements constructor, `LoadFromINIFile`, serialization (`Serialize`), and common hooks.
- **`Hooks.cpp` / `Hooks.*.cpp`**: `DEFINE_HOOK(address, Name, size)` macros for Syringe code injection.

Look extensions up only through the typed static accessors available at every level of the hierarchy: `<Name>Ext::Fetch(pThis)` (fatals if the object has no extension attached) or `<Name>Ext::TryFetch(pThis)` (returns null instead — also the right choice while a savegame is loading). The container lookups `ExtMap.Find`/`ExtMap.TryFind` are deprecated compatibility forwards, as is each pre-rework class's `ExtData` alias (the extension class itself replaced the old nested data class); `ExtMap`'s remaining role is lifecycle and persistence machinery (`Allocate`/`Remove`/`LoadFromINI` plus the centralized streaming driven from `Phobos.Ext.cpp`).

After creating a new extension class, **always register it** in `src/Phobos.Ext.cpp` inside the `PhobosTypeRegistry` alias (the `using PhobosTypeRegistry = TypeRegistry<...>` declaration).

### Adding a new enumerable type
Expand Down
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,7 @@ This page lists all the individual contributions to the project by their author.
- Replace `BLOWFISH.DLL` using Red Alert source code
- Adjust the dehardcoding of the 255 `OverlayType` limit to a different format
- Voxel drawing invisible sections skip
- Extension system rework
- **CrimRecya**:
- Fix `LimboKill` not working reliably
- Allow using waypoints, area guard and attack move with aircraft
Expand Down
35 changes: 34 additions & 1 deletion Phobos.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@
<PropertyGroup>
<ShowAllFiles>true</ShowAllFiles>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DevBuild|Win32'">
<ClCompile>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
</ItemDefinitionGroup>
<PropertyGroup Label="Globals">
<ProjectName>Phobos</ProjectName>
<RootNamespace>Phobos</RootNamespace>
Expand Down Expand Up @@ -61,6 +76,11 @@
<ClCompile Include="src\Ext\Beacon\Hooks.cpp" />
<!-- src\Ext\Building -->
<ClCompile Include="src\Ext\Building\Body.cpp" />
<ClCompile Include="src\Ext\Unit\Body.cpp" />
<ClCompile Include="src\Ext\Infantry\Body.cpp" />
<ClCompile Include="src\Ext\UnitType\Body.cpp" />
<ClCompile Include="src\Ext\InfantryType\Body.cpp" />
<ClCompile Include="src\Ext\AircraftType\Body.cpp" />
<ClCompile Include="src\Ext\Building\Hooks.cpp" />
<ClCompile Include="src\Ext\Building\Hooks.Grinding.cpp" />
<ClCompile Include="src\Ext\Building\Hooks.Production.cpp" />
Expand Down Expand Up @@ -277,6 +297,7 @@
<ClCompile Include="src\Utilities\AresHelper.cpp" />
<ClCompile Include="src\Utilities\Constructs.cpp" />
<ClCompile Include="src\Utilities\Debug.cpp" />
<ClCompile Include="src\Utilities\Detach.cpp" />
<ClCompile Include="src\Utilities\EnumFunctions.cpp" />
<ClCompile Include="src\Utilities\GeneralUtils.cpp" />
<ClCompile Include="src\Utilities\Patch.cpp" />
Expand Down Expand Up @@ -317,6 +338,17 @@
<ClInclude Include="src\Ext\Anim\Body.h" />
<ClInclude Include="src\Ext\AnimType\Body.h" />
<ClInclude Include="src\Ext\Building\Body.h" />
<ClInclude Include="src\Ext\Object\Body.h" />
<ClInclude Include="src\Ext\Mission\Body.h" />
<ClInclude Include="src\Ext\Radio\Body.h" />
<ClInclude Include="src\Ext\Foot\Body.h" />
<ClInclude Include="src\Ext\AbstractType\Body.h" />
<ClInclude Include="src\Ext\ObjectType\Body.h" />
<ClInclude Include="src\Ext\Unit\Body.h" />
<ClInclude Include="src\Ext\Infantry\Body.h" />
<ClInclude Include="src\Ext\UnitType\Body.h" />
<ClInclude Include="src\Ext\InfantryType\Body.h" />
<ClInclude Include="src\Ext\AircraftType\Body.h" />
<ClInclude Include="src\Ext\BuildingType\Body.h" />
<ClInclude Include="src\Ext\Bullet\Body.h" />
<ClInclude Include="src\Ext\Bullet\Trajectories\BombardTrajectory.h" />
Expand Down Expand Up @@ -403,6 +435,7 @@
<ClInclude Include="src\Utilities\AresHelper.h" />
<ClInclude Include="src\Utilities\Interpolation.h" />
<ClInclude Include="src\Utilities\Constructs.h" />
<ClInclude Include="src\Utilities\Detach.h" />
<ClInclude Include="src\Utilities\Container.h" />
<ClInclude Include="src\Utilities\Debug.h" />
<ClInclude Include="src\Utilities\Enum.h" />
Expand All @@ -424,4 +457,4 @@
<ClInclude Include="src\Utilities\Template.h" />
<ClInclude Include="src\Utilities\TemplateDef.h" />
</ItemGroup>
</Project>
</Project>
15 changes: 8 additions & 7 deletions docs/Project-guidelines-and-policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,13 @@ Assuming you've successfully cloned and built the project before getting here, y
- `New/` - source code for new ingame classes.
- `Type/` - new enumerated types (types that are declared with a list section in an INI, for example, radiation types) implemented in the project. Every enumerated type class inherits `Enumerable<T>` (where `T` is an enum. type class) class that is defined in `Enumerable.h`.
- `Entity/` - classes that represent ingame entities are located here.
- `Ext/` - source code for vanilla engine class extensions. Each class extension is kept in a separate folder named after vanilla engine class name and contains the following:
- `Body.h` and `Body.cpp` contain class and method definitions/declarations and common extension hooks. Each extension class must contain the following to work correctly:
- `ExtData` - extension data class definition which inherits `Extension<T>` from `Container.h` (where `T` is the class that is being extended), which is the actual class that contains new data for vanilla classes;
- `ExtContainer` - a definition of a special map class to store and look up `ExtData` instances for base class instances which inherits `Container<T>` from `Container.h` (where `T` is the extension data class);
- `ExtMap` - a static instance of `ExtContainer` map;
- constructor, destructor, serialization, deserialization and (for appropriate classes) INI reading hooks.
- `Ext/` - source code for vanilla engine class extensions. Extension classes form a parallel inheritance hierarchy mirroring the game's own class tree (`AbstractExt` from `Container.h` is the root; e.g. `BuildingExt : TechnoExt : RadioExt : MissionExt : ObjectExt : AbstractExt`), and every game object carries exactly one extension instance of the most derived matching type, cached inside the object at the unified `AbstractClass` `0x18` slot. Each class extension is kept in a separate folder named after vanilla engine class name and contains the following:
- `Body.h` and `Body.cpp` contain class and method definitions/declarations and common extension hooks. Each extension class contains the following to work correctly:
- new data members and (for appropriate classes) `LoadFromINIFile`/`SaveToStream`/`LoadFromStream` overrides, plus static helper methods;
- `ExtContainer`/`ExtMap` - the per-class container (inherits `Container<T>` from `Container.h`) that tracks all live instances of one concrete extension class for bulk operations (allocation/removal, centralized savegame streaming, post-load relinking, scenario clearing); only concrete leaf classes (e.g. `BuildingExt`) have containers, while every level of the hierarchy provides typed `Fetch`/`TryFetch`. The container's own `Find`/`TryFind` lookups are deprecated compatibility forwards (`TechnoExt`/`TechnoTypeExt`, whose containers were split into per-leaf ones, keep a lookup-only `ExtMap` stand-in for the same reason), and every pre-rework class carries a deprecated `ExtData` alias of itself so code written against the old nested data classes keeps compiling;
- `Fetch`/`TryFetch` statics - the O(1) lookup used at call sites (`TechnoExt::Fetch(pThis)`); `Fetch` fatals if the object carries no extension, `TryFetch` returns null instead (also the right choice while a savegame is loading);
- constructor/destructor and (for appropriate classes) INI reading hooks. Serialization is centralized in `Phobos.Ext.cpp` - there are no per-class savegame hooks. The one exception is `CellExt`: the game treats cells as value objects with unstable identity (it re-initializes them in place and copies them around wholesale), so cell extensions are persisted inline within each cell's own savegame block instead (see `Ext/Cell/Body.cpp`).
- Extensions subscribe to pointer invalidation by inheriting `Detach::Listener<T>` from `Utilities/Detach.h` and overriding `OnDetach` (see `HouseExt` for an example).
- `Hooks.cpp` and `Hooks.*.cpp` contain non-common hooks to correctly patch in new custom logics.
- `ExtraHeaders/` - extra header files to interact with / describe types included in game binary that are not included in YRpp yet.
- `Misc/` - uncategorized source code, including hooks that don't belong to an extension class.
Expand Down Expand Up @@ -197,7 +198,7 @@ if (SomeCondition())
- A space must be put between braces of empty curly brace blocks.
- To have less Git merge conflicts member initializer lists and other list-like syntax structures used in frequently modified places should be split per-item with item separation characters (commas, for example) placed *after newline character*:
```cpp
ExtData(TerrainTypeClass* OwnerObject) : Extension<TerrainTypeClass>(OwnerObject)
TerrainTypeExt(TerrainTypeClass* OwnerObject) : ObjectTypeExt(OwnerObject)
, SpawnsTiberium_Type(0)
, SpawnsTiberium_Range(1)
, SpawnsTiberium_GrowthStage({ 3, 0 })
Expand Down
2 changes: 2 additions & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ This serves as a changelog for when you just need to drop the new version in wit
- `[WarheadType] -> KillWeapon.OnFirer.AffectsHouses` -> `[WarheadType] -> KillWeapon.OnFirer.AffectsHouse`
- `[WarheadType/SuperWeaponType] -> Convert(N).AffectedHouses` -> `[WarheadType/SuperWeaponType] -> Convert(N).AffectsHouse`
- `[SuperWeaponType] -> LimboKill.Affected` -> `[SuperWeaponType] -> LimboKill.AffectsHouse`
- The extension system has been reworked to follow the game's own class model. Savegames made with earlier Phobos builds are incompatible with this version.

```{note}
- If it is detected that you are using the old INI flags, a warning will be outputted to `debug.log`.
Expand Down Expand Up @@ -614,6 +615,7 @@ HideShakeEffects=false ; boolean
- [RA1-Style Multi-Turret and Multi-Barrel](New-or-Enhanced-Logics.md#ra1-style-multi-turret-and-multi-barrel) (by TaranDahl & CrimRecya)
- [Allow Laser drawing position update](New-or-Enhanced-Logics.md#allow-laser-drawing-position-update) (by Noble_Fish)
- Customize the distance for `NoQueueUpToEnter` transport units to board passengers (by Noble_Fish)
- Reworked the extension system internals to form a class hierarchy mirroring the game's own, with centralized savegame serialization (by ZivDero)

#### Vanilla fixes:
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
Expand Down
2 changes: 1 addition & 1 deletion src/Commands/NextIdleHarvester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ void NextIdleHarvesterCommandClass::Execute(WWKey eInput) const
{
if (auto const pTechno = abstract_cast<TechnoClass*>(pNextObject))
{
auto const pTypeExt = TechnoExt::ExtMap.Find(pTechno)->TypeExtData;
auto const pTypeExt = TechnoExt::Fetch(pTechno)->TypeExtData;

if (pTypeExt->Harvester_Counted && !TechnoExt::IsHarvesting(pTechno))
{
Expand Down
4 changes: 2 additions & 2 deletions src/Commands/ObjectInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ void ObjectInfoCommandClass::Execute(WWKey eInput) const

append("Current HP = (%d / %d)", pFoot->Health, pType->Strength);

auto const pTechnoExt = TechnoExt::ExtMap.Find(pFoot);
Comment thread
ZivDero marked this conversation as resolved.
auto const pTechnoExt = TechnoExt::Fetch(pFoot);
auto const pShieldData = pTechnoExt->Shield.get();

if (pTechnoExt->CurrentShieldType && pShieldData)
Expand Down Expand Up @@ -202,7 +202,7 @@ void ObjectInfoCommandClass::Execute(WWKey eInput) const

append("Current HP = (%d / %d)\n", pBuilding->Health, pBuilding->Type->Strength);

auto const pTechnoExt = TechnoExt::ExtMap.Find(pBuilding);
auto const pTechnoExt = TechnoExt::Fetch(pBuilding);
auto const pShieldData = pTechnoExt->Shield.get();

if (pTechnoExt->CurrentShieldType && pShieldData)
Expand Down
27 changes: 27 additions & 0 deletions src/Ext/AbstractType/Body.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include <AbstractTypeClass.h>
#include <Utilities/Container.h>

// Empty intermediate base mirroring AbstractTypeClass in the extension hierarchy.
class AbstractTypeExt : public AbstractExt
{
public:
explicit AbstractTypeExt(AbstractTypeClass* const OwnerObject) : AbstractExt(OwnerObject)
{ }

AbstractTypeClass* OwnerObject() const
{
return static_cast<AbstractTypeClass*>(this->GetAttachedObject());
}

static AbstractTypeExt* Fetch(const AbstractTypeClass* pThis)
{
return AbstractExt::Fetch<AbstractTypeExt>(pThis);
}

static AbstractTypeExt* TryFetch(const AbstractTypeClass* pThis)
{
return AbstractExt::TryFetch<AbstractTypeExt>(pThis);
}
};
44 changes: 37 additions & 7 deletions src/Ext/Aircraft/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
#include <Ext/BuildingType/Body.h>
#include <Ext/WeaponType/Body.h>

// TODO: Implement proper extended AircraftClass.
AircraftExt::ExtContainer AircraftExt::ExtMap;

void AircraftExt::FireWeapon(AircraftClass* pThis, AbstractClass* pTarget)
{
auto const pExt = TechnoExt::ExtMap.Find(pThis);
auto const pExt = TechnoExt::Fetch(pThis);
const int weaponIndex = pExt->CurrentAircraftWeaponIndex;
auto const pWeapon = pThis->GetWeapon(weaponIndex)->WeaponType;
auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon);
auto const pWeaponExt = WeaponTypeExt::Fetch(pWeapon);
const int burstCount = pWeapon->Burst;
const bool isStrafe = pThis->Is_Strafe();

Expand Down Expand Up @@ -51,7 +51,7 @@ void AircraftExt::FireWeapon(AircraftClass* pThis, AbstractClass* pTarget)
bool AircraftExt::PlaceReinforcementAircraft(AircraftClass* pThis, CoordStruct edgeCoords)
{
auto const pType = pThis->Type;
auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pType);
auto const pTypeExt = TechnoTypeExt::Fetch(pType);
auto dir = DirType::North;
auto coords = edgeCoords;
coords.Z = 0;
Expand Down Expand Up @@ -83,7 +83,7 @@ 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);
auto const pTypeExt = TechnoTypeExt::Fetch(pPlaneType);
auto const edgeMode = !isOnRetreat ? pTypeExt->SpawnFromEdge : pTypeExt->RetreatToEdge;
auto spawnEdge = edge;
auto refCell = CellStruct::Empty;
Expand Down Expand Up @@ -143,7 +143,7 @@ DirType AircraftExt::GetLandingDir(AircraftClass* pThis, BuildingClass* pDock)
if (auto const pBuilding = pDock ? pDock : abstract_cast<BuildingClass*, true>(pLink))
{
auto const pBuildingType = pBuilding->Type;
auto const pBuildingTypeExt = BuildingTypeExt::ExtMap.Find(pBuildingType);
auto const pBuildingTypeExt = BuildingTypeExt::Fetch(pBuildingType);
const int docks = pBuildingType->NumberOfDocks;
const int linkIndex = pBuilding->FindLinkIndex(pThis);

Expand All @@ -159,10 +159,40 @@ DirType AircraftExt::GetLandingDir(AircraftClass* pThis, BuildingClass* pDock)
return pLink->PrimaryFacing.Current().GetDir();
}

const int landingDir = TechnoTypeExt::ExtMap.Find(pType)->LandingDir.Get((int)poseDir);
const int landingDir = TechnoTypeExt::Fetch(pType)->LandingDir.Get((int)poseDir);

if (!pType->AirportBound && landingDir < 0)
return pThis->PrimaryFacing.Current().GetDir();

return static_cast<DirType>(std::clamp(landingDir, 0, 255));
}

// =============================
// container

AircraftExt::ExtContainer::ExtContainer() : Container("AircraftClass") { }
AircraftExt::ExtContainer::~ExtContainer() = default;

// =============================
// container hooks

DEFINE_HOOK(0x413D30, AircraftClass_CTOR, 0x7)
{
GET(AircraftClass*, pItem, ESI);

AircraftExt::ExtMap.Allocate(pItem);

return 0;
}

// Late in every destructor body of the class, right before it chains into the
// base destructor: the last point where the extension is no longer used.
DEFINE_HOOK_AGAIN(0x41426D, AircraftClass_DTOR, 0x9)
DEFINE_HOOK(0x4141FA, AircraftClass_DTOR, 0x9)
{
GET(AircraftClass*, pItem, EDI);

AircraftExt::ExtMap.Remove(pItem);

return 0;
}
38 changes: 35 additions & 3 deletions src/Ext/Aircraft/Body.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,45 @@
#pragma once
#include <Ext/Techno/Body.h>
#include <Ext/Foot/Body.h>
#include <AircraftClass.h>

// TODO: Implement proper extended AircraftClass.

class AircraftExt
// Concrete leaf extension for AircraftClass (empty; techno data lives in TechnoExt).
class AircraftExt final : public FootExt
{
public:
using base_type = AircraftClass;

static constexpr DWORD Canary = 0xA1A2A3A4;

explicit AircraftExt(AircraftClass* const OwnerObject) : FootExt(OwnerObject)
{ }

AircraftClass* OwnerObject() const
{
return static_cast<AircraftClass*>(this->GetAttachedObject());
}

static void FireWeapon(AircraftClass* pThis, AbstractClass* pTarget);
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);

class ExtContainer final : public Container<AircraftExt>
{
public:
ExtContainer();
~ExtContainer();
};

static ExtContainer ExtMap;

static AircraftExt* Fetch(const AircraftClass* pThis)
{
return AbstractExt::Fetch<AircraftExt>(pThis);
}

static AircraftExt* TryFetch(const AircraftClass* pThis)
{
return AbstractExt::TryFetch<AircraftExt>(pThis);
}
};
Loading
Loading