diff --git a/CREDITS.md b/CREDITS.md index 5bcca208cc..e52c09cf05 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -463,6 +463,7 @@ This page lists all the individual contributions to the project by their author. - Customize Ares's radar jam logic - Customize if cloning need power - Customize type selection for IFV + - Customize Inhibitors and Designators - **Apollo** - Translucent SHP drawing patches - **ststl**: - Customizable `ShowTimer` priority of superweapons diff --git a/Phobos.vcxproj b/Phobos.vcxproj index 2def0db9a8..ff93548aa9 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -21,6 +21,7 @@ + @@ -181,6 +182,7 @@ + @@ -219,6 +221,7 @@ + @@ -277,6 +280,7 @@ + diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index b124266b4f..40aef896e2 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -948,6 +948,20 @@ Shrapnel.AffectsBuildings=false ; boolean Shrapnel.UseWeaponTargeting=false ; boolean ``` +## Super Weapons + +### Custom affected houses for Ares' Inhibitor and Designator + +- In Ares, only enemies inhibitors and selfowned designators are eligible. Now you can customize it. +- This is independent from [Custom Inhibitor and Designator Type](New-or-Enhanced-Logics.md#custom-inhibitor-and-designator-type) introduced by Phobos. + +In `rulesmd.ini`: +```ini +[SOMESW] ; SuperWeaponType +SW.Inhibitors.Houses=enemies ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) +SW.Designators.Houses=owner ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) +``` + ## Technos ### Airstrike flare visual customizations diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 38342ff07d..d737b24182 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -58,6 +58,8 @@ This page describes all the engine features that are either new and introduced b - `DisableWeapons` can be used to disable ability to fire any and all weapons. - On TechnoTypes with `OpenTopped=true`, `OpenTopped.CheckTransportDisableWeapons` can be set to true to make passengers not be able to fire out if transport's weapons are disabled by `DisableWeapons`. - `Unkillable` can be used to prevent the techno from being killed by taken damage (minimum health will be 1). + - `LaserTrail.Type` can be used to grant a [laser trail](#laser-trails) for the attached object. + - `InhibitType` and `DesignateType` can be used to grant a [super weapon signal](#custom-super-weapon-signal-type) which can act as Inhibitor and Designator for the attached object. - It is possible to set groups for attach effect types by defining strings in `Groups`. - Groups can be used instead of types for removing effects and weapon filters. @@ -149,6 +151,8 @@ ReflectDamage.UseInvokerAsOwner=false ; boolean DisableWeapons=false ; boolean Unkillable=false ; boolean LaserTrail.Type= ; LaserTrailType +InhibitType= ; SuperWeaponSignalType +DesignateType= ; SuperWeaponSignalType Groups= ; comma-separated list of strings (group IDs) [SOMETECHNO] ; TechnoType @@ -187,6 +191,39 @@ SuppressReflectDamage.Types= ; List of AttachEffectTypes SuppressReflectDamage.Groups= ; comma-separated list of strings (group IDs) ``` +### Custom Super Weapon Signal Type + +- It's now possible to define Inhibitor and Designator properties in a Super Weapon Signal Type, which allows more customization than [Ares' Inibitors and Designators](https://ares-developers.github.io/Ares-docs/new/superweapons/range.html). +- `Range` determines the radius of the Inhibitor and Designator effects. Default to the attached techno's `Sight` if not set. +- `Affects` determines the affected house of the Inhibitor and Designator effects. Default to `enemies` if it's used as an Inhibitor, and `owner` if it's Designator. +- `Powered` determines whether or not the effect is rendered inactive if the object it is attached to is deactivated (`PoweredUnit` or affected by EMP) or on low power. + - Notice that it's different from Ares' behavior, which always checks if the building is on low power for Inhibitor, and doesn't check EMP. +- `StopInTemporal` determines whether or not the effect is rendered inactive if the object is being warped out by a `Temporal=yes` warhead. +- `SW.InhibitTypes` determines the Super Weapon Signals that'll act as an Inhibitor for this super weapon, which prevent it to be launched in this area. +- `SW.DesignateTypes` determines the Super Weapon Signals that'll act as an Designator for this super weapon, which only allow it to be launched in this area. +- `InhibitTypes` and `DesignateTypes` determine the Super Weapon Signals a techno owned, which make it an Inhibitor/Designator to super weapons with corresponding `SW.Inhibit/DesignateTypes`. +- This allow a techno to own multiple Inhibitor and Designator effects towards different super weapons, rather than limited to one for all super weapons. + +In `rulesmd.ini` +```ini +[SuperWeaponSignalTypes] +0=SOMESWSIGNAL + +[SOMESWSIGNAL] ; SuperWeaponSignalType +Range= ; integer, default to [SOMETECHNO] -> Sight +Affects= ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all), default to enemies for Inhibitor and owner for Designator +Powered=false ; boolean +StopInTemporal=false ; boolean + +[SOMESW] ; SuperWeaponType +SW.InhibitTypes= ; List of SuperWeaponSignalType +SW.DesignateTypes= ; List of SuperWeaponSignalType + +[SOMETECHNO] ; TechnoType +InhibitTypes= ; List of SuperWeaponSignalType +DesignateTypes= ; List of SuperWeaponSignalType +``` + ### Custom Radiation Types ![image](_static/images/radtype-01.png) diff --git a/docs/Whats-New.md b/docs/Whats-New.md index c92dcbc479..8dc894b0de 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -463,6 +463,7 @@ New: - [Customize if cloning need power](Fixed-or-Improved-Logics.md#customize-if-cloning-need-power) (by NetsuNegi) - [Added Target Filtering Options to AttachEffect System](New-or-Enhanced-Logics.md#attached-effects) (by Flactine) - [Customize type selection for IFV](Fixed-or-Improved-Logics.md#customize-type-selection-for-ifv) (by NetsuNegi) +- Customize Inhibitors and Designators (by NetsuNegi) 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/SWType/Ares/NewSWType.cpp b/src/Ext/SWType/Ares/NewSWType.cpp new file mode 100644 index 0000000000..1458916081 --- /dev/null +++ b/src/Ext/SWType/Ares/NewSWType.cpp @@ -0,0 +1,167 @@ +#include "NewSWType.h" + +TargetingData& AresNewSWType::GetTargetingData(TargetingData& data, SuperWeaponTypeClass** pExt_Ares, HouseClass* pOwner) const +{ + data.TypeExt_Ares = pExt_Ares; + data.Owner = pOwner; + data.NeedsLaunchSite = false; + data.NeedsDesignator = false; + std::memset(&data.LaunchSites, 0, sizeof(data.LaunchSites)); + std::memset(&data.Designators, 0, sizeof(data.Designators)); + std::memset(&data.Inhibitors, 0, sizeof(data.Inhibitors)); + + // get launchsite data + const auto& [minRange, maxRange] = this->GetLaunchSiteRange(pExt_Ares); + + if (minRange >= 0.0 || maxRange >= 0.0) + { + data.NeedsLaunchSite = true; + + for (const auto pBuilding : pOwner->Buildings) + { + if (this->IsLaunchSite(pExt_Ares, pBuilding)) + { + const auto& [minSiteRange, maxSiteRange] = this->GetLaunchSiteRange(pExt_Ares, pBuilding); + const auto center = pBuilding->GetCoords(); + data.LaunchSites.emplace_back(pBuilding, CellClass::Coord2Cell(center), minSiteRange, maxSiteRange); + } + } + } + + const auto pExt = SWTypeExt::ExtMap.Find(*pExt_Ares); + const bool hasDesignateType = !pExt->SW_DesignateTypes.empty(); + const bool hasDesignator = !pExt->SW_Designators.empty() || pExt->SW_AnyDesignator; + const bool hasInhibitType = !pExt->SW_InhibitTypes.empty(); + const bool hasInhibitor = !pExt->SW_Inhibitors.empty() || pExt->SW_AnyInhibitor; + + if (hasDesignateType || hasDesignator || hasInhibitType || hasInhibitor) + { + if (hasDesignateType || hasDesignator) + data.NeedsDesignator = true; + + for (const auto pTechno : TechnoClass::Array) + { + if (!pTechno->IsAlive || !pTechno->Health || pTechno->InLimbo) + continue; + + const bool deactivated = pTechno->Deactivated; + + if (deactivated && !hasDesignateType && !hasInhibitType) + continue; + + const bool isTemporal = pTechno->TemporalTargetingMe || pTechno->IsBeingWarpedOut(); + bool inactive = deactivated || pTechno->IsUnderEMP(); + bool buildingOnline = true; + + if (const auto pBuilding = abstract_cast(pTechno)) + { + buildingOnline = pBuilding->IsPowerOnline(); + inactive |= !buildingOnline; + } + + const auto center = pTechno->GetCoords(); + const auto cell = CellClass::Coord2Cell(center); + const auto pTechnoExt = TechnoExt::ExtMap.Find(pTechno); + const auto pTechnoTypeExt = pTechnoExt->TypeExtData; + const auto pTechnoType = pTechnoTypeExt->OwnerObject(); + const auto pTechnoOwner = pTechno->Owner; + const int sight = pTechnoType->Sight; + + if (hasDesignateType) + { + for (const auto signal : pExt->SW_DesignateTypes) + { + if (inactive && signal->Powered) + continue; + + if (isTemporal && signal->StopInTemporal) + continue; + + if (!EnumFunctions::CanTargetHouse(signal->Affects.Get(AffectedHouse::Owner), pOwner, pTechnoOwner)) + continue; + + bool findSignal = std::ranges::find(pTechnoTypeExt->DesignateTypes, signal) == pTechnoTypeExt->DesignateTypes.cend(); + + if (!findSignal && pTechnoExt->AE.HasDesignator) + { + for (const auto& attachEffect : pTechnoExt->AttachedEffects) + { + if (signal == attachEffect->GetType()->DesignateType) + { + findSignal = true; + break; + } + } + } + + if (!findSignal) + continue; + + const int range = signal->Range.Get(sight); + + if (range > 0) + data.Designators.emplace_back(range * range, cell); + } + } + + if (hasDesignator && !deactivated + && EnumFunctions::CanTargetHouse(pExt->SW_Designators_Houses, pOwner, pTechnoOwner) + && (pExt->SW_AnyDesignator || pExt->SW_Designators.Contains(pTechnoType))) + { + const int range = pTechnoTypeExt->DesignatorRange.Get(sight); + + if (range > 0) + data.Designators.emplace_back(range * range, cell); + } + + if (hasInhibitType) + { + for (const auto signal : pExt->SW_InhibitTypes) + { + if (inactive && signal->Powered) + continue; + + if (isTemporal && signal->StopInTemporal) + continue; + + if (!EnumFunctions::CanTargetHouse(signal->Affects.Get(AffectedHouse::Enemies), pOwner, pTechnoOwner)) + continue; + + bool findSignal = std::ranges::find(pTechnoTypeExt->InhibitTypes, signal) == pTechnoTypeExt->InhibitTypes.cend(); + + if (!findSignal && pTechnoExt->AE.HasInhibitor) + { + for (const auto& attachEffect : pTechnoExt->AttachedEffects) + { + if (signal == attachEffect->GetType()->InhibitType) + { + findSignal = true; + break; + } + } + } + + if (!findSignal) + continue; + + const int range = signal->Range.Get(sight); + + if (range > 0) + data.Inhibitors.emplace_back(range * range, cell); + } + } + + if (hasInhibitor && buildingOnline && !deactivated + && EnumFunctions::CanTargetHouse(pExt->SW_Inhibitors_Houses, pOwner, pTechnoOwner) + && (pExt->SW_AnyInhibitor || pExt->SW_Inhibitors.Contains(pTechnoType))) + { + const int range = pTechnoTypeExt->InhibitorRange.Get(sight); + + if (range > 0) + data.Inhibitors.emplace_back(range * range, cell); + } + } + } + + return data; +} diff --git a/src/Ext/SWType/Ares/NewSWType.h b/src/Ext/SWType/Ares/NewSWType.h new file mode 100644 index 0000000000..2336ffd51a --- /dev/null +++ b/src/Ext/SWType/Ares/NewSWType.h @@ -0,0 +1,53 @@ +#pragma once +#include "../Body.h" + +struct TargetingData +{ + struct LaunchSite + { + BuildingClass* Building; + CellStruct Center; + double MinRange; + double MaxRange; + }; + + struct RangedItem + { + int RangeSqr; + CellStruct Center; + }; + + SuperWeaponTypeClass** TypeExt_Ares; + HouseClass* Owner; + bool NeedsLaunchSite; + bool NeedsDesignator; + std::vector LaunchSites; + std::vector Designators; + std::vector Inhibitors; +}; + +class AresNewSWType +{ +public: + virtual ~AresNewSWType() = default; + virtual bool CanFireAt(const TargetingData& data, const CellStruct cell, bool manual) const { return false; } + virtual bool AbortFire(SuperClass* pSuper, bool isPlayer) const { return false; } + virtual bool Activate(SuperClass* pSuper, const CellStruct cell, bool isPlayer) { return false; } + virtual void Deactivate(SuperClass* pSuper, const CellStruct cell, bool isPlayer) { } + virtual void Initialize(SuperWeaponTypeClass** pExt_Ares) const { } + virtual void LoadFromINI(SuperWeaponTypeClass** pExt_Ares, CCINIClass* pINI) const { } + virtual WarheadTypeClass* GetWarhead(const SuperWeaponTypeClass** pExt_Ares) const { return nullptr; } + virtual AnimTypeClass* GetAnim(const SuperWeaponTypeClass** pExt_Ares) const { return nullptr; } + virtual int GetSound(const SuperWeaponTypeClass** pExt_Ares) const { return -1; } + virtual int GetDamage(const SuperWeaponTypeClass** pExt_Ares) const { return 0; } + virtual std::pair GetRange(const SuperWeaponTypeClass** pExt_Ares) const { return {}; } + virtual const char* GetTypeString() const { return nullptr; } + virtual bool HandlesType(SuperWeaponType type) const { return false; } + virtual SuperWeaponFlags Flags() const { return SuperWeaponFlags::None; } + virtual bool IsLaunchSite(SuperWeaponTypeClass** pExt_Ares, BuildingClass* pBuilding) const { return false; } + virtual std::pair GetLaunchSiteRange(SuperWeaponTypeClass** pExt_Ares, BuildingClass* pBuilding = nullptr) const { return {}; } + virtual bool IsDesignator(SuperWeaponTypeClass** pExt_Ares, HouseClass* pOwner, TechnoClass* pTechno) const { return false; } + virtual bool IsInhibitor(SuperWeaponTypeClass** pExt_Ares, HouseClass* pOwner, TechnoClass* pTechno) const { return false; } + + TargetingData& GetTargetingData(TargetingData& data, SuperWeaponTypeClass** pExt_Ares, HouseClass* pOwner) const; +}; diff --git a/src/Ext/SWType/Body.cpp b/src/Ext/SWType/Body.cpp index f230746dc7..30f9fc16f4 100644 --- a/src/Ext/SWType/Body.cpp +++ b/src/Ext/SWType/Body.cpp @@ -33,8 +33,10 @@ void SWTypeExt::ExtData::Serialize(T& Stm) .Process(this->SW_Unstoppable) .Process(this->SW_Inhibitors) .Process(this->SW_AnyInhibitor) + .Process(this->SW_Inhibitors_Houses) .Process(this->SW_Designators) .Process(this->SW_AnyDesignator) + .Process(this->SW_Designators_Houses) .Process(this->SW_RangeMinimum) .Process(this->SW_RangeMaximum) .Process(this->SW_RequiredHouses) @@ -45,6 +47,8 @@ void SWTypeExt::ExtData::Serialize(T& Stm) .Process(this->SW_PostDependent) .Process(this->SW_MaxCount) .Process(this->SW_Shots) + .Process(this->SW_DesignateTypes) + .Process(this->SW_InhibitTypes) .Process(this->Message_CannotFire) .Process(this->Message_InsufficientFunds) .Process(this->Message_ColorScheme) @@ -118,8 +122,10 @@ void SWTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->SW_Unstoppable.Read(exINI, pSection, "SW.Unstoppable"); this->SW_Inhibitors.Read(exINI, pSection, "SW.Inhibitors"); this->SW_AnyInhibitor.Read(exINI, pSection, "SW.AnyInhibitor"); + this->SW_Inhibitors_Houses.Read(exINI, pSection, "SW.Inhibitors.Houses"); this->SW_Designators.Read(exINI, pSection, "SW.Designators"); this->SW_AnyDesignator.Read(exINI, pSection, "SW.AnyDesignator"); + this->SW_Designators_Houses.Read(exINI, pSection, "SW.Designators.Houses"); this->SW_RangeMinimum.Read(exINI, pSection, "SW.RangeMinimum"); this->SW_RangeMaximum.Read(exINI, pSection, "SW.RangeMaximum"); this->SW_RequiredHouses = pINI->ReadHouseTypesList(pSection, "SW.RequiredHouses", this->SW_RequiredHouses); @@ -131,6 +137,9 @@ void SWTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->SW_MaxCount.Read(exINI, pSection, "SW.MaxCount"); this->SW_Shots.Read(exINI, pSection, "SW.Shots"); + this->SW_DesignateTypes.Read(exINI, pSection, "SW.DesignateTypes"); + this->SW_InhibitTypes.Read(exINI, pSection, "SW.InhibitTypes"); + this->Message_CannotFire.Read(exINI, pSection, "Message.CannotFire"); this->Message_InsufficientFunds.Read(exINI, pSection, "Message.InsufficientFunds"); diff --git a/src/Ext/SWType/Body.h b/src/Ext/SWType/Body.h index e4af1c3879..442b4ce603 100644 --- a/src/Ext/SWType/Body.h +++ b/src/Ext/SWType/Body.h @@ -8,6 +8,7 @@ #include #include +#include #include class SWTypeExt @@ -36,8 +37,10 @@ class SWTypeExt Valueable SW_Unstoppable; ValueableVector SW_Inhibitors; Valueable SW_AnyInhibitor; + Valueable SW_Inhibitors_Houses; ValueableVector SW_Designators; Valueable SW_AnyDesignator; + Valueable SW_Designators_Houses; Valueable SW_RangeMinimum; Valueable SW_RangeMaximum; Valueable SW_Shots; @@ -50,6 +53,9 @@ class SWTypeExt ValueableIdx SW_PostDependent; Valueable SW_MaxCount; + ValueableVector SW_DesignateTypes; + ValueableVector SW_InhibitTypes; + Valueable Message_CannotFire; Valueable Message_InsufficientFunds; ValueableIdx Message_ColorScheme; @@ -125,8 +131,10 @@ class SWTypeExt , SW_Unstoppable { false } , SW_Inhibitors {} , SW_AnyInhibitor { false } + , SW_Inhibitors_Houses { AffectedHouse::Enemies } , SW_Designators { } , SW_AnyDesignator { false } + , SW_Designators_Houses { AffectedHouse::Owner } , SW_RangeMinimum { -1.0 } , SW_RangeMaximum { -1.0 } , SW_RequiredHouses { 0xFFFFFFFFu } @@ -136,6 +144,8 @@ class SWTypeExt , SW_InitialReady { false } , SW_PostDependent {} , SW_MaxCount { -1 } + , SW_DesignateTypes {} + , SW_InhibitTypes {} , SW_Shots { -1 } , Message_CannotFire {} , Message_InsufficientFunds {} @@ -190,10 +200,10 @@ class SWTypeExt { } // Ares 0.A functions - bool IsInhibitor(HouseClass* pOwner, TechnoClass* pTechno) const; + //bool IsInhibitor(HouseClass* pOwner, TechnoClass* pTechno) const; bool HasInhibitor(HouseClass* pOwner, const CellStruct& coords) const; bool IsInhibitorEligible(HouseClass* pOwner, const CellStruct& coords, TechnoClass* pTechno) const; - bool IsDesignator(HouseClass* pOwner, TechnoClass* pTechno) const; + //bool IsDesignator(HouseClass* pOwner, TechnoClass* pTechno) const; bool HasDesignator(HouseClass* pOwner, const CellStruct& coords) const; bool IsDesignatorEligible(HouseClass* pOwner, const CellStruct& coords, TechnoClass* pTechno) const; bool IsLaunchSiteEligible(const CellStruct& Coords, BuildingClass* pBuilding, bool ignoreRange) const; diff --git a/src/Ext/SWType/Hooks.cpp b/src/Ext/SWType/Hooks.cpp index 023073853e..7666c1a9ff 100644 --- a/src/Ext/SWType/Hooks.cpp +++ b/src/Ext/SWType/Hooks.cpp @@ -61,35 +61,140 @@ DEFINE_HOOK(0x6DBE74, Tactical_SuperLinesCircles_ShowDesignatorRange, 0x7) return 0; const auto pSuperType = SuperWeaponTypeClass::Array.GetItem(Unsorted::CurrentSWType); - const auto pExt = SWTypeExt::ExtMap.Find(pSuperType); + const auto pSWExt = SWTypeExt::ExtMap.Find(pSuperType); - if (!pExt->ShowDesignatorRange) + if (!pSWExt->ShowDesignatorRange) return 0; - for (const auto pCurrentTechno : TechnoClass::Array) + const bool hasDesignateType = !pSWExt->SW_DesignateTypes.empty(); + const bool hasDesignator = !pSWExt->SW_Designators.empty(); + const bool hasInhibitType = !pSWExt->SW_InhibitTypes.empty(); + const bool hasInhibitor = !pSWExt->SW_Inhibitors.empty(); + + for (const auto pTechno : TechnoClass::Array) { - const auto pCurrentTechnoType = pCurrentTechno->GetTechnoType(); - const auto pOwner = pCurrentTechno->Owner; - - if (!pCurrentTechno->IsAlive - || pCurrentTechno->InLimbo - || (pOwner != HouseClass::CurrentPlayer && pOwner->IsAlliedWith(HouseClass::CurrentPlayer)) // Ally objects are never designators or inhibitors - || (pOwner == HouseClass::CurrentPlayer && !pExt->SW_Designators.Contains(pCurrentTechnoType)) // Only owned objects can be designators - || (!pOwner->IsAlliedWith(HouseClass::CurrentPlayer) && !pExt->SW_Inhibitors.Contains(pCurrentTechnoType))) // Only enemy objects can be inhibitors - { + if (!pTechno->IsAlive || !pTechno->Health || pTechno->InLimbo) continue; - } - const auto pTechnoTypeExt = TechnoTypeExt::ExtMap.Find(pCurrentTechnoType); + const bool deactivated = pTechno->Deactivated; - const float radius = pOwner == HouseClass::CurrentPlayer - ? (float)(pTechnoTypeExt->DesignatorRange.Get(pCurrentTechnoType->Sight)) - : (float)(pTechnoTypeExt->InhibitorRange.Get(pCurrentTechnoType->Sight)); + if (deactivated && !hasDesignateType && !hasInhibitType) + continue; + + const bool isTemporal = pTechno->TemporalTargetingMe || pTechno->IsBeingWarpedOut(); + bool inactive = deactivated || pTechno->IsUnderEMP(); + bool buildingOnline = true; - CoordStruct coords = pCurrentTechno->GetCenterCoords(); + if (const auto pBuilding = abstract_cast(pTechno)) + { + buildingOnline = pBuilding->IsPowerOnline(); + inactive |= !buildingOnline; + } + + CoordStruct coords = pTechno->GetCenterCoords(); coords.Z = MapClass::Instance.GetCellFloorHeight(coords); + const auto pOwner = pTechno->Owner; const auto color = pOwner->Color; - Game::DrawRadialIndicator(false, true, coords, color, radius, false, true); + const auto pTechnoExt = TechnoExt::ExtMap.Find(pTechno); + const auto pTechnoTypeExt = pTechnoExt->TypeExtData; + const auto pTechnoType = pTechnoTypeExt->OwnerObject(); + const int sight = pTechnoType->Sight; + + if (hasDesignateType) + { + for (const auto signal : pSWExt->SW_DesignateTypes) + { + if (inactive && signal->Powered) + continue; + + if (isTemporal && signal->StopInTemporal) + continue; + + if (!EnumFunctions::CanTargetHouse(signal->Affects.Get(AffectedHouse::Owner), HouseClass::CurrentPlayer, pOwner)) + continue; + + bool findSignal = std::ranges::find(pTechnoTypeExt->DesignateTypes, signal) == pTechnoTypeExt->DesignateTypes.cend(); + + if (!findSignal && pTechnoExt->AE.HasDesignator) + { + for (const auto& attachEffect : pTechnoExt->AttachedEffects) + { + if (signal == attachEffect->GetType()->DesignateType) + { + findSignal = true; + break; + } + } + } + + if (!findSignal) + continue; + + const int radius = signal->Range.Get(sight); + + if (radius > 0) + Game::DrawRadialIndicator(false, true, coords, color, static_cast(radius), false, true); + } + } + + if (hasDesignator && !deactivated) + { + if (EnumFunctions::CanTargetHouse(pSWExt->SW_Designators_Houses, HouseClass::CurrentPlayer, pOwner) && pSWExt->SW_Designators.Contains(pTechnoType)) + { + const int radius = pTechnoTypeExt->DesignatorRange.Get(sight); + + if (radius > 0) + Game::DrawRadialIndicator(false, true, coords, color, static_cast(radius), false, true); + } + } + + if (hasInhibitType) + { + for (const auto signal : pSWExt->SW_InhibitTypes) + { + if (inactive && signal->Powered) + continue; + + if (isTemporal && signal->StopInTemporal) + continue; + + if (!EnumFunctions::CanTargetHouse(signal->Affects.Get(AffectedHouse::Enemies), HouseClass::CurrentPlayer, pOwner)) + continue; + + bool findSignal = std::ranges::find(pTechnoTypeExt->InhibitTypes, signal) == pTechnoTypeExt->InhibitTypes.cend(); + + if (!findSignal && pTechnoExt->AE.HasInhibitor) + { + for (const auto& attachEffect : pTechnoExt->AttachedEffects) + { + if (signal == attachEffect->GetType()->InhibitType) + { + findSignal = true; + break; + } + } + } + + if (!findSignal) + continue; + + const int radius = signal->Range.Get(sight); + + if (radius > 0) + Game::DrawRadialIndicator(false, true, coords, color, static_cast(radius), false, true); + } + } + + if (hasInhibitor && buildingOnline && !deactivated) + { + if (EnumFunctions::CanTargetHouse(pSWExt->SW_Inhibitors_Houses, HouseClass::CurrentPlayer, pOwner) && pSWExt->SW_Inhibitors.Contains(pTechnoType)) + { + const int radius = pTechnoTypeExt->InhibitorRange.Get(sight); + + if (radius > 0) + Game::DrawRadialIndicator(false, true, coords, color, static_cast(radius), false, true); + } + } } return 0; diff --git a/src/Ext/SWType/SWHelpers.cpp b/src/Ext/SWType/SWHelpers.cpp index 20e5b6b9a9..15ef97cbb1 100644 --- a/src/Ext/SWType/SWHelpers.cpp +++ b/src/Ext/SWType/SWHelpers.cpp @@ -42,37 +42,89 @@ std::vector SWTypeExt::ExtData::WeightedRollsHandler(ValueableVector // ============================= // Ares 0.A helpers // Inhibitors check -bool SWTypeExt::ExtData::IsInhibitor(HouseClass* pOwner, TechnoClass* pTechno) const +//bool SWTypeExt::ExtData::IsInhibitor(HouseClass* pOwner, TechnoClass* pTechno) const +//{ +// if (pTechno->IsAlive && pTechno->Health && !pTechno->InLimbo && !pTechno->Deactivated) +// { +// if (EnumFunctions::CanTargetHouse(this->SW_Inhibitors_Houses, pOwner, pTechno->Owner)) +// { +// if (const auto pBuilding = abstract_cast(pTechno)) +// { +// if (!pBuilding->IsPowerOnline()) +// return false; +// } +// +// return this->SW_AnyInhibitor || this->SW_Inhibitors.Contains(pTechno->GetTechnoType()); +// } +// } +// +// return false; +//} + +bool SWTypeExt::ExtData::IsInhibitorEligible(HouseClass* pOwner, const CellStruct& coords, TechnoClass* pTechno) const { - if (pTechno->IsAlive && pTechno->Health && !pTechno->InLimbo && !pTechno->Deactivated) + if (!pTechno->IsAlive || !pTechno->Health || pTechno->InLimbo) + return false; + + const bool deactivated = pTechno->Deactivated; + + if (deactivated && this->SW_InhibitTypes.empty()) + return false; + + const bool isTemporal = pTechno->TemporalTargetingMe || pTechno->IsBeingWarpedOut(); + bool inactive = deactivated || pTechno->IsUnderEMP(); + + if (const auto pBuilding = abstract_cast(pTechno)) + inactive |= !pBuilding->IsPowerOnline(); + + const auto center = pTechno->GetCenterCoords(); + const double distanceSqr = coords.DistanceFromSquared(CellClass::Coord2Cell(center)); + const auto pTechnoExt = TechnoExt::ExtMap.Find(pTechno); + const auto pTechnoTypeExt = pTechnoExt->TypeExtData; + const auto pTechnoType = pTechnoTypeExt->OwnerObject(); + const auto pTechnoOwner = pTechno->Owner; + const int sight = pTechnoType->Sight; + + for (const auto signal : this->SW_InhibitTypes) { - if (!pOwner->IsAlliedWith(pTechno)) + if (inactive && signal->Powered) + continue; + + if (isTemporal && signal->StopInTemporal) + continue; + + if (!EnumFunctions::CanTargetHouse(signal->Affects.Get(AffectedHouse::Enemies), pOwner, pTechnoOwner)) + continue; + + bool findSignal = std::ranges::find(pTechnoTypeExt->InhibitTypes, signal) == pTechnoTypeExt->InhibitTypes.cend(); + + if (!findSignal && pTechnoExt->AE.HasInhibitor) { - if (const auto pBld = abstract_cast(pTechno)) + for (const auto& attachEffect : pTechnoExt->AttachedEffects) { - if (!pBld->IsPowerOnline()) - return false; + if (signal == attachEffect->GetType()->InhibitType) + { + findSignal = true; + break; + } } - - return this->SW_AnyInhibitor || this->SW_Inhibitors.Contains(pTechno->GetTechnoType()); } - } - return false; -} + if (!findSignal) + continue; -bool SWTypeExt::ExtData::IsInhibitorEligible(HouseClass* pOwner, const CellStruct& coords, TechnoClass* pTechno) const -{ - if (this->IsInhibitor(pOwner, pTechno)) - { - const auto pType = pTechno->GetTechnoType(); - const auto pExt = TechnoTypeExt::ExtMap.Find(pType); + const int range = signal->Range.Get(sight); - // get the inhibitor's center - const auto center = pTechno->GetCenterCoords(); + if (distanceSqr <= range * range) + return true; + } - // has to be closer than the inhibitor range (which defaults to Sight) - return coords.DistanceFrom(CellClass::Coord2Cell(center)) <= pExt->InhibitorRange.Get(pType->Sight); + if (EnumFunctions::CanTargetHouse(this->SW_Inhibitors_Houses, pOwner, pTechnoOwner) && (this->SW_AnyInhibitor || this->SW_Inhibitors.Contains(pTechnoType))) + { + const int range = pTechnoTypeExt->InhibitorRange.Get(sight); + + if (distanceSqr <= range * range) + return true; } return false; @@ -81,36 +133,91 @@ bool SWTypeExt::ExtData::IsInhibitorEligible(HouseClass* pOwner, const CellStruc bool SWTypeExt::ExtData::HasInhibitor(HouseClass* pOwner, const CellStruct& coords) const { // does not allow inhibitors - if (this->SW_Inhibitors.empty() && !this->SW_AnyInhibitor) + if (this->SW_InhibitTypes.empty() && this->SW_Inhibitors.empty() && !this->SW_AnyInhibitor) return false; // a single inhibitor in range suffices - return std::any_of(TechnoClass::Array.begin(), TechnoClass::Array.end(), [=, &coords](TechnoClass* pTechno) + return std::ranges::any_of(TechnoClass::Array, [=, &coords](TechnoClass* pTechno) { return this->IsInhibitorEligible(pOwner, coords, pTechno); } ); } // Designators check -bool SWTypeExt::ExtData::IsDesignator(HouseClass* pOwner, TechnoClass* pTechno) const -{ - if (pTechno->Owner == pOwner && pTechno->IsAlive && pTechno->Health && !pTechno->InLimbo && !pTechno->Deactivated) - return this->SW_AnyDesignator || this->SW_Designators.Contains(pTechno->GetTechnoType()); - - return false; -} +//bool SWTypeExt::ExtData::IsDesignator(HouseClass* pOwner, TechnoClass* pTechno) const +//{ +// if (EnumFunctions::CanTargetHouse(this->SW_Designators_Houses, pOwner, pTechno->Owner)) +// { +// if (pTechno->IsAlive && pTechno->Health && !pTechno->InLimbo && !pTechno->Deactivated) +// return this->SW_AnyDesignator || this->SW_Designators.Contains(pTechno->GetTechnoType()); +// } +// +// return false; +//} bool SWTypeExt::ExtData::IsDesignatorEligible(HouseClass* pOwner, const CellStruct& coords, TechnoClass* pTechno) const { - if (this->IsDesignator(pOwner, pTechno)) + if (!pTechno->IsAlive || !pTechno->Health || pTechno->InLimbo) + return false; + + const bool deactivated = pTechno->Deactivated; + + if (deactivated && this->SW_DesignateTypes.empty()) + return false; + + const bool isTemporal = pTechno->TemporalTargetingMe || pTechno->IsBeingWarpedOut(); + bool inactive = deactivated || pTechno->IsUnderEMP(); + + if (const auto pBuilding = abstract_cast(pTechno)) + inactive |= !pBuilding->IsPowerOnline(); + + const auto center = pTechno->GetCenterCoords(); + const double distanceSqr = coords.DistanceFromSquared(CellClass::Coord2Cell(center)); + const auto pTechnoExt = TechnoExt::ExtMap.Find(pTechno); + const auto pTechnoTypeExt = pTechnoExt->TypeExtData; + const auto pTechnoType = pTechnoTypeExt->OwnerObject(); + const auto pTechnoOwner = pTechno->Owner; + const int sight = pTechnoType->Sight; + + for (const auto signal : this->SW_DesignateTypes) { - const auto pType = pTechno->GetTechnoType(); - const auto pExt = TechnoTypeExt::ExtMap.Find(pType); + if (inactive && signal->Powered) + continue; + + if (isTemporal && signal->StopInTemporal) + continue; - // get the designator's center - const auto center = pTechno->GetCenterCoords(); + if (!EnumFunctions::CanTargetHouse(signal->Affects.Get(AffectedHouse::Owner), pOwner, pTechnoOwner)) + continue; + + bool findSignal = std::ranges::find(pTechnoTypeExt->DesignateTypes, signal) == pTechnoTypeExt->DesignateTypes.cend(); + + if (!findSignal && pTechnoExt->AE.HasDesignator) + { + for (const auto& attachEffect : pTechnoExt->AttachedEffects) + { + if (signal == attachEffect->GetType()->DesignateType) + { + findSignal = true; + break; + } + } + } + + if (!findSignal) + continue; + + const int range = signal->Range.Get(sight); + + if (distanceSqr <= range * range) + return true; + } + + if (EnumFunctions::CanTargetHouse(this->SW_Designators_Houses, pOwner, pTechnoOwner) && (this->SW_AnyDesignator || this->SW_Designators.Contains(pTechnoType))) + { + const int range = pTechnoTypeExt->DesignatorRange.Get(sight); - // has to be closer than the designator range (which defaults to Sight) - return coords.DistanceFrom(CellClass::Coord2Cell(center)) <= pExt->DesignatorRange.Get(pType->Sight); + if (distanceSqr <= range * range) + return true; } return false; @@ -119,11 +226,11 @@ bool SWTypeExt::ExtData::IsDesignatorEligible(HouseClass* pOwner, const CellStru bool SWTypeExt::ExtData::HasDesignator(HouseClass* pOwner, const CellStruct& coords) const { // does not require designators - if (this->SW_Designators.empty() && !this->SW_AnyDesignator) + if (this->SW_DesignateTypes.empty() && this->SW_Designators.empty() && !this->SW_AnyDesignator) return true; // a single designator in range suffices - return std::any_of(TechnoClass::Array.begin(), TechnoClass::Array.end(), [=, &coords](TechnoClass* pTechno) + return std::ranges::any_of(TechnoClass::Array, [=, &coords](TechnoClass* pTechno) { return this->IsDesignatorEligible(pOwner, coords, pTechno); }); } diff --git a/src/Ext/Script/Mission.Attack.cpp b/src/Ext/Script/Mission.Attack.cpp index 536a26f77a..f642a13211 100644 --- a/src/Ext/Script/Mission.Attack.cpp +++ b/src/Ext/Script/Mission.Attack.cpp @@ -976,7 +976,8 @@ bool ScriptExt::EvaluateObjectWithMask(TechnoClass* pTechno, int mask, int attac const auto pTechnoTypeExt = TechnoTypeExt::ExtMap.Find(pTechnoType); if (pTechnoTypeExt->RadarJamRadius > 0 - || pTechnoTypeExt->InhibitorRange.isset()) + || pTechnoTypeExt->InhibitorRange.isset() + || !pTechnoTypeExt->InhibitTypes.empty()) { return true; } @@ -1250,10 +1251,12 @@ bool ScriptExt::EvaluateObjectWithMask(TechnoClass* pTechno, int mask, int attac case 30: // Inhibitor - if (!pTechno->Owner->IsNeutral() - && TechnoTypeExt::ExtMap.Find(pTechnoType)->InhibitorRange.isset()) + if (!pTechno->Owner->IsNeutral()) { - return true; + const auto pTechnoTypeExt = TechnoTypeExt::ExtMap.Find(pTechnoType); + + if (pTechnoTypeExt->InhibitorRange.isset() || !pTechnoTypeExt->InhibitTypes.empty()) + return true; } break; diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 23728fd023..f855a60f56 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -2006,6 +2006,8 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() bool hasOnFireDiscardables = false; bool hasRestrictedArmorMultipliers = false; bool hasCritModifiers = false; + bool hasInibitor = false; + bool hasDesignator = false; for (const auto& attachEffect : this->AttachedEffects) { @@ -2031,6 +2033,8 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() reflectsDamage |= type->ReflectDamage; hasOnFireDiscardables |= (type->DiscardOn & DiscardCondition::Firing) != DiscardCondition::None; hasCritModifiers |= (type->Crit_Multiplier != 1.0 || type->Crit_ExtraChance != 0.0); + hasInibitor |= type->InhibitType != nullptr; + hasDesignator |= type->DesignateType != nullptr; } pAE.FirepowerMultiplier = firepower; @@ -2047,6 +2051,8 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() pAE.HasOnFireDiscardables = hasOnFireDiscardables; pAE.HasRestrictedArmorMultipliers = hasRestrictedArmorMultipliers; pAE.HasCritModifiers = hasCritModifiers; + pAE.HasInhibitor = hasInibitor; + pAE.HasDesignator = hasDesignator; if (forceDecloak && pThis->CloakState == CloakState::Cloaked) pThis->Uncloak(true); diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index d6840dcbf9..4ea9ede1d8 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -962,6 +962,9 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Power.Read(exINI, pSection, "Power"); + this->DesignateTypes.Read(exINI, pSection, "DesignateTypes"); + this->InhibitTypes.Read(exINI, pSection, "InhibitTypes"); + this->AllowAirstrike.Read(exINI, pSection, "AllowAirstrike"); this->Image_ConditionYellow.Read(exINI, pSection, "Image.ConditionYellow"); @@ -1603,6 +1606,9 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->Power) + .Process(this->DesignateTypes) + .Process(this->InhibitTypes) + .Process(this->AllowAirstrike) .Process(this->Image_ConditionYellow) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 3ada442893..ca6787a420 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -338,6 +339,9 @@ class TechnoTypeExt Valueable Power; + ValueableVector DesignateTypes; + ValueableVector InhibitTypes; + Nullable AllowAirstrike; Nullable Image_ConditionYellow; @@ -750,6 +754,9 @@ class TechnoTypeExt , Power { } + , DesignateTypes { } + , InhibitTypes { } + , AllowAirstrike { } , Image_ConditionYellow { } diff --git a/src/Misc/Hooks.Ares.cpp b/src/Misc/Hooks.Ares.cpp index 583676d07e..66854a05c9 100644 --- a/src/Misc/Hooks.Ares.cpp +++ b/src/Misc/Hooks.Ares.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include @@ -42,7 +44,6 @@ bool __stdcall ConvertToType(TechnoClass* pThis, TechnoTypeClass* pToType) TechnoTypeClass* __fastcall ShowPromoteAnim(TechnoClass* pThis) { TechnoExt::ShowPromoteAnim(pThis); - return pThis->GetTechnoType(); } @@ -51,6 +52,8 @@ WeaponStruct* __fastcall GetLaserWeapon(BuildingClass* pThis) return BuildingExt::GetLaserWeapon(pThis); } +_GET_FUNCTION_ADDRESS(AresNewSWType::GetTargetingData, AresNewSWType_GetTargetingData_GetAddr) + EBolt* __stdcall CreateEBolt(WeaponTypeClass** pWeaponData) { return EBoltExt::CreateEBolt(*pWeaponData); @@ -118,6 +121,9 @@ void Apply_Ares3_0_Patches() // Redirect Ares's function to our implementation: Patch::Apply_LJMP(AresHelper::AresBaseAddress + 0x112D0, &BuildingExt::KickOutClone); + + // Redirect Ares' NewSWType::GetTargetingData() to our implementation: + Patch::Apply_LJMP(AresHelper::AresBaseAddress + 0x6D1E0, AresNewSWType_GetTargetingData_GetAddr()); } void Apply_Ares3_0p1_Patches() @@ -177,4 +183,7 @@ void Apply_Ares3_0p1_Patches() // Redirect Ares's function to our implementation: Patch::Apply_LJMP(AresHelper::AresBaseAddress + 0x11860, &BuildingExt::KickOutClone); + + // Redirect Ares' NewSWType::GetTargetingData() to our implementation: + Patch::Apply_LJMP(AresHelper::AresBaseAddress + 0x6E1F0, AresNewSWType_GetTargetingData_GetAddr()); } diff --git a/src/New/Entity/AttachEffectClass.h b/src/New/Entity/AttachEffectClass.h index fe792c7a3a..d65fe8d717 100644 --- a/src/New/Entity/AttachEffectClass.h +++ b/src/New/Entity/AttachEffectClass.h @@ -120,6 +120,8 @@ struct AttachEffectTechnoProperties bool HasOnFireDiscardables; bool HasRestrictedArmorMultipliers; bool HasCritModifiers; + bool HasInhibitor; + bool HasDesignator; AttachEffectTechnoProperties() : FirepowerMultiplier { 1.0 } @@ -136,5 +138,7 @@ struct AttachEffectTechnoProperties , HasOnFireDiscardables { false } , HasRestrictedArmorMultipliers { false } , HasCritModifiers { false } + , HasInhibitor { false } + , HasDesignator { false } { } }; diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index c4a4d0c208..b4ef6238b8 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -170,6 +170,8 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->DisableWeapons.Read(exINI, pSection, "DisableWeapons"); this->Unkillable.Read(exINI, pSection, "Unkillable"); this->LaserTrail_Type.Read(exINI, pSection, "LaserTrail.Type"); + this->InhibitType.Read(exINI, pSection, "InhibitType"); + this->DesignateType.Read(exINI, pSection, "DesignateType"); // Groups exINI.ParseStringList(this->Groups, pSection, "Groups"); @@ -239,6 +241,8 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->DisableWeapons) .Process(this->Unkillable) .Process(this->LaserTrail_Type) + .Process(this->InhibitType) + .Process(this->DesignateType) .Process(this->Groups) ; } diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 95a7fa216d..885b16ccb6 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -7,6 +7,7 @@ #include #include #include "LaserTrailTypeClass.h" +#include "SWSignalTypeClass.h" // AE discard condition enum class DiscardCondition : unsigned char @@ -101,6 +102,8 @@ class AttachEffectTypeClass final : public Enumerable Valueable DisableWeapons; Valueable Unkillable; ValueableIdx LaserTrail_Type; + Valueable InhibitType; + Valueable DesignateType; std::vector Groups; @@ -164,6 +167,8 @@ class AttachEffectTypeClass final : public Enumerable , DisableWeapons { false } , Unkillable { false } , LaserTrail_Type { -1 } + , InhibitType {} + , DesignateType {} , Groups {} {}; diff --git a/src/New/Type/SWSignalTypeClass.cpp b/src/New/Type/SWSignalTypeClass.cpp new file mode 100644 index 0000000000..99bdde901c --- /dev/null +++ b/src/New/Type/SWSignalTypeClass.cpp @@ -0,0 +1,46 @@ +#include "SWSignalTypeClass.h" + +const char* Enumerable::GetMainSection() +{ + return "SuperWeaponSignalTypes"; +} + +void SWSignalTypeClass::LoadFromINI(CCINIClass* pINI) +{ + const char* pSection = this->Name; + + if (!pINI->GetSection(pSection)) + return; + + INI_EX exINI(pINI); + //char tempBuffer[0x20]; + + this->Range.Read(exINI, pSection, "Range"); + this->Affects.Read(exINI, pSection, "Affects"); + this->Powered.Read(exINI, pSection, "Powered"); + this->StopInTemporal.Read(exINI, pSection, "StopInTemporal"); +} + +// ============================= +// load / save + +template +void SWSignalTypeClass::Serialize(T& stm) +{ + stm + .Process(this->Range) + .Process(this->Affects) + .Process(this->Powered) + .Process(this->StopInTemporal) + ; +} + +void SWSignalTypeClass::LoadFromStream(PhobosStreamReader& stm) +{ + this->Serialize(stm); +} + +void SWSignalTypeClass::SaveToStream(PhobosStreamWriter& stm) +{ + this->Serialize(stm); +} diff --git a/src/New/Type/SWSignalTypeClass.h b/src/New/Type/SWSignalTypeClass.h new file mode 100644 index 0000000000..ae45abb4ab --- /dev/null +++ b/src/New/Type/SWSignalTypeClass.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +class SWSignalTypeClass final : public Enumerable +{ +public: + Nullable Range {}; + Nullable Affects {}; + Valueable Powered { false }; + Valueable StopInTemporal { false }; + + SWSignalTypeClass(const char* pTitle = NONE_STR) : Enumerable(pTitle) + { } + + void LoadFromINI(CCINIClass* pINI); + void LoadFromStream(PhobosStreamReader& stm); + void SaveToStream(PhobosStreamWriter& stm); + +private: + template + void Serialize(T& stm); +}; diff --git a/src/Phobos.Ext.cpp b/src/Phobos.Ext.cpp index ac02d779bf..7ff52ac63d 100644 --- a/src/Phobos.Ext.cpp +++ b/src/Phobos.Ext.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -242,7 +243,8 @@ using PhobosTypeRegistry = TypeRegistry < AttachEffectTypeClass, AttachEffectClass, NewSWType, - SelectBoxTypeClass + SelectBoxTypeClass, + SWSignalTypeClass // other classes > ;