From 7d6df67e4ee93cf692b6ff879fd65f33cb05788a Mon Sep 17 00:00:00 2001 From: memo Date: Fri, 31 May 2024 22:51:47 +0200 Subject: [PATCH 1/8] add property to unhide hidden reward buildings in submenus --- README.md | 14 +++++++++++++ src/SubmenusDllDirector.cpp | 42 +++++++++++++++++++++++++++++++++++++ vendor/new_properties.xml | 2 ++ 3 files changed, 58 insertions(+) diff --git a/README.md b/README.md index 5ce83f9..ecdb68b 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,20 @@ Add the properties `Item Submenu Parent ID` and `Item Button Class` to the catal The latter property is set to `2 = Network Item in Submenu` or `4 = Flora Item in Submenu`. (The `Building Submenus` property is not used for these items.) +### Reward buildings + +Reward buildings normally do not require any special considerations for submenus. +They can be included in submenus just like any other buildings. + +If you want to include a *hidden* reward building in the Reward menu *and* in a submenu, +and want the item to be hidden in the Reward menu while unlocked, but unhidden in the submenu, then: +- include the two menu IDs in the `Building Submenus` property +- `Item Button Class` (0x8a2602cc): set to `8 = Building unhidden in Submenu`. +For example, this applies to the House of Worship. +(For *non-hidden* reward buildings such as the Mayor's House, this is not needed.) + +This way, unlocked rewards can be found in the Reward menu as usual, but also in a thematic submenu. + ### Exemplar patching Exemplar patching is a technique to change the behavior of Exemplar files on-the-fly at runtime, without altering the original DBPF files. diff --git a/src/SubmenusDllDirector.cpp b/src/SubmenusDllDirector.cpp index f9d54cf..6f15960 100644 --- a/src/SubmenusDllDirector.cpp +++ b/src/SubmenusDllDirector.cpp @@ -80,6 +80,7 @@ #define ITEM_BUTTON_CLASS_SUBMENU 0x00000001 #define ITEM_BUTTON_CLASS_NETWORK 0x00000002 #define ITEM_BUTTON_CLASS_FLORA 0x00000004 // currently not enforced (subject to change) +#define ITEM_BUTTON_CLASS_BUILDING_UNHIDE 0x00000008 // (hidden reward) building that should be unhidden in submenus // the catalog item type of submenu buttons #define SUBMENU_ITEM_TYPE 0x9ab38dac @@ -155,6 +156,9 @@ static constexpr uint32_t AddBuildingsToItemList_ContinueJump_UseOrigProp = 0x7f static constexpr uint32_t AddBuildingsToItemList2_InjectPoint = 0x7f036a; static constexpr uint32_t AddBuildingsToItemList2_ContinueJump = 0x7f0371; +static constexpr uint32_t AddBuildingsToItemList3_InjectPoint = 0x7f0493; +static constexpr uint32_t AddBuildingsToItemList3_ContinueJump = 0x7f049c; + static uint32_t CreateBuildingMenu_InjectPoint = 0x7f074e; static uint32_t CreateBuildingMenu_ContinueJump = 0x7f0756; @@ -418,6 +422,43 @@ namespace } } + bool shouldForciblyUnhideBuilding(cISCPropertyHolder* propHolder) + { + bool isSubmenuActive = (lastVirtualButtonId == VIRTUAL_SUBMENU_ITEM_TYPE); + if (!isSubmenuActive) { + return false; // we are in a top-level menu (like Reward menu) in which we do not unhide hidden buildings + } else { + uint32_t propValueBuffer = 0; + propHolder->GetProperty((uint32_t)ITEM_BUTTON_CLASS_PROP, propValueBuffer); + return propValueBuffer == ITEM_BUTTON_CLASS_BUILDING_UNHIDE; + } + } + + // forcibly unhide (locked) reward buildings if they are inside a submenu and have the corresponding property set + void NAKED_FUN Hook_AddBuildingsToItemList3(void) + { + __asm { + test byte ptr [esi], 0x1; + jz skipHiding; + + push eax; // store + push ecx; // store + push edx; // store + push edi; // propHolder + call shouldForciblyUnhideBuilding; // (cdecl) + add esp, 0x4; + pop edx; // restore + pop ecx; // restore + test al, al; + pop eax; // restore + jnz skipHiding; + mov byte ptr [esp + 0x12], bl; // hides hidden building +skipHiding: + push AddBuildingsToItemList3_ContinueJump; + ret; + } + } + uint32_t menuFramePngFlora = FLORA_MENU_ID; uint32_t menuFramePngZone = ZONE_MENU_ID; uint32_t menuFramePngTransport = TRANSPORT_MENU_ID; @@ -1048,6 +1089,7 @@ namespace InstallHook(CreateBuildingMenu_InjectPoint, Hook_CreateBuildingMenu); InstallHook(CreateCatalogView_InjectPoint, Hook_CreateCatalogView); InstallHook(AddBuildingsToItemList_InjectPoint, Hook_AddBuildingsToItemList); + InstallHook(AddBuildingsToItemList3_InjectPoint, Hook_AddBuildingsToItemList3); InstallHook(InvokeTertiaryMenu_InjectPoint, Hook_InvokeTertiaryMenu); // With the following replacement, plugins can be designed for use with and without the DLL. diff --git a/vendor/new_properties.xml b/vendor/new_properties.xml index e4ef8d0..e77194c 100644 --- a/vendor/new_properties.xml +++ b/vendor/new_properties.xml @@ -5244,6 +5244,8 @@ For use with Submenus DLL: classifier that determines the function of this item + From e925ca24278e8570236247c612396a9abbb4f5da Mon Sep 17 00:00:00 2001 From: memo Date: Sat, 1 Jun 2024 10:38:06 +0200 Subject: [PATCH 2/8] add religion submenu --- README.md | 3 +++ src/Categorization.cpp | 7 +++++++ src/MenuIds.h | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/README.md b/README.md index ecdb68b..eb69441 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,9 @@ Health: - Medium (0xB7B594D6)* - Large (0xBC251B69)* +Landmark: +- Religion (0x26EB3057)* + Parks: - Green Spaces (0xBF776D40) - Plazas (0xEB75882C) diff --git a/src/Categorization.cpp b/src/Categorization.cpp index 2ab8f75..2cbab6b 100644 --- a/src/Categorization.cpp +++ b/src/Categorization.cpp @@ -24,6 +24,7 @@ const std::unordered_set Categorization::autoPrefilledSubmenus = { policeSmallSubmenuId, policeLargeSubmenuId, policeDeluxeSubmenuId, elementarySchoolSubmenuId, highSchoolSubmenuId, collegeSubmenuId, libraryMuseumSubmenuId, healthSmallSubmenuId, healthMediumSubmenuId, healthLargeSubmenuId, + religionSubmenuId, }; Categorization::Categorization(std::unordered_set* reachableSubmenus) : reachableSubmenus(reachableSubmenus) @@ -140,6 +141,8 @@ bool Categorization::belongsToSubmenu(cISCPropertyHolder* propHolder, uint32_t s case healthLargeSubmenuId: return hasOg(OgHealth) && hasOg(OgHealthOther) && PropertyUtil::arrayContains(propHolder, budgetItemDepartmentPropId, nullptr, budgetItemDepartmentProp_HealthCoverage); + case religionSubmenuId: return hasOg(OgWorship) || hasOg(OgCemetery); + default: // generic submenu return false; } @@ -180,6 +183,7 @@ Categorization::TriState Categorization::belongsToMenu(cISCPropertyHolder* propH && !belongsToSubmenu(propHolder, r1SubmenuId) && !belongsToSubmenu(propHolder, r2SubmenuId) && !belongsToSubmenu(propHolder, r3SubmenuId) + && !belongsToSubmenu(propHolder, religionSubmenuId) ); case railButtonId: @@ -240,9 +244,12 @@ Categorization::TriState Categorization::belongsToMenu(cISCPropertyHolder* propH && !belongsToSubmenu(propHolder, healthLargeSubmenuId) ); + // case rewardButtonId // for now, we do not exclude rewards like churches from the Reward menu + case parkButtonId: return bool2tri(hasOg(OgPark) && !belongsToSubmenu(propHolder, canalSubmenuId) + && !belongsToSubmenu(propHolder, religionSubmenuId) ); default: diff --git a/src/MenuIds.h b/src/MenuIds.h index eb945d0..16e6e93 100644 --- a/src/MenuIds.h +++ b/src/MenuIds.h @@ -100,6 +100,9 @@ static constexpr uint32_t OgId = 0x14200; static constexpr uint32_t OgIm = 0x14300; static constexpr uint32_t OgIht = 0x14400; +static constexpr uint32_t OgWorship = 0x1907; +static constexpr uint32_t OgCemetery = 0x1700; + static constexpr uint32_t OgPark = 0x1006; static constexpr uint32_t passengerRailSubmenuId = 0x35380C75; @@ -137,6 +140,8 @@ static constexpr uint32_t healthSmallSubmenuId = 0xB1F7AC5B; // medical clinic static constexpr uint32_t healthMediumSubmenuId = 0xB7B594D6; // hospital with helicopter static constexpr uint32_t healthLargeSubmenuId = 0xBC251B69; // medical center with helicopter +static constexpr uint32_t religionSubmenuId = 0x26EB3057; + static constexpr uint32_t r1SubmenuId = 0x93DADFE9; static constexpr uint32_t r2SubmenuId = 0x984E5034; static constexpr uint32_t r3SubmenuId = 0x9F83F133; From ce5b46bba34f10b9d1638dd7ee26cca1d1a6c285 Mon Sep 17 00:00:00 2001 From: memo Date: Sat, 1 Jun 2024 12:28:56 +0200 Subject: [PATCH 3/8] keep only conditional churches in reward menu Unconditional churches only appear in the reward menu. --- src/Categorization.cpp | 17 ++++++++++++++++- src/MenuIds.h | 3 +++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Categorization.cpp b/src/Categorization.cpp index 2cbab6b..f2bc379 100644 --- a/src/Categorization.cpp +++ b/src/Categorization.cpp @@ -52,6 +52,16 @@ static bool isHeightBelow(cISCPropertyHolder* propHolder, float_t height) return result; } +static bool isConditional(cISCPropertyHolder* propHolder) +{ + bool result = false; + if (propHolder->HasProperty(conditionalBuildingPropId)) { + auto property = propHolder->GetProperty(conditionalBuildingPropId); + property->GetPropertyValue()->GetValBool(result); + } + return result; +} + static inline Categorization::TriState bool2tri(bool b) { return b ? Categorization::TriState::Yes : Categorization::TriState::No; @@ -244,7 +254,12 @@ Categorization::TriState Categorization::belongsToMenu(cISCPropertyHolder* propH && !belongsToSubmenu(propHolder, healthLargeSubmenuId) ); - // case rewardButtonId // for now, we do not exclude rewards like churches from the Reward menu + // for buildings in submenus (e.g. Religion), we keep a copy here + // if they are *conditional* reward buildings (to make them easy to find when unlocked) + case rewardButtonId: + return bool2tri(hasOg(OgReward) + && (!belongsToSubmenu(propHolder, religionSubmenuId) || isConditional(propHolder)) + ); case parkButtonId: return bool2tri(hasOg(OgPark) diff --git a/src/MenuIds.h b/src/MenuIds.h index 16e6e93..e8f9107 100644 --- a/src/MenuIds.h +++ b/src/MenuIds.h @@ -34,6 +34,7 @@ static constexpr uint32_t budgetItemDepartmentProp_HealthCoverage = 0xaa538cb3; static constexpr uint32_t occupantSizePropId = 0x27812810; static constexpr uint32_t powerPlantTypePropId = 0x27812853; enum PowerPlantType : uint32_t { Coal = 1, Hydrogen = 2, NaturalGas = 3, Nuclear = 5, Oil = 6, Solar = 7, Waste = 8, Wind = 9, Auxiliary = 0xA }; +static constexpr uint32_t conditionalBuildingPropId = 0xea3209f8; static constexpr uint32_t railButtonId = RAIL_BUTTON_ID; static constexpr uint32_t miscTransitButtonId = MISCTRANSP_BUTTON_ID; @@ -45,6 +46,7 @@ static constexpr uint32_t policeButtonId = POLICE_BUTTON_ID; static constexpr uint32_t educationButtonId = EDUCATION_BUTTON_ID; static constexpr uint32_t healthButtonId = HEALTH_BUTTON_ID; static constexpr uint32_t landmarkButtonId = LANDMARK_BUTTON_ID; +static constexpr uint32_t rewardButtonId = REWARD_BUTTON_ID; static constexpr uint32_t parkButtonId = PARK_BUTTON_ID; static constexpr uint32_t OgRail = 0x1300; @@ -100,6 +102,7 @@ static constexpr uint32_t OgId = 0x14200; static constexpr uint32_t OgIm = 0x14300; static constexpr uint32_t OgIht = 0x14400; +static constexpr uint32_t OgReward = 0x150B; static constexpr uint32_t OgWorship = 0x1907; static constexpr uint32_t OgCemetery = 0x1700; From 5644b393dbd88a007001f6895dd9fa6815662e92 Mon Sep 17 00:00:00 2001 From: memo Date: Sat, 1 Jun 2024 22:26:04 +0200 Subject: [PATCH 4/8] add government submenu --- README.md | 1 + src/Categorization.cpp | 15 +++++++++++---- src/MenuIds.h | 7 +++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index eb69441..56eca39 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ Health: - Large (0xBC251B69)* Landmark: +- Government (0x9FAF7A3B)* - Religion (0x26EB3057)* Parks: diff --git a/src/Categorization.cpp b/src/Categorization.cpp index f2bc379..b8e337f 100644 --- a/src/Categorization.cpp +++ b/src/Categorization.cpp @@ -24,7 +24,7 @@ const std::unordered_set Categorization::autoPrefilledSubmenus = { policeSmallSubmenuId, policeLargeSubmenuId, policeDeluxeSubmenuId, elementarySchoolSubmenuId, highSchoolSubmenuId, collegeSubmenuId, libraryMuseumSubmenuId, healthSmallSubmenuId, healthMediumSubmenuId, healthLargeSubmenuId, - religionSubmenuId, + governmentSubmenuId, religionSubmenuId, }; Categorization::Categorization(std::unordered_set* reachableSubmenus) : reachableSubmenus(reachableSubmenus) @@ -151,6 +151,10 @@ bool Categorization::belongsToSubmenu(cISCPropertyHolder* propHolder, uint32_t s case healthLargeSubmenuId: return hasOg(OgHealth) && hasOg(OgHealthOther) && PropertyUtil::arrayContains(propHolder, budgetItemDepartmentPropId, nullptr, budgetItemDepartmentProp_HealthCoverage); + case governmentSubmenuId: + return PropertyUtil::arrayContains(propHolder, budgetItemDepartmentPropId, nullptr, budgetItemDepartmentProp_GovernmentBuildings) + || hasOg(OgMayorHouse) || hasOg(OgBureaucracy) || hasOg(OgConventionCrowd) || hasOg(OgStockExchange) + || (hasOg(OgCourthouse) && !hasOg(OgLandmark)); // to exclude US Capitol case religionSubmenuId: return hasOg(OgWorship) || hasOg(OgCemetery); default: // generic submenu @@ -193,6 +197,7 @@ Categorization::TriState Categorization::belongsToMenu(cISCPropertyHolder* propH && !belongsToSubmenu(propHolder, r1SubmenuId) && !belongsToSubmenu(propHolder, r2SubmenuId) && !belongsToSubmenu(propHolder, r3SubmenuId) + && !belongsToSubmenu(propHolder, governmentSubmenuId) && !belongsToSubmenu(propHolder, religionSubmenuId) ); @@ -257,9 +262,11 @@ Categorization::TriState Categorization::belongsToMenu(cISCPropertyHolder* propH // for buildings in submenus (e.g. Religion), we keep a copy here // if they are *conditional* reward buildings (to make them easy to find when unlocked) case rewardButtonId: - return bool2tri(hasOg(OgReward) - && (!belongsToSubmenu(propHolder, religionSubmenuId) || isConditional(propHolder)) - ); + return bool2tri(hasOg(OgReward) && ( + isConditional(propHolder) + || !belongsToSubmenu(propHolder, governmentSubmenuId) + && !belongsToSubmenu(propHolder, religionSubmenuId) + )); case parkButtonId: return bool2tri(hasOg(OgPark) diff --git a/src/MenuIds.h b/src/MenuIds.h index e8f9107..b6ae507 100644 --- a/src/MenuIds.h +++ b/src/MenuIds.h @@ -31,6 +31,7 @@ static constexpr uint32_t itemSubmenuParentPropId = 0xaa1dd399; // occupantGrou static constexpr uint32_t capacitySatisfiedPropId = 0x27812834; static constexpr uint32_t budgetItemDepartmentPropId = 0xea54d283; static constexpr uint32_t budgetItemDepartmentProp_HealthCoverage = 0xaa538cb3; +static constexpr uint32_t budgetItemDepartmentProp_GovernmentBuildings = 0xea59717a; static constexpr uint32_t occupantSizePropId = 0x27812810; static constexpr uint32_t powerPlantTypePropId = 0x27812853; enum PowerPlantType : uint32_t { Coal = 1, Hydrogen = 2, NaturalGas = 3, Nuclear = 5, Oil = 6, Solar = 7, Waste = 8, Wind = 9, Auxiliary = 0xA }; @@ -103,6 +104,11 @@ static constexpr uint32_t OgIm = 0x14300; static constexpr uint32_t OgIht = 0x14400; static constexpr uint32_t OgReward = 0x150B; +static constexpr uint32_t OgMayorHouse = 0x1938; +static constexpr uint32_t OgCourthouse = 0x1511; // or city hall +static constexpr uint32_t OgBureaucracy = 0x1905; // DMV +static constexpr uint32_t OgStockExchange = 0x1913; // Biz Lawyer Attack +static constexpr uint32_t OgConventionCrowd = 0x1921; static constexpr uint32_t OgWorship = 0x1907; static constexpr uint32_t OgCemetery = 0x1700; @@ -143,6 +149,7 @@ static constexpr uint32_t healthSmallSubmenuId = 0xB1F7AC5B; // medical clinic static constexpr uint32_t healthMediumSubmenuId = 0xB7B594D6; // hospital with helicopter static constexpr uint32_t healthLargeSubmenuId = 0xBC251B69; // medical center with helicopter +static constexpr uint32_t governmentSubmenuId = 0x9FAF7A3B; static constexpr uint32_t religionSubmenuId = 0x26EB3057; static constexpr uint32_t r1SubmenuId = 0x93DADFE9; From 999ff2cb0f7935eedfc6415d9a229ef495c3cd53 Mon Sep 17 00:00:00 2001 From: memo Date: Sun, 2 Jun 2024 07:49:40 +0200 Subject: [PATCH 5/8] add entertainment submenu --- README.md | 3 ++- src/Categorization.cpp | 14 ++++++++++---- src/MenuIds.h | 15 +++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 56eca39..c0e3f1f 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,8 @@ Health: Landmark: - Government (0x9FAF7A3B)* -- Religion (0x26EB3057)* +- Churches and Cemeteries (0x26EB3057)* +- Entertainment (0xBE9FDA0C)* (theaters, operas, cinemas, stadiums, night clubs, etc.) Parks: - Green Spaces (0xBF776D40) diff --git a/src/Categorization.cpp b/src/Categorization.cpp index b8e337f..db7ecef 100644 --- a/src/Categorization.cpp +++ b/src/Categorization.cpp @@ -24,7 +24,7 @@ const std::unordered_set Categorization::autoPrefilledSubmenus = { policeSmallSubmenuId, policeLargeSubmenuId, policeDeluxeSubmenuId, elementarySchoolSubmenuId, highSchoolSubmenuId, collegeSubmenuId, libraryMuseumSubmenuId, healthSmallSubmenuId, healthMediumSubmenuId, healthLargeSubmenuId, - governmentSubmenuId, religionSubmenuId, + governmentSubmenuId, religionSubmenuId, entertainmentSubmenuId, }; Categorization::Categorization(std::unordered_set* reachableSubmenus) : reachableSubmenus(reachableSubmenus) @@ -113,9 +113,9 @@ bool Categorization::belongsToSubmenu(cISCPropertyHolder* propHolder, uint32_t s // case parkingSubmenuId: // no auto-categorization case portFerrySubmenuId: return hasOg(OgWaterTransit) && (hasOg(OgPassengerFerry) || hasOg(OgCarFerry) || hasOg(OgSeaport)) && !hasOg(OgAirport); - case canalSubmenuId: return (hasOg(OgWaterTransit) || hasOg(OgPark)) && hasOg(OgBteInlandWaterways); + case canalSubmenuId: return (hasOg(OgWaterTransit) || hasOg(OgPark)) && (hasOg(OgBteInlandWaterways) || hasOg(OgSgWaterway)); // case seawallSubmenuId: // no auto-categorization - case waterfrontSubmenuId: return hasOg(OgWaterTransit) && hasOg(OgBteWaterfront) && !hasOg(OgBteInlandWaterways) && !(hasOg(OgPassengerFerry) || hasOg(OgCarFerry) || hasOg(OgSeaport)); + case waterfrontSubmenuId: return hasOg(OgWaterTransit) && hasOg(OgBteWaterfront) && !hasOg(OgBteInlandWaterways) && !hasOg(OgSgWaterway) && !(hasOg(OgPassengerFerry) || hasOg(OgCarFerry) || hasOg(OgSeaport)); case energyDirtySubmenuId: return hasOg(OgPower) && ( @@ -155,7 +155,10 @@ bool Categorization::belongsToSubmenu(cISCPropertyHolder* propHolder, uint32_t s return PropertyUtil::arrayContains(propHolder, budgetItemDepartmentPropId, nullptr, budgetItemDepartmentProp_GovernmentBuildings) || hasOg(OgMayorHouse) || hasOg(OgBureaucracy) || hasOg(OgConventionCrowd) || hasOg(OgStockExchange) || (hasOg(OgCourthouse) && !hasOg(OgLandmark)); // to exclude US Capitol - case religionSubmenuId: return hasOg(OgWorship) || hasOg(OgCemetery); + case religionSubmenuId: return hasOg(OgWorship) || hasOg(OgCemetery) || hasOg(OgBteReligious); + case entertainmentSubmenuId: return hasOg(OgStadium) || hasOg(OgOpera) || hasOg(OgNiteClub) || hasOg(OgZoo) || hasOg(OgStateFair) + // || hasOg(OgCommercialCinema) || hasOg(OgCommercialMaxisSimTheatre) || (hasOg(OgCommercialMovie) && !hasOg(OgCommercialDrivein)) + || hasOg(OgCasino) || hasOg(OgSgEntertainment) || hasOg(OgBteCommEntertainment); default: // generic submenu return false; @@ -199,6 +202,7 @@ Categorization::TriState Categorization::belongsToMenu(cISCPropertyHolder* propH && !belongsToSubmenu(propHolder, r3SubmenuId) && !belongsToSubmenu(propHolder, governmentSubmenuId) && !belongsToSubmenu(propHolder, religionSubmenuId) + && !belongsToSubmenu(propHolder, entertainmentSubmenuId) ); case railButtonId: @@ -266,12 +270,14 @@ Categorization::TriState Categorization::belongsToMenu(cISCPropertyHolder* propH isConditional(propHolder) || !belongsToSubmenu(propHolder, governmentSubmenuId) && !belongsToSubmenu(propHolder, religionSubmenuId) + && !belongsToSubmenu(propHolder, entertainmentSubmenuId) )); case parkButtonId: return bool2tri(hasOg(OgPark) && !belongsToSubmenu(propHolder, canalSubmenuId) && !belongsToSubmenu(propHolder, religionSubmenuId) + && !belongsToSubmenu(propHolder, entertainmentSubmenuId) ); default: diff --git a/src/MenuIds.h b/src/MenuIds.h index b6ae507..2060cba 100644 --- a/src/MenuIds.h +++ b/src/MenuIds.h @@ -66,6 +66,7 @@ static constexpr uint32_t OgWaterTransit = 0x1519; static constexpr uint32_t OgBteWaterfront = 0xB5C00DD6; static constexpr uint32_t OgBteInlandWaterways = 0xB5C00DD8; +static constexpr uint32_t OgSgWaterway = 0xB5C00185; static constexpr uint32_t OgPower = 0x1400; @@ -111,6 +112,19 @@ static constexpr uint32_t OgStockExchange = 0x1913; // Biz Lawyer Attack static constexpr uint32_t OgConventionCrowd = 0x1921; static constexpr uint32_t OgWorship = 0x1907; static constexpr uint32_t OgCemetery = 0x1700; +static constexpr uint32_t OgBteReligious = 0xB5C00DDF; +static constexpr uint32_t OgStadium = 0x1906; +static constexpr uint32_t OgOpera = 0x1909; +static constexpr uint32_t OgNiteClub = 0x1908; +static constexpr uint32_t OgZoo = 0x1702; +static constexpr uint32_t OgStateFair = 0x1925; +static constexpr uint32_t OgCasino = 0x1940; +static constexpr uint32_t OgCommercialCinema = 0x1112; +static constexpr uint32_t OgCommercialMaxisSimTheatre = 0x1113; +static constexpr uint32_t OgCommercialMovie = 0x21003; +static constexpr uint32_t OgCommercialDrivein = 0x1103; +static constexpr uint32_t OgSgEntertainment = 0xB5C00157; +static constexpr uint32_t OgBteCommEntertainment = 0xB5C00A0A; static constexpr uint32_t OgPark = 0x1006; @@ -151,6 +165,7 @@ static constexpr uint32_t healthLargeSubmenuId = 0xBC251B69; // medical center static constexpr uint32_t governmentSubmenuId = 0x9FAF7A3B; static constexpr uint32_t religionSubmenuId = 0x26EB3057; +static constexpr uint32_t entertainmentSubmenuId = 0xBE9FDA0C; static constexpr uint32_t r1SubmenuId = 0x93DADFE9; static constexpr uint32_t r2SubmenuId = 0x984E5034; From 8d238760c34532a8d9bf20a2cd3fd74bba5fcfc2 Mon Sep 17 00:00:00 2001 From: memo Date: Sun, 2 Jun 2024 09:57:44 +0200 Subject: [PATCH 6/8] improve categorization of hospital menus --- src/Categorization.cpp | 24 ++++++++++++++++++++---- src/MenuIds.h | 3 +++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/Categorization.cpp b/src/Categorization.cpp index db7ecef..2085bcc 100644 --- a/src/Categorization.cpp +++ b/src/Categorization.cpp @@ -52,6 +52,19 @@ static bool isHeightBelow(cISCPropertyHolder* propHolder, float_t height) return result; } +static bool isPatientCapacityGreaterThan(cISCPropertyHolder* propHolder, uint32_t threshold) +{ + if (propHolder->HasProperty(hospitalPatientCapacityPropId)) { + auto property = propHolder->GetProperty(hospitalPatientCapacityPropId); + uint32_t capacity = threshold; + bool success = property->GetPropertyValue()->GetValUint32(capacity); + if (success) { + return capacity > threshold; + } + } + return false; +} + static bool isConditional(cISCPropertyHolder* propHolder) { bool result = false; @@ -146,10 +159,13 @@ bool Categorization::belongsToSubmenu(cISCPropertyHolder* propHolder, uint32_t s case collegeSubmenuId: return hasOg(OgCollege); case libraryMuseumSubmenuId: return hasOg(OgLibrary) || hasOg(OgMuseum); - case healthSmallSubmenuId: return hasOg(OgHealth) && hasOg(OgHospital) && !hasOg(OgHealthLarge) && !hasOg(OgHealthOther); - case healthMediumSubmenuId: return hasOg(OgHealth) && hasOg(OgHospital) && hasOg(OgHealthLarge) && !hasOg(OgHealthOther); - case healthLargeSubmenuId: return hasOg(OgHealth) && hasOg(OgHealthOther) - && PropertyUtil::arrayContains(propHolder, budgetItemDepartmentPropId, nullptr, budgetItemDepartmentProp_HealthCoverage); + case healthSmallSubmenuId: return hasOg(OgHealth) && hasOg(OgHospital) && !hasOg(OgHealthOther) && (hasOg(OgClinic) || !hasOg(OgHealthLarge) && !hasOg(OgAmbulanceMaker)); + case healthMediumSubmenuId: return hasOg(OgHealth) && hasOg(OgHospital) && !hasOg(OgHealthOther) && !(hasOg(OgClinic) || !hasOg(OgHealthLarge) && !hasOg(OgAmbulanceMaker)) + && !isPatientCapacityGreaterThan(propHolder, 20000); + case healthLargeSubmenuId: return hasOg(OgHealth) && ( + hasOg(OgHealthOther) && PropertyUtil::arrayContains(propHolder, budgetItemDepartmentPropId, nullptr, budgetItemDepartmentProp_HealthCoverage) + || isPatientCapacityGreaterThan(propHolder, 20000) + ); case governmentSubmenuId: return PropertyUtil::arrayContains(propHolder, budgetItemDepartmentPropId, nullptr, budgetItemDepartmentProp_GovernmentBuildings) diff --git a/src/MenuIds.h b/src/MenuIds.h index 2060cba..31779b7 100644 --- a/src/MenuIds.h +++ b/src/MenuIds.h @@ -36,6 +36,7 @@ static constexpr uint32_t occupantSizePropId = 0x27812810; static constexpr uint32_t powerPlantTypePropId = 0x27812853; enum PowerPlantType : uint32_t { Coal = 1, Hydrogen = 2, NaturalGas = 3, Nuclear = 5, Oil = 6, Solar = 7, Waste = 8, Wind = 9, Auxiliary = 0xA }; static constexpr uint32_t conditionalBuildingPropId = 0xea3209f8; +static constexpr uint32_t hospitalPatientCapacityPropId = 0x69220415; static constexpr uint32_t railButtonId = RAIL_BUTTON_ID; static constexpr uint32_t miscTransitButtonId = MISCTRANSP_BUTTON_ID; @@ -86,9 +87,11 @@ static constexpr uint32_t OgSchoolHigh = 0x1510; static constexpr uint32_t OgSchoolPrivate = 0x1514; static constexpr uint32_t OgHealth = 0x1507; +static constexpr uint32_t OgClinic = 0x1512; static constexpr uint32_t OgHospital = 0x1513; static constexpr uint32_t OgHealthLarge = 0x151a; static constexpr uint32_t OgHealthOther = 0x151c; +static constexpr uint32_t OgAmbulanceMaker = 0x1904; static constexpr uint32_t OgLandmark = 0x150a; static constexpr uint32_t OgR1 = 0x11010; From fecf0ea929531392a1a88c10af0d345fe069a006 Mon Sep 17 00:00:00 2001 From: memo Date: Sun, 2 Jun 2024 10:10:33 +0200 Subject: [PATCH 7/8] include TV and Radio stations in Entertainment --- src/Categorization.cpp | 4 +++- src/MenuIds.h | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Categorization.cpp b/src/Categorization.cpp index 2085bcc..ef92226 100644 --- a/src/Categorization.cpp +++ b/src/Categorization.cpp @@ -174,7 +174,9 @@ bool Categorization::belongsToSubmenu(cISCPropertyHolder* propHolder, uint32_t s case religionSubmenuId: return hasOg(OgWorship) || hasOg(OgCemetery) || hasOg(OgBteReligious); case entertainmentSubmenuId: return hasOg(OgStadium) || hasOg(OgOpera) || hasOg(OgNiteClub) || hasOg(OgZoo) || hasOg(OgStateFair) // || hasOg(OgCommercialCinema) || hasOg(OgCommercialMaxisSimTheatre) || (hasOg(OgCommercialMovie) && !hasOg(OgCommercialDrivein)) - || hasOg(OgCasino) || hasOg(OgSgEntertainment) || hasOg(OgBteCommEntertainment); + || hasOg(OgCasino) || hasOg(OgTvStation) + || PropertyUtil::arrayContains(propHolder, queryExemplarGuidPropId, nullptr, queryExemplarGuidProp_RadioStation) + || hasOg(OgSgEntertainment) || hasOg(OgBteCommEntertainment); default: // generic submenu return false; diff --git a/src/MenuIds.h b/src/MenuIds.h index 31779b7..dde2f08 100644 --- a/src/MenuIds.h +++ b/src/MenuIds.h @@ -37,6 +37,8 @@ static constexpr uint32_t powerPlantTypePropId = 0x27812853; enum PowerPlantType : uint32_t { Coal = 1, Hydrogen = 2, NaturalGas = 3, Nuclear = 5, Oil = 6, Solar = 7, Waste = 8, Wind = 9, Auxiliary = 0xA }; static constexpr uint32_t conditionalBuildingPropId = 0xea3209f8; static constexpr uint32_t hospitalPatientCapacityPropId = 0x69220415; +static constexpr uint32_t queryExemplarGuidPropId = 0x2a499f85; +static constexpr uint32_t queryExemplarGuidProp_RadioStation = 0x0A8B9C43; static constexpr uint32_t railButtonId = RAIL_BUTTON_ID; static constexpr uint32_t miscTransitButtonId = MISCTRANSP_BUTTON_ID; @@ -117,8 +119,9 @@ static constexpr uint32_t OgWorship = 0x1907; static constexpr uint32_t OgCemetery = 0x1700; static constexpr uint32_t OgBteReligious = 0xB5C00DDF; static constexpr uint32_t OgStadium = 0x1906; -static constexpr uint32_t OgOpera = 0x1909; static constexpr uint32_t OgNiteClub = 0x1908; +static constexpr uint32_t OgOpera = 0x1909; +static constexpr uint32_t OgTvStation = 0x1910; static constexpr uint32_t OgZoo = 0x1702; static constexpr uint32_t OgStateFair = 0x1925; static constexpr uint32_t OgCasino = 0x1940; From a528a917c51d20b1196b8539a3fb8c66d4b6a62e Mon Sep 17 00:00:00 2001 From: memo Date: Sun, 2 Jun 2024 18:48:28 +0200 Subject: [PATCH 8/8] add catalog state for reward menu --- README.md | 4 ++-- src/SubmenusDllDirector.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c0e3f1f..f861531 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ Submenus for SimCity 4. ## Features - enables submenu functionality: This DLL is a dependency for other submenu-compatible plugins. -- adds a number of standard submenus such as Plazas, Green Spaces, Sports. -- fixes the game's menu rendering issues caused by duplicate or missing menu icons +- adds a number of standard submenus such as Plazas, Green Spaces, Sports and many more. +- fixes the game's menu rendering issues caused by duplicate or missing menu icons. - implements Exemplar Patching functionality ## System requirements diff --git a/src/SubmenusDllDirector.cpp b/src/SubmenusDllDirector.cpp index 6f15960..27d8596 100644 --- a/src/SubmenusDllDirector.cpp +++ b/src/SubmenusDllDirector.cpp @@ -129,6 +129,9 @@ static uint32_t HandleButtonActivated_ContinueJump_Transport = 0x7f4add; static uint32_t HandleButtonActivated2_InjectPoint = 0x7f4eb5; static uint32_t HandleButtonActivated2_ContinueJump = 0x7f4ebe; +static constexpr uint32_t HandleButtonActivatedReward_InjectPoint = 0x7f4dbe; +static constexpr uint32_t HandleButtonActivatedReward_ContinueJump = 0x7f4ea0; + static uint32_t DoUtilitiesMenu_InjectPoint = 0x7f3b5d; static uint32_t DoUtilitiesMenu_ContinueJump = 0x7f3b64; @@ -978,6 +981,7 @@ namespace std::unordered_map catalogStates = {}; + // also handles reward menu button CatalogState* getVirtualButtonCatalogState(const uint32_t virtualButtonId) { if (catalogStates.contains(virtualButtonId)) @@ -1039,6 +1043,25 @@ namespace } } + // adds catalog state for reward menu + void NAKED_FUN Hook_HandleButtonActivatedReward(void) + { + __asm { + push eax; // store + push ecx; // store + push edx; // store + push dword ptr [rewardButtonId]; + call getVirtualButtonCatalogState; // (cdecl) + add esp, 0x4; + mov ebx, eax; // pointer to catalog state of reward menu + pop edx; // restore + pop ecx; // restore + pop eax; // restore + push HandleButtonActivatedReward_ContinueJump; + ret; + } + } + void InstallDuplicateIconsPatch(const uint16_t gameVersion) { Logger& logger = Logger::GetInstance(); @@ -1091,6 +1114,7 @@ namespace InstallHook(AddBuildingsToItemList_InjectPoint, Hook_AddBuildingsToItemList); InstallHook(AddBuildingsToItemList3_InjectPoint, Hook_AddBuildingsToItemList3); InstallHook(InvokeTertiaryMenu_InjectPoint, Hook_InvokeTertiaryMenu); + InstallHook(HandleButtonActivatedReward_InjectPoint, Hook_HandleButtonActivatedReward); // With the following replacement, plugins can be designed for use with and without the DLL. // The property ITEM_SUBMENU_PARENT_ID_PROP is not loaded without the DLL, but otherwise behaves like ITEM_SUBMENU_PROP.