diff --git a/CREDITS.md b/CREDITS.md index e7dfbace7b..c2fc181c27 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -763,3 +763,4 @@ This page lists all the individual contributions to the project by their author. - **RAZER**: - Wall overlay unit sell exploit fix - Multiplayer gamespeed fix for RealTimeTimers + - Rebuild last technotype keyboard commands diff --git a/Phobos.vcxproj b/Phobos.vcxproj index d45a6ac91f..e36e118307 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -36,6 +36,8 @@ + + @@ -132,6 +134,7 @@ + @@ -226,6 +229,7 @@ + diff --git a/docs/User-Interface.md b/docs/User-Interface.md index e459622af6..9db0f6c40f 100644 --- a/docs/User-Interface.md +++ b/docs/User-Interface.md @@ -521,6 +521,24 @@ For this command to work in multiplayer - you need to use a version of [YRpp spa - Switches on/off [Task subtitles' label in the middle of the screen](#task-subtitles-display-in-the-middle-of-the-screen). - For localization add `TXT_TOGGLE_MESSAGE` and `TXT_TOGGLE_MESSAGE_DESC` into your `.csf` file. +### `[ ]` Rebuild Structure +- Re-queue the last produced Power/Resource/Tech Building you placed. + +### `[ ]` Rebuild Defense +- Re-queue the last produced Defensive Building you placed. + +### `[ ]` Rebuild Infantry +- Re-queue the last produced Infantry Unit you built. + +### `[ ]` Rebuild Vehicle +- Re-queue the last produced Vehicle Unit you built. + +### `[ ]` Rebuild Aircraft +- Re-queue the last produced Aircraft Unit you built. + +### `[ ]` Rebuild Naval +- Re-queue the last produced Naval Unit you built. + ## Loading screen - PCX files can now be used as loadscreen images. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 5d9d1aa65b..4ddd364b8f 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -656,6 +656,7 @@ Fixes / interactions with other extensions: - Fixed the issue that technos cannot spawn survivors due to non-probabilistic reasons when the tech type was destroyed (by NetsuNegi) - Fixed the bug that vehicle survivor can spawn on wrong position when transport has been destroyed (by NetsuNegi) - Fixed the bug that building with `Explodes=yes` use Ares's rubble logic will cause it's owner cannot defeat normally (by NetsuNegi) +- New keyboard commands under `Interface`: `Rebuild Structure`, `Rebuild Defense`, `Rebuild Infantry`, `Rebuild Vehicle`,`Rebuild Aircraft`, `Rebuild Naval` (by RAZER) ``` ### 0.4.0.3 diff --git a/src/Commands/BuildLastOfTab.cpp b/src/Commands/BuildLastOfTab.cpp new file mode 100644 index 0000000000..6420c3e835 --- /dev/null +++ b/src/Commands/BuildLastOfTab.cpp @@ -0,0 +1,99 @@ +#include "BuildLastOfTab.h" + +#include +#include +#include +#include +#include + +static constexpr const char* BuildLastTabNames[3] = +{ + "RebuildStructure", + "RebuildDefense", + "RebuildInfantry", +}; + +static constexpr const char* BuildLastTabDescKeys[3] = +{ + "RebuildStructure_Desc", + "RebuildDefense_Desc", + "RebuildInfantry_Desc", +}; + +static constexpr const wchar_t* BuildLastTabUINames[3] = +{ + L"Rebuild Structure", + L"Rebuild Defense", + L"Rebuild Infantry", +}; + +static constexpr const wchar_t* BuildLastTabUIDescs[3] = +{ + L"Re-queue the last produced Power/Resources building.", + L"Re-queue the last produced Defense/Combat building.", + L"Re-queue the last produced Infantry unit.", +}; + +template +const char* BuildLastOfTabCommandClass::GetName() const +{ + return BuildLastTabNames[TabIndex]; +} + +template +const wchar_t* BuildLastOfTabCommandClass::GetUIName() const +{ + return GeneralUtils::LoadStringUnlessMissing(BuildLastTabNames[TabIndex], BuildLastTabUINames[TabIndex]); +} + +template +const wchar_t* BuildLastOfTabCommandClass::GetUICategory() const +{ + return CATEGORY_INTERFACE; +} + +template +const wchar_t* BuildLastOfTabCommandClass::GetUIDescription() const +{ + static_assert(TabIndex >= 0 && TabIndex < 3, "TabIndex out of range"); + return GeneralUtils::LoadStringUnlessMissing(BuildLastTabDescKeys[TabIndex], BuildLastTabUIDescs[TabIndex]); +} + +template +void BuildLastOfTabCommandClass::Execute(WWKey eInput) const +{ + auto const pPlayer = HouseClass::CurrentPlayer; + if (!pPlayer) + return; + + auto const pExt = HouseExt::ExtMap.Find(pPlayer); + if (!pExt) + return; + + auto const typeIndex = pExt->LastBuiltPerTab[TabIndex]; + if (typeIndex < 0) + return; + + // Focus the sidebar to the corresponding tab. + if (SidebarClass::Instance.IsSidebarActive) + { + SidebarClass::Instance.ActiveTabIndex = TabIndex; + SidebarClass::Instance.SidebarNeedsRepaint(); + } + + auto const rtti = pExt->LastBuiltRTTIPerTab[TabIndex]; + auto const isNaval = pExt->LastBuiltIsNavalPerTab[TabIndex]; + + EventClass::OutList.Add(EventClass( + pPlayer->ArrayIndex, + EventType::Produce, + static_cast(rtti), + typeIndex, + isNaval ? TRUE : FALSE + )); +} + +// Explicit instantiations +template class BuildLastOfTabCommandClass<0>; +template class BuildLastOfTabCommandClass<1>; +template class BuildLastOfTabCommandClass<2>; diff --git a/src/Commands/BuildLastOfTab.h b/src/Commands/BuildLastOfTab.h new file mode 100644 index 0000000000..35929fe334 --- /dev/null +++ b/src/Commands/BuildLastOfTab.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Commands.h" + +// Re-queue the last produced item from a specific production tab. +// TabIndex: 0 = Power/Infrastructure, 1 = Defense/Combat, 2 = Infantry, 3 = Vehicles/Aircraft +template +class BuildLastOfTabCommandClass : 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/Commands/Commands.cpp b/src/Commands/Commands.cpp index 2460c6b971..15375158ba 100644 --- a/src/Commands/Commands.cpp +++ b/src/Commands/Commands.cpp @@ -12,6 +12,8 @@ #include "ToggleSWSidebar.h" #include "FireTacticalSW.h" #include "ToggleMessageList.h" +#include "BuildLastOfTab.h" +#include "RebuildTab3Subtypes.h" #include @@ -28,6 +30,12 @@ DEFINE_HOOK(0x533066, CommandClassCallback_Register, 0x6) MakeCommand(); MakeCommand(); MakeCommand(); + MakeCommand>(); + MakeCommand>(); + MakeCommand>(); + MakeCommand(); + MakeCommand(); + MakeCommand(); if (Phobos::Config::SuperWeaponSidebarCommands) { diff --git a/src/Commands/RebuildTab3Subtypes.cpp b/src/Commands/RebuildTab3Subtypes.cpp new file mode 100644 index 0000000000..8ef657d4ff --- /dev/null +++ b/src/Commands/RebuildTab3Subtypes.cpp @@ -0,0 +1,137 @@ +#include "RebuildTab3Subtypes.h" + +#include +#include +#include +#include +#include + +// Helper: issue a Produce event for a tracked subtype and focus sidebar to tab 3. +static void ExecuteRebuildSubtype(int typeIndex, AbstractType rtti, bool isNaval) +{ + auto const pPlayer = HouseClass::CurrentPlayer; + if (!pPlayer) + return; + + if (typeIndex < 0) + return; + + if (SidebarClass::Instance.IsSidebarActive) + { + SidebarClass::Instance.ActiveTabIndex = 3; + SidebarClass::Instance.SidebarNeedsRepaint(); + } + + EventClass::OutList.Add(EventClass( + pPlayer->ArrayIndex, + EventType::Produce, + static_cast(rtti), + typeIndex, + isNaval ? TRUE : FALSE + )); +} + +// --- RebuildVehicle (non-naval UnitType) --- + +const char* RebuildVehicleCommandClass::GetName() const +{ + return "RebuildVehicleOnly"; +} + +const wchar_t* RebuildVehicleCommandClass::GetUIName() const +{ + return GeneralUtils::LoadStringUnlessMissing("RebuildVehicleOnly", L"Rebuild Vehicle"); +} + +const wchar_t* RebuildVehicleCommandClass::GetUICategory() const +{ + return CATEGORY_INTERFACE; +} + +const wchar_t* RebuildVehicleCommandClass::GetUIDescription() const +{ + return GeneralUtils::LoadStringUnlessMissing("RebuildVehicleOnly_Desc", L"Re-queue the last produced ground vehicle (non-naval)."); +} + +void RebuildVehicleCommandClass::Execute(WWKey eInput) const +{ + auto const pPlayer = HouseClass::CurrentPlayer; + if (!pPlayer) + return; + + auto const pExt = HouseExt::ExtMap.Find(pPlayer); + if (!pExt) + return; + + ExecuteRebuildSubtype(pExt->LastBuiltVehicleTypeIndex, pExt->LastBuiltVehicleRTTI, false); +} + +// --- RebuildAircraft (AircraftType) --- + +const char* RebuildAircraftCommandClass::GetName() const +{ + return "RebuildAircraft"; +} + +const wchar_t* RebuildAircraftCommandClass::GetUIName() const +{ + return GeneralUtils::LoadStringUnlessMissing("RebuildAircraft", L"Rebuild Aircraft"); +} + +const wchar_t* RebuildAircraftCommandClass::GetUICategory() const +{ + return CATEGORY_INTERFACE; +} + +const wchar_t* RebuildAircraftCommandClass::GetUIDescription() const +{ + return GeneralUtils::LoadStringUnlessMissing("RebuildAircraft_Desc", L"Re-queue the last produced aircraft."); +} + +void RebuildAircraftCommandClass::Execute(WWKey eInput) const +{ + auto const pPlayer = HouseClass::CurrentPlayer; + if (!pPlayer) + return; + + auto const pExt = HouseExt::ExtMap.Find(pPlayer); + if (!pExt) + return; + + ExecuteRebuildSubtype(pExt->LastBuiltAircraftTypeIndex, pExt->LastBuiltAircraftRTTI, false); +} + +// --- RebuildNaval (naval UnitType) --- + +const char* RebuildNavalCommandClass::GetName() const +{ + return "RebuildNaval"; +} + +const wchar_t* RebuildNavalCommandClass::GetUIName() const +{ + return GeneralUtils::LoadStringUnlessMissing("RebuildNaval", L"Rebuild Naval"); +} + +const wchar_t* RebuildNavalCommandClass::GetUICategory() const +{ + return CATEGORY_INTERFACE; +} + +const wchar_t* RebuildNavalCommandClass::GetUIDescription() const +{ + return GeneralUtils::LoadStringUnlessMissing("RebuildNaval_Desc", L"Re-queue the last produced naval unit."); +} + +void RebuildNavalCommandClass::Execute(WWKey eInput) const +{ + auto const pPlayer = HouseClass::CurrentPlayer; + if (!pPlayer) + return; + + auto const pExt = HouseExt::ExtMap.Find(pPlayer); + if (!pExt) + return; + + ExecuteRebuildSubtype(pExt->LastBuiltNavalTypeIndex, pExt->LastBuiltNavalRTTI, true); +} diff --git a/src/Commands/RebuildTab3Subtypes.h b/src/Commands/RebuildTab3Subtypes.h new file mode 100644 index 0000000000..b19603e379 --- /dev/null +++ b/src/Commands/RebuildTab3Subtypes.h @@ -0,0 +1,38 @@ +#pragma once + +#include "Commands.h" + +// Dedicated rebuild commands for tab 3 subtypes. +// RebuildVehicle: re-queues the last non-naval UnitType. +// RebuildAircraft: re-queues the last AircraftType. +// RebuildNaval: re-queues the last naval UnitType. + +class RebuildVehicleCommandClass : 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; +}; + +class RebuildAircraftCommandClass : 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; +}; + +class RebuildNavalCommandClass : 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/House/Body.cpp b/src/Ext/House/Body.cpp index 176b2a680c..2def8d5c16 100644 --- a/src/Ext/House/Body.cpp +++ b/src/Ext/House/Body.cpp @@ -696,6 +696,15 @@ void HouseExt::ExtData::Serialize(T& Stm) .Process(this->RestrictedFactoryPlants) .Process(this->LastBuiltNavalVehicleType) .Process(this->ProducingNavalUnitTypeIndex) + .Process(this->LastBuiltPerTab) + .Process(this->LastBuiltRTTIPerTab) + .Process(this->LastBuiltIsNavalPerTab) + .Process(this->LastBuiltVehicleTypeIndex) + .Process(this->LastBuiltAircraftTypeIndex) + .Process(this->LastBuiltNavalTypeIndex) + .Process(this->LastBuiltVehicleRTTI) + .Process(this->LastBuiltAircraftRTTI) + .Process(this->LastBuiltNavalRTTI) .Process(this->CombatAlertTimer) .Process(this->NumAirpads_NonMFB) .Process(this->NumBarracks_NonMFB) diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h index 3c1109a7e1..07b9f7b939 100644 --- a/src/Ext/House/Body.h +++ b/src/Ext/House/Body.h @@ -46,6 +46,20 @@ class HouseExt int LastBuiltNavalVehicleType; int ProducingNavalUnitTypeIndex; + // Per-tab last-built tracking for the Build Last commands. + // Indexed by sidebar tab: 0=Power/Infra, 1=Combat, 2=Infantry, 3=Vehicles/Aircraft. + int LastBuiltPerTab[4]; + AbstractType LastBuiltRTTIPerTab[4]; + bool LastBuiltIsNavalPerTab[4]; + + // Per-subtype tracking within tab 3 for dedicated rebuild commands. + int LastBuiltVehicleTypeIndex; // Last non-naval UnitType + int LastBuiltAircraftTypeIndex; // Last AircraftType + int LastBuiltNavalTypeIndex; // Last naval UnitType + AbstractType LastBuiltVehicleRTTI; + AbstractType LastBuiltAircraftRTTI; + AbstractType LastBuiltNavalRTTI; + // Factories that exist but don't count towards multiple factory bonus. int NumAirpads_NonMFB; int NumBarracks_NonMFB; @@ -85,6 +99,15 @@ class HouseExt , RestrictedFactoryPlants {} , LastBuiltNavalVehicleType { -1 } , ProducingNavalUnitTypeIndex { -1 } + , LastBuiltPerTab { -1, -1, -1, -1 } + , LastBuiltRTTIPerTab { AbstractType::None, AbstractType::None, AbstractType::None, AbstractType::None } + , LastBuiltIsNavalPerTab { false, false, false, false } + , LastBuiltVehicleTypeIndex { -1 } + , LastBuiltAircraftTypeIndex { -1 } + , LastBuiltNavalTypeIndex { -1 } + , LastBuiltVehicleRTTI { AbstractType::None } + , LastBuiltAircraftRTTI { AbstractType::None } + , LastBuiltNavalRTTI { AbstractType::None } , CombatAlertTimer {} , NumAirpads_NonMFB { 0 } , NumBarracks_NonMFB { 0 } diff --git a/src/Ext/House/Hooks.BuildLastTab.cpp b/src/Ext/House/Hooks.BuildLastTab.cpp new file mode 100644 index 0000000000..13fa4db0b7 --- /dev/null +++ b/src/Ext/House/Hooks.BuildLastTab.cpp @@ -0,0 +1,62 @@ +#include "Body.h" + +#include +#include +#include + +// Hook into HouseClass::RegisterObjectGain_FromFactory (0x4FB6B0). +// Fires when a unit or building is delivered from a factory. +// At this point: EDI = HouseClass*, EBP = TechnoClass* (the object being delivered). +// We track the delivered type per sidebar tab so the "Build Last" commands can re-queue it. + +DEFINE_HOOK(0x4FB6C2, HouseClass_RegisterObjectGain_TrackLastBuiltTab, 0x7) +{ + GET(HouseClass* const, pThis, EDI); + GET(TechnoClass* const, pTechno, EBP); + + if (pThis != HouseClass::CurrentPlayer) + return 0; + + auto const pType = pTechno->GetTechnoType(); + if (!pType) + return 0; + + auto const absType = pType->WhatAmI(); + auto const isNaval = pType->Naval; + + auto buildCat = BuildCat::DontCare; + if (absType == AbstractType::BuildingType) + buildCat = static_cast(pType)->BuildCat; + + int const tabIdx = SidebarClass::GetObjectTabIdx(absType, buildCat, isNaval); + + if (tabIdx >= 0 && tabIdx < 4) + { + auto const pExt = HouseExt::ExtMap.Find(pThis); + pExt->LastBuiltPerTab[tabIdx] = pType->GetArrayIndex(); + pExt->LastBuiltRTTIPerTab[tabIdx] = absType; + pExt->LastBuiltIsNavalPerTab[tabIdx] = isNaval; + + // Track per-subtype within tab 3 for dedicated rebuild commands. + if (tabIdx == 3) + { + if (absType == AbstractType::AircraftType) + { + pExt->LastBuiltAircraftTypeIndex = pType->GetArrayIndex(); + pExt->LastBuiltAircraftRTTI = absType; + } + else if (isNaval) + { + pExt->LastBuiltNavalTypeIndex = pType->GetArrayIndex(); + pExt->LastBuiltNavalRTTI = absType; + } + else + { + pExt->LastBuiltVehicleTypeIndex = pType->GetArrayIndex(); + pExt->LastBuiltVehicleRTTI = absType; + } + } + } + + return 0; +}