diff --git a/CHANGELOG.md b/CHANGELOG.md index f6d76df..f020de0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +### [2.3.3] + +Miscellaneous fixes for various issues. +- Purchasing items now immediately removes from the permanent item list (**NOT** the config file's `itemWhitelist`) when `stockPurchased` is set to disabled. +- Made `saleChance` determine sales likelihood more accurately now. + - My original goal was to recreate how the vanilla game handles sales, only parameterized to allow for configuration; however, due to integer rounding, a sales chance of e.g. `85%` and above could end up being exactly the same as `100%`, depending on the `maxSaleItems` setting. +- Fixed `Terminal.TextPostProcess()` transpiler occasionally replacing other items' displayed prices when appending a sale tag to a rotating item. + - Also simplified it significantly by doing what was used for the [TerminalFormatter](https://thunderstore.io/c/lethal-company/p/mrov/TerminalFormatter) compatibility transpiler. +- Made `minDiscount` be used for `maxDiscount` when `minDiscount` is greater than `maxDiscount`, just like the other range settings. +- Added a couple more null checks and debug messages. + ### [2.3.2] Compatibility with TerminalFormatter's modified store, among other things. @@ -72,8 +83,8 @@ Update to 'CSync' v5, more configuration for terminal scrolling. ### [2.0.1] -Fixes for 'showPurchased' and 'relativeScroll' settings. -- Purchased items should now properly sync between clients when `showPurchased` is set to disabled. +Fixes for 'stockPurchased' and 'relativeScroll' settings. +- Purchased items should now properly sync between clients when `stockPurchased` is set to disabled. - `Terminal.RotateShipDecorSelection()` now waits until after `StartOfRound.SyncShipUnlockablesClientRpc()` is executed when first joining a lobby. - Newly-purchased items should now be properly removed from store rotations for every client, not just the host. - `relativeScroll` scroll amount should no longer apply an additional time for each player in the lobby. @@ -90,7 +101,7 @@ Update to 'CSync' v4; support for v3 relegated to previous release. ### [1.3.0] Added setting to configure whether already-purchased items should show up in the store rotation. -- Added `showPurchased` server-side setting (on by default). +- Added `stockPurchased` server-side setting (on by default). - Determines whether or not to include already-purchased items in both the current store rotation and any future ones. - Also immediately removes them from the current store rotation, if disabled. - Next release will target `CSync v4`, this specific version can be downgraded to if `CSync v3` compatibility is required. diff --git a/README.md b/README.md index 85eaaf5..4338619 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ By default, the number of available items in the store is increased from **4-5** Alternatively, the `showAll` setting (off by default) can be enabled to simply add every purchasable item to the store rotation. Partly intended for fixing name conflict issues when buying stuff at the terminal, but there should be no problems using it during a regular run. -Disabling the `showPurchased` setting (on by default) will prevent already-purchased items from showing up in future store rotations, and will also immediately remove newly-purchased items from the current rotation. +Disabling the `stockPurchased` setting (on by default) will prevent already-purchased items from showing up in future store rotations, and will also immediately remove newly-purchased items from the current rotation. To guarantee an item showing up in the store rotation, its name can be added to the comma-separated `itemWhitelist` setting, which adds the specified items to every store rotation separate from the range of items defined by the `minItems` and `maxItems` settings. Likewise, to prevent items from ever showing up in the store rotation, its name can be added to `itemBlacklist`. diff --git a/StoreRotationConfig/Compatibility/TerminalFormatterCompatibility.cs b/StoreRotationConfig/Compatibility/TerminalFormatterCompatibility.cs index d9a92b9..947a87f 100644 --- a/StoreRotationConfig/Compatibility/TerminalFormatterCompatibility.cs +++ b/StoreRotationConfig/Compatibility/TerminalFormatterCompatibility.cs @@ -59,7 +59,7 @@ private static IEnumerable GetNodeTextTranspiler(IEnumerable { // Return full cost if 'salesChance' is disabled OR the 'RotationSales' dictionary doesn't contain a discount for the item about to be displayed. - if (Plugin.Settings.SALE_CHANCE == 0 || (RotationSales != null && !RotationSales.ContainsKey(item))) + if (Plugin.Settings.SALE_CHANCE == 0 || RotationSales == null || !RotationSales.ContainsKey(item)) { return $"{item.itemCost}"; } diff --git a/StoreRotationConfig/Patches/RotateShipDecorSelectionPatch.cs b/StoreRotationConfig/Patches/RotateShipDecorSelectionPatch.cs index 72b42c2..a36ec37 100644 --- a/StoreRotationConfig/Patches/RotateShipDecorSelectionPatch.cs +++ b/StoreRotationConfig/Patches/RotateShipDecorSelectionPatch.cs @@ -107,7 +107,7 @@ private static void RotateShipDecorSelection(List shipDecorSelecti // Clear previous store rotation. shipDecorSelection.Clear(); - // Use 'maxItems' for 'minItems' if the latter is greater than the former. + // Use 'minItems' for 'maxItems', if the former is greater than the latter. if (minItems > maxItems) { Plugin.StaticLogger.LogWarning("Value for 'minItems' is larger than 'maxItems', using it instead..."); diff --git a/StoreRotationConfig/Patches/TerminalItemSalesPatches.cs b/StoreRotationConfig/Patches/TerminalItemSalesPatches.cs index 153dc5c..de4bdfd 100644 --- a/StoreRotationConfig/Patches/TerminalItemSalesPatches.cs +++ b/StoreRotationConfig/Patches/TerminalItemSalesPatches.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Reflection.Emit; -using System.Text; using HarmonyLib; using Unity.Netcode; @@ -38,16 +37,22 @@ private static void SetRotationSales(Terminal __instance) // Initialize 'Random' instance using the same seed as vanilla sales. Random random = new(StartOfRound.Instance.randomMapSeed + 90); - // Obtain values from the config file. - float saleChance = 100f / Plugin.Settings.SALE_CHANCE; + // Return if no items are on sale for this rotation. + if (random.Next(0, 100) > Plugin.Settings.SALE_CHANCE - 1) + { + Plugin.StaticLogger.LogInfo("No items on sale for this rotation..."); + + return; + } + // Obtain values from the config file. int minSaleItems = Math.Abs(Plugin.Settings.MIN_SALE_ITEMS), maxSaleItems = Math.Abs(Plugin.Settings.MAX_SALE_ITEMS); int minDiscount = Plugin.Settings.MIN_DISCOUNT, maxDiscount = Plugin.Settings.MAX_DISCOUNT; // ... - // Use 'maxItems' for 'minItems' if the latter is greater than the former. + // Use 'minSaleItems' for 'maxSaleItems', if the former is greater than the latter. if (minSaleItems > maxSaleItems) { Plugin.StaticLogger.LogWarning("Value for 'minSaleItems' is larger than 'maxSaleItems', using it instead..."); @@ -55,8 +60,16 @@ private static void SetRotationSales(Terminal __instance) maxSaleItems = minSaleItems; } - // Obtain number of items with sales for this rotation, using parameters from the config file. - int itemsOnSale = random.Next(maxSaleItems - (int)(maxSaleItems * saleChance) + 1, maxSaleItems + 1); + // Use 'minSaleItems' for 'maxDiscount', if the former is greater than the latter. + if (minDiscount > maxDiscount) + { + Plugin.StaticLogger.LogWarning("Value for 'minDiscount' is larger than 'maxDiscount', using it instead..."); + + maxDiscount = minDiscount; + } + + // Obtain number of items with sales for this rotation. + int itemsOnSale = random.Next(minSaleItems, maxSaleItems + 1); // Return if no items are on sale for this rotation. if (itemsOnSale <= 0) @@ -149,14 +162,13 @@ private static IEnumerable TerminalLoadNewNodeIfAffordableTrans /// /// Displays rotating item discounts and their modified prices in the store page. - /// Dynamic operands maintain compatibility between game versions. /// /// ... (Terminal:344) /// for (int m = 0; m < this.ShipDecorSelection.Count; m++) /// { - /// stringBuilder*.Append(string.Format("\n{0} // ${1}", this.ShipDecorSelection[m].creatureName, this.ShipDecorSelection[m].itemCost)); - /// - /// -> call(this.ShipDecorSelection, stringBuilder*, m); + /// stringBuilder5.Append(string.Format("\n{0} // ${1}", this.ShipDecorSelection[m].creatureName, + /// // this.ShipDecorSelection[m].itemCost)); + /// -> call(this.ShipDecorSelection))); /// } /// Iterator with original IL instructions. /// Iterator with modified IL instructions. @@ -164,42 +176,27 @@ private static IEnumerable TerminalLoadNewNodeIfAffordableTrans [HarmonyTranspiler] private static IEnumerable TextPostProcessTranspiler(IEnumerable instructions) { - CodeMatcher matcher = new CodeMatcher(instructions).MatchForward(false, + return new CodeMatcher(instructions).MatchForward(false, new(OpCodes.Ldstr, "\n{0} // ${1}"), new(OpCodes.Ldarg_0), - new(OpCodes.Ldfld, AccessTools.Field(typeof(Terminal), nameof(Terminal.ShipDecorSelection)))); - - // Obtain operand for the StringBuilder instance using cloned instructions. - object sbOperand = matcher.Clone().MatchBack(false, new CodeMatch(OpCodes.Newobj)).Advance(1).Operand; - - // Obtain operand for the loop index using cloned instructions. - object indexOperand = matcher.Clone().Advance(3).Operand; - - return matcher.MatchForward(false, new CodeMatch(OpCodes.Pop)) - .Advance(1) - .InsertAndAdvance( - new(OpCodes.Ldarg_0), - new(OpCodes.Ldfld, AccessTools.Field(typeof(Terminal), nameof(Terminal.ShipDecorSelection))), - new(OpCodes.Ldloc_S, sbOperand), - new(OpCodes.Ldloc_S, indexOperand)) - .Insert(Transpilers.EmitDelegate((List storeRotation, StringBuilder sb, int index) => + new(OpCodes.Ldfld, AccessTools.Field(typeof(Terminal), nameof(Terminal.ShipDecorSelection)))) + .MatchForward(false, + new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(TerminalNode), nameof(TerminalNode.itemCost)))) + .SetInstructionAndAdvance(Transpilers.EmitDelegate((TerminalNode item) => + { + // Return if 'salesChance' is disabled OR the 'RotationSales' dictionary doesn't contain a discount for the item about to be displayed. + if (Plugin.Settings.SALE_CHANCE == 0 || RotationSales == null || !RotationSales.ContainsKey(item)) { - // Obtain item about to be displayed in the store page. - TerminalNode item = storeRotation[index]; - - // Return if 'salesChance' is disabled OR the 'RotationSales' dictionary doesn't contain a discount for the item about to be displayed. - if (Plugin.Settings.SALE_CHANCE == 0 || (RotationSales != null && !RotationSales.ContainsKey(item))) - { - return; - } + return $"{item.itemCost}"; + } - Plugin.StaticLogger.LogDebug($"Appending {RotationSales[item]} to {item.creatureName}..."); + Plugin.StaticLogger.LogDebug($"Appending {RotationSales[item]} to {item.creatureName}..."); - // Replace old price with the discounted price, and append sale tag to the displayed text. - _ = sb.Replace(item.itemCost.ToString(), (item.itemCost - (int)(item.itemCost * (RotationSales[item] / 100f))).ToString()) - .Append($" ({RotationSales[item]}% OFF!)"); - } - )).InstructionEnumeration(); + // Append discounted price with sale tag to the displayed text. + return $"{item.itemCost - (int)(item.itemCost * (RotationSales[item] / 100f))} ({RotationSales[item]}% OFF!)"; + })) + .SetOperandAndAdvance(typeof(string)) + .InstructionEnumeration(); } } } \ No newline at end of file diff --git a/StoreRotationConfig/Patches/UnlockShipObjectPatches.cs b/StoreRotationConfig/Patches/UnlockShipObjectPatches.cs index e94f4ff..2e1824a 100644 --- a/StoreRotationConfig/Patches/UnlockShipObjectPatches.cs +++ b/StoreRotationConfig/Patches/UnlockShipObjectPatches.cs @@ -51,29 +51,45 @@ private static void RemoveItemFromRotation(int unlockableID) return; } - // Obtain item from list of purchasable items. + // Obtain item from the list of purchasable items. UnlockableItem item = StartOfRound.Instance.unlockablesList.unlockables[unlockableID]; + // Return if item is not present in the 'UnlockablesList.unlockables' list, OR its shop node does not exist. + if (item == null || item.shopSelectionNode == null) + { + Plugin.StaticLogger.LogWarning($"Unlockable #{unlockableID} could not be found."); + + return; + } + // Return if item has already been purchased (likely a redundant check, but done just in case). if (item.hasBeenUnlockedByPlayer || item.alreadyUnlocked) { - Plugin.StaticLogger.LogWarning($"Unlockable #{unlockableID} has already been purchased."); + Plugin.StaticLogger.LogWarning($"Item '{item.shopSelectionNode.creatureName}' has already been purchased."); return; } - // Remove item from 'RotateShipDecorSelectionPatch.AllItems' list. + Plugin.StaticLogger.LogDebug($"Removing item '{item.shopSelectionNode.creatureName}' from the store rotation..."); + + // Attempt to remove item from the 'RotateShipDecorSelectionPatch.AllItems' list. if (RotateShipDecorSelectionPatch.AllItems.Remove(item)) { - // Remove item from 'Terminal.ShipDecorSelection' list. + // Attempt to remove item from the 'Terminal.ShipDecorSelection' list. if (!Plugin.Terminal.ShipDecorSelection.Remove(item.shopSelectionNode)) { - Plugin.StaticLogger.LogWarning($"Unlockable #{unlockableID} was not found in the store rotation."); + Plugin.StaticLogger.LogWarning($"Item '{item.shopSelectionNode.creatureName}' was not found in the store rotation."); } } else { - Plugin.StaticLogger.LogWarning($"Unlockable #{unlockableID} was not found in the list of purchasable items."); + Plugin.StaticLogger.LogWarning($"Item '{item.shopSelectionNode.creatureName}' was not found in the list of purchasable items."); + } + + // Attempt to remove item from the 'RotateShipDecorSelectionPatch.PermanentItems' list. + if (RotateShipDecorSelectionPatch.PermanentItems != null && RotateShipDecorSelectionPatch.PermanentItems.Remove(item)) + { + Plugin.StaticLogger.LogDebug($"Item '{item.shopSelectionNode.creatureName}' was removed from the list of permanent items."); } } } diff --git a/StoreRotationConfig/Plugin.cs b/StoreRotationConfig/Plugin.cs index 1d2dfc5..badf078 100644 --- a/StoreRotationConfig/Plugin.cs +++ b/StoreRotationConfig/Plugin.cs @@ -14,7 +14,7 @@ namespace StoreRotationConfig [BepInDependency("TerminalFormatter", BepInDependency.DependencyFlags.SoftDependency)] public class Plugin : BaseUnityPlugin { - internal const string GUID = "pacoito.StoreRotationConfig", PLUGIN_NAME = "StoreRotationConfig", VERSION = "2.3.2"; + internal const string GUID = "pacoito.StoreRotationConfig", PLUGIN_NAME = "StoreRotationConfig", VERSION = "2.3.3"; internal static ManualLogSource StaticLogger { get; private set; } /// diff --git a/StoreRotationConfig/StoreRotationConfig.csproj b/StoreRotationConfig/StoreRotationConfig.csproj index 48e7b5b..5246a25 100644 --- a/StoreRotationConfig/StoreRotationConfig.csproj +++ b/StoreRotationConfig/StoreRotationConfig.csproj @@ -3,7 +3,7 @@ StoreRotationConfig Configure the number of items in each store rotation, show them all, remove purchases, sort them, and/or enable sales for them. - 2.3.2 + 2.3.3 diff --git a/manifest.json b/manifest.json index b3c09d2..064e4f5 100644 --- a/manifest.json +++ b/manifest.json @@ -1,6 +1,6 @@ { "name": "StoreRotationConfig", - "version_number": "2.3.2", + "version_number": "2.3.3", "website_url": "https://github.com/pacoito123/LC_StoreRotationConfig", "description": "Configure the number of items in each store rotation, show them all, remove purchases, sort them, and/or enable sales for them. Also includes a fix for the terminal scrolling too far and skipping lines.", "dependencies": [