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;
+}