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