Skip to content

Commit

Permalink
[v2.3.3] Miscellaneous fixes for various issues.
Browse files Browse the repository at this point in the history
- 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` 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.
  • Loading branch information
pacoito123 committed Aug 9, 2024
1 parent 8c1b01d commit a20d3c8
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 56 deletions.
17 changes: 14 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ private static IEnumerable<CodeInstruction> GetNodeTextTranspiler(IEnumerable<Co
.SetInstructionAndAdvance(Transpilers.EmitDelegate((TerminalNode item) =>
{
// 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}";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ private static void RotateShipDecorSelection(List<TerminalNode> 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...");
Expand Down
79 changes: 38 additions & 41 deletions StoreRotationConfig/Patches/TerminalItemSalesPatches.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Reflection.Emit;
using System.Text;
using HarmonyLib;
using Unity.Netcode;

Expand Down Expand Up @@ -38,25 +37,39 @@ 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...");

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)
Expand Down Expand Up @@ -149,57 +162,41 @@ private static IEnumerable<CodeInstruction> TerminalLoadNewNodeIfAffordableTrans

/// <summary>
/// Displays rotating item discounts and their modified prices in the store page.
/// Dynamic operands maintain compatibility between game versions.
/// </summary>
/// ... (Terminal:344)
/// for (int m = 0; m &lt; 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)));
/// }
/// <param name="instructions">Iterator with original IL instructions.</param>
/// <returns>Iterator with modified IL instructions.</returns>
[HarmonyPatch("TextPostProcess")]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> TextPostProcessTranspiler(IEnumerable<CodeInstruction> 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<TerminalNode> 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();
}
}
}
28 changes: 22 additions & 6 deletions StoreRotationConfig/Patches/UnlockShipObjectPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion StoreRotationConfig/Plugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion StoreRotationConfig/StoreRotationConfig.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<AssemblyName>StoreRotationConfig</AssemblyName>
<Description>Configure the number of items in each store rotation, show them all, remove purchases, sort them, and/or enable sales for them.</Description>
<Version>2.3.2</Version>
<Version>2.3.3</Version>
</PropertyGroup>

<!-- Project properties. -->
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down

0 comments on commit a20d3c8

Please sign in to comment.