From c3e734929c717c079800cdce717e82433e650fe9 Mon Sep 17 00:00:00 2001 From: ZivDero Date: Tue, 26 Nov 2024 22:18:05 +0300 Subject: [PATCH] Allow refreshing buildables after losing buildings, allow removing MCV ActLike check --- CREDITS.md | 2 + docs/Miscellaneous.md | 22 ++++++ docs/Whats-New.md | 2 + src/extensions/building/buildingext_hooks.cpp | 9 +-- src/extensions/factory/factoryext_hooks.cpp | 72 +++++++++---------- src/extensions/house/houseext_hooks.cpp | 24 ++++++- .../objecttype/objecttypeext_hooks.cpp | 18 +++++ src/extensions/rules/rulesext.cpp | 11 ++- src/extensions/rules/rulesext.h | 10 +++ src/extensions/sidebar/sidebarext_hooks.cpp | 44 ++++++++++++ 10 files changed, 167 insertions(+), 47 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index bbd093d85..59f87352c 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -204,4 +204,6 @@ This page lists all the individual contributions to the project by their author. - Fix a bug where AI players would send teams to attack their allies. - Allow customizing minimum damage per warhead. - Implement required and forbidden houses. + - Allow turning off "sticky" technologies. + - Allow disabling the ActLike check on construction yards to allow for faction-specific MCVs. diff --git a/docs/Miscellaneous.md b/docs/Miscellaneous.md index 9fef6c9a9..4a6c5ed1b 100644 --- a/docs/Miscellaneous.md +++ b/docs/Miscellaneous.md @@ -39,6 +39,28 @@ PrePlacedConYards=no ; boolean, should pre-place construction yards instead of ; NOTE: This option has priority over AutoDeployMCV. ``` +## Prerequisites + +### Multi-MCV + +- Vinifera allows turning off the check for the house that built the MCV to allow giving each faction their own MCV (instead of a shared MCV). + +In `RULES.INI`: +```ini +[General] +MultiMCV=no ; boolean, should MCVs only allow the construction of buildings of any house, not only the house that built them? +``` + +### Sticky Technologies + +- In vanilla, technologies are "sticky", that is, for example, if you lose a tech center, you will not lose access to objects that require a tech center until you lose all factories of the type. Vinifera allows turning off this behavior. + +In `RULES.INI`: +```ini +[General] +RecheckPrerequisites=no ; boolean, should prerequisites be rechecked, and unavailable items removed from the sidebar, when buildings are lost? +``` + ## Multi-Engineer - Vinifera fixes `EngineerDamage` and `EngineerCaptureLevel` to be considered by the game, like they were in Tiberian Dawn and Red Alert. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 9614fb564..c1f4d9e33 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -164,6 +164,8 @@ New: - Implement support for a Saved Games subdirectory (by ZivDero) - Allow customizing minimum damage per warhead (by ZivDero) - Implement required and forbidden houses (by ZivDero) +- Allow turning off "sticky" technologies (by ZivDero) +- Allow disabling the ActLike check on construction yards to allow for faction-specific MCVs (by ZivDero) Vanilla fixes: diff --git a/src/extensions/building/buildingext_hooks.cpp b/src/extensions/building/buildingext_hooks.cpp index d813bfd91..d87c6efeb 100644 --- a/src/extensions/building/buildingext_hooks.cpp +++ b/src/extensions/building/buildingext_hooks.cpp @@ -67,6 +67,7 @@ #include "hooker.h" #include "hooker_macros.h" +#include "rulesext.h" /** @@ -124,7 +125,7 @@ void BuildingClassExt::_Update_Buildables() case RTTI_AIRCRAFTTYPE: for (int i = 0; i < AircraftTypes.Count(); i++) { - if (PlayerPtr->Can_Build(AircraftTypes[i], false, true) && AircraftTypes[i]->Who_Can_Build_Me(true, false, false, PlayerPtr) != nullptr) + if (PlayerPtr->Can_Build(AircraftTypes[i], false, true) && AircraftTypes[i]->Who_Can_Build_Me(true, false, RuleExtension->IsRecheckPrerequisites, PlayerPtr) != nullptr) { Map.Add(RTTI_AIRCRAFTTYPE, i); } @@ -134,7 +135,7 @@ void BuildingClassExt::_Update_Buildables() case RTTI_BUILDINGTYPE: for (int i = 0; i < BuildingTypes.Count(); i++) { - if (PlayerPtr->Can_Build(BuildingTypes[i], false, true) && BuildingTypes[i]->Who_Can_Build_Me(true, false, false, PlayerPtr) != nullptr) + if (PlayerPtr->Can_Build(BuildingTypes[i], false, true) && BuildingTypes[i]->Who_Can_Build_Me(true, false, RuleExtension->IsRecheckPrerequisites, PlayerPtr) != nullptr) { Map.Add(RTTI_BUILDINGTYPE, i); } @@ -144,7 +145,7 @@ void BuildingClassExt::_Update_Buildables() case RTTI_INFANTRYTYPE: for (int i = 0; i < InfantryTypes.Count(); i++) { - if (PlayerPtr->Can_Build(InfantryTypes[i], false, true) && InfantryTypes[i]->Who_Can_Build_Me(true, false, false, PlayerPtr) != nullptr) + if (PlayerPtr->Can_Build(InfantryTypes[i], false, true) && InfantryTypes[i]->Who_Can_Build_Me(true, false, RuleExtension->IsRecheckPrerequisites, PlayerPtr) != nullptr) { Map.Add(RTTI_INFANTRYTYPE, i); } @@ -154,7 +155,7 @@ void BuildingClassExt::_Update_Buildables() case RTTI_UNITTYPE: for (int i = 0; i < UnitTypes.Count(); i++) { - if (PlayerPtr->Can_Build(UnitTypes[i], false, true) && UnitTypes[i]->Who_Can_Build_Me(true, false, false, PlayerPtr) != nullptr) + if (PlayerPtr->Can_Build(UnitTypes[i], false, true) && UnitTypes[i]->Who_Can_Build_Me(true, false, RuleExtension->IsRecheckPrerequisites, PlayerPtr) != nullptr) { Map.Add(RTTI_UNITTYPE, i); } diff --git a/src/extensions/factory/factoryext_hooks.cpp b/src/extensions/factory/factoryext_hooks.cpp index fb3222631..400e80738 100644 --- a/src/extensions/factory/factoryext_hooks.cpp +++ b/src/extensions/factory/factoryext_hooks.cpp @@ -35,13 +35,13 @@ #include "asserthandler.h" #include "extension_globals.h" #include "factoryext_init.h" +#include "techno.h" #include "technotype.h" - #include "hooker.h" #include "hooker_macros.h" #include "mouse.h" +#include "rulesext.h" #include "sidebarext.h" -#include "techno.h" /** @@ -70,8 +70,9 @@ void FactoryClassExt::_Sanitize_Queue() { const TechnoClass* producing_object = Get_Object(); - if (producing_object == nullptr) + if (producing_object == nullptr) { return; + } const TechnoTypeClass* producing_type = producing_object->Techno_Type_Class(); const RTTIType type = producing_type->Kind_Of(); @@ -80,14 +81,12 @@ void FactoryClassExt::_Sanitize_Queue() bool need_update = false; // Check the thing we're currently building - if (!House->Can_Build(producing_type, false, true)) - { + if (!House->Can_Build(producing_type, false, true)) { Abandon(); need_update = true; // Cancel map placement - if (is_building && House == PlayerPtr) - { + if (is_building && House == PlayerPtr) { Map.PendingObject = nullptr; Map.PendingObjectPtr = nullptr; Map.PendingHouse = HOUSE_NONE; @@ -96,20 +95,18 @@ void FactoryClassExt::_Sanitize_Queue() } // Make sure there are no unavailable objects in the queue - for (int i = 0; i < QueuedObjects.Count(); i++) - { - if (!House->Can_Build(QueuedObjects[i], false, true)) - { + for (int i = 0; i < QueuedObjects.Count(); i++) { + if (!House->Can_Build(QueuedObjects[i], false, true)) { Remove_From_Queue(*QueuedObjects[i]); need_update = true; i--; } } - if (need_update) - { - if (House == PlayerPtr) - SidebarExtension->Get_Tab(type).Flag_To_Redraw(); + if (need_update) { + if (House == PlayerPtr) { + SidebarExtension->Flag_Strip_To_Redraw(type); + } House->Update_Factories(type); Resume_Queue(); @@ -126,8 +123,7 @@ void FactoryClassExt::_Sanitize_Queue() */ bool FactoryClassExt::_Start(bool suspend) { - if ((Object || SpecialItem) && IsSuspended && !Has_Completed()) - { + if ((Object || SpecialItem) && IsSuspended && !Has_Completed()) { const int time = Object ? Object->Time_To_Build() : 0; int rate = time / STEP_COUNT; rate = std::clamp(rate, 1, 255); @@ -135,16 +131,16 @@ bool FactoryClassExt::_Start(bool suspend) Set_Rate(rate); IsSuspended = false; - if (House->Available_Money() >= Cost_Per_Tick()) - { + if (House->Available_Money() >= Cost_Per_Tick()) { IsPlayerSuspended = true; - - if (suspend) + if (suspend) { Suspend(true); + } return true; } } + return false; } @@ -156,12 +152,15 @@ bool FactoryClassExt::_Start(bool suspend) */ void FactoryClassExt::_AI() { - //_Sanitize_Queue(); + /** + * If sticky techs are disabled, clear anything that's no longer available from the build queue. + */ + if (RuleExtension->IsRecheckPrerequisites) { + _Sanitize_Queue(); + } - if (!IsSuspended && (Object != nullptr || SpecialItem)) - { - if (!Has_Completed() && Graphic_Logic()) - { + if (!IsSuspended && (Object != nullptr || SpecialItem)) { + if (!Has_Completed() && Graphic_Logic()) { IsDifferent = true; int cost = Cost_Per_Tick(); @@ -173,12 +172,10 @@ void FactoryClassExt::_AI() ** continue the countdown. The idea being that by the time the next ** production step occurs, there may be sufficient funds available. */ - if (cost > House->Available_Money()) - { + if (cost > House->Available_Money()) { Set_Stage(Fetch_Stage() - 1); } - else - { + else { House->Spend_Money(cost); Balance -= cost; } @@ -188,14 +185,12 @@ void FactoryClassExt::_AI() * * @author: CCHyper */ - if (Vinifera_DeveloperMode) - { + if (Vinifera_DeveloperMode) { /* ** If AIInstantBuild is toggled on, make sure this is a non-human AI house. */ if (Vinifera_Developer_AIInstantBuild - && !House->Is_Human_Control() && House != PlayerPtr) - { + && !House->Is_Human_Control() && House != PlayerPtr) { Set_Stage(STEP_COUNT); } @@ -203,8 +198,7 @@ void FactoryClassExt::_AI() ** If InstantBuild is toggled on, make sure the local player is a human house. */ if (Vinifera_Developer_InstantBuild - && House->Is_Human_Control() && House == PlayerPtr) - { + && House->Is_Human_Control() && House == PlayerPtr) { Set_Stage(STEP_COUNT); } @@ -212,8 +206,7 @@ void FactoryClassExt::_AI() ** If the AI has taken control of the player house, it needs a special ** case to handle the "player" instant build mode. */ - if (Vinifera_Developer_InstantBuild) - { + if (Vinifera_Developer_InstantBuild) { if (Vinifera_Developer_AIControl && House == PlayerPtr) Set_Stage(STEP_COUNT); } @@ -223,8 +216,7 @@ void FactoryClassExt::_AI() /* ** If the production has completed, then suspend further production. */ - if (Fetch_Stage() == STEP_COUNT) - { + if (Fetch_Stage() == STEP_COUNT) { IsSuspended = true; Set_Rate(0); House->Spend_Money(Balance); diff --git a/src/extensions/house/houseext_hooks.cpp b/src/extensions/house/houseext_hooks.cpp index 712b073d8..cf323e472 100644 --- a/src/extensions/house/houseext_hooks.cpp +++ b/src/extensions/house/houseext_hooks.cpp @@ -51,6 +51,7 @@ #include "hooker.h" #include "hooker_macros.h" +#include "rulesext.h" #include "tibsun_functions.h" @@ -941,6 +942,27 @@ DECLARE_PATCH(_Can_Build_Required_Forbidden_Houses_Patch) } +/** + * Allow to skip the check for the MCV's ActLike. + * + * Author: ZivDero + */ +DECLARE_PATCH(_HouseClass_Can_Build_Multi_MCV_Patch) +{ + GET_REGISTER_STATIC(BuildingClass*, building, esi); + + if (RuleExtension->IsMultiMCV) { + JMP(0x004BC102); + } + + static HousesType act_like; + act_like = building->ActLike; + + _asm mov ecx, act_like + JMP(0x004BC0BD); +} + + /** * Main function for patching the hooks. */ @@ -959,7 +981,6 @@ void HouseClassExtension_Hooks() Patch_Jump(0x004CB777, &_HouseClass_ShouldDisableCameo_BuildLimit_Fix); Patch_Jump(0x004BC187, &_HouseClass_Can_Build_BuildLimit_Handle_Vehicle_Transform); - Patch_Jump(0x004CB6C1, &_HouseClass_Enable_SWs_Check_For_Building_Power); Patch_Jump(0x004C10E0, &HouseClassExt::_AI_Building); @@ -967,4 +988,5 @@ void HouseClassExtension_Hooks() Patch_Jump(0x004BBC74, &_Can_Build_Required_Forbidden_Houses_Patch); Patch_Jump(0x004BAC2C, 0x004BAC39); // Patch a jump in the constructor to always allocate unit trackers + Patch_Jump(0x004BC0B7, &_HouseClass_Can_Build_Multi_MCV_Patch); } diff --git a/src/extensions/objecttype/objecttypeext_hooks.cpp b/src/extensions/objecttype/objecttypeext_hooks.cpp index 1c452a54e..6476d71b8 100644 --- a/src/extensions/objecttype/objecttypeext_hooks.cpp +++ b/src/extensions/objecttype/objecttypeext_hooks.cpp @@ -45,6 +45,7 @@ #include "hooker.h" #include "hooker_macros.h" #include "miscutil.h" +#include "rulesext.h" /** @@ -222,6 +223,22 @@ void ObjectTypeClassExt::_Clear_Voxel_Indexes() } +/** + * Allow to skip the check for the MCV's ActLike. + * + * Author: ZivDero + */ +DECLARE_PATCH(_ObjectTypeClass_Who_Can_Build_Me_Multi_MCV_Patch) +{ + if (RuleExtension->IsMultiMCV) { + JMP(0x00587C0B); + } + + _asm mov ecx, [esi+0x9C] + JMP_REG(edx, 0x00587C00); +} + + /** * Main function for patching the hooks. */ @@ -232,4 +249,5 @@ void ObjectTypeClassExtension_Hooks() Patch_Jump(0x0058891D, &_ObjectTypeClass_Load_Theater_Art_Assign_Theater_Name_Theater_Patch); Patch_Jump(0x00587C80, &ObjectTypeClassExt::_Fetch_Voxel_Image); Patch_Jump(0x00589030, &ObjectTypeClassExt::_Clear_Voxel_Indexes); + Patch_Jump(0x00587BFA, &_ObjectTypeClass_Who_Can_Build_Me_Multi_MCV_Patch); } diff --git a/src/extensions/rules/rulesext.cpp b/src/extensions/rules/rulesext.cpp index 5b8fa1212..ae398db3f 100644 --- a/src/extensions/rules/rulesext.cpp +++ b/src/extensions/rules/rulesext.cpp @@ -82,7 +82,9 @@ RulesClassExtension::RulesClassExtension(const RulesClass *this_ptr) : IsShowSuperWeaponTimers(true), IceStrength(0), WeedPipIndex(1), - MaxFreeRefineryDistanceBias(16) + MaxFreeRefineryDistanceBias(16), + IsRecheckPrerequisites(false), + IsMultiMCV(false) { //if (this_ptr) EXT_DEBUG_TRACE("RulesClassExtension::RulesClassExtension - 0x%08X\n", (uintptr_t)(ThisPtr)); @@ -214,6 +216,8 @@ void RulesClassExtension::Compute_CRC(WWCRCEngine &crc) const crc(IsShowSuperWeaponTimers); crc(IceStrength); crc(MaxFreeRefineryDistanceBias); + crc(IsRecheckPrerequisites); + crc(IsMultiMCV); } @@ -605,12 +609,15 @@ bool RulesClassExtension::General(CCINIClass &ini) * #issue-632 * * "EngineerDamage" was incorrectly loaded with "EngineerCaptureLevel", so - * the value the value correctly. + * load the value correctly. * * @author: CCHyper */ This()->EngineerDamage = ini.Get_Float(GENERAL, "EngineerDamage", This()->EngineerDamage); + MaxFreeRefineryDistanceBias = ini.Get_Int(GENERAL, "MaxFreeRefineryDistanceBias", MaxFreeRefineryDistanceBias); + IsRecheckPrerequisites = ini.Get_Bool(GENERAL, "RecheckPrerequisites", IsRecheckPrerequisites); + IsMultiMCV = ini.Get_Bool(GENERAL, "MultiMCV", IsMultiMCV); return true; } diff --git a/src/extensions/rules/rulesext.h b/src/extensions/rules/rulesext.h index a82e9ec90..1b86c2c33 100644 --- a/src/extensions/rules/rulesext.h +++ b/src/extensions/rules/rulesext.h @@ -117,4 +117,14 @@ class RulesClassExtension final : public GlobalExtensionClass * difference in cells is less than this. */ int MaxFreeRefineryDistanceBias; + + /** + * Should prerequisites be rechecked when buildings are lost, making the player lose access to units/buildings? + */ + bool IsRecheckPrerequisites; + + /** + * Should the game assume there is more than one MCV (that factions don't share their MCV?) + */ + bool IsMultiMCV; }; diff --git a/src/extensions/sidebar/sidebarext_hooks.cpp b/src/extensions/sidebar/sidebarext_hooks.cpp index 928ca788a..76176fcda 100644 --- a/src/extensions/sidebar/sidebarext_hooks.cpp +++ b/src/extensions/sidebar/sidebarext_hooks.cpp @@ -65,9 +65,11 @@ #include "debughandler.h" #include "fatal.h" #include "asserthandler.h" +#include "building.h" #include "hooker.h" #include "hooker_macros.h" #include "optionsext.h" +#include "rulesext.h" #include "uicontrol.h" #include "vinifera_globals.h" @@ -2163,6 +2165,45 @@ DECLARE_PATCH(_SidebarClass_StripClass_Help_Text_Extended_Tooltip_Patch) } +/** + * Makes the sidebar recheck prerequisites when doing a recalc. + * + * @author: ZivDero + */ +static bool _Can_Build_Helper(BuildingClass* who, TechnoTypeClass* tech) +{ + return who->House->Can_Build(tech, !RuleExtension->IsRecheckPrerequisites, true); +} + +DECLARE_PATCH(_StripClass_Recalc_Recheck_Prerequisites_Patch) +{ + GET_REGISTER_STATIC(BuildingClass*, who, eax); + GET_REGISTER_STATIC(TechnoTypeClass*, tech, ebp); + + if (!_Can_Build_Helper(who, tech)) + { + JMP(0x005F5799); + } + + JMP(0x005F5852); +} + + +/** + * Corrects the max visible buildables count for the new sidebar. + * + * @author: ZivDero + */ +DECLARE_PATCH(_StripClass_Recalc_MaxVisible_Patch) +{ + static int maxvisible; + maxvisible = SidebarClassExtension::Max_Visible(!Vinifera_NewSidebar); + _asm mov eax, maxvisible + _asm mov [esp+0x20], eax + JMP(0x005F569D); +} + + /** * Main function for patching the hooks. */ @@ -2184,6 +2225,9 @@ void SidebarClassExtension_Hooks() // Change jle to jl to allow rendering tooltips that are exactly as wide as the sidebar Patch_Byte(0x0044E605 + 1, 0x8C); + Patch_Jump(0x005F5759, &_StripClass_Recalc_Recheck_Prerequisites_Patch); + Patch_Jump(0x005F563D, &_StripClass_Recalc_MaxVisible_Patch); + /** * Legacy patches for the old sidebar. */