diff --git a/CREDITS.md b/CREDITS.md index a8c1c4742f..42e696c651 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -399,6 +399,7 @@ This page lists all the individual contributions to the project by their author. - Fix the bug that ships can travel on elevated bridges - Original `Arcing` elevation inaccuracy fix - Fix the bug that uncontrolled scatter when elite techno attacked by aircraft or some unit try crush it + - Exclusive SuperWeapon Sidebar - **Apollo** - Translucent SHP drawing patches - **ststl**: - Customizable `ShowTimer` priority of superweapons @@ -473,6 +474,7 @@ This page lists all the individual contributions to the project by their author. - Aggressive attack move mission - Amphibious access vehicle - Fix an issue that spawned `Strafe` aircraft on aircraft carriers may not be able to return normally if aircraft carriers moved a short distance when the aircraft is landing + - Exclusive SuperWeapon Sidebar - **Ollerus**: - Build limit group enhancement - Customizable rocker amplitude diff --git a/Phobos.vcxproj b/Phobos.vcxproj index 9085e61db9..6d5a59f83c 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -23,6 +23,11 @@ + + + + + @@ -200,6 +205,12 @@ + + + + + + diff --git a/docs/User-Interface.md b/docs/User-Interface.md index a07fd43cbc..4cd348e580 100644 --- a/docs/User-Interface.md +++ b/docs/User-Interface.md @@ -678,3 +678,58 @@ In `RA2MD.INI`: [Phobos] ToolTipBlur=false ; boolean, whether the blur effect of tooltips will be enabled. ``` + +### Exclusive SuperWeapon Sidebar + +![image](_static/images/sw_sidebar-01-on.png) +![image](_static/images/sw_sidebar-02-on.png) + +- It is possible to put sw cameos on the left of screen like C&C3 when `SuperWeaponSidebar` is true. Cameos arranged in a pyramid shape. In theory, it should be compatible with Ares. + - `SuperWeaponSidebar.Interval` controls the distance between two column cameos (excluding the background). When you need to make a background, the width of the background should be (`SuperWeaponSidebar.Interval` + cameo fixed width 60). + - `SuperWeaponSidebar.LeftOffset` controls the distance between the left side of cameo and the left side of its column (background). This will not be greater than `SuperWeaponSidebar.Interval`. + - `SuperWeaponSidebar.CameoHeight` controls the distance from the top of the previous cameo to the top of the next cameo. That is, the space between the upper and lower cameos is (`SuperWeaponSidebar.CameoHeight` - cameo fixed height 48). This will not be less than 48. When you need to make a background, this is the height of the background. + - `SuperWeaponSidebar.Max` controls the maximum number of cameos on the leftmost column, which also depends on the current game resolution. + - `SuperWeaponSidebar.MaxColumns` controls that maximum count of columns. + - Only sw with `SuperWeaponSidebar.Significance` not lower than `SuperWeaponSidebar.RequiredSignificance` are allowed to be added to the sw sidebar. +- `SuperWeaponSidebarKeysEnabled` should be true that you can use hotkeys about superweapon sidebar. +- You can also launch first 10 SW by hotkey in INTERFACE category. + - For localization of hotkey, add `TXT_FIRE_TACTICAL_SW_XX`, `TXT_FIRE_TACTICAL_SW_XX_DESC`, `TXT_TOGGLE_SW_SIDEBAR` and `TXT_TOGGLE_SW_SIDEBAR_DESC` into your `.csf` file. + +In `uimd.ini`: +```ini +[Sidebar] +SuperWeaponSidebar=false ; boolean +SuperWeaponSidebar.Interval=0 ; integer, pixels +SuperWeaponSidebar.LeftOffset=0 ; integer, pixels +SuperWeaponSidebar.CameoHeight=48 ; integer, pixels +SuperWeaponSidebar.Max=0 ; integer +SuperWeaponSidebar.MaxColumns= ; integer +``` + +In `rulesmd.ini` +```ini +[GlobalControls] +SuperWeaponSidebarKeysEnabled=false ; boolean + +[AudioVisual] +SuperWeaponSidebar.AllowByDefault=false ; boolean + +[SOMESIDE] +SuperWeaponSidebar.OnPCX= ; filename - including the .pcx extension +SuperWeaponSidebar.OffPCX= ; filename - including the .pcx extension +SuperWeaponSidebar.TopPCX= ; filename - including the .pcx extension +SuperWeaponSidebar.CenterPCX= ; filename - including the .pcx extension +SuperWeaponSidebar.BottomPCX= ; filename - including the .pcx extension + +[SOMESW] +SuperWeaponSidebar.Allow= ; boolean +SuperWeaponSidebar.PriorityHouses= ; list of house types +SuperWeaponSidebar.RequiredHouses= ; list of house types +SuperWeaponSidebar.Significance=0 ; integer +``` + +In `ra2md.ini` +```ini +[Phobos] +SuperWeaponSidebar.RequiredSignificance=0 ; integer +``` diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 67ad42dd39..15edfdcbe5 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -381,6 +381,7 @@ New: - Dehardcoded 255 limit of `OverlayType` (by secsome) - [Customizable airstrike flare colors](Fixed-or-Improved-Logics.md#airstrike-flare-customizations) (by Starkku) - Allowed player's self-healing effects to be benefited by allied or `PlayerControl=true` houses (by Ollerus) +- Exclusive SuperWeapon Sidebar (by NetsuNegi & CrimRecya) 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/docs/_static/images/sw_sidebar-01-on.png b/docs/_static/images/sw_sidebar-01-on.png new file mode 100644 index 0000000000..81fd868162 Binary files /dev/null and b/docs/_static/images/sw_sidebar-01-on.png differ diff --git a/docs/_static/images/sw_sidebar-02-on.png b/docs/_static/images/sw_sidebar-02-on.png new file mode 100644 index 0000000000..db96e181c2 Binary files /dev/null and b/docs/_static/images/sw_sidebar-02-on.png differ diff --git a/src/Commands/Commands.cpp b/src/Commands/Commands.cpp index 712a596467..8d9d2611a1 100644 --- a/src/Commands/Commands.cpp +++ b/src/Commands/Commands.cpp @@ -10,6 +10,9 @@ #include "ToggleDigitalDisplay.h" #include "ToggleDesignatorRange.h" #include "SaveVariablesToFile.h" +#include "ToggleSWSidebar.h" +#include "FireTacticalSW.h" +#include DEFINE_HOOK(0x533066, CommandClassCallback_Register, 0x6) { @@ -19,6 +22,21 @@ DEFINE_HOOK(0x533066, CommandClassCallback_Register, 0x6) MakeCommand(); MakeCommand(); MakeCommand(); + MakeCommand(); + + if (Phobos::Config::SuperWeaponSidebarCommands) + { + SWSidebarClass::Commands[0] = MakeCommand>(); + SWSidebarClass::Commands[1] = MakeCommand>(); + SWSidebarClass::Commands[2] = MakeCommand>(); + SWSidebarClass::Commands[3] = MakeCommand>(); + SWSidebarClass::Commands[4] = MakeCommand>(); + SWSidebarClass::Commands[5] = MakeCommand>(); + SWSidebarClass::Commands[6] = MakeCommand>(); + SWSidebarClass::Commands[7] = MakeCommand>(); + SWSidebarClass::Commands[8] = MakeCommand>(); + SWSidebarClass::Commands[9] = MakeCommand>(); + } if (Phobos::Config::DevelopmentCommands) { diff --git a/src/Commands/Commands.h b/src/Commands/Commands.h index 9b66ed02f1..ae3c89f503 100644 --- a/src/Commands/Commands.h +++ b/src/Commands/Commands.h @@ -7,10 +7,11 @@ #include template -void MakeCommand() +T* MakeCommand() { T* command = GameCreate(); CommandClass::Array.AddItem(command); + return command; }; #define CATEGORY_TEAM StringTable::LoadString(GameStrings::TXT_TEAM) diff --git a/src/Commands/FireTacticalSW.h b/src/Commands/FireTacticalSW.h new file mode 100644 index 0000000000..e4777a2dd2 --- /dev/null +++ b/src/Commands/FireTacticalSW.h @@ -0,0 +1,61 @@ +#pragma once +#include "Commands.h" + +#include +#include + +template +class FireTacticalSWCommandClass : public CommandClass +{ + virtual const char* GetName() const override; + virtual const wchar_t* GetUIName() const override; + virtual const wchar_t* GetUICategory() const override; + virtual const wchar_t* GetUIDescription() const override; + virtual void Execute(WWKey eInput) const override; +}; + +template +inline const char* FireTacticalSWCommandClass::GetName() const +{ + _snprintf_s(Phobos::readBuffer, Phobos::readLength, "FireTacticalSW%d", Index + 1); + return Phobos::readBuffer; +} + +template +inline const wchar_t* FireTacticalSWCommandClass::GetUIName() const +{ + const wchar_t* csfString = StringTable::TryFetchString("TXT_FIRE_TACTICAL_SW_XX", L"Fire Super Weapon %d"); + _snwprintf_s(Phobos::wideBuffer, std::size(Phobos::wideBuffer), csfString, Index + 1); + return Phobos::wideBuffer; +} + +template +inline const wchar_t* FireTacticalSWCommandClass::GetUICategory() const +{ + return CATEGORY_INTERFACE; +} + +template +inline const wchar_t* FireTacticalSWCommandClass::GetUIDescription() const +{ + const wchar_t* csfString = StringTable::TryFetchString("TXT_FIRE_TACTICAL_SW_XX_DESC", L"Fires the Super Weapon at position %d in the Super Weapon sidebar."); + _snwprintf_s(Phobos::wideBuffer, std::size(Phobos::wideBuffer), csfString, Index + 1); + return Phobos::wideBuffer; +} + +template +inline void FireTacticalSWCommandClass::Execute(WWKey eInput) const +{ + if (!SWSidebarClass::IsEnabled()) + return; + + const auto& columns = SWSidebarClass::Instance.Columns; + + if (columns.empty()) + return; + + const auto& buttons = columns.front()->Buttons; + + if (Index < buttons.size()) + buttons[Index]->LaunchSuper(); +} diff --git a/src/Commands/ToggleSWSidebar.cpp b/src/Commands/ToggleSWSidebar.cpp new file mode 100644 index 0000000000..49369828c7 --- /dev/null +++ b/src/Commands/ToggleSWSidebar.cpp @@ -0,0 +1,33 @@ +#include "ToggleSWSidebar.h" +#include + +#include +#include + +const char* ToggleSWSidebar::GetName() const +{ + return "Toggle Super Weapon Sidebar"; +} + +const wchar_t* ToggleSWSidebar::GetUIName() const +{ + return GeneralUtils::LoadStringUnlessMissing("TXT_TOGGLE_SW_SIDEBAR", L"Toggle Super Weapon Sidebar"); +} + +const wchar_t* ToggleSWSidebar::GetUICategory() const +{ + return CATEGORY_INTERFACE; +} + +const wchar_t* ToggleSWSidebar::GetUIDescription() const +{ + return GeneralUtils::LoadStringUnlessMissing("TXT_TOGGLE_SW_SIDEBAR_DESC", L"Toggle the Super Weapon Sidebar."); +} + +void ToggleSWSidebar::Execute(WWKey eInput) const +{ + ToggleSWButtonClass::SwitchSidebar(); + + if (SWSidebarClass::Instance.CurrentColumn) + MouseClass::Instance.UpdateCursor(MouseCursorType::Default, false); +} diff --git a/src/Commands/ToggleSWSidebar.h b/src/Commands/ToggleSWSidebar.h new file mode 100644 index 0000000000..e12ac13364 --- /dev/null +++ b/src/Commands/ToggleSWSidebar.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Commands.h" + +// Display damage strings +class ToggleSWSidebar : public CommandClass +{ +public: + virtual const char* GetName() const override; + virtual const wchar_t* GetUIName() const override; + virtual const wchar_t* GetUICategory() const override; + virtual const wchar_t* GetUIDescription() const override; + virtual void Execute(WWKey eInput) const override; +}; diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index 00ad26ccd7..d824e9cd86 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -105,6 +105,8 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->PlacementPreview.Read(exINI, GameStrings::AudioVisual, "PlacementPreview"); this->PlacementPreview_Translucency.Read(exINI, GameStrings::AudioVisual, "PlacementPreview.Translucency"); + this->SuperWeaponSidebar_AllowByDefault.Read(exINI, GameStrings::AudioVisual, "SuperWeaponSidebar.AllowByDefault"); + this->ConditionYellow_Terrain.Read(exINI, GameStrings::AudioVisual, "ConditionYellow.Terrain"); this->Shield_ConditionYellow.Read(exINI, GameStrings::AudioVisual, "Shield.ConditionYellow"); this->Shield_ConditionRed.Read(exINI, GameStrings::AudioVisual, "Shield.ConditionRed"); @@ -370,6 +372,7 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->PlacementGrid_TranslucencyWithPreview) .Process(this->PlacementPreview) .Process(this->PlacementPreview_Translucency) + .Process(this->SuperWeaponSidebar_AllowByDefault) .Process(this->ConditionYellow_Terrain) .Process(this->Shield_ConditionYellow) .Process(this->Shield_ConditionRed) diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index d3d9c1a7c3..6492f09b32 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -69,6 +69,8 @@ class RulesExt Valueable PlacementPreview; TranslucencyLevel PlacementPreview_Translucency; + Valueable SuperWeaponSidebar_AllowByDefault; + Nullable ConditionYellow_Terrain; Nullable Shield_ConditionYellow; Nullable Shield_ConditionRed; @@ -259,6 +261,8 @@ class RulesExt , PlacementPreview { false } , PlacementPreview_Translucency { 75 } + , SuperWeaponSidebar_AllowByDefault { false } + , Shield_ConditionYellow { } , Shield_ConditionRed { } , Pips_Shield_Background { } diff --git a/src/Ext/SWType/Body.cpp b/src/Ext/SWType/Body.cpp index c009999ab2..a937ed5e6f 100644 --- a/src/Ext/SWType/Body.cpp +++ b/src/Ext/SWType/Body.cpp @@ -6,6 +6,14 @@ SWTypeExt::ExtContainer SWTypeExt::ExtMap; +void SWTypeExt::ExtData::Initialize() +{ + this->EVA_InsufficientFunds = VoxClass::FindIndex(GameStrings::EVA_InsufficientFunds); + this->EVA_SelectTarget = VoxClass::FindIndex("EVA_SelectTarget"); + + this->Message_CannotFire = CSFText("MSG:CannotFire"); +} + // ============================= // load / save @@ -15,6 +23,14 @@ void SWTypeExt::ExtData::Serialize(T& Stm) Stm .Process(this->TypeID) .Process(this->Money_Amount) + .Process(this->EVA_Impatient) + .Process(this->EVA_InsufficientFunds) + .Process(this->EVA_SelectTarget) + .Process(this->SW_UseAITargeting) + .Process(this->SW_AutoFire) + .Process(this->SW_ManualFire) + .Process(this->SW_ShowCameo) + .Process(this->SW_Unstoppable) .Process(this->SW_Inhibitors) .Process(this->SW_AnyInhibitor) .Process(this->SW_Designators) @@ -29,6 +45,10 @@ void SWTypeExt::ExtData::Serialize(T& Stm) .Process(this->SW_PostDependent) .Process(this->SW_MaxCount) .Process(this->SW_Shots) + .Process(this->Message_CannotFire) + .Process(this->Message_InsufficientFunds) + .Process(this->Message_ColorScheme) + .Process(this->Message_FirerColor) .Process(this->UIDescription) .Process(this->CameoPriority) .Process(this->LimboDelivery_Types) @@ -53,6 +73,12 @@ void SWTypeExt::ExtData::Serialize(T& Stm) .Process(this->Convert_Pairs) .Process(this->ShowDesignatorRange) .Process(this->TabIndex) + .Process(this->SuperWeaponSidebar_Allow) + .Process(this->SuperWeaponSidebar_PriorityHouses) + .Process(this->SuperWeaponSidebar_RequiredHouses) + .Process(this->SuperWeaponSidebar_Significance) + .Process(this->SidebarPal) + .Process(this->SidebarPCX) .Process(this->UseWeeds) .Process(this->UseWeeds_Amount) .Process(this->UseWeeds_StorageTimer) @@ -80,6 +106,14 @@ void SWTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) // from ares this->Money_Amount.Read(exINI, pSection, "Money.Amount"); + this->EVA_Impatient.Read(exINI, pSection, "EVA.Impatient"); + this->EVA_InsufficientFunds.Read(exINI, pSection, "EVA.InsufficientFunds"); + this->EVA_SelectTarget.Read(exINI, pSection, "EVA.SelectTarget"); + this->SW_UseAITargeting.Read(exINI, pSection, "SW.UseAITargeting"); + this->SW_AutoFire.Read(exINI, pSection, "SW.AutoFire"); + this->SW_ManualFire.Read(exINI, pSection, "SW.ManualFire"); + this->SW_ShowCameo.Read(exINI, pSection, "SW.ShowCameo"); + 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_Designators.Read(exINI, pSection, "SW.Designators"); @@ -95,6 +129,18 @@ void SWTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->SW_MaxCount.Read(exINI, pSection, "SW.MaxCount"); this->SW_Shots.Read(exINI, pSection, "SW.Shots"); + this->Message_CannotFire.Read(exINI, pSection, "Message.CannotFire"); + this->Message_InsufficientFunds.Read(exINI, pSection, "Message.InsufficientFunds"); + + // messages and their properties + this->Message_FirerColor.Read(exINI, pSection, "Message.FirerColor"); + if (pINI->ReadString(pSection, "Message.Color", NONE_STR, Phobos::readBuffer)) + { + this->Message_ColorScheme = ColorScheme::FindIndex(Phobos::readBuffer); + if (this->Message_ColorScheme < 0) + Debug::INIParseFailed(pSection, "Message.Color", Phobos::readBuffer, "Expected a valid color scheme name."); + } + this->UIDescription.Read(exINI, pSection, "UIDescription"); this->CameoPriority.Read(exINI, pSection, "CameoPriority"); this->LimboDelivery_Types.Read(exINI, pSection, "LimboDelivery.Types"); @@ -182,6 +228,14 @@ void SWTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->TabIndex.Read(exINI, pSection, "TabIndex"); GeneralUtils::IntValidCheck(&this->TabIndex, pSection, "TabIndex", 1, 0, 3); + this->SuperWeaponSidebar_Allow.Read(exINI, pSection, "SuperWeaponSidebar.Allow"); + this->SuperWeaponSidebar_PriorityHouses = pINI->ReadHouseTypesList(pSection, "SuperWeaponSidebar.PriorityHouses", this->SuperWeaponSidebar_PriorityHouses); + this->SuperWeaponSidebar_RequiredHouses = pINI->ReadHouseTypesList(pSection, "SuperWeaponSidebar.RequiredHouses", this->SuperWeaponSidebar_RequiredHouses); + this->SuperWeaponSidebar_Significance.Read(exINI, pSection, "SuperWeaponSidebar.Significance"); + + this->SidebarPal.LoadFromINI(pINI, pSection, "SidebarPalette"); + this->SidebarPCX.Read(pINI, pSection, "SidebarPCX"); + this->UseWeeds.Read(exINI, pSection, "UseWeeds"); this->UseWeeds_Amount.Read(exINI, pSection, "UseWeeds.Amount"); this->UseWeeds_StorageTimer.Read(exINI, pSection, "UseWeeds.StorageTimer"); diff --git a/src/Ext/SWType/Body.h b/src/Ext/SWType/Body.h index aae1713b62..e4fd387560 100644 --- a/src/Ext/SWType/Body.h +++ b/src/Ext/SWType/Body.h @@ -26,6 +26,14 @@ class SWTypeExt //Ares 0.A Valueable Money_Amount; + ValueableIdx EVA_Impatient; + ValueableIdx EVA_InsufficientFunds; + ValueableIdx EVA_SelectTarget; + Valueable SW_UseAITargeting; + Valueable SW_AutoFire; + Valueable SW_ManualFire; + Valueable SW_ShowCameo; + Valueable SW_Unstoppable; ValueableVector SW_Inhibitors; Valueable SW_AnyInhibitor; ValueableVector SW_Designators; @@ -42,6 +50,11 @@ class SWTypeExt ValueableIdx SW_PostDependent; Valueable SW_MaxCount; + Valueable Message_CannotFire; + Valueable Message_InsufficientFunds; + Valueable Message_ColorScheme; + Valueable Message_FirerColor; + Valueable UIDescription; Valueable CameoPriority; ValueableVector LimboDelivery_Types; @@ -67,6 +80,14 @@ class SWTypeExt Valueable TabIndex; + Nullable SuperWeaponSidebar_Allow; + DWORD SuperWeaponSidebar_PriorityHouses; + DWORD SuperWeaponSidebar_RequiredHouses; + Valueable SuperWeaponSidebar_Significance; + + CustomPalette SidebarPal; + PhobosPCXFile SidebarPCX; + std::vector> LimboDelivery_RandomWeightsData; std::vector> SW_Next_RandomWeightsData; @@ -85,6 +106,14 @@ class SWTypeExt ExtData(SuperWeaponTypeClass* OwnerObject) : Extension(OwnerObject) , TypeID { "" } , Money_Amount { 0 } + , EVA_Impatient { -1 } + , EVA_InsufficientFunds { -1 } + , EVA_SelectTarget { -1 } + , SW_UseAITargeting { false } + , SW_AutoFire { false } + , SW_ManualFire { true } + , SW_ShowCameo { true } + , SW_Unstoppable { false } , SW_Inhibitors {} , SW_AnyInhibitor { false } , SW_Designators { } @@ -99,6 +128,10 @@ class SWTypeExt , SW_PostDependent {} , SW_MaxCount { -1 } , SW_Shots { -1 } + , Message_CannotFire {} + , Message_InsufficientFunds {} + , Message_ColorScheme { -1 } + , Message_FirerColor { false } , UIDescription {} , CameoPriority { 0 } , LimboDelivery_Types {} @@ -123,6 +156,12 @@ class SWTypeExt , Convert_Pairs {} , ShowDesignatorRange { true } , TabIndex { 1 } + , SuperWeaponSidebar_Allow {} + , SuperWeaponSidebar_PriorityHouses { 0u } + , SuperWeaponSidebar_RequiredHouses { 0xFFFFFFFFu } + , SuperWeaponSidebar_Significance { 0 } + , SidebarPal {} + , SidebarPCX {} , UseWeeds { false } , UseWeeds_Amount { RulesClass::Instance->WeedCapacity } , UseWeeds_StorageTimer { false } @@ -144,6 +183,7 @@ class SWTypeExt bool IsLaunchSite(BuildingClass* pBuilding) const; std::pair GetLaunchSiteRange(BuildingClass* pBuilding = nullptr) const; bool IsAvailable(HouseClass* pHouse) const; + void PrintMessage(const CSFText& message, HouseClass* pFirer) const; void ApplyLimboDelivery(HouseClass* pHouse); void ApplyLimboKill(HouseClass* pHouse); @@ -155,6 +195,8 @@ class SWTypeExt std::pair GetEMPulseCannonRange(BuildingClass* pBuilding) const; virtual void LoadFromINIFile(CCINIClass* pINI) override; + virtual void Initialize() override; + virtual ~ExtData() = default; virtual void InvalidatePointer(void* ptr, bool bRemoved) override { } diff --git a/src/Ext/SWType/SWHelpers.cpp b/src/Ext/SWType/SWHelpers.cpp index 2d95c15233..985d4038f0 100644 --- a/src/Ext/SWType/SWHelpers.cpp +++ b/src/Ext/SWType/SWHelpers.cpp @@ -1,4 +1,5 @@ #include "Body.h" +#include // Universal handler of the rolls-weights system std::vector SWTypeExt::ExtData::WeightedRollsHandler(ValueableVector* rolls, std::vector>* weights, size_t size) @@ -268,3 +269,35 @@ std::pair SWTypeExt::ExtData::GetEMPulseCannonRange(BuildingClas return std::make_pair(this->SW_RangeMinimum.Get(), this->SW_RangeMaximum.Get()); } + +void SWTypeExt::ExtData::PrintMessage(const CSFText& message, HouseClass* pFirer) const +{ + if (message.empty()) + return; + + int color = ColorScheme::FindIndex("Gold"); + if (this->Message_FirerColor) + { + // firer color + if (pFirer) + { + color = pFirer->ColorSchemeIndex; + } + } + else + { + if (this->Message_ColorScheme > -1) + { + // user defined color + color = this->Message_ColorScheme; + } + else if (const auto pCurrent = HouseClass::CurrentPlayer) + { + // default way: the current player's color + color = pCurrent->ColorSchemeIndex; + } + } + + // print the message + MessageListClass::Instance.PrintMessage(message, RulesClass::Instance->MessageDelay, color); +} diff --git a/src/Ext/Scenario/Body.cpp b/src/Ext/Scenario/Body.cpp index c4bdaaaeb5..0a81b06fdd 100644 --- a/src/Ext/Scenario/Body.cpp +++ b/src/Ext/Scenario/Body.cpp @@ -161,6 +161,8 @@ void ScenarioExt::ExtData::Serialize(T& Stm) .Process(this->BriefingTheme) .Process(this->AutoDeathObjects) .Process(this->TransportReloaders) + .Process(this->SWSidebar_Enable) + .Process(this->SWSidebar_Indices) ; } diff --git a/src/Ext/Scenario/Body.h b/src/Ext/Scenario/Body.h index 3d296a5c5f..6a51659d2b 100644 --- a/src/Ext/Scenario/Body.h +++ b/src/Ext/Scenario/Body.h @@ -36,6 +36,9 @@ class ScenarioExt std::vector AutoDeathObjects; std::vector TransportReloaders; // Objects that can reload ammo in limbo + bool SWSidebar_Enable; + std::vector SWSidebar_Indices; + ExtData(ScenarioClass* OwnerObject) : Extension(OwnerObject) , ShowBriefing { false } , BriefingTheme { -1 } @@ -43,6 +46,8 @@ class ScenarioExt , Variables { } , AutoDeathObjects {} , TransportReloaders {} + , SWSidebar_Enable { true } + , SWSidebar_Indices {} { } void SetVariableToByID(bool bIsGlobal, int nIndex, char bState); diff --git a/src/Ext/Side/Body.cpp b/src/Ext/Side/Body.cpp index d763574cb1..8784fab9cf 100644 --- a/src/Ext/Side/Body.cpp +++ b/src/Ext/Side/Body.cpp @@ -42,6 +42,11 @@ void SideExt::ExtData::LoadFromINIFile(CCINIClass* pINI) this->ToolTip_Background_Opacity.Read(exINI, pSection, "ToolTip.Background.Opacity"); this->ToolTip_Background_BlurSize.Read(exINI, pSection, "ToolTip.Background.BlurSize"); this->BriefingTheme = pINI->ReadTheme(pSection, "BriefingTheme", this->BriefingTheme); + this->SuperWeaponSidebar_OnPCX.Read(pINI, pSection, "SuperWeaponSidebar.OnPCX"); + this->SuperWeaponSidebar_OffPCX.Read(pINI, pSection, "SuperWeaponSidebar.OffPCX"); + this->SuperWeaponSidebar_TopPCX.Read(pINI, pSection, "SuperWeaponSidebar.TopPCX"); + this->SuperWeaponSidebar_CenterPCX.Read(pINI, pSection, "SuperWeaponSidebar.CenterPCX"); + this->SuperWeaponSidebar_BottomPCX.Read(pINI, pSection, "SuperWeaponSidebar.BottomPCX"); } // ============================= @@ -71,6 +76,11 @@ void SideExt::ExtData::Serialize(T& Stm) .Process(this->IngameScore_WinTheme) .Process(this->IngameScore_LoseTheme) .Process(this->BriefingTheme) + .Process(this->SuperWeaponSidebar_OnPCX) + .Process(this->SuperWeaponSidebar_OffPCX) + .Process(this->SuperWeaponSidebar_TopPCX) + .Process(this->SuperWeaponSidebar_CenterPCX) + .Process(this->SuperWeaponSidebar_BottomPCX) ; } diff --git a/src/Ext/Side/Body.h b/src/Ext/Side/Body.h index 88e694aa0c..e0f3362ef0 100644 --- a/src/Ext/Side/Body.h +++ b/src/Ext/Side/Body.h @@ -36,6 +36,11 @@ class SideExt Nullable ToolTip_Background_Opacity; Nullable ToolTip_Background_BlurSize; Valueable BriefingTheme; + PhobosPCXFile SuperWeaponSidebar_OnPCX; + PhobosPCXFile SuperWeaponSidebar_OffPCX; + PhobosPCXFile SuperWeaponSidebar_TopPCX; + PhobosPCXFile SuperWeaponSidebar_CenterPCX; + PhobosPCXFile SuperWeaponSidebar_BottomPCX; ExtData(SideClass* OwnerObject) : Extension(OwnerObject) , ArrayIndex { -1 } @@ -58,6 +63,11 @@ class SideExt , ToolTip_Background_Opacity { } , ToolTip_Background_BlurSize { } , BriefingTheme { -1 } + , SuperWeaponSidebar_OnPCX {} + , SuperWeaponSidebar_OffPCX {} + , SuperWeaponSidebar_TopPCX {} + , SuperWeaponSidebar_CenterPCX {} + , SuperWeaponSidebar_BottomPCX {} { } virtual ~ExtData() = default; diff --git a/src/Ext/Sidebar/Body.cpp b/src/Ext/Sidebar/Body.cpp index 3f9baa9cc1..a463cc63ca 100644 --- a/src/Ext/Sidebar/Body.cpp +++ b/src/Ext/Sidebar/Body.cpp @@ -1,4 +1,5 @@ #include "Body.h" +#include "SWSidebar/SWSidebarClass.h" #include #include @@ -37,7 +38,12 @@ bool __stdcall SidebarExt::AresTabCameo_RemoveCameo(BuildType* pItem) const auto& supers = pCurrent->Supers; if (supers.ValidIndex(pItem->ItemIndex) && supers[pItem->ItemIndex]->IsPresent) - return false; + { + if (SWSidebarClass::Instance.AddButton(pItem->ItemIndex)) + ScenarioExt::Global()->SWSidebar_Indices.emplace_back(pItem->ItemIndex); + else + return false; + } } // The following sections have been modified @@ -45,6 +51,7 @@ bool __stdcall SidebarExt::AresTabCameo_RemoveCameo(BuildType* pItem) if (pItem->ItemType == AbstractType::BuildingType || pItem->ItemType == AbstractType::Building) { + __assume(pTechnoType != nullptr); buildCat = static_cast(pTechnoType)->BuildCat; auto& pDisplay = DisplayClass::Instance; pDisplay.SetActiveFoundation(nullptr); diff --git a/src/Ext/Sidebar/Hooks.cpp b/src/Ext/Sidebar/Hooks.cpp index 4a41be815a..dbee38ba07 100644 --- a/src/Ext/Sidebar/Hooks.cpp +++ b/src/Ext/Sidebar/Hooks.cpp @@ -1,4 +1,5 @@ #include "Body.h" +#include "SWSidebar/SWSidebarClass.h" #include #include @@ -97,3 +98,31 @@ DEFINE_HOOK(0x72FCB5, InitSideRectangles_CenterBackground, 0x5) return 0; } + +#pragma region SWSidebarButtonsRelated + +DEFINE_HOOK(0x692419, DisplayClass_ProcessClickCoords_SWSidebar, 0x7) +{ + enum { DoNothing = 0x6925FC }; + + if (SWSidebarClass::IsEnabled() && SWSidebarClass::Instance.CurrentColumn) + return DoNothing; + + const auto toggleButton = SWSidebarClass::Instance.ToggleButton; + + return toggleButton && toggleButton->IsHovering ? DoNothing : 0; +} + +DEFINE_HOOK(0x6A5082, SidebarClass_InitClear_InitializeSWSidebar, 0x5) +{ + SWSidebarClass::Instance.InitClear(); + return 0; +} + +DEFINE_HOOK(0x6A5839, SidebarClass_InitIO_InitializeSWSidebar, 0x5) +{ + SWSidebarClass::Instance.InitIO(); + return 0; +} + +#pragma endregion diff --git a/src/Ext/Sidebar/SWSidebar/SWButtonClass.cpp b/src/Ext/Sidebar/SWSidebar/SWButtonClass.cpp new file mode 100644 index 0000000000..9ab43f48a0 --- /dev/null +++ b/src/Ext/Sidebar/SWSidebar/SWButtonClass.cpp @@ -0,0 +1,229 @@ +#include "SWButtonClass.h" +#include "SWSidebarClass.h" +#include +#include +#include +#include + +#include +#include + +SWButtonClass::SWButtonClass(unsigned int id, int superIdx, int x, int y, int width, int height) + : ControlClass(id, x, y, width, height, (GadgetFlag::LeftPress | GadgetFlag::RightPress), false) + , SuperIndex(superIdx) +{ + if (const auto backColumn = SWSidebarClass::Instance.Columns.back()) + backColumn->Buttons.emplace_back(this); + + this->Disabled = !SWSidebarClass::IsEnabled(); +} + +bool SWButtonClass::Draw(bool forced) +{ + if (!forced) + return false; + + const auto pSurface = DSurface::Composite; + auto bounds = pSurface->GetRect(); + Point2D location = { this->X, this->Y }; + RectangleStruct destRect = { location.X, location.Y, this->Width, this->Height }; + + const auto pCurrent = HouseClass::CurrentPlayer; + const auto pSuper = pCurrent->Supers[this->SuperIndex]; + const auto pSWExt = SWTypeExt::ExtMap.Find(pSuper->Type); + + // support for pcx cameos + if (const auto pPCXCameo = pSWExt->SidebarPCX.GetSurface()) + { + PCX::Instance.BlitToSurface(&destRect, pSurface, pPCXCameo); + } + else if (const auto pCameo = pSuper->Type->SidebarImage) // old shp cameos, fixed palette + { + const auto pCameoRef = pCameo->AsReference(); + char pFilename[0x20]; + strcpy_s(pFilename, RulesExt::Global()->MissingCameo.data()); + _strlwr_s(pFilename); + + if (!_stricmp(pCameoRef->Filename, GameStrings::XXICON_SHP) && strstr(pFilename, ".pcx")) + { + PCX::Instance.LoadFile(pFilename); + + if (const auto CameoPCX = PCX::Instance.GetSurface(pFilename)) + PCX::Instance.BlitToSurface(&destRect, pSurface, CameoPCX); + } + else + { + const auto pConvert = pSWExt->SidebarPal.Convert ? pSWExt->SidebarPal.GetConvert() : FileSystem::CAMEO_PAL; + pSurface->DrawSHP(pConvert, pCameo, 0, &location, &bounds, BlitterFlags::bf_400, 0, 0, ZGradient::Ground, 1000, 0, nullptr, 0, 0, 0); + } + } + + if (this->IsHovering) + { + RectangleStruct cameoRect = { location.X, location.Y, this->Width, this->Height }; + const COLORREF tooltipColor = Drawing::RGB_To_Int(Drawing::TooltipColor); + pSurface->DrawRect(&cameoRect, tooltipColor); + } + + if (pSuper->IsReady && !pCurrent->CanTransactMoney(pSWExt->Money_Amount) || + (pSWExt->SW_UseAITargeting && AresFunctions::IsTargetConstraintsEligible && !AresFunctions::IsTargetConstraintsEligible(AresFunctions::SWTypeExtMap_Find(pSuper->Type), HouseClass::CurrentPlayer, true))) + { + RectangleStruct darkenBounds { 0, 0, location.X + this->Width, location.Y + this->Height }; + pSurface->DrawSHP(FileSystem::SIDEBAR_PAL, FileSystem::DARKEN_SHP, 0, &location, &darkenBounds, BlitterFlags::bf_400 | BlitterFlags::Darken, 0, 0, ZGradient::Ground, 1000, 0, nullptr, 0, 0, 0); + } + + const bool ready = !pSuper->IsSuspended && (pSuper->Type->UseChargeDrain ? pSuper->ChargeDrainState == ChargeDrainState::Ready : pSuper->IsReady); + bool drawReadiness = true; + + if (ready && this->ColumnIndex == 0) + { + auto& buttons = SWSidebarClass::Instance.Columns[this->ColumnIndex]->Buttons; + const int buttonId = std::distance(buttons.begin(), std::find(buttons.begin(), buttons.end(), this)); + + if (buttonId < 10) + { + unsigned short hotkey = 0; + + for (int idx = 0; idx < CommandClass::Hotkeys.IndexCount; idx++) + { + if (CommandClass::Hotkeys.IndexTable[idx].Data == SWSidebarClass::Commands[buttonId]) + hotkey = CommandClass::Hotkeys.IndexTable[idx].ID; + } + + Point2D textLoc = { location.X + this->Width / 2, location.Y }; + const COLORREF foreColor = Drawing::RGB_To_Int(Drawing::TooltipColor); + constexpr TextPrintType printType = TextPrintType::FullShadow | TextPrintType::Point8 | TextPrintType::Background | TextPrintType::Center; + + wchar_t buffer[64]; + UI::GetKeyboardKeyString(hotkey, buffer); + + if (std::wcslen(buffer)) + { + pSurface->DrawTextA(buffer, &bounds, &textLoc, foreColor, 0, printType); + drawReadiness = false; + } + } + } + + if (drawReadiness) + { + if (const auto buffer = pSuper->NameReadiness()) + { + Point2D textLoc = { location.X + this->Width / 2, location.Y }; + const COLORREF foreColor = Drawing::RGB_To_Int(Drawing::TooltipColor); + constexpr TextPrintType printType = TextPrintType::FullShadow | TextPrintType::Point8 | TextPrintType::Background | TextPrintType::Center; + + pSurface->DrawTextA(buffer, &bounds, &textLoc, foreColor, 0, printType); + } + } + + if (pSuper->ShouldDrawProgress()) + { + Point2D loc = { location.X, location.Y }; + pSurface->DrawSHP(FileSystem::SIDEBAR_PAL, FileSystem::GCLOCK2_SHP, pSuper->AnimStage() + 1, &loc, &bounds, BlitterFlags::bf_400 | BlitterFlags::TransLucent50, 0, 0, ZGradient::Ground, 1000, 0, nullptr, 0, 0, 0); + } + + return true; +} + +void SWButtonClass::OnMouseEnter() +{ + if (!SWSidebarClass::IsEnabled()) + return; + + this->IsHovering = true; + SWSidebarClass::Instance.CurrentButton = this; + SWSidebarClass::Instance.Columns[this->ColumnIndex]->OnMouseEnter(); + CCToolTip::Instance->SaveTimerDelay(); + CCToolTip::Instance->SetTimerDelay(0); +} + +void SWButtonClass::OnMouseLeave() +{ + this->IsHovering = false; + SWSidebarClass::Instance.CurrentButton = nullptr; + SWSidebarClass::Instance.Columns[this->ColumnIndex]->OnMouseLeave(); + CCToolTip::Instance->RestoreTimeDelay(); +} + +bool SWButtonClass::Action(GadgetFlag flags, DWORD* pKey, KeyModifier modifier) +{ + if (!SWSidebarClass::IsEnabled()) + return false; + + if (flags & GadgetFlag::RightPress) + DisplayClass::Instance.CurrentSWTypeIndex = -1; + + if (flags & GadgetFlag::LeftPress) + { + MouseClass::Instance.UpdateCursor(MouseCursorType::Default, false); + VocClass::PlayGlobal(RulesClass::Instance->GUIBuildSound, 0x2000, 1.0); + this->LaunchSuper(); + } + + // this->ControlClass::Action(flags, pKey, KeyModifier::None); + reinterpret_cast(0x48E5A0)(this, flags, pKey, KeyModifier::None); + return true; +} + +void SWButtonClass::SetColumn(int column) +{ + this->ColumnIndex = column; +} + +bool SWButtonClass::LaunchSuper() const +{ + const auto pCurrent = HouseClass::CurrentPlayer; + const auto pSuper = pCurrent->Supers[this->SuperIndex]; + const auto pSWExt = SWTypeExt::ExtMap.Find(pSuper->Type); + const bool manual = !pSWExt->SW_ManualFire && pSWExt->SW_AutoFire; + const bool unstoppable = pSuper->Type->UseChargeDrain && pSuper->ChargeDrainState == ChargeDrainState::Draining && pSWExt->SW_Unstoppable; + + if (!pSuper->CanFire() && !manual) + { + VoxClass::PlayIndex(pSWExt->EVA_Impatient); + return false; + } + + if (!pCurrent->CanTransactMoney(pSWExt->Money_Amount)) + { + VoxClass::PlayIndex(pSWExt->EVA_InsufficientFunds); + pSWExt->PrintMessage(pSWExt->Message_InsufficientFunds, pCurrent); + } + else if (!pSWExt->SW_UseAITargeting || (AresFunctions::IsTargetConstraintsEligible && AresFunctions::IsTargetConstraintsEligible(AresFunctions::SWTypeExtMap_Find(pSuper->Type), HouseClass::CurrentPlayer, true))) + { + if (!manual && !unstoppable) + { + const auto swIndex = pSuper->Type->ArrayIndex; + + if (pSuper->Type->Action == Action::None || pSWExt->SW_UseAITargeting) + { + EventClass Event = EventClass(pCurrent->ArrayIndex, EventType::SpecialPlace, swIndex, CellStruct::Empty); + EventClass::AddEvent(Event); + } + else + { + DisplayClass::Instance.CurrentBuilding = nullptr; + DisplayClass::Instance.CurrentBuildingType = nullptr; + DisplayClass::Instance.CurrentBuildingOwnerArrayIndex = -1; + DisplayClass::Instance.SetActiveFoundation(nullptr); + MapClass::Instance.SetRepairMode(0); + MapClass::Instance.SetSellMode(0); + DisplayClass::Instance.PowerToggleMode = false; + DisplayClass::Instance.PlanningMode = false; + DisplayClass::Instance.PlaceBeaconMode = false; + DisplayClass::Instance.CurrentSWTypeIndex = swIndex; + MapClass::Instance.UnselectAll(); + VoxClass::PlayIndex(pSWExt->EVA_SelectTarget); + } + + return true; + } + } + else + { + pSWExt->PrintMessage(pSWExt->Message_CannotFire, pCurrent); + } + + return false; +} diff --git a/src/Ext/Sidebar/SWSidebar/SWButtonClass.h b/src/Ext/Sidebar/SWSidebar/SWButtonClass.h new file mode 100644 index 0000000000..3defc7e7ff --- /dev/null +++ b/src/Ext/Sidebar/SWSidebar/SWButtonClass.h @@ -0,0 +1,26 @@ +#pragma once +#include + +class SWButtonClass : public ControlClass +{ +public: + SWButtonClass() = default; + SWButtonClass(unsigned int id, int superIdx, int x, int y, int width, int height); + + ~SWButtonClass() = default; + + virtual bool Draw(bool forced) override; + virtual void OnMouseEnter() override; + virtual void OnMouseLeave() override; + virtual bool Action(GadgetFlag fags, DWORD* pKey, KeyModifier modifier) override; + + void SetColumn(int column); + bool LaunchSuper() const; + +public: + static constexpr int StartID = 2200; + + bool IsHovering { false }; + int ColumnIndex { -1 }; + int SuperIndex { -1 }; +}; diff --git a/src/Ext/Sidebar/SWSidebar/SWColumnClass.cpp b/src/Ext/Sidebar/SWSidebar/SWColumnClass.cpp new file mode 100644 index 0000000000..50073f6dd3 --- /dev/null +++ b/src/Ext/Sidebar/SWSidebar/SWColumnClass.cpp @@ -0,0 +1,173 @@ +#include "SWColumnClass.h" +#include "SWSidebarClass.h" + +#include +#include + +SWColumnClass::SWColumnClass(unsigned int id, int maxButtons, int x, int y, int width, int height) + : ControlClass(id, x, y, width, height, static_cast(0), false) + , MaxButtons(maxButtons) +{ + SWSidebarClass::Instance.Columns.emplace_back(this); + this->Disabled = !SWSidebarClass::IsEnabled(); +} + +bool SWColumnClass::Draw(bool forced) +{ + if (!SWSidebarClass::IsEnabled()) + return false; + + const auto pSideExt = SideExt::ExtMap.Find(SideClass::Array.Items[ScenarioClass::Instance->PlayerSideIndex]); + const int cameoWidth = 60, cameoHeight = 48; + const int cameoBackgroundWidth = Phobos::UI::SuperWeaponSidebar_Interval + cameoWidth; + + if (const auto pCenterPCX = pSideExt->SuperWeaponSidebar_CenterPCX.GetSurface()) + { + const int cameoHarfInterval = (Phobos::UI::SuperWeaponSidebar_CameoHeight - cameoHeight) / 2; + + for (const auto button : this->Buttons) + { + RectangleStruct drawRect { this->X, button->Y - cameoHarfInterval, cameoBackgroundWidth, Phobos::UI::SuperWeaponSidebar_CameoHeight }; + PCX::Instance.BlitToSurface(&drawRect, DSurface::Composite, pCenterPCX); + } + } + + if (const auto pTopPCX = pSideExt->SuperWeaponSidebar_TopPCX.GetSurface()) + { + const int height = pTopPCX->GetHeight(); + RectangleStruct drawRect { this->X, this->Y, cameoBackgroundWidth, height }; + PCX::Instance.BlitToSurface(&drawRect, DSurface::Composite, pTopPCX); + } + + if (const auto pBottomPCX = pSideExt->SuperWeaponSidebar_BottomPCX.GetSurface()) + { + const int height = pBottomPCX->GetHeight(); + RectangleStruct drawRect { this->X, this->Y + this->Height - height, cameoBackgroundWidth, height }; + PCX::Instance.BlitToSurface(&drawRect, DSurface::Composite, pBottomPCX); + } + + for (const auto button : this->Buttons) + button->Draw(true); + + return true; +} + +void SWColumnClass::OnMouseEnter() +{ + if (!SWSidebarClass::IsEnabled()) + return; + + SWSidebarClass::Instance.CurrentColumn = this; + MouseClass::Instance.UpdateCursor(MouseCursorType::Default, false); +} + +void SWColumnClass::OnMouseLeave() +{ + SWSidebarClass::Instance.CurrentColumn = nullptr; + MouseClass::Instance.UpdateCursor(MouseCursorType::Default, false); +} + +bool SWColumnClass::Clicked(DWORD* pKey, GadgetFlag flags, int x, int y, KeyModifier modifier) +{ + return false; +} + +bool SWColumnClass::AddButton(int superIdx) +{ + auto& buttons = this->Buttons; + const int buttonCount = static_cast(buttons.size()); + auto& sidebar = SWSidebarClass::Instance; + + if (buttonCount >= this->MaxButtons && !SWSidebarClass::Instance.AddColumn()) + { + const unsigned int ownerBits = 1u << HouseClass::CurrentPlayer->Type->ArrayIndex; + + auto Compare = [ownerBits](const int left, const int right) + { + const auto pExtA = SWTypeExt::ExtMap.Find(SuperWeaponTypeClass::Array.GetItemOrDefault(left)); + const auto pExtB = SWTypeExt::ExtMap.Find(SuperWeaponTypeClass::Array.GetItemOrDefault(right)); + + if (pExtB && (pExtB->SuperWeaponSidebar_PriorityHouses & ownerBits) && (!pExtA || !(pExtA->SuperWeaponSidebar_PriorityHouses & ownerBits))) + return false; + + if ((!pExtB || !(pExtB->SuperWeaponSidebar_PriorityHouses & ownerBits)) && pExtA && (pExtA->SuperWeaponSidebar_PriorityHouses & ownerBits)) + return true; + + return BuildType::SortsBefore(AbstractType::Special, left, AbstractType::Special, right); + }; + + const int backIdx = buttons.back()->SuperIndex; + + if (!Compare(superIdx, backIdx)) + return false; + + this->RemoveButton(backIdx); + sidebar.DisableEntry = true; + SidebarClass::Instance.AddCameo(AbstractType::Special, backIdx); + SidebarClass::Instance.RepaintSidebar(SidebarClass::GetObjectTabIdx(AbstractType::Super, backIdx, false)); + sidebar.DisableEntry = false; + } + + const int cameoWidth = 60, cameoHeight = 48; + const auto button = GameCreate(SWButtonClass::StartID + superIdx, superIdx, 0, 0, cameoWidth, cameoHeight); + + if (!button) + return false; + + button->Zap(); + GScreenClass::Instance.AddButton(button); + SWSidebarClass::Instance.SortButtons(); + + if (const auto toggleButton = SWSidebarClass::Instance.ToggleButton) + toggleButton->UpdatePosition(); + + return true; +} + +bool SWColumnClass::RemoveButton(int superIdx) +{ + auto& buttons = this->Buttons; + + const auto it = std::find_if(buttons.begin(), buttons.end(), [superIdx](SWButtonClass* const button) { return button->SuperIndex == superIdx; }); + + if (it == buttons.end()) + return false; + + AnnounceInvalidPointer(SWSidebarClass::Instance.CurrentButton, *it); + + auto& indices = ScenarioExt::Global()->SWSidebar_Indices; + const auto it_Idx = std::find(indices.cbegin(), indices.cend(), superIdx); + + if (it_Idx != indices.cend()) + indices.erase(it_Idx); + + GScreenClass::Instance.RemoveButton(*it); + buttons.erase(it); + return true; +} + +void SWColumnClass::ClearButtons(bool remove) +{ + auto& buttons = this->Buttons; + + if (remove) + { + for (const auto button : buttons) + GScreenClass::Instance.RemoveButton(button); + } + + buttons.clear(); +} + +void SWColumnClass::SetHeight(int height) +{ + const auto pSideExt = SideExt::ExtMap.Find(SideClass::Array.Items[ScenarioClass::Instance->PlayerSideIndex]); + + this->Height = height; + + if (const auto pTopPCX = pSideExt->SuperWeaponSidebar_TopPCX.GetSurface()) + this->Height += pTopPCX->GetHeight(); + + if (const auto pBottomPCX = pSideExt->SuperWeaponSidebar_BottomPCX.GetSurface()) + this->Height += pBottomPCX->GetHeight(); +} diff --git a/src/Ext/Sidebar/SWSidebar/SWColumnClass.h b/src/Ext/Sidebar/SWSidebar/SWColumnClass.h new file mode 100644 index 0000000000..4f3181a091 --- /dev/null +++ b/src/Ext/Sidebar/SWSidebar/SWColumnClass.h @@ -0,0 +1,28 @@ +#pragma once +#include "SWButtonClass.h" +#include + +#include + +class SWColumnClass : public ControlClass +{ +public: + SWColumnClass() = default; + SWColumnClass(unsigned int id, int maxButtons, int x, int y, int width, int height); + + ~SWColumnClass() = default; + + virtual bool Draw(bool forced) override; + virtual void OnMouseEnter() override; + virtual void OnMouseLeave() override; + virtual bool Clicked(DWORD* pKey, GadgetFlag flags, int x, int y, KeyModifier modifier) override; + + bool AddButton(int superIdx); + bool RemoveButton(int superIdx); + void ClearButtons(bool remove = true); + + void SetHeight(int height); + + std::vector Buttons {}; + int MaxButtons { 0 }; +}; diff --git a/src/Ext/Sidebar/SWSidebar/SWSidebarClass.cpp b/src/Ext/Sidebar/SWSidebar/SWSidebarClass.cpp new file mode 100644 index 0000000000..b96e71b574 --- /dev/null +++ b/src/Ext/Sidebar/SWSidebar/SWSidebarClass.cpp @@ -0,0 +1,342 @@ +#include "SWSidebarClass.h" +#include + +#include +#include +#include + +SWSidebarClass SWSidebarClass::Instance; +CommandClass* SWSidebarClass::Commands[10]; + +// ============================= +// functions + +bool SWSidebarClass::AddColumn() +{ + auto& columns = this->Columns; + + if (static_cast(columns.size()) >= Phobos::UI::SuperWeaponSidebar_MaxColumns) + return false; + + const int maxButtons = Phobos::UI::SuperWeaponSidebar_Max - static_cast(columns.size()); + + if (maxButtons <= 0) + return false; + + const int cameoWidth = 60; + const auto column = GameCreate(SWButtonClass::StartID + SuperWeaponTypeClass::Array.Count + 1 + static_cast(columns.size()), maxButtons, 0, 0, cameoWidth + Phobos::UI::SuperWeaponSidebar_Interval, Phobos::UI::SuperWeaponSidebar_CameoHeight); + + if (!column) + return false; + + column->Zap(); + GScreenClass::Instance.AddButton(column); + return true; +} + +bool SWSidebarClass::RemoveColumn() +{ + auto& columns = this->Columns; + + if (columns.empty()) + return false; + + if (const auto backColumn = columns.back()) + { + AnnounceInvalidPointer(SWSidebarClass::Instance.CurrentColumn, backColumn); + GScreenClass::Instance.RemoveButton(backColumn); + + columns.erase(columns.end() - 1); + return true; + } + + return false; +} + +void SWSidebarClass::InitClear() +{ + this->CurrentColumn = nullptr; + this->CurrentButton = nullptr; + + if (const auto toggleButton = this->ToggleButton) + { + this->ToggleButton = nullptr; + GScreenClass::Instance.RemoveButton(toggleButton); + } + + auto& columns = this->Columns; + + for (const auto column : columns) + { + column->ClearButtons(); + GScreenClass::Instance.RemoveButton(column); + } + + columns.clear(); +} + +void SWSidebarClass::InitIO() +{ + if (!Phobos::UI::SuperWeaponSidebar || Unsorted::ArmageddonMode) + return; + + if (const auto pSideExt = SideExt::ExtMap.Find(SideClass::Array.Items[ScenarioClass::Instance->PlayerSideIndex])) + { + const auto pOnPCX = pSideExt->SuperWeaponSidebar_OnPCX.GetSurface(); + const auto pOffPCX = pSideExt->SuperWeaponSidebar_OffPCX.GetSurface(); + int width = 0, height = 0; + + if (pOnPCX) + { + if (pOffPCX) + { + width = std::max(pOnPCX->GetWidth(), pOffPCX->GetWidth()); + height = std::max(pOnPCX->GetHeight(), pOffPCX->GetHeight()); + } + else + { + width = pOnPCX->GetWidth(); + height = pOnPCX->GetHeight(); + } + } + else if (pOffPCX) + { + width = pOffPCX->GetWidth(); + height = pOffPCX->GetHeight(); + } + + if (width > 0 && height > 0) + { + if (const auto toggleButton = GameCreate(SWButtonClass::StartID + SuperWeaponTypeClass::Array.Count, 0, 0, width, height)) + { + toggleButton->Zap(); + GScreenClass::Instance.AddButton(toggleButton); + SWSidebarClass::Instance.ToggleButton = toggleButton; + toggleButton->UpdatePosition(); + } + } + } + + for (const auto superIdx : ScenarioExt::Global()->SWSidebar_Indices) + SWSidebarClass::Instance.AddButton(superIdx); +} + +bool SWSidebarClass::AddButton(int superIdx) +{ + if (!Phobos::UI::SuperWeaponSidebar || this->DisableEntry) + return false; + + const auto pSWType = SuperWeaponTypeClass::Array.GetItemOrDefault(superIdx); + + if (!pSWType) + return false; + + const auto pSWExt = SWTypeExt::ExtMap.Find(pSWType); + + if (!pSWExt->SW_ShowCameo || !pSWExt->SuperWeaponSidebar_Allow.Get(RulesExt::Global()->SuperWeaponSidebar_AllowByDefault)) + return false; + + const unsigned int ownerBits = 1u << HouseClass::CurrentPlayer->Type->ArrayIndex; + + if ((pSWExt->SuperWeaponSidebar_RequiredHouses & ownerBits) == 0) + return false; + + if (pSWExt->SuperWeaponSidebar_Significance < Phobos::Config::SuperWeaponSidebar_RequiredSignificance) + return false; + + auto& columns = this->Columns; + + if (columns.empty() && !this->AddColumn()) + return false; + + if (std::any_of(columns.begin(), columns.end(), [superIdx](SWColumnClass* column) { return std::any_of(column->Buttons.begin(), column->Buttons.end(), [superIdx](SWButtonClass* button) { return button->SuperIndex == superIdx; }); })) + return true; + + return columns.back()->AddButton(superIdx); +} + +void SWSidebarClass::SortButtons() +{ + auto& columns = this->Columns; + + if (columns.empty()) + { + if (const auto toggleButton = this->ToggleButton) + toggleButton->UpdatePosition(); + + return; + } + + std::vector vec_Buttons; + vec_Buttons.reserve(this->GetMaximumButtonCount()); + + for (const auto column : columns) + { + for (const auto button : column->Buttons) + vec_Buttons.emplace_back(button); + + column->ClearButtons(false); + } + + const unsigned int ownerBits = 1u << HouseClass::CurrentPlayer->Type->ArrayIndex; + + std::stable_sort(vec_Buttons.begin(), vec_Buttons.end(), [ownerBits](SWButtonClass* const a, SWButtonClass* const b) + { + const auto pExtA = SWTypeExt::ExtMap.Find(SuperWeaponTypeClass::Array.GetItemOrDefault(a->SuperIndex)); + const auto pExtB = SWTypeExt::ExtMap.Find(SuperWeaponTypeClass::Array.GetItemOrDefault(b->SuperIndex)); + + if (pExtB && (pExtB->SuperWeaponSidebar_PriorityHouses & ownerBits) && (!pExtA || !(pExtA->SuperWeaponSidebar_PriorityHouses & ownerBits))) + return false; + + if ((!pExtB || !(pExtB->SuperWeaponSidebar_PriorityHouses & ownerBits)) && pExtA && (pExtA->SuperWeaponSidebar_PriorityHouses & ownerBits)) + return true; + + return BuildType::SortsBefore(AbstractType::Special, a->SuperIndex, AbstractType::Special, b->SuperIndex); + }); + + const int buttonCount = static_cast(vec_Buttons.size()); + const int cameoWidth = 60, cameoHeight = 48; + const int maximum = Phobos::UI::SuperWeaponSidebar_Max; + const int cameoHarfInterval = (Phobos::UI::SuperWeaponSidebar_CameoHeight - cameoHeight) / 2; + int location_Y = (DSurface::ViewBounds.Height - std::min(buttonCount, maximum) * Phobos::UI::SuperWeaponSidebar_CameoHeight) / 2; + Point2D location = { Phobos::UI::SuperWeaponSidebar_LeftOffset, location_Y + cameoHarfInterval }; + int rowIdx = 0, columnIdx = 0; + + for (const auto button : vec_Buttons) + { + const auto column = columns[columnIdx]; + + if (rowIdx == 0) + { + const auto pTopPCX = SideExt::ExtMap.Find(SideClass::Array.Items[ScenarioClass::Instance->PlayerSideIndex])->SuperWeaponSidebar_TopPCX.GetSurface(); + column->SetPosition(location.X - Phobos::UI::SuperWeaponSidebar_LeftOffset, location_Y - (pTopPCX ? pTopPCX->GetHeight() : 0)); + } + + column->Buttons.emplace_back(button); + button->SetColumn(columnIdx); + button->SetPosition(location.X, location.Y); + rowIdx++; + + if (rowIdx >= maximum - columnIdx) + { + rowIdx = 0; + columnIdx++; + location_Y += Phobos::UI::SuperWeaponSidebar_CameoHeight / 2; + location.X += cameoWidth + Phobos::UI::SuperWeaponSidebar_Interval; + location.Y = location_Y + cameoHarfInterval; + } + else + { + location.Y += Phobos::UI::SuperWeaponSidebar_CameoHeight; + } + } + + for (const auto column : columns) + column->SetHeight(column->Buttons.size() * Phobos::UI::SuperWeaponSidebar_CameoHeight); +} + +int SWSidebarClass::GetMaximumButtonCount() +{ + const int firstColumn = Phobos::UI::SuperWeaponSidebar_Max; + const int columns = std::min(firstColumn, Phobos::UI::SuperWeaponSidebar_MaxColumns); + return (firstColumn + (firstColumn - (columns - 1))) * columns / 2; +} + +bool SWSidebarClass::IsEnabled() +{ + return ScenarioExt::Global()->SWSidebar_Enable; +} + +void SWSidebarClass::RecheckCameo() +{ + auto& sidebar = SWSidebarClass::Instance; + + for (const auto& column : sidebar.Columns) + { + std::vector removeButtons; + + for (const auto& button : column->Buttons) + { + if (HouseClass::CurrentPlayer->Supers[button->SuperIndex]->IsPresent) + continue; + + removeButtons.push_back(button->SuperIndex); + } + + if (removeButtons.size()) + HouseClass::CurrentPlayer->RecheckTechTree = true; + + for (const auto& index : removeButtons) + column->RemoveButton(index); + } + + sidebar.SortButtons(); + int removes = 0; + + for (const auto& column : sidebar.Columns) + { + if (column->Buttons.empty()) + ++removes; + } + + for (; removes > 0; --removes) + sidebar.RemoveColumn(); + + if (const auto toggleButton = sidebar.ToggleButton) + toggleButton->UpdatePosition(); +} + +// Hooks + +DEFINE_HOOK(0x4F92FB, HouseClass_UpdateTechTree_SWSidebar, 0x7) +{ + enum { SkipGameCode = 0x4F9302 }; + + GET(HouseClass*, pHouse, ESI); + + pHouse->AISupers(); + + if (pHouse->IsCurrentPlayer()) + SWSidebarClass::RecheckCameo(); + + return SkipGameCode; +} + +DEFINE_HOOK(0x6A6316, SidebarClass_AddCameo_SuperWeapon_SWSidebar, 0x6) +{ + enum { ReturnFalse = 0x6A65FF }; + + GET_STACK(AbstractType, whatAmI, STACK_OFFSET(0x14, 0x4)); + + if (whatAmI != AbstractType::Special && whatAmI != AbstractType::SuperWeaponType && whatAmI != AbstractType::Super) + return 0; + + GET_STACK(int, index, STACK_OFFSET(0x14, 0x8)); + + if (SWSidebarClass::Instance.AddButton(index)) + { + ScenarioExt::Global()->SWSidebar_Indices.emplace_back(index); + return ReturnFalse; + } + + return 0; +} + +DEFINE_HOOK(0x6AA790, StripClass_RecheckCameo_RemoveCameo, 0x6) +{ + enum { ShouldRemove = 0x6AA7B6, ShouldNotRemove = 0x6AAA68 }; + + GET(BuildType*, pItem, ESI); + const auto pCurrent = HouseClass::CurrentPlayer; + const auto& supers = pCurrent->Supers; + + if (supers.ValidIndex(pItem->ItemIndex) && supers[pItem->ItemIndex]->IsPresent) + { + if (SWSidebarClass::Instance.AddButton(pItem->ItemIndex)) + ScenarioExt::Global()->SWSidebar_Indices.emplace_back(pItem->ItemIndex); + else + return ShouldNotRemove; + } + + return ShouldRemove; +} diff --git a/src/Ext/Sidebar/SWSidebar/SWSidebarClass.h b/src/Ext/Sidebar/SWSidebar/SWSidebarClass.h new file mode 100644 index 0000000000..5cfaf415a9 --- /dev/null +++ b/src/Ext/Sidebar/SWSidebar/SWSidebarClass.h @@ -0,0 +1,35 @@ +#pragma once +#include "SWButtonClass.h" +#include "SWColumnClass.h" +#include "ToggleSWButtonClass.h" +#include +#include + +class SWSidebarClass +{ +public: + bool AddColumn(); + bool RemoveColumn(); + + void InitClear(); + void InitIO(); + + bool AddButton(int superIdx); + void SortButtons(); + + int GetMaximumButtonCount(); + + static bool IsEnabled(); + static void RecheckCameo(); + + static SWSidebarClass Instance; + +public: + std::vector Columns {}; + SWColumnClass* CurrentColumn { nullptr }; + SWButtonClass* CurrentButton { nullptr }; + ToggleSWButtonClass* ToggleButton { nullptr }; + bool DisableEntry { false }; + + static CommandClass* Commands[10]; +}; diff --git a/src/Ext/Sidebar/SWSidebar/ToggleSWButtonClass.cpp b/src/Ext/Sidebar/SWSidebar/ToggleSWButtonClass.cpp new file mode 100644 index 0000000000..92204e1c1f --- /dev/null +++ b/src/Ext/Sidebar/SWSidebar/ToggleSWButtonClass.cpp @@ -0,0 +1,130 @@ +#include "ToggleSWButtonClass.h" +#include "SWSidebarClass.h" +#include +#include + +#include + +ToggleSWButtonClass::ToggleSWButtonClass(unsigned int id, int x, int y, int width, int height) + : ControlClass(id, x, y, width, height, (GadgetFlag::LeftPress | GadgetFlag::LeftRelease), false) +{ + SWSidebarClass::Instance.ToggleButton = this; +} + +bool ToggleSWButtonClass::Draw(bool forced) +{ + auto& columns = SWSidebarClass::Instance.Columns; + + if (columns.empty()) + return false; + + const auto pSideExt = SideExt::ExtMap.Find(SideClass::Array.Items[ScenarioClass::Instance->PlayerSideIndex]); + const auto pTogglePCX = SWSidebarClass::IsEnabled() ? pSideExt->SuperWeaponSidebar_OnPCX.GetSurface() : pSideExt->SuperWeaponSidebar_OffPCX.GetSurface(); + + if (!pTogglePCX) + return false; + + RectangleStruct destRect { this->X, this->Y, this->Width, this->Height }; + PCX::Instance.BlitToSurface(&destRect, DSurface::Composite, pTogglePCX); + + if (this->IsHovering) + { + const COLORREF tooltipColor = Drawing::RGB_To_Int(Drawing::TooltipColor); + DSurface::Composite->DrawRect(&destRect, tooltipColor); + } + + return true; +} + +void ToggleSWButtonClass::OnMouseEnter() +{ + auto& columns = SWSidebarClass::Instance.Columns; + + if (columns.empty()) + return; + + this->IsHovering = true; + MouseClass::Instance.UpdateCursor(MouseCursorType::Default, false); +} + +void ToggleSWButtonClass::OnMouseLeave() +{ + this->IsHovering = false; + this->IsPressed = false; + MouseClass::Instance.UpdateCursor(MouseCursorType::Default, false); +} + +bool ToggleSWButtonClass::Action(GadgetFlag flags, DWORD* pKey, KeyModifier modifier) +{ + auto& columns = SWSidebarClass::Instance.Columns; + + if (columns.empty()) + return false; + + const auto pSideExt = SideExt::ExtMap.Find(SideClass::Array.Items[ScenarioClass::Instance->PlayerSideIndex]); + + if (SWSidebarClass::IsEnabled() ? !pSideExt->SuperWeaponSidebar_OnPCX.GetSurface() : !pSideExt->SuperWeaponSidebar_OffPCX.GetSurface()) + return false; + + if (flags & GadgetFlag::LeftPress) + this->IsPressed = true; + + if ((flags & GadgetFlag::LeftRelease) && this->IsPressed) + { + this->IsPressed = false; + ToggleSWButtonClass::SwitchSidebar(); + } + + // this->ControlClass::Action(flags, pKey, KeyModifier::None); + reinterpret_cast(0x48E5A0)(this, flags, pKey, KeyModifier::None); + return true; +} + +void ToggleSWButtonClass::UpdatePosition() +{ + Point2D position = Point2D::Empty; + auto& columns = SWSidebarClass::Instance.Columns; + + if (!columns.empty()) + { + const auto backColumn = columns.back(); + position.X = SWSidebarClass::Instance.IsEnabled() ? backColumn->X + backColumn->Width : 0; + position.Y = backColumn->Y + (backColumn->Height - this->Height) / 2; + } + else + { + position.X = 0; + position.Y = (GameOptionsClass::Instance.ScreenHeight - this->Height) / 2; + } + + this->SetPosition(position.X, position.Y); +} + +bool ToggleSWButtonClass::SwitchSidebar() +{ + VocClass::PlayGlobal(RulesClass::Instance->GUIMainButtonSound, 0x2000, 1.0); + ScenarioExt::Global()->SWSidebar_Enable = !ScenarioExt::Global()->SWSidebar_Enable; + + const bool disabled = !SWSidebarClass::IsEnabled(); + const auto& columns = SWSidebarClass::Instance.Columns; + + if (!columns.empty()) + { + for (const auto& pColumn : columns) + { + pColumn->Disabled = disabled; + const auto& buttons = pColumn->Buttons; + + if (!buttons.empty()) + { + for (const auto& pButton : buttons) + pButton->Disabled = disabled; + } + } + } + + if (const auto pToggle = SWSidebarClass::Instance.ToggleButton) + pToggle->UpdatePosition(); + + return !disabled; +} diff --git a/src/Ext/Sidebar/SWSidebar/ToggleSWButtonClass.h b/src/Ext/Sidebar/SWSidebar/ToggleSWButtonClass.h new file mode 100644 index 0000000000..45e293f1ce --- /dev/null +++ b/src/Ext/Sidebar/SWSidebar/ToggleSWButtonClass.h @@ -0,0 +1,24 @@ +#pragma once +#include + +class ToggleSWButtonClass : public ControlClass +{ +public: + ToggleSWButtonClass() = default; + ToggleSWButtonClass(unsigned int id, int x, int y, int width, int height); + + ~ToggleSWButtonClass() = default; + + virtual bool Draw(bool forced) override; + virtual void OnMouseEnter() override; + virtual void OnMouseLeave() override; + virtual bool Action(GadgetFlag fags, DWORD* pKey, KeyModifier modifier) override; + + void UpdatePosition(); + + static bool SwitchSidebar(); + +public: + bool IsHovering { false }; + bool IsPressed { false }; +}; diff --git a/src/Misc/PhobosToolTip.cpp b/src/Misc/PhobosToolTip.cpp index 73bf7c06c0..6321cacf6e 100644 --- a/src/Misc/PhobosToolTip.cpp +++ b/src/Misc/PhobosToolTip.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -230,6 +231,41 @@ DEFINE_HOOK(0x6A9316, SidebarClass_StripClass_HelpText, 0x6) return 0x6A93DE; } +DEFINE_HOOK(0x4AE51E, DisplayClass_GetToolTip_HelpText, 0x6) +{ + enum { ApplyToolTip = 0x4AE69D }; + + if (SWSidebarClass::IsEnabled()) + { + const auto& swSidebar = SWSidebarClass::Instance; + + if (const auto button = swSidebar.CurrentButton) + { + PhobosToolTip::Instance.IsCameo = true; + + if (PhobosToolTip::Instance.IsEnabled()) + { + PhobosToolTip::Instance.HelpText_Super(button->SuperIndex); + R->EAX(PhobosToolTip::Instance.GetBuffer()); + } + else + { + const auto pSuper = HouseClass::CurrentPlayer->Supers[button->SuperIndex]; + R->EAX(pSuper->Type->UIName); + } + + return ApplyToolTip; + } + else if (swSidebar.CurrentColumn || (swSidebar.ToggleButton && swSidebar.ToggleButton->IsHovering)) + { + R->EAX(0); + return ApplyToolTip; + } + } + + return 0; +} + // TODO: reimplement CCToolTip::Draw2 completely DEFINE_HOOK(0x478EE1, CCToolTip_Draw2_SetBuffer, 0x6) @@ -282,7 +318,6 @@ DEFINE_HOOK(0x478EF8, CCToolTip_Draw2_SetMaxWidth, 0x5) R->EAX(Phobos::UI::MaxToolTipWidth); else R->EAX(DSurface::ViewBounds.Width); - } return 0; } @@ -337,14 +372,57 @@ DEFINE_HOOK(0x478FDC, CCToolTip_Draw2_FillRect, 0x5) const bool isCameo = PhobosToolTip::Instance.IsCameo; - if (isCameo && Phobos::UI::AnchoredToolTips && PhobosToolTip::Instance.IsEnabled() && Phobos::Config::ToolTipDescriptions) + if (isCameo && Phobos::UI::AnchoredToolTips + && PhobosToolTip::Instance.IsEnabled() + && Phobos::Config::ToolTipDescriptions) { - LEA_STACK(LTRBStruct*, a2, STACK_OFFSET(0x44, -0x20)); - auto x = DSurface::SidebarBounds.X - pRect->Width - 2; + LEA_STACK(LTRBStruct*, pTextRect, STACK_OFFSET(0x44, -0x20)); + + if (const auto pButton = SWSidebarClass::Instance.CurrentButton) + { + // Being too far away may actually be bad + /* + const auto& columns = SWSidebarClass::Instance.Columns; + const int size = columns.size(); + const int y = pButton->Y + 3; + + auto pColumn = columns.back(); + if (columns.size() > 1 && y > (pColumn->Y + pColumn->Height)) + pColumn = columns[size - 2]; + + const int x = pColumn->X + pColumn->Width + 2; + */ + const auto pColumn = SWSidebarClass::Instance.Columns[pButton->ColumnIndex]; + const int x = pColumn->X + pColumn->Width + 2; + const int y = pButton->Y + 3; + pRect->X = x; + pTextRect->Right += (x - pTextRect->Left); + pTextRect->Left = x; + pRect->Y = y; + pTextRect->Bottom += (y - pTextRect->Top); + pTextRect->Top = y; + } + else + { + const int x = DSurface::SidebarBounds.X - pRect->Width - 2; + pRect->X = x; + pTextRect->Left = x; + pRect->Y -= 40; + pTextRect->Top -= 40; + } + } + else if (const auto pButton = SWSidebarClass::Instance.CurrentButton) + { + LEA_STACK(LTRBStruct*, pTextRect, STACK_OFFSET(0x44, -0x20)); + + const int x = pButton->X + pButton->Width; + const int y = pButton->Y + 43; pRect->X = x; - a2->Left = x; - pRect->Y -= 40; - a2->Top -= 40; + pTextRect->Right += (x - pTextRect->Left); + pTextRect->Left = x; + pRect->Y = y; + pTextRect->Bottom += (y - pTextRect->Top); + pTextRect->Top = y; } // Should we make some SideExt items as static to improve the effeciency? diff --git a/src/Phobos.INI.cpp b/src/Phobos.INI.cpp index e22bdedde8..552cc8f8f6 100644 --- a/src/Phobos.INI.cpp +++ b/src/Phobos.INI.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -32,6 +33,12 @@ bool Phobos::UI::PowerDelta_Show = false; double Phobos::UI::PowerDelta_ConditionYellow = 0.75; double Phobos::UI::PowerDelta_ConditionRed = 1.0; bool Phobos::UI::CenterPauseMenuBackground = false; +bool Phobos::UI::SuperWeaponSidebar = false; +int Phobos::UI::SuperWeaponSidebar_Interval = 0; +int Phobos::UI::SuperWeaponSidebar_LeftOffset = 0; +int Phobos::UI::SuperWeaponSidebar_CameoHeight = 48; +int Phobos::UI::SuperWeaponSidebar_Max = 0; +int Phobos::UI::SuperWeaponSidebar_MaxColumns = INT32_MAX; bool Phobos::UI::WeedsCounter_Show = false; bool Phobos::UI::AnchoredToolTips = false; @@ -39,6 +46,7 @@ bool Phobos::Config::ToolTipDescriptions = true; bool Phobos::Config::ToolTipBlur = false; bool Phobos::Config::PrioritySelectionFiltering = true; bool Phobos::Config::DevelopmentCommands = true; +bool Phobos::Config::SuperWeaponSidebarCommands = false; bool Phobos::Config::ShowPlanningPath = false; bool Phobos::Config::ArtImageSwap = false; bool Phobos::Config::ShowPlacementPreview = false; @@ -58,6 +66,7 @@ bool Phobos::Config::ShowWeedsCounter = false; bool Phobos::Config::HideLightFlashEffects = true; bool Phobos::Config::ShowFlashOnSelecting = false; bool Phobos::Config::UnitPowerDrain = false; +int Phobos::Config::SuperWeaponSidebar_RequiredSignificance = 0; bool Phobos::Misc::CustomGS = false; int Phobos::Misc::CustomGS_ChangeInterval[7] = { -1, -1, -1, -1, -1, -1, -1 }; @@ -66,24 +75,27 @@ int Phobos::Misc::CustomGS_DefaultDelay[7] = { 0, 1, 2, 3, 4, 5, 6 }; DEFINE_HOOK(0x5FACDF, OptionsClass_LoadSettings_LoadPhobosSettings, 0x5) { - Phobos::Config::ToolTipDescriptions = CCINIClass::INI_RA2MD.ReadBool("Phobos", "ToolTipDescriptions", true); - Phobos::Config::ToolTipBlur = CCINIClass::INI_RA2MD.ReadBool("Phobos", "ToolTipBlur", false); - Phobos::Config::PrioritySelectionFiltering = CCINIClass::INI_RA2MD.ReadBool("Phobos", "PrioritySelectionFiltering", true); - Phobos::Config::ShowPlacementPreview = CCINIClass::INI_RA2MD.ReadBool("Phobos", "ShowPlacementPreview", true); - Phobos::Config::RealTimeTimers = CCINIClass::INI_RA2MD.ReadBool("Phobos", "RealTimeTimers", false); - Phobos::Config::RealTimeTimers_Adaptive = CCINIClass::INI_RA2MD.ReadBool("Phobos", "RealTimeTimers.Adaptive", false); - Phobos::Config::EnableSelectBox = CCINIClass::INI_RA2MD.ReadBool("Phobos", "EnableSelectBox", false); - Phobos::Config::DigitalDisplay_Enable = CCINIClass::INI_RA2MD.ReadBool("Phobos", "DigitalDisplay.Enable", false); - Phobos::Config::SaveGameOnScenarioStart = CCINIClass::INI_RA2MD.ReadBool("Phobos", "SaveGameOnScenarioStart", true); - Phobos::Config::ShowBriefing = CCINIClass::INI_RA2MD.ReadBool("Phobos", "ShowBriefing", true); - Phobos::Config::ShowPowerDelta = CCINIClass::INI_RA2MD.ReadBool("Phobos", "ShowPowerDelta", true); - Phobos::Config::ShowHarvesterCounter = CCINIClass::INI_RA2MD.ReadBool("Phobos", "ShowHarvesterCounter", true); - Phobos::Config::ShowWeedsCounter = CCINIClass::INI_RA2MD.ReadBool("Phobos", "ShowWeedsCounter", true); - Phobos::Config::HideLightFlashEffects = CCINIClass::INI_RA2MD.ReadBool("Phobos", "HideLightFlashEffects", false); - Phobos::Config::ShowFlashOnSelecting = CCINIClass::INI_RA2MD.ReadBool("Phobos", "ShowFlashOnSelecting", false); + const auto phobosSection = "Phobos"; + + Phobos::Config::ToolTipDescriptions = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "ToolTipDescriptions", true); + Phobos::Config::ToolTipBlur = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "ToolTipBlur", false); + Phobos::Config::PrioritySelectionFiltering = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "PrioritySelectionFiltering", true); + Phobos::Config::ShowPlacementPreview = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "ShowPlacementPreview", true); + Phobos::Config::RealTimeTimers = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "RealTimeTimers", false); + Phobos::Config::RealTimeTimers_Adaptive = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "RealTimeTimers.Adaptive", false); + Phobos::Config::EnableSelectBox = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "EnableSelectBox", false); + Phobos::Config::DigitalDisplay_Enable = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "DigitalDisplay.Enable", false); + Phobos::Config::SaveGameOnScenarioStart = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "SaveGameOnScenarioStart", true); + Phobos::Config::ShowBriefing = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "ShowBriefing", true); + Phobos::Config::ShowPowerDelta = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "ShowPowerDelta", true); + Phobos::Config::ShowHarvesterCounter = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "ShowHarvesterCounter", true); + Phobos::Config::ShowWeedsCounter = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "ShowWeedsCounter", true); + Phobos::Config::HideLightFlashEffects = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "HideLightFlashEffects", false); + Phobos::Config::ShowFlashOnSelecting = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "ShowFlashOnSelecting", false); + Phobos::Config::SuperWeaponSidebar_RequiredSignificance = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "SuperWeaponSidebar.RequiredSignificance", 0); // Custom game speeds, 6 - i so that GS6 is index 0, just like in the engine - Phobos::Config::CampaignDefaultGameSpeed = 6 - CCINIClass::INI_RA2MD.ReadInteger("Phobos", "CampaignDefaultGameSpeed", 4); + Phobos::Config::CampaignDefaultGameSpeed = 6 - CCINIClass::INI_RA2MD.ReadInteger(phobosSection, "CampaignDefaultGameSpeed", 4); if (Phobos::Config::CampaignDefaultGameSpeed > 6 || Phobos::Config::CampaignDefaultGameSpeed < 0) { Phobos::Config::CampaignDefaultGameSpeed = 2; @@ -96,7 +108,7 @@ DEFINE_HOOK(0x5FACDF, OptionsClass_LoadSettings_LoadPhobosSettings, 0x5) Patch::Apply_RAW(0x55D78D, { temp }); // when speed control is off. Doesn't need a hook. } - Phobos::Config::ShowDesignatorRange = CCINIClass::INI_RA2MD.ReadBool("Phobos", "ShowDesignatorRange", false); + Phobos::Config::ShowDesignatorRange = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "ShowDesignatorRange", false); CCINIClass ini_uimd {}; ini_uimd.LoadFromFile(GameStrings::UIMD_INI); @@ -165,6 +177,36 @@ DEFINE_HOOK(0x5FACDF, OptionsClass_LoadSettings_LoadPhobosSettings, 0x5) Phobos::UI::CenterPauseMenuBackground = ini_uimd.ReadBool(SIDEBAR_SECTION, "CenterPauseMenuBackground", Phobos::UI::CenterPauseMenuBackground); + + Phobos::UI::SuperWeaponSidebar = + ini_uimd.ReadBool(SIDEBAR_SECTION, "SuperWeaponSidebar", Phobos::UI::SuperWeaponSidebar); + + Phobos::UI::SuperWeaponSidebar_Interval = + ini_uimd.ReadInteger(SIDEBAR_SECTION, "SuperWeaponSidebar.Interval", Phobos::UI::SuperWeaponSidebar_Interval); + + Phobos::UI::SuperWeaponSidebar_LeftOffset = + ini_uimd.ReadInteger(SIDEBAR_SECTION, "SuperWeaponSidebar.LeftOffset", Phobos::UI::SuperWeaponSidebar_LeftOffset); + + Phobos::UI::SuperWeaponSidebar_LeftOffset = std::min(Phobos::UI::SuperWeaponSidebar_Interval, Phobos::UI::SuperWeaponSidebar_LeftOffset); + + Phobos::UI::SuperWeaponSidebar_CameoHeight = + ini_uimd.ReadInteger(SIDEBAR_SECTION, "SuperWeaponSidebar.CameoHeight", Phobos::UI::SuperWeaponSidebar_CameoHeight); + + Phobos::UI::SuperWeaponSidebar_CameoHeight = std::max(48, Phobos::UI::SuperWeaponSidebar_CameoHeight); + + Phobos::UI::SuperWeaponSidebar_Max = + ini_uimd.ReadInteger(SIDEBAR_SECTION, "SuperWeaponSidebar.Max", Phobos::UI::SuperWeaponSidebar_Max); + + const int reserveHeight = 96; + const int screenHeight = GameOptionsClass::Instance.ScreenHeight - reserveHeight; + + if (Phobos::UI::SuperWeaponSidebar_Max > 0) + Phobos::UI::SuperWeaponSidebar_Max = std::min(Phobos::UI::SuperWeaponSidebar_Max, screenHeight / Phobos::UI::SuperWeaponSidebar_CameoHeight); + else + Phobos::UI::SuperWeaponSidebar_Max = screenHeight / Phobos::UI::SuperWeaponSidebar_CameoHeight; + + Phobos::UI::SuperWeaponSidebar_MaxColumns = + ini_uimd.ReadInteger(SIDEBAR_SECTION, "SuperWeaponSidebar.MaxColumns", Phobos::UI::SuperWeaponSidebar_MaxColumns); } // UISettings @@ -222,6 +264,7 @@ DEFINE_HOOK(0x52D21F, InitRules_ThingsThatShouldntBeSerailized, 0x6) #ifndef DEBUG Phobos::Config::DevelopmentCommands = pINI_RULESMD->ReadBool("GlobalControls", "DebugKeysEnabled", Phobos::Config::DevelopmentCommands); #endif + Phobos::Config::SuperWeaponSidebarCommands = pINI_RULESMD->ReadBool("GlobalControls", "SuperWeaponSidebarKeysEnabled", Phobos::Config::SuperWeaponSidebarCommands); Phobos::Config::ShowPlanningPath = pINI_RULESMD->ReadBool("GlobalControls", "DebugPlanningPaths", Phobos::Config::ShowPlanningPath); return 0; diff --git a/src/Phobos.h b/src/Phobos.h index d7e993fd7e..3573722d64 100644 --- a/src/Phobos.h +++ b/src/Phobos.h @@ -54,6 +54,12 @@ class Phobos static double PowerDelta_ConditionYellow; static double PowerDelta_ConditionRed; static bool CenterPauseMenuBackground; + static bool SuperWeaponSidebar; + static int SuperWeaponSidebar_Interval; + static int SuperWeaponSidebar_LeftOffset; + static int SuperWeaponSidebar_CameoHeight; + static int SuperWeaponSidebar_Max; + static int SuperWeaponSidebar_MaxColumns; static bool WeedsCounter_Show; static bool AnchoredToolTips; @@ -74,6 +80,7 @@ class Phobos static bool ToolTipBlur; static bool PrioritySelectionFiltering; static bool DevelopmentCommands; + static bool SuperWeaponSidebarCommands; static bool ArtImageSwap; static bool ShowPlacementPreview; static bool EnableBuildingPlacementPreview; @@ -94,6 +101,7 @@ class Phobos static bool HideLightFlashEffects; static bool ShowFlashOnSelecting; static bool UnitPowerDrain; + static int SuperWeaponSidebar_RequiredSignificance; }; class Misc diff --git a/src/Utilities/AresAddressInit.cpp b/src/Utilities/AresAddressInit.cpp index a6eca44b4e..1c4830c43c 100644 --- a/src/Utilities/AresAddressInit.cpp +++ b/src/Utilities/AresAddressInit.cpp @@ -6,6 +6,7 @@ decltype(AresFunctions::ConvertTypeTo) AresFunctions::ConvertTypeTo = nullptr; decltype(AresFunctions::CreateAresEBolt) AresFunctions::CreateAresEBolt = nullptr; decltype(AresFunctions::SpawnSurvivors) AresFunctions::SpawnSurvivors = nullptr; +decltype(AresFunctions::IsTargetConstraintsEligible) AresFunctions::IsTargetConstraintsEligible = nullptr; std::function AresFunctions::SWTypeExtMap_Find; PhobosMap* AresFunctions::AlphaExtMap = nullptr; @@ -27,7 +28,11 @@ void AresFunctions::InitAres3_0() Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x48C69, { 0x30 }); } else + { NOTE_ARES_FUN(SpawnSurvivors, 0x464C0); + } + + NOTE_ARES_FUN(IsTargetConstraintsEligible, 0x032110); NOTE_ARES_FUN(_SWTypeExtMapFind, 0x57C70); NOTE_ARES_FUN(_SWTypeExtMap, 0xC1C54); @@ -52,7 +57,11 @@ void AresFunctions::InitAres3_0p1() Patch::Apply_RAW(AresHelper::AresBaseAddress + 0x498B9, { 0x30 }); } else + { NOTE_ARES_FUN(SpawnSurvivors, 0x47030); + } + + NOTE_ARES_FUN(IsTargetConstraintsEligible, 0x032AF0); NOTE_ARES_FUN(_SWTypeExtMapFind, 0x58900); NOTE_ARES_FUN(_SWTypeExtMap, 0xC2C50); diff --git a/src/Utilities/AresFunctions.h b/src/Utilities/AresFunctions.h index 83dcf1c7f7..2e3ff0ef58 100644 --- a/src/Utilities/AresFunctions.h +++ b/src/Utilities/AresFunctions.h @@ -31,10 +31,14 @@ class AresFunctions static EBolt* (__stdcall* CreateAresEBolt)(WeaponTypeClass* pWeapon); static void(__stdcall* SpawnSurvivors)(FootClass* pThis, TechnoClass* pKiller, bool Select, bool IgnoreDefenses); + + static bool(__thiscall* IsTargetConstraintsEligible)(void*, HouseClass*, bool); + static std::function SWTypeExtMap_Find; static PhobosMap* AlphaExtMap; private: + static constexpr bool _maybe = false; static constexpr bool AresWasWrongAboutSpawnSurvivors = _maybe;