Skip to content

Commit

Permalink
[v2.2.0] Transpilers now used in place of some prefix patches, some m…
Browse files Browse the repository at this point in the history
…inor refactoring.

- Switched to using Transpilers for `Terminal.RotateShipDecorSelection()` and `PlayerControllerB.ScrollMouse_performed()` patches.
	- Should be much better for compatibility with any other mods that might potentially want to patch these methods as well.
	- From initial testing, everything seems to be working fine, but please let me know if any issues are encountered.
- Changed `maxItems` and `minItems` to use their absolute values when rotating the store, to avoid any issues with negative numbers in the configuration file.
- Modifying `linesToScroll` in-game (e.g. through `LethalConfig`) should now apply changes immediately, instead of until after scrolling on a different terminal page.
- Updated minimum `CSync` library dependency to patch `v5.0.1`.
	- Previous release also works with `v5.0.1`, and is recommended.
  • Loading branch information
pacoito123 committed Jun 6, 2024
1 parent b249ccd commit 1ceb2df
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 101 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
### [2.2.0]

Transpilers now used in place of some prefix patches, some minor refactoring.
- Switched to using Transpilers for `Terminal.RotateShipDecorSelection()` and `PlayerControllerB.ScrollMouse_performed()` patches.
- Should be much better for compatibility with any other mods that might potentially want to patch these methods as well.
- From initial testing, everything seems to be working fine, but please let me know if any issues are encountered.
- Changed `maxItems` and `minItems` to use their absolute values when rotating the store, to avoid any issues with negative numbers in the configuration file.
- Modifying `linesToScroll` in-game (e.g. through `LethalConfig`) should now apply changes immediately, instead of until after scrolling on a different terminal page.
- Updated minimum `CSync` library dependency to patch `v5.0.1`.
- Previous release also works with `v5.0.1`, and is recommended.

### [2.1.0]

Update to 'CSync' v5, more configuration for terminal scrolling.
Expand Down
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![License](https://img.shields.io/github/license/pacoito123/LC_StoreRotationConfig?style=for-the-badge&logo=github&color=teal
)](https://github.com/pacoito123/LC_StoreRotationConfig/blob/main/LICENSE)

> Configure the number of purchasable items in each store rotation, or simply show them all.
> Configure the number of items in each store rotation, show them all, remove purchased items, or sort them.
## Description

Expand All @@ -17,42 +17,42 @@ Intended for when there's a large number of modded items (suits, furniture, etc.

Compatible with `v45`, `v49`, and `v50`.

Uses [CSync (v5.0.0 and above)](https://thunderstore.io/c/lethal-company/p/Sigurd/CSync) by [Lordfirespeed](https://github.com/Lorefirespeed) to sync config settings between host and clients.
Uses [CSync (v5.0.1 and above)](https://thunderstore.io/c/lethal-company/p/Sigurd/CSync) by [Lordfirespeed](https://github.com/Lorefirespeed) to sync config settings between host and clients.

**NOTE:** In case an older version of CSync is needed, usually due to mods that have not yet updated to the latest versions, refer to the following table for which specific version of this mod to downgrade to:

| CSync | StoreRotationConfig |
| :-------------: | :-----------------: |
| v5.0.0 | `v2.1.0` |
| v5.0.1 | `v2.1.0+` |
| v4.1.0 | `v2.0.1` |
| v3.1.1 | `v1.3.0` |

## Configuration

By default, the number of available items in the store is increased from **4-5** (vanilla) to **8-12**, but this range can be configured via the `minItems` and `maxItems` config settings. Set both numbers to the same value to have a fixed number of items in every rotation.
By default, the number of available items in the store is increased from **4-5** (vanilla) to **8-12**, but this range can be configured via the `minItems` and `maxItems` config settings. Set both numbers to the same value to have a fixed number of items in every rotation. If `minItems` is larger than `maxItems`, both numbers are set to the larger value. To avoid any issues with negative numbers, the absolute value of these two settings is used when generating the store rotation.

Alternatively, the `showAll` setting (off by default) can be toggled 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.
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.

Toggling 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 `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.

The store rotation can also be displayed in alphabetical order by toggling the `sortItems` setting (off by default).
The store rotation can also be displayed in alphabetical order by enabling the `sortItems` setting (off by default).

For cases where having too many items in the store rotation causes scrolling to skip over several lines, either with `stockAll` enabled or with a high `minItems`/`maxItems` value, toggling the `relativeScroll` setting (off by default) will adapt scrolling to a certain number of lines at a time, determined by the `linesToScroll` setting (20 by default).
For cases where having too many items in the store rotation causes scrolling to skip over several lines, either with `stockAll` enabled or with a high `minItems`/`maxItems` value, enabling the `relativeScroll` setting (off by default) will adapt scrolling to a certain number of lines at a time, determined by the `linesToScroll` setting (20 by default), and relative to the length of the currently shown terminal page.

## Compatibility

The patched `Terminal.RotateShipDecorSelection()` method is functionally the same as vanilla, only with some configurability added, so it _should_ play nicely with other mods (as long as they don't also clear the `Terminal.ShipDecorSelection` list to generate their own, or forcibly add items without checking if they're already present in it).

There's also the possibility of something going wrong if the `Terminal.ShipDecorSelection` list is needed by another mod immediately after joining a lobby, but prior to the config file sync; the patched method to fill the list with additional items only runs _after_ a successful sync, so it remains empty until then. So far I haven't encountered any issues with it, but if any incompatibilities _are_ found, please let me know in the [relevant thread](https://discord.com/channels/1168655651455639582/1212542584610881557) in the Lethal Company Modding Discord server, or [open an issue on GitHub](https://github.com/pacoito123/LC_StoreRotationConfig/issues).
There's also the possibility of something going wrong if the `Terminal.ShipDecorSelection` list is required by another mod immediately after joining a lobby, but prior to the ship unlockables sync; the list is only filled _after_ a successful sync with the host, and it remains empty until then. So far I haven't encountered any issues with it, but if any incompatibilities _are_ found, please let me know in the [relevant thread](https://discord.com/channels/1168655651455639582/1212542584610881557) in the Lethal Company Modding Discord server, or [open an issue on GitHub](https://github.com/pacoito123/LC_StoreRotationConfig/issues).

In `v49`, there's a name conflict between the `Purple suit` and the `Pajama suit`, which adds a second, unpurchasable `Pajama suit` to the store. If every item available in the store is bought, with the `showPurchased` setting disabled, only the `Pajama suit` will remain in the store rotation. This is fixed in `v50`, with the `Purple suit` having been made properly purchasable.

The `relativeScroll` tweak is not limited to just the store page, and could potentially fix scrolling issues in other terminal pages, but it could also be incompatible or cause issues with other mods that modify or set the `PlayerControllerB.terminalScrollVertical` value.

~~**NOTE:** This mod is _technically_ server-side, but clients need the mod installed to be able to see and purchase any of the additional items added to the vanilla store rotation. Similarly, joining a lobby that doesn't have this mod installed will not modify the store rotation.~~

**NOTE:** As of `v2.0.0`, this mod is now required to be installed on **both host and clients**, though I have commented code ready to once again make it (technically) server-side, if `CSync v4` reimplements the ability to join a lobby with either client or host missing this mod.
**NOTE:** As of `v2.0.0`, this mod is now required to be installed on **both host and clients**, though I have commented code ready to once again make it (technically) server-side, if `CSync` reimplements the ability to join a lobby with either client or host missing a mod that depends on it.

---

![alt](https://files.catbox.moe/o35ptg.png "Store with every vanilla item available for purchase, in alphabetical order.")
![alt](https://files.catbox.moe/5nli7q.png "Store with every vanilla item available for purchase in v50, in alphabetical order.")
15 changes: 9 additions & 6 deletions StoreRotationConfig/Config.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using BepInEx.Configuration;
using CSync.Extensions;
using CSync.Lib;
using StoreRotationConfig.Patches;
using System.Runtime.Serialization;
// using Unity.Netcode;
// using UnityEngine;
Expand Down Expand Up @@ -79,10 +80,16 @@ public Config(ConfigFile cfg) : base(Plugin.GUID)
+ "'relativeScroll' enabled.", new AcceptableValueRange<int>(1, 28)));
// ...

// Reset cached text if 'linesToScroll' is updated in-game.
LINES_TO_SCROLL.SettingChanged += new((_, _) =>
{
TerminalScrollMousePatch.CurrentText = "";
});

// Register to sync config files between host and clients.
ConfigManager.Register(this);

// Function to run once config is synced.
// Function to run once the config sync is completed.
/* InitialSyncCompleted += new((_, _) =>
{
// Return if local game instance is hosting the server.
Expand All @@ -91,15 +98,11 @@ public Config(ConfigFile cfg) : base(Plugin.GUID)
return;
}
Plugin.StaticLogger.LogInfo("Config synced! ⭮ Rotating store...");
// Set config sync status to true (successfully synced).
ConfigSynced = true;
// Manually trigger a store rotation after config sync.
Terminal terminal = Object.FindObjectOfType<Terminal>();
terminal?.ShipDecorSelection.Clear();
terminal?.RotateShipDecorSelection();
Plugin.Terminal.RotateShipDecorSelection();
}); */
}
}
Expand Down
90 changes: 62 additions & 28 deletions StoreRotationConfig/Patches/RotateShipDecorSelectionPatch.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Reflection.Emit;
using Unity.Netcode;

namespace StoreRotationConfig.Patches
Expand All @@ -14,29 +15,33 @@ internal class RotateShipDecorSelectionPatch
// Cached list of every purchasable, non-persistent item available in the store.
public static List<UnlockableItem> AllItems { get; private set; }

private static bool Prefix(Terminal __instance)
/// <summary>
/// Fills 'Terminal.ShipDecorSelection' list with items, reading from the configuration file.
/// </summary>
/// <param name="shipDecorSelection">List containing items currently shown in the store rotation.</param>
/// <param name="random">Seeded Random instance used for generating store rotation.</param>
private static void RotateShipDecorSelection(List<TerminalNode> shipDecorSelection, Random random)
{
// Return if client has not yet fully synced with the host.
if (!NetworkManager.Singleton.IsHost && !NetworkManager.Singleton.IsServer && !SyncShipUnlockablesPatch.UnlockablesSynced)
// && !Plugin.Settings.ConfigSynced)
{
Plugin.StaticLogger.LogInfo("Waiting for sync from server before rotating store...");

// Return false to stop vanilla method from executing.
return false;
return;
}

// Obtain values from config file.
int maxItems = Plugin.Settings.MAX_ITEMS.Value,
minItems = Plugin.Settings.MIN_ITEMS.Value;
// Obtain values from the config file.
int maxItems = Math.Abs(Plugin.Settings.MAX_ITEMS.Value),
minItems = Math.Abs(Plugin.Settings.MIN_ITEMS.Value);
bool stockAll = Plugin.Settings.STOCK_ALL.Value,
sortItems = Plugin.Settings.SORT_ITEMS.Value;
// ...

// Check if 'Terminal.ShipDecorSelection' list is empty (first load).
if (__instance.ShipDecorSelection.Count == 0)
if (shipDecorSelection.Count == 0)
{
// Initialize 'AllItems' list with specified capacity.
// Initialize 'AllItems' list with its capacity set to the total number of unlockable items.
AllItems = new(StartOfRound.Instance.unlockablesList.unlockables.Count);

// Fill 'AllItems' list with every purchasable, non-persistent item.
Expand All @@ -55,54 +60,83 @@ private static bool Prefix(Terminal __instance)
AllItems.Sort((x, y) => string.Compare(x.unlockableName, y.unlockableName));
}

// Fill store rotation with every item in 'AllItems'.
AllItems.ForEach(item => __instance.ShipDecorSelection.Add(item.shopSelectionNode));
// Fill store rotation with every item in the 'AllItems' list.
AllItems.ForEach(item => shipDecorSelection.Add(item.shopSelectionNode));
}
}

// Return false if 'stockAll' setting is enabled, since the store rotation list has already been filled at this point.
// Return if 'stockAll' setting is enabled, since the store rotation list has already been filled at this point.
if (stockAll)
{
return false;
return;
}

Plugin.StaticLogger.LogInfo("Rotating store...");

// Clear previous store rotation list.
__instance.ShipDecorSelection.Clear();
// Clear previous store rotation.
shipDecorSelection.Clear();

// Use 'maxItems' for 'minItems' if the latter is greater than the former.
minItems = (minItems < maxItems) ? minItems : maxItems;
if (minItems > maxItems)
{
minItems = maxItems;
}

// Obtain a random number of items using the map seed, or use a fixed number if 'minItems' and 'maxItems' are equal.
Random random = new(StartOfRound.Instance.randomMapSeed + 65);
int num = (minItems != maxItems) ? random.Next(minItems, maxItems + 1) : maxItems;
int numItems = (minItems != maxItems) ? random.Next(minItems, maxItems + 1) : maxItems;

// Create 'storeRotation' list (for sorting), and clone 'AllItems' list (for item selection).
List<UnlockableItem> storeRotation = new(num), allItems = new(AllItems);
// Create 'storeRotation' list (for sorting), and clone the 'AllItems' list (for item selection).
List<UnlockableItem> storeRotation = new(numItems), allItems = new(AllItems);

// Iterate for every item to add to the store rotation (and exit early if there are no more items in 'allItems' cloned list).
for (int i = 0; i < num && allItems.Count != 0; i++)
// Iterate for every item to add to the store rotation, exiting early if there are no more items in the 'allItems' cloned list.
for (int i = 0; i < numItems && allItems.Count != 0; i++)
{
// Add random item to 'storeRotation' list, and remove it from 'allItems' cloned list.
// Obtain a random item from the 'allItems' cloned list.
int index = random.Next(0, allItems.Count);

// Add random item to the 'storeRotation' list, and remove it from the 'allItems' cloned list.
storeRotation.Add(allItems[index]);
allItems.RemoveAt(index);
}

// Sort 'storeRotation' list alphabetically if 'sortItems' is enabled.
if (sortItems && storeRotation.Count > 1)
// Check if 'sortItems' setting is enabled, and if there's more than one item in the 'shipDecorSelection' list.
if (sortItems && shipDecorSelection.Count > 1)
{
// Sort 'storeRotation' list alphabetically.
storeRotation.Sort((x, y) => string.Compare(x.unlockableName, y.unlockableName));
}

// Fill store rotation with every item in 'storeRotation' list.
storeRotation.ForEach(item => __instance.ShipDecorSelection.Add(item.shopSelectionNode));
// Fill store rotation with every item in the 'storeRotation' list.
storeRotation.ForEach(item => shipDecorSelection.Add(item.shopSelectionNode));

Plugin.StaticLogger.LogInfo("Store rotated!");
}

// Return false to stop vanilla method from executing.
return false;
/// <summary>
/// Inserts a call to 'RotateShipDecorSelectionPatch.RotateShipDecorSelection()', followed by a return instruction.
/// </summary>
/// ... (Terminal:1564)
/// Random random = new Random(StartOfRound.Instance.randomMapSeed + 65);
///
/// -> StoreRotationConfig.Patches.RotateShipDecorSelectionPatch.RotateShipDecorSelection(this.ShipDecorSelection, random);
/// -> return;
///
/// this.ShipDecorSelection.Clear();
/// <param name="instructions">Iterator with original IL instructions.</param>
/// <returns>Iterator with modified IL instructions.</returns>
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
return new CodeMatcher(instructions).MatchForward(false,
new(OpCodes.Ldarg_0),
new(OpCodes.Ldfld, AccessTools.Field(typeof(Terminal), nameof(Terminal.ShipDecorSelection))),
new(OpCodes.Callvirt, AccessTools.Method(typeof(List<TerminalNode>), nameof(List<TerminalNode>.Clear))))
.Insert(
new(OpCodes.Ldarg_0),
new(OpCodes.Ldfld, AccessTools.Field(typeof(Terminal), nameof(Terminal.ShipDecorSelection))),
new(OpCodes.Ldloc_0),
new(OpCodes.Call, AccessTools.Method(typeof(RotateShipDecorSelectionPatch), nameof(RotateShipDecorSelection))),
new(OpCodes.Ret)
).InstructionEnumeration();
}
}
}
5 changes: 3 additions & 2 deletions StoreRotationConfig/Patches/SyncShipUnlockablesPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@ private static void SyncShipUnlockablesClientPost(int[] playerSuitIDs, bool ship
// Return if unlockables are already synced with the host; toggle unlockable sync status if not.
if (UnlockablesSynced)
{
Plugin.StaticLogger.LogWarning("Purchased unlockable items already synced with the host...");
Plugin.StaticLogger.LogDebug("Purchased unlockable items already synced with the host.");

return;
}
UnlockablesSynced = true;

// Manually trigger a store rotation.
Object.FindObjectOfType<Terminal>().RotateShipDecorSelection();
Plugin.Terminal.RotateShipDecorSelection();
}

[HarmonyPatch(typeof(MenuManager), "Start")]
Expand Down
Loading

0 comments on commit 1ceb2df

Please sign in to comment.