diff --git a/CREDITS.md b/CREDITS.md index 491db45ce1..b15f8c5332 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -151,6 +151,7 @@ This page lists all the individual contributions to the project by their author. - Warhead activation target health thresholds enhancements - Event 606: AttachEffect is attaching to a Techno - Linked superweapons + - Battle Points economy for super weapons - **Starkku**: - Misc. minor bugfixes & improvements - AI script actions: diff --git a/Phobos.vcxproj b/Phobos.vcxproj index dda0f91b28..c637abead9 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -30,6 +30,7 @@ + @@ -224,6 +225,7 @@ + diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 52818ffeaa..5bd8fb30eb 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -1037,6 +1037,54 @@ In `rulesmd.ini`: AISuperWeaponDelay= ; integer, game frames ``` +### Battle Points economy for super weapons +- This system displayes a new currency to be used (optionally) in Super Weapons. +- The new currency will be modified by the kills against other players and the system is enabled. +- If set `BattlePointsCollector` in any structure then the system will be enabled if the structure is built. +- If set `BattlePoints` in houses they will have the system enabled by default. +- If set [General] -> `BattlePoints` enables/disables the system for every player in the scenario, overriding the country preferences & `BattlePointsCollector` setting. +- If set [General] -> `BattlePoints.DefaultValue` and the enemy destroyed object doesn't have a value then this generic value will be used instead. +- If set [General] -> `BattlePoints.DefaultFriendlyValue` and the friendly destroyed object doesn't have a value then this generic value will be used instead. This tag doesn't work with the own objects of the player. +- If is set `BattlePoints.CanUseStandardPoints` and the destroyed object doesn't have the `BattlePoints` value then the points are obtained from the `Points` tag of the destroyed object. If `BattlePoints.DefaultValue` is present then this will be ignored. +- If set `BattlePoints` in the destroyed object the calculation is made with this value. Self-Kills done by the own player doesn't count in this system. +- If `BattlePoints.Amount` is set with a value different from `0` then the super weapon is influenced by this system. If the value is positive then this super weapon won't be launched until the affected player gathered the required ammount. + +In `uimd.ini`: +```ini +[Sidebar] +BattlePointsSidebar.Label= ; CSF entry key `★ `, code U+2605 +BattlePointsSidebar.Label.InvertPosition=false ; bool, ` ★`, code U+2605 + +[ToolTips] +BattlePoints.Label= ; CSF entry key, default to `★: `, code U+2605 +``` + +In `rulesmd.ini`: +```ini +[General] +BattlePoints= ; bool +BattlePoints.DefaultValue= ; int +BattlePoints.DefaultFriendlyValue= ; int + +[SOMESIDE] ; Side +Sidebar.BattlePoints.Offset=0,0 ; X,Y, pixels relative to default +Sidebar.BattlePoints.Color= ; integer - R,G,B +Sidebar.BattlePoints.Align=Left ; Left, Right, Center/Centre + +[SOMECOUNTRY] ; HouseType +BattlePoints=false ; bool +BattlePoints.CanUseStandardPoints=false ; bool + +[SOMEBUILDING] ; BuildingType +BattlePointsCollector= ; bool + +[SOMETECHNO] ; TechnoType +BattlePoints= ; int + +[SOMESW] +BattlePoints.Amount=0 ; int +``` + ### Convert TechnoType - Warheads can now change TechnoTypes of affected units to other Types in the same category (infantry to infantry, vehicles to vehicles, aircraft to aircraft). diff --git a/docs/Whats-New.md b/docs/Whats-New.md index f8686b7488..75f1ec9d2a 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -440,6 +440,7 @@ New: - [Customize hardcoded projectile initial facing behavior](Fixed-or-Improved-Logics.md#customizing-initial-facing-behavior) (by Starkku) - Health bar permanently displayed (by FlyStar) - [`IsSimpleDeployer` facing customization & directional deploy animations](Fixed-or-Improved-Logics.md#issimpleDeployer-facing-and-animation-customization) (by Starkku) +- Battle Points economy for super weapons (by FS-21) 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/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index acad4d12e9..e6716a985c 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -218,6 +218,8 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) } this->Refinery_UseNormalActiveAnim.Read(exArtINI, pArtSection, "Refinery.UseNormalActiveAnim"); + + this->BattlePointsCollector.Read(exINI, pSection, "BattlePointsCollector"); // Ares tag this->SpyEffect_Custom.Read(exINI, pSection, "SpyEffect.Custom"); @@ -334,6 +336,7 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm) .Process(this->BuildingRepairedSound) .Process(this->Refinery_UseNormalActiveAnim) .Process(this->HasPowerUpAnim) + .Process(this->BattlePointsCollector) ; } diff --git a/src/Ext/BuildingType/Body.h b/src/Ext/BuildingType/Body.h index 9cf41a7af4..159220c39a 100644 --- a/src/Ext/BuildingType/Body.h +++ b/src/Ext/BuildingType/Body.h @@ -99,6 +99,8 @@ class BuildingTypeExt ValueableVector HasPowerUpAnim; + Nullable BattlePointsCollector; + ExtData(BuildingTypeClass* OwnerObject) : Extension(OwnerObject) , PowersUp_Owner { AffectedHouse::Owner } , PowersUp_Buildings {} @@ -161,6 +163,7 @@ class BuildingTypeExt , BuildingRepairedSound {} , Refinery_UseNormalActiveAnim { false } , HasPowerUpAnim {} + , BattlePointsCollector {} { } // Ares 0.A functions diff --git a/src/Ext/House/Body.cpp b/src/Ext/House/Body.cpp index cf2dceb1b2..0299fdae1e 100644 --- a/src/Ext/House/Body.cpp +++ b/src/Ext/House/Body.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -653,6 +654,7 @@ void HouseExt::ExtData::Serialize(T& Stm) .Process(this->SuspendedEMPulseSWs) .Process(this->SuperExts) .Process(this->ForceEnemyIndex) + .Process(this->BattlePoints) ; } @@ -1101,3 +1103,54 @@ bool HouseExt::ReachedBuildLimit(const HouseClass* pHouse, const TechnoTypeClass return false; } #pragma endregion + +void HouseExt::ExtData::UpdateBattlePoints(int modifier) +{ + this->BattlePoints += modifier; + this->BattlePoints = this->BattlePoints < 0 ? 0 : this->BattlePoints; +} + +bool HouseExt::ExtData::AreBattlePointsEnabled() +{ + const auto pThis = this->OwnerObject(); + const auto pOwnerTypeExt = HouseTypeExt::ExtMap.Find(pThis->Type); + + // Global setting + if (RulesExt::Global()->BattlePoints.isset()) + return RulesExt::Global()->BattlePoints.Get(); + + // House specific setting + if (!pOwnerTypeExt->BattlePoints) + { + // Structures can enable this logic overwriting the house's setting + for (const auto pBuilding : pThis->Buildings) + { + const auto pBuildingTypeExt = BuildingTypeExt::ExtMap.Find(pBuilding->Type); + if (pBuildingTypeExt->BattlePointsCollector.Get(false)) + return true; + } + + return false; + } + + return true; +} + +int HouseExt::ExtData::CalculateBattlePoints(TechnoClass* pTechno) +{ + if (!pTechno) + return 0; + + const auto pThis = this->OwnerObject(); + const auto pThisTypeExt = HouseTypeExt::ExtMap.Find(pThis->Type); + const auto pTechnoTypeExt = TechnoTypeExt::ExtMap.Find(pTechno->GetTechnoType()); + + int defaultValue = RulesExt::Global()->BattlePoints_DefaultValue.Get(0); + int defaultFriendlyValue = RulesExt::Global()->BattlePoints_DefaultFriendlyValue.Get(0); + + int points = pThis->IsAlliedWith(pTechno)? defaultFriendlyValue : defaultValue; + points = pTechnoTypeExt->BattlePoints.Get(points); + points = points == 0 && pThisTypeExt->BattlePoints_CanUseStandardPoints ? pTechno->GetTechnoType()->Points : points; + + return points; +} diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h index 8f99bffc31..1dd361e8a3 100644 --- a/src/Ext/House/Body.h +++ b/src/Ext/House/Body.h @@ -66,6 +66,8 @@ class HouseExt int ForceEnemyIndex; + int BattlePoints; + ExtData(HouseClass* OwnerObject) : Extension(OwnerObject) , PowerPlantEnhancers {} , OwnedLimboDeliveredBuildings {} @@ -94,6 +96,7 @@ class HouseExt , SuspendedEMPulseSWs {} , SuperExts(SuperWeaponTypeClass::Array.Count) , ForceEnemyIndex(-1) + , BattlePoints(0) { } bool OwnsLimboDeliveredBuilding(BuildingClass* pBuilding); @@ -115,6 +118,10 @@ class HouseExt void UpdateVehicleProduction(); + void UpdateBattlePoints(int modifier); + bool AreBattlePointsEnabled(); + int CalculateBattlePoints(TechnoClass* pTechno); + virtual void LoadFromStream(PhobosStreamReader& Stm) override; virtual void SaveToStream(PhobosStreamWriter& Stm) override; diff --git a/src/Ext/HouseType/Body.cpp b/src/Ext/HouseType/Body.cpp new file mode 100644 index 0000000000..030bf86454 --- /dev/null +++ b/src/Ext/HouseType/Body.cpp @@ -0,0 +1,141 @@ +#include "Body.h" + +#include + +static constexpr DWORD Canary = 0x1111111A; +HouseTypeExt::ExtContainer HouseTypeExt::ExtMap; + +void HouseTypeExt::ExtData::Initialize() +{ +} + +// ============================= +// load / save + +void HouseTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) +{ + auto pThis = this->OwnerObject(); + const char* pSection = pThis->ID; + + if (!pINI->GetSection(pSection)) + return; + + INI_EX exINI(pINI); + + this->BattlePoints.Read(exINI, pSection, "BattlePoints"); + this->BattlePoints_CanUseStandardPoints.Read(exINI, pSection, "BattlePoints.CanUseStandardPoints"); +} + +void HouseTypeExt::ExtData::CompleteInitialization() +{ + auto const pThis = this->OwnerObject(); + UNREFERENCED_PARAMETER(pThis); +} + +template +void HouseTypeExt::ExtData::Serialize(T& Stm) +{ + Stm + .Process(this->BattlePoints) + .Process(this->BattlePoints_CanUseStandardPoints) + ; +} + +void HouseTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) +{ + Extension::LoadFromStream(Stm); + this->Serialize(Stm); +} + +void HouseTypeExt::ExtData::SaveToStream(PhobosStreamWriter& Stm) +{ + Extension::SaveToStream(Stm); + this->Serialize(Stm); +} + +bool HouseTypeExt::ExtContainer::Load(HouseTypeClass* pThis, IStream* pStm) +{ + HouseTypeExt::ExtData* pData = this->LoadKey(pThis, pStm); + return pData != nullptr; +}; + +bool HouseTypeExt::LoadGlobals(PhobosStreamReader& Stm) +{ + return Stm.Success(); +} + +bool HouseTypeExt::SaveGlobals(PhobosStreamWriter& Stm) +{ + return Stm.Success(); +} + +// ============================= +// container + +HouseTypeExt::ExtContainer::ExtContainer() : Container("HouseTypeClass") { } + +HouseTypeExt::ExtContainer::~ExtContainer() = default; + +// ============================= +// container hooks + +DEFINE_HOOK(0x511635, HouseTypeClass_CTOR_1, 0x5) +{ + GET(HouseTypeClass*, pItem, EAX); + + HouseTypeExt::ExtMap.Allocate(pItem); + + return 0; +} + +DEFINE_HOOK(0x511643, HouseTypeClass_CTOR_2, 0x5) +{ + GET(HouseTypeClass*, pItem, EAX); + + HouseTypeExt::ExtMap.Allocate(pItem); + + return 0; +} + +DEFINE_HOOK(0x5127CF, HouseTypeClass_DTOR, 0x6) +{ + GET(HouseTypeClass*, pItem, ESI); + + HouseTypeExt::ExtMap.Remove(pItem); + + return 0; +} + +DEFINE_HOOK_AGAIN(0x512480, HouseTypeClass_SaveLoad_Prefix, 0x5) +DEFINE_HOOK(0x512290, HouseTypeClass_SaveLoad_Prefix, 0x5) +{ + GET_STACK(HouseTypeClass*, pItem, 0x4); + GET_STACK(IStream*, pStm, 0x8); + + HouseTypeExt::ExtMap.PrepareStream(pItem, pStm); + + return 0; +} + +DEFINE_HOOK(0x51246D, HouseTypeClass_Load_Suffix, 0x5) +{ + HouseTypeExt::ExtMap.LoadStatic(); + return 0; +} + +DEFINE_HOOK(0x51255C, HouseTypeClass_Save_Suffix, 0x5) +{ + HouseTypeExt::ExtMap.SaveStatic(); + return 0; +} + +DEFINE_HOOK_AGAIN(0x51215A, HouseTypeClass_LoadFromINI, 0x5) +DEFINE_HOOK(0x51214F, HouseTypeClass_LoadFromINI, 0x5) +{ + GET(HouseTypeClass*, pItem, EBX); + GET_BASE(CCINIClass*, pINI, 0x8); + + HouseTypeExt::ExtMap.LoadFromINI(pItem, pINI); + + return 0; +} diff --git a/src/Ext/HouseType/Body.h b/src/Ext/HouseType/Body.h new file mode 100644 index 0000000000..fbe78a919c --- /dev/null +++ b/src/Ext/HouseType/Body.h @@ -0,0 +1,55 @@ +#pragma once +#include + +#include +#include +#include + +class HouseTypeExt +{ +public: + using base_type = HouseTypeClass; + static constexpr DWORD Canary = 0x11112222; + + class ExtData final : public Extension + { + public: + Valueable BattlePoints; + Valueable BattlePoints_CanUseStandardPoints; + + ExtData(HouseTypeClass* OwnerObject) : Extension(OwnerObject) + , BattlePoints { false } + , BattlePoints_CanUseStandardPoints { false } + { } + + virtual ~ExtData() = default; + + virtual void LoadFromINIFile(CCINIClass* pINI) override; + virtual void Initialize() override; + virtual void CompleteInitialization(); + + virtual void InvalidatePointer(void* ptr, bool bRemoved) override + { + } + + virtual void LoadFromStream(PhobosStreamReader& Stm) override; + virtual void SaveToStream(PhobosStreamWriter& Stm) override; + + private: + template + void Serialize(T& Stm); + }; + + class ExtContainer final : public Container + { + public: + ExtContainer(); + ~ExtContainer(); + + virtual bool Load(HouseTypeClass* pThis, IStream* pStm) override; + }; + + static ExtContainer ExtMap; + static bool LoadGlobals(PhobosStreamReader& Stm); + static bool SaveGlobals(PhobosStreamWriter& Stm); +}; diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index c30c2e8849..03052c2d4c 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -313,6 +313,10 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->InfantryAutoDeploy.Read(exINI, GameStrings::General, "InfantryAutoDeploy"); + this->BattlePoints.Read(exINI, GameStrings::General, "BattlePoints"); + this->BattlePoints_DefaultValue.Read(exINI, GameStrings::General, "BattlePoints.DefaultValue"); + this->BattlePoints_DefaultFriendlyValue.Read(exINI, GameStrings::General, "BattlePoints.DefaultFriendlyValue"); + // Section AITargetTypes int itemsCount = pINI->GetKeyCount("AITargetTypes"); for (int i = 0; i < itemsCount; ++i) @@ -577,6 +581,9 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->AttackMove_StopWhenTargetAcquired) .Process(this->Parasite_GrappleAnim) .Process(this->InfantryAutoDeploy) + .Process(this->BattlePoints) + .Process(this->BattlePoints_DefaultValue) + .Process(this->BattlePoints_DefaultFriendlyValue) ; } diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index 2584a189dc..a8db395c6e 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -264,6 +264,10 @@ class RulesExt Valueable InfantryAutoDeploy; + Nullable BattlePoints; + Nullable BattlePoints_DefaultValue; + Nullable BattlePoints_DefaultFriendlyValue; + ExtData(RulesClass* OwnerObject) : Extension(OwnerObject) , Storage_TiberiumIndex { -1 } , HarvesterDumpAmount { 0.0f } @@ -468,6 +472,10 @@ class RulesExt , Parasite_GrappleAnim {} , InfantryAutoDeploy { false } + + , BattlePoints {} + , BattlePoints_DefaultValue {} + , BattlePoints_DefaultFriendlyValue {} { } virtual ~ExtData() = default; diff --git a/src/Ext/SWType/Body.cpp b/src/Ext/SWType/Body.cpp index f230746dc7..2e370134d7 100644 --- a/src/Ext/SWType/Body.cpp +++ b/src/Ext/SWType/Body.cpp @@ -95,6 +95,7 @@ void SWTypeExt::ExtData::Serialize(T& Stm) .Process(this->SW_Link_RollChances) .Process(this->Message_LinkedSWAcquired) .Process(this->EVA_LinkedSWAcquired) + .Process(this->BattlePoints_Amount) ; } @@ -276,6 +277,8 @@ void SWTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) pNewSWType->Initialize(const_cast(this), OwnerObject()); pNewSWType->LoadFromINI(const_cast(this), OwnerObject(), pINI); } + + this->BattlePoints_Amount.Read(exINI, pSection, "BattlePoints.Amount"); } void SWTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) diff --git a/src/Ext/SWType/Body.h b/src/Ext/SWType/Body.h index e4af1c3879..a01cdff54a 100644 --- a/src/Ext/SWType/Body.h +++ b/src/Ext/SWType/Body.h @@ -112,6 +112,8 @@ class SWTypeExt Valueable Message_LinkedSWAcquired; NullableIdx EVA_LinkedSWAcquired; + Valueable BattlePoints_Amount; + ExtData(SuperWeaponTypeClass* OwnerObject) : Extension(OwnerObject) , TypeID { "" } , Money_Amount { 0 } @@ -187,6 +189,7 @@ class SWTypeExt , SW_Link_RandomWeightsData {} , Message_LinkedSWAcquired {} , EVA_LinkedSWAcquired {} + , BattlePoints_Amount { 0 } { } // Ares 0.A functions @@ -213,6 +216,8 @@ class SWTypeExt void ApplyLinkedSW(SuperClass* pSW); + void ApplyBattlePoints(SuperClass* pSW); + virtual void LoadFromINIFile(CCINIClass* pINI) override; virtual void Initialize() override; diff --git a/src/Ext/SWType/FireSuperWeapon.cpp b/src/Ext/SWType/FireSuperWeapon.cpp index b43b5c35fe..348d270d41 100644 --- a/src/Ext/SWType/FireSuperWeapon.cpp +++ b/src/Ext/SWType/FireSuperWeapon.cpp @@ -43,6 +43,9 @@ void SWTypeExt::FireSuperWeaponExt(SuperClass* pSW, const CellStruct& cell) if (static_cast(pType->Type) == 28 && !pTypeExt->EMPulse_TargetSelf) // Ares' Type=EMPulse SW pTypeExt->HandleEMPulseLaunch(pSW, cell); + if (pTypeExt->BattlePoints_Amount != 0) + pTypeExt->ApplyBattlePoints(pSW); + auto& sw_ext = HouseExt::ExtMap.Find(pHouse)->SuperExts[pType->ArrayIndex]; sw_ext.ShotCount++; @@ -458,3 +461,9 @@ void SWTypeExt::ExtData::ApplyLinkedSW(SuperClass* pSW) MessageListClass::Instance.PrintMessage(this->Message_LinkedSWAcquired.Get(), RulesClass::Instance->MessageDelay, HouseClass::CurrentPlayer->ColorSchemeIndex, true); } } + +void SWTypeExt::ExtData::ApplyBattlePoints(SuperClass* pSW) +{ + auto pOwnerExt = HouseExt::ExtMap.Find(pSW->Owner); + pOwnerExt->UpdateBattlePoints(this->BattlePoints_Amount); +} diff --git a/src/Ext/SWType/Hooks.cpp b/src/Ext/SWType/Hooks.cpp index 023073853e..3fb2db98f6 100644 --- a/src/Ext/SWType/Hooks.cpp +++ b/src/Ext/SWType/Hooks.cpp @@ -267,3 +267,50 @@ DEFINE_HOOK(0x6AC67A, SidebarClass_6AC5F0_TabIndex, 0x5) DEFINE_JUMP(LJMP, 0x6A8D07, 0x6A8D17) // Skip tabIndex check #pragma endregion + +// Full rewrite +DEFINE_HOOK(0x6CC367, SuperClass_IsReady_BattlePoints, 0xD) +{ + GET(SuperClass*, pSuper, ECX); + + enum{ ReturnIsReady = 0x6CC37D, ReturnZero = 0x6CC381, SkipAll = 0x6CC383}; + + if (pSuper->IsSuspended) + return ReturnZero; + + if (pSuper->Type->UseChargeDrain) + { + R->AL(pSuper->ChargeDrainState != ChargeDrainState::Charging); + return SkipAll; + } + + const auto pExt = SWTypeExt::ExtMap.Find(pSuper->Type); + if (pExt->BattlePoints_Amount != 0) + { + const auto pOwnerExt = HouseExt::ExtMap.Find(pSuper->Owner); + + if (pExt->BattlePoints_Amount < 0) + { + if (pOwnerExt->BattlePoints < std::abs(pExt->BattlePoints_Amount)) + return ReturnZero; + } + } + + return ReturnIsReady; +} + +// Executed before the Ares hook for launching AI super weapons, SW->IsReady property won't be updated anymore +DEFINE_HOOK(0x4FD77C, ExpertAI_SuperWeaponAI_RecheckIsReady, 0x5) +{ + GET(HouseClass*, pHouse, EBX); + if (!SessionClass::IsCampaign() || pHouse->IQLevel2 >= RulesClass::Instance->SuperWeapons) + { + for (auto const& pSuper : pHouse->Supers) + { + if (pSuper->IsReady) + pSuper->IsReady = pSuper->CanFire(); + } + } + + return 0; +} diff --git a/src/Ext/Side/Body.cpp b/src/Ext/Side/Body.cpp index 9d59ab3cdb..5ac3922c2c 100644 --- a/src/Ext/Side/Body.cpp +++ b/src/Ext/Side/Body.cpp @@ -36,6 +36,9 @@ void SideExt::ExtData::LoadFromINIFile(CCINIClass* pINI) this->Sidebar_PowerDelta_Red.Read(exINI, pSection, "Sidebar.PowerDelta.ColorRed"); this->Sidebar_PowerDelta_Grey.Read(exINI, pSection, "Sidebar.PowerDelta.ColorGrey"); this->Sidebar_PowerDelta_Align.Read(exINI, pSection, "Sidebar.PowerDelta.Align"); + this->Sidebar_BattlePoints_Offset.Read(exINI, pSection, "Sidebar.BattlePoints.Offset"); + this->Sidebar_BattlePoints_Color.Read(exINI, pSection, "Sidebar.BattlePoints.Color"); + this->Sidebar_BattlePoints_Align.Read(exINI, pSection, "Sidebar.BattlePoints.Align"); this->ToolTip_Background_Color.Read(exINI, pSection, "ToolTip.Background.Color"); this->ToolTip_Background_Opacity.Read(exINI, pSection, "ToolTip.Background.Opacity"); this->ToolTip_Background_BlurSize.Read(exINI, pSection, "ToolTip.Background.BlurSize"); @@ -68,6 +71,9 @@ void SideExt::ExtData::Serialize(T& Stm) .Process(this->Sidebar_PowerDelta_Red) .Process(this->Sidebar_PowerDelta_Grey) .Process(this->Sidebar_PowerDelta_Align) + .Process(this->Sidebar_BattlePoints_Offset) + .Process(this->Sidebar_BattlePoints_Color) + .Process(this->Sidebar_BattlePoints_Align) .Process(this->ToolTip_Background_Color) .Process(this->ToolTip_Background_Opacity) .Process(this->ToolTip_Background_BlurSize) diff --git a/src/Ext/Side/Body.h b/src/Ext/Side/Body.h index e0f3362ef0..3e659606d9 100644 --- a/src/Ext/Side/Body.h +++ b/src/Ext/Side/Body.h @@ -32,6 +32,9 @@ class SideExt Valueable Sidebar_PowerDelta_Red; Valueable Sidebar_PowerDelta_Grey; Valueable Sidebar_PowerDelta_Align; + Valueable Sidebar_BattlePoints_Offset; + Nullable Sidebar_BattlePoints_Color; + Valueable Sidebar_BattlePoints_Align; Nullable ToolTip_Background_Color; Nullable ToolTip_Background_Opacity; Nullable ToolTip_Background_BlurSize; @@ -59,6 +62,9 @@ class SideExt , Sidebar_PowerDelta_Red { { 255, 0, 0 } } , Sidebar_PowerDelta_Grey { { 0x80,0x80,0x80 } } , Sidebar_PowerDelta_Align { TextAlign::Left } + , Sidebar_BattlePoints_Offset { { 0, 0 } } + , Sidebar_BattlePoints_Color {} + , Sidebar_BattlePoints_Align { TextAlign::Left } , ToolTip_Background_Color { } , ToolTip_Background_Opacity { } , ToolTip_Background_BlurSize { } diff --git a/src/Ext/Techno/Body.Internal.cpp b/src/Ext/Techno/Body.Internal.cpp index 391e1847e2..2adb0a70ee 100644 --- a/src/Ext/Techno/Body.Internal.cpp +++ b/src/Ext/Techno/Body.Internal.cpp @@ -3,6 +3,9 @@ #include #include +#include +#include +#include // Unsorted methods @@ -18,20 +21,40 @@ void TechnoExt::ExtData::InitializeLaserTrails() this->LaserTrails.emplace_back(std::make_unique(entry.GetType(), this->OwnerObject()->Owner, entry.FLH, entry.IsOnTurret)); } -void TechnoExt::ObjectKilledBy(TechnoClass* pVictim, TechnoClass* pKiller) +void TechnoExt::ObjectKilledBy(TechnoClass* pVictim, TechnoClass* pKiller, HouseClass* pHouseKiller) { - auto const pKillerType = pKiller->GetTechnoType(); - auto const pObjectKiller = ((pKillerType->Spawned || pKillerType->MissileSpawn) && pKiller->SpawnOwner) - ? pKiller->SpawnOwner : pKiller; + TechnoClass* pObjectKiller = nullptr; + + if (pKiller) + { + pObjectKiller = ((pKiller->GetTechnoType()->Spawned || pKiller->GetTechnoType()->MissileSpawn) && pKiller->SpawnOwner) ? + pKiller->SpawnOwner : pKiller; + + if (!pObjectKiller) + return; + } if (pObjectKiller && pObjectKiller->BelongsToATeam()) { if (auto const pFootKiller = generic_cast(pObjectKiller)) { - auto const pKillerTechnoData = TechnoExt::ExtMap.Find(pObjectKiller); + auto pKillerTechnoData = TechnoExt::ExtMap.Find(pObjectKiller); pKillerTechnoData->LastKillWasTeamTarget = pFootKiller->Team->Focus == pVictim; } } + + HouseClass* pHouse = pKiller ? pKiller->Owner : pHouseKiller; + + if (pHouse != pVictim->Owner) + { + auto pHouseExt = HouseExt::ExtMap.Find(pHouse); + + if (pHouseExt->AreBattlePointsEnabled()) + { + int points = pHouseExt->CalculateBattlePoints(pVictim); + pHouseExt->UpdateBattlePoints(points); + } + } } // reversed from 6F3D60 diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 830680d45e..e93781ca0c 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -242,7 +242,7 @@ class TechnoExt static void ChangeOwnerMissionFix(FootClass* pThis); static void KillSelf(TechnoClass* pThis, AutoDeathBehavior deathOption, AnimTypeClass* pVanishAnimation, bool isInLimbo = false); - static void ObjectKilledBy(TechnoClass* pThis, TechnoClass* pKiller); + static void ObjectKilledBy(TechnoClass* pVictim, TechnoClass* pKiller = nullptr, HouseClass* pHouseKiller = nullptr); static void UpdateSharedAmmo(TechnoClass* pThis); static double GetCurrentSpeedMultiplier(FootClass* pThis); static double GetCurrentFirepowerMultiplier(TechnoClass* pThis); diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index 0e5c440e2e..09f1cf4304 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -530,12 +530,24 @@ DEFINE_HOOK(0x702E4E, TechnoClass_RegisterDestruction_SaveKillerInfo, 0x6) GET(TechnoClass*, pKiller, EDI); GET(TechnoClass*, pVictim, ECX); + // Note: Some SW never had a "killer" or a "house" (hello "NukeSpecial"), probably never scored to the killer? if (pKiller && pVictim) TechnoExt::ObjectKilledBy(pVictim, pKiller); return 0; } +// AFAIK, only used by the teleport of the Chronoshift SW +DEFINE_HOOK(0x70337D, HouseClass_RegisterDestruction_SaveKillerInfo, 0x6) +{ + GET(HouseClass*, pHouse, EDI); + GET(TechnoClass*, pVictim, ESI); + + TechnoExt::ObjectKilledBy(pVictim, nullptr, pHouse); + + return 0; +} + DEFINE_HOOK(0x71067B, TechnoClass_EnterTransport_LaserTrails, 0x7) { GET(TechnoClass*, pTechno, EDI); diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 260d52b713..1741d2e40b 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -962,6 +962,8 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->InfantryAutoDeploy.Read(exINI, pSection, "InfantryAutoDeploy"); + this->BattlePoints.Read(exINI, pSection, "BattlePoints"); + // Ares 0.2 this->RadarJamRadius.Read(exINI, pSection, "RadarJamRadius"); @@ -1604,6 +1606,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->InfantryAutoDeploy) .Process(this->TurretResponse) + + .Process(this->BattlePoints) ; } void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index cec8af62a7..9f220493de 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -425,6 +425,8 @@ class TechnoTypeExt Nullable TurretResponse; + Nullable BattlePoints; + ExtData(TechnoTypeClass* OwnerObject) : Extension(OwnerObject) , HealthBar_Hide { false } , HealthBar_HidePips { false } @@ -799,6 +801,8 @@ class TechnoTypeExt , InfantryAutoDeploy {} , TurretResponse {} + + , BattlePoints {} { } virtual ~ExtData() = default; diff --git a/src/Misc/Hooks.UI.cpp b/src/Misc/Hooks.UI.cpp index dbe60a0657..358044a86e 100644 --- a/src/Misc/Hooks.UI.cpp +++ b/src/Misc/Hooks.UI.cpp @@ -88,6 +88,32 @@ DEFINE_HOOK(0x4A25E0, CreditsClass_GraphicLogic_HarvesterCounter, 0x7) return 0; RectangleStruct vRect = DSurface::Sidebar->GetRect(); + auto pHouseExt = HouseExt::ExtMap.Find(pPlayer); + + if (pHouseExt->AreBattlePointsEnabled()) + { + auto pSideExt = SideExt::ExtMap.Find(SideClass::Array.GetItem(pPlayer->SideIndex)); + wchar_t counter[0x20]; + + ColorStruct clrToolTip = pSideExt->Sidebar_BattlePoints_Color.Get(Drawing::TooltipColor); + + int points = pHouseExt->BattlePoints; + + if (Phobos::UI::BattlePointsSidebar_Label_InvertPosition) + swprintf_s(counter, L"%d %ls", points, Phobos::UI::BattlePointsSidebar_Label); + else + swprintf_s(counter, L"%ls %d", Phobos::UI::BattlePointsSidebar_Label, points); + + Point2D vPos = { + DSurface::Sidebar->GetWidth() / 2 - 70 + pSideExt->Sidebar_BattlePoints_Offset.Get().X, + 2 + pSideExt->Sidebar_BattlePoints_Offset.Get().Y + }; + + auto const TextFlags = static_cast(static_cast(TextPrintType::UseGradPal | TextPrintType::Metal12) + | static_cast(pSideExt->Sidebar_BattlePoints_Align.Get())); + + DSurface::Sidebar->DrawText(counter, &vRect, &vPos, Drawing::RGB_To_Int(clrToolTip), 0, TextFlags); + } if (Phobos::UI::HarvesterCounter_Show && Phobos::Config::ShowHarvesterCounter) { diff --git a/src/Misc/PhobosToolTip.cpp b/src/Misc/PhobosToolTip.cpp index 5ae67f4f71..26e98e45eb 100644 --- a/src/Misc/PhobosToolTip.cpp +++ b/src/Misc/PhobosToolTip.cpp @@ -183,6 +183,18 @@ void PhobosToolTip::HelpText_Super(int swidx) showSth = true; } + if (int nPoints = std::abs(pData->BattlePoints_Amount)) + { + oss << L"\n"; + + if (pData->BattlePoints_Amount > 0) + oss << Phobos::UI::BattlePoints_Label << L"+" << nPoints; + else if (pData->BattlePoints_Amount < 0) + oss << Phobos::UI::BattlePoints_Label << L"-" << nPoints; + + showSth = true; + } + const int rechargeTime = TickTimeToSeconds(pSuper->GetRechargeTime()); if (rechargeTime > 0) { diff --git a/src/Phobos.INI.cpp b/src/Phobos.INI.cpp index 32f6a0b953..bdfb995eca 100644 --- a/src/Phobos.INI.cpp +++ b/src/Phobos.INI.cpp @@ -28,6 +28,9 @@ const wchar_t* Phobos::UI::TimeLabel = L""; const wchar_t* Phobos::UI::HarvesterLabel = L""; const wchar_t* Phobos::UI::ShowBriefingResumeButtonLabel = L""; const wchar_t* Phobos::UI::SWShotsFormat = L""; +const wchar_t* Phobos::UI::BattlePoints_Label = L""; +const wchar_t* Phobos::UI::BattlePointsSidebar_Label = L""; +bool Phobos::UI::BattlePointsSidebar_Label_InvertPosition = false; char Phobos::UI::ShowBriefingResumeButtonStatusLabel[32]; bool Phobos::UI::PowerDelta_Show = false; double Phobos::UI::PowerDelta_ConditionYellow = 0.75; @@ -149,6 +152,9 @@ DEFINE_HOOK(0x5FACDF, OptionsClass_LoadSettings_LoadPhobosSettings, 0x5) ini_uimd.ReadString(GameStrings::ToolTips, "SWShotsFormat", NONE_STR, Phobos::readBuffer); Phobos::UI::SWShotsFormat = GeneralUtils::LoadStringOrDefault(Phobos::readBuffer, L"Shots: %d"); // ⌚ + + ini_uimd.ReadString(GameStrings::ToolTips, "BattlePoints.Label", NONE_STR, Phobos::readBuffer); + Phobos::UI::BattlePoints_Label = GeneralUtils::LoadStringOrDefault(Phobos::readBuffer, L"\u2605: "); // ★: } // Sidebar @@ -215,6 +221,11 @@ DEFINE_HOOK(0x5FACDF, OptionsClass_LoadSettings_LoadPhobosSettings, 0x5) Phobos::UI::SuperWeaponSidebar_MaxColumns = ini_uimd.ReadInteger(SIDEBAR_SECTION, "SuperWeaponSidebar.MaxColumns", Phobos::UI::SuperWeaponSidebar_MaxColumns); + + Phobos::UI::BattlePointsSidebar_Label_InvertPosition = ini_uimd.ReadBool(SIDEBAR_SECTION, "BattlePointsSidebar.Label.InvertPosition", false); + + ini_uimd.ReadString(SIDEBAR_SECTION, "BattlePointsSidebar.Label", NONE_STR, Phobos::readBuffer); + Phobos::UI::BattlePointsSidebar_Label = GeneralUtils::LoadStringOrDefault(Phobos::readBuffer, L"\u2605"); // %d ★ } // UISettings diff --git a/src/Phobos.h b/src/Phobos.h index e30ca6b066..05b20fb6b2 100644 --- a/src/Phobos.h +++ b/src/Phobos.h @@ -73,6 +73,9 @@ class Phobos static const wchar_t* ShowBriefingResumeButtonLabel; static const wchar_t* SWShotsFormat; static char ShowBriefingResumeButtonStatusLabel[0x20]; + static const wchar_t* BattlePoints_Label; + static const wchar_t* BattlePointsSidebar_Label; + static bool BattlePointsSidebar_Label_InvertPosition; }; class Config