using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using CMF;
using DevModeQoL.Localization;
using DevModeQoL.Patches;
using HarmonyLib;
using I2.Loc;
using PerfectRandom.Sulfur.Core;
using PerfectRandom.Sulfur.Core.DevTools;
using PerfectRandom.Sulfur.Core.Items;
using PerfectRandom.Sulfur.Core.LevelGeneration;
using PerfectRandom.Sulfur.Core.Movement;
using PerfectRandom.Sulfur.Core.Units;
using PerfectRandom.Sulfur.Core.Utilities;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Dev Mode QoL")]
[assembly: AssemblyDescription("Developer-mode quality of life tweaks for SULFUR.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Dev Mode QoL")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("da9f75b7-f2c4-4de3-b0c0-8811f2bf5454")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.2.0.0")]
namespace DevModeQoL
{
internal sealed class DevToolsEscapeInput : IDisposable
{
private InputAction closeAction;
private bool spawnMenuOpen;
private int lastCancelFrame = -1;
public void Initialize()
{
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_0022: Expected O, but got Unknown
if (closeAction == null)
{
closeAction = new InputAction("DevModeQoL/CloseDevTools", (InputActionType)1, "<Keyboard>/escape", (string)null, (string)null, (string)null);
closeAction.performed += OnClosePerformed;
closeAction.Enable();
}
}
public void OnFreeCameraStateChanged()
{
spawnMenuOpen = false;
}
public void OnSpawnMenuOpened(DevToolsManager manager)
{
spawnMenuOpen = (Object)(object)manager != (Object)null && manager.shouldShow;
}
public void OnSpawnMenuClosed()
{
spawnMenuOpen = false;
}
public void OnCancel(DevToolsManager manager, bool byGamepad)
{
if (!byGamepad && !((Object)(object)manager == (Object)null) && manager.shouldShow)
{
lastCancelFrame = Time.frameCount;
if (Plugin.CloseWithEscape.Value && !IsBackspaceCancel() && !IsTypingInTextField() && DevToolsInternals.IsLevelSelectOpen(manager) == false)
{
Plugin.LogVerbose("Escape closed the dev tools spawn menu.");
manager.HideOptions();
}
}
}
public bool IsBackspaceEditingText(TMP_InputField field)
{
if ((Object)(object)field == (Object)null)
{
return false;
}
if (!IsBackspaceCancel())
{
return false;
}
DevToolsManager instance = StaticInstance<DevToolsManager>.Instance;
if ((Object)(object)instance == (Object)null || !instance.shouldShow)
{
return false;
}
return DevToolsInternals.GetFilterField(instance) == field;
}
private static bool IsBackspaceCancel()
{
Keyboard current = Keyboard.current;
if (current != null && ((ButtonControl)current.backspaceKey).isPressed)
{
return !((ButtonControl)current.escapeKey).isPressed;
}
return false;
}
private static bool IsTypingInTextField()
{
EventSystem current = EventSystem.current;
if ((Object)(object)current == (Object)null)
{
return false;
}
GameObject currentSelectedGameObject = current.currentSelectedGameObject;
if ((Object)(object)currentSelectedGameObject == (Object)null)
{
return false;
}
TMP_InputField component = currentSelectedGameObject.GetComponent<TMP_InputField>();
if ((Object)(object)component != (Object)null)
{
return component.isFocused;
}
return false;
}
private void OnClosePerformed(CallbackContext context)
{
if (Plugin.CloseWithEscape.Value)
{
DevToolsManager instance = StaticInstance<DevToolsManager>.Instance;
if (!((Object)(object)instance == (Object)null) && instance.shouldShow && !spawnMenuOpen && lastCancelFrame != Time.frameCount)
{
Plugin.LogVerbose("Escape closed the developer overlay.");
instance.Toggle(false);
}
}
}
public void Dispose()
{
if (closeAction != null)
{
closeAction.performed -= OnClosePerformed;
if (closeAction.enabled)
{
closeAction.Disable();
}
closeAction.Dispose();
closeAction = null;
}
}
}
internal static class DevToolsInternals
{
private static FieldInfo cameraControllerField;
private static FieldInfo levelPanelField;
private static FieldInfo selectorButtonsField;
private static FieldInfo selectedTextField;
private static FieldInfo filterInputFieldField;
private static FieldInfo itemListsField;
private static MethodInfo addToItemListMethod;
private static FieldInfo selectorItemField;
private static FieldInfo selectorItemNameField;
private static FieldInfo selectorTextField;
private static bool resolved;
internal static ExtendedCameraController GetCameraController(DevToolsManager manager)
{
Resolve();
if ((Object)(object)manager == (Object)null || cameraControllerField == null)
{
return null;
}
try
{
object? value = cameraControllerField.GetValue(manager);
return (ExtendedCameraController)((value is ExtendedCameraController) ? value : null);
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not read the dev camera controller: " + ex.Message));
return null;
}
}
internal static bool? IsLevelSelectOpen(DevToolsManager manager)
{
Resolve();
if ((Object)(object)manager == (Object)null || levelPanelField == null)
{
return null;
}
try
{
object? value = levelPanelField.GetValue(manager);
Transform val = (Transform)((value is Transform) ? value : null);
if ((Object)(object)val == (Object)null)
{
return null;
}
return ((Component)val).gameObject.activeSelf;
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not read the level select state: " + ex.Message));
return null;
}
}
internal static List<SelectorButton> GetSelectorButtons(DevToolsManager manager)
{
Resolve();
if ((Object)(object)manager == (Object)null || selectorButtonsField == null)
{
return null;
}
try
{
return selectorButtonsField.GetValue(manager) as List<SelectorButton>;
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not read the spawn menu buttons: " + ex.Message));
return null;
}
}
internal static TMP_InputField GetFilterField(DevToolsManager manager)
{
Resolve();
if ((Object)(object)manager == (Object)null || filterInputFieldField == null)
{
return null;
}
try
{
object? value = filterInputFieldField.GetValue(manager);
return (TMP_InputField)((value is TMP_InputField) ? value : null);
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not read the spawn menu search box: " + ex.Message));
return null;
}
}
internal static TextMeshProUGUI GetSelectedLabel(DevToolsManager manager)
{
Resolve();
if ((Object)(object)manager == (Object)null || selectedTextField == null)
{
return null;
}
try
{
object? value = selectedTextField.GetValue(manager);
return (TextMeshProUGUI)((value is TextMeshProUGUI) ? value : null);
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not read the selected entry label: " + ex.Message));
return null;
}
}
internal static Dictionary<string, Dictionary<ItemId, ItemDefinition>> GetItemLists(DevToolsManager manager)
{
Resolve();
if ((Object)(object)manager == (Object)null || itemListsField == null)
{
return null;
}
try
{
return itemListsField.GetValue(manager) as Dictionary<string, Dictionary<ItemId, ItemDefinition>>;
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not read the spawn menu item lists: " + ex.Message));
return null;
}
}
internal static void AddToItemList(DevToolsManager manager, ItemDefinition item)
{
Resolve();
if ((Object)(object)manager == (Object)null || (Object)(object)item == (Object)null || addToItemListMethod == null)
{
return;
}
try
{
addToItemListMethod.Invoke(manager, new object[1] { item });
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not add '" + ((Object)item).name + "' to the spawn menu: " + ex.Message));
}
}
internal static ItemDefinition GetSelectorItem(SelectorButton button)
{
Resolve();
if ((Object)(object)button == (Object)null || selectorItemField == null)
{
return null;
}
try
{
object? value = selectorItemField.GetValue(button);
return (ItemDefinition)((value is ItemDefinition) ? value : null);
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not read a spawn menu button's item: " + ex.Message));
return null;
}
}
internal static TextMeshProUGUI GetSelectorLabel(SelectorButton button)
{
Resolve();
if ((Object)(object)button == (Object)null || selectorTextField == null)
{
return null;
}
try
{
object? value = selectorTextField.GetValue(button);
return (TextMeshProUGUI)((value is TextMeshProUGUI) ? value : null);
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not read a spawn menu button's label: " + ex.Message));
return null;
}
}
internal static void SetSelectorSearchName(SelectorButton button, string searchName)
{
Resolve();
if ((Object)(object)button == (Object)null || selectorItemNameField == null || string.IsNullOrEmpty(searchName))
{
return;
}
try
{
selectorItemNameField.SetValue(button, searchName);
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not rename a spawn menu button: " + ex.Message));
}
}
private static void Resolve()
{
if (!resolved)
{
resolved = true;
cameraControllerField = AccessTools.Field(typeof(DevToolsManager), "cameraController");
if (cameraControllerField == null)
{
Plugin.Log.LogWarning((object)"DevToolsManager.cameraController not found; using component lookup only.");
}
levelPanelField = AccessTools.Field(typeof(DevToolsManager), "levelPanel");
if (levelPanelField == null)
{
Plugin.Log.LogWarning((object)"DevToolsManager.levelPanel not found; Escape will not close the spawn menu.");
}
selectorButtonsField = AccessTools.Field(typeof(DevToolsManager), "selectorButtons");
if (selectorButtonsField == null)
{
Plugin.Log.LogWarning((object)"DevToolsManager.selectorButtons not found; spawn menu names stay untranslated.");
}
selectedTextField = AccessTools.Field(typeof(DevToolsManager), "selectedText");
if (selectedTextField == null)
{
Plugin.Log.LogWarning((object)"DevToolsManager.selectedText not found; the selected entry stays untranslated.");
}
filterInputFieldField = AccessTools.Field(typeof(DevToolsManager), "filterInputField");
if (filterInputFieldField == null)
{
Plugin.Log.LogWarning((object)"DevToolsManager.filterInputField not found; Backspace will clear the search box.");
}
itemListsField = AccessTools.Field(typeof(DevToolsManager), "itemListsByCategory");
addToItemListMethod = AccessTools.Method(typeof(DevToolsManager), "AddToItemList", new Type[1] { typeof(ItemDefinition) }, (Type[])null);
if (itemListsField == null || addToItemListMethod == null)
{
Plugin.Log.LogWarning((object)"DevToolsManager item list internals not found; the spawn menu keeps the game's own item list.");
}
selectorItemField = AccessTools.Field(typeof(SelectorButton), "item");
selectorItemNameField = AccessTools.Field(typeof(SelectorButton), "itemName");
selectorTextField = AccessTools.Field(typeof(SelectorButton), "text");
if (selectorItemField == null || selectorItemNameField == null || selectorTextField == null)
{
Plugin.Log.LogWarning((object)"SelectorButton internals not found; spawn menu names stay untranslated.");
}
}
}
}
internal sealed class FreeCameraOrientation
{
private bool teleportRequested;
public void OnFreeCameraEnabled(DevToolsManager manager)
{
//IL_011d: Unknown result type (might be due to invalid IL or missing references)
//IL_009b: Unknown result type (might be due to invalid IL or missing references)
//IL_0134: Unknown result type (might be due to invalid IL or missing references)
//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
teleportRequested = false;
if (!Plugin.InheritOrientationOnEnter.Value)
{
return;
}
Player player = GetPlayer();
if ((Object)(object)player == (Object)null)
{
return;
}
ExtendedCameraController val = ResolveDevCameraController(manager);
if ((Object)(object)val == (Object)null)
{
Plugin.Log.LogWarning((object)"Free camera controller not found; entry orientation left untouched.");
return;
}
Transform playerViewTransform = GetPlayerViewTransform(player);
if ((Object)(object)playerViewTransform == (Object)null)
{
Plugin.Log.LogWarning((object)"Player view transform not found; entry orientation left untouched.");
return;
}
if (Plugin.VerboseLogging.Value)
{
Plugin.Log.LogInfo((object)("Free camera entry: view " + Describe(playerViewTransform) + " " + Describe(playerViewTransform.forward) + ", cameraRoot " + (((Object)(object)player.cameraRoot != (Object)null) ? Describe(player.cameraRoot.forward) : "none") + ", target " + Describe(((Component)val).transform) + ", camera " + Describe(((Component)manager.GetCamera()).transform) + "."));
}
if (ApplyLookDirection(val, playerViewTransform.forward))
{
Plugin.LogVerbose("Free camera now faces " + Describe(((Component)val).transform.forward) + ".");
}
}
public void OnTeleportRequested()
{
if (!teleportRequested)
{
teleportRequested = true;
Plugin.LogVerbose("Teleport requested; the player view direction will follow the free camera on exit.");
}
}
public void OnFreeCameraDisabled(DevToolsManager manager)
{
//IL_0057: Unknown result type (might be due to invalid IL or missing references)
//IL_006e: Unknown result type (might be due to invalid IL or missing references)
bool num = teleportRequested;
teleportRequested = false;
if (!num || !Plugin.InheritOrientationOnExit.Value)
{
return;
}
Camera camera = manager.GetCamera();
if (!((Object)(object)camera == (Object)null))
{
Player player = GetPlayer();
if (!((Object)(object)player == (Object)null) && !((Object)(object)player.playerCamController == (Object)null) && ApplyLookDirection(player.playerCamController, ((Component)camera).transform.forward))
{
Plugin.LogVerbose("Player view direction inherited from the free camera " + Describe(((Component)camera).transform.forward) + ".");
}
}
}
public void Reset()
{
teleportRequested = false;
}
private static Player GetPlayer()
{
GameManager instance = StaticInstance<GameManager>.Instance;
if ((Object)(object)instance == (Object)null)
{
return null;
}
Player playerScript = instance.PlayerScript;
if (!((Object)(object)playerScript != (Object)null))
{
return null;
}
return playerScript;
}
private static Transform GetPlayerViewTransform(Player player)
{
if ((Object)(object)player.playerCamController != (Object)null)
{
return ((Component)player.playerCamController).transform;
}
if (!((Object)(object)player.cameraControls != (Object)null))
{
return null;
}
return player.cameraControls;
}
private static string Describe(Vector3 direction)
{
return "(" + direction.x.ToString("0.00") + ", " + direction.y.ToString("0.00") + ", " + direction.z.ToString("0.00") + ")";
}
private static string Describe(Transform transform)
{
string text = ((Object)transform).name;
Transform parent = transform.parent;
while ((Object)(object)parent != (Object)null)
{
text = ((Object)parent).name + "/" + text;
parent = parent.parent;
}
return text;
}
private static ExtendedCameraController ResolveDevCameraController(DevToolsManager manager)
{
Camera camera = manager.GetCamera();
if ((Object)(object)camera == (Object)null)
{
return null;
}
ExtendedCameraController val = ((Component)camera).GetComponent<ExtendedCameraController>();
if ((Object)(object)val == (Object)null)
{
val = ((Component)camera).GetComponentInParent<ExtendedCameraController>();
}
if ((Object)(object)val == (Object)null)
{
val = DevToolsInternals.GetCameraController(manager);
}
return val;
}
private static bool ApplyLookDirection(ExtendedCameraController controller, Vector3 worldDirection)
{
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
if (((Vector3)(ref worldDirection)).sqrMagnitude < 1E-06f)
{
return false;
}
controller.RotateTowardDirection(ClampPitch(controller, ((Vector3)(ref worldDirection)).normalized), 0f);
return true;
}
private static Vector3 ClampPitch(ExtendedCameraController controller, Vector3 worldDirection)
{
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
//IL_006d: Unknown result type (might be due to invalid IL or missing references)
//IL_0088: Unknown result type (might be due to invalid IL or missing references)
//IL_008d: Unknown result type (might be due to invalid IL or missing references)
//IL_0092: Unknown result type (might be due to invalid IL or missing references)
//IL_0097: Unknown result type (might be due to invalid IL or missing references)
//IL_0065: Unknown result type (might be due to invalid IL or missing references)
//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
Transform parent = ((Component)controller).transform.parent;
Vector3 val = (((Object)(object)parent != (Object)null) ? parent.InverseTransformDirection(worldDirection) : worldDirection);
float num = (0f - Mathf.Asin(Mathf.Clamp(val.y, -1f, 1f))) * 57.29578f;
float num2 = Mathf.Clamp(num, 0f - ((CameraController)controller).upperVerticalLimit, ((CameraController)controller).lowerVerticalLimit);
if (Mathf.Abs(num2 - num) < 0.01f)
{
return worldDirection;
}
float num3 = Mathf.Atan2(val.x, val.z) * 57.29578f;
Vector3 val2 = Quaternion.Euler(num2, num3, 0f) * Vector3.forward;
if (!((Object)(object)parent != (Object)null))
{
return val2;
}
return parent.TransformDirection(val2);
}
}
[BepInPlugin("ryuka.sulfur.dev_mode_qol", "Dev Mode QoL", "1.2.0")]
public sealed class Plugin : BaseUnityPlugin
{
public const string PluginGuid = "ryuka.sulfur.dev_mode_qol";
public const string PluginName = "Dev Mode QoL";
public const string PluginVersion = "1.2.0";
internal static ConfigEntry<bool> InheritOrientationOnEnter;
internal static ConfigEntry<bool> InheritOrientationOnExit;
internal static ConfigEntry<bool> CloseWithEscape;
internal static ConfigEntry<bool> ShowEveryItem;
internal static ConfigEntry<bool> LocalizeItemNames;
internal static ConfigEntry<bool> SearchEveryLanguage;
internal static ConfigEntry<bool> PinyinSearch;
internal static ConfigEntry<bool> ShowLevelNames;
internal static ConfigEntry<bool> VerboseLogging;
private Harmony harmony;
internal static ManualLogSource Log { get; private set; }
internal static FreeCameraOrientation Orientation { get; private set; }
internal static DevToolsEscapeInput EscapeInput { get; private set; }
internal static SpawnMenuLocalization SpawnMenu { get; private set; }
internal static SpawnMenuCatalog SpawnCatalog { get; private set; }
internal static LevelMenuLocalization LevelMenu { get; private set; }
private void Awake()
{
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
BindConfig();
Orientation = new FreeCameraOrientation();
EscapeInput = new DevToolsEscapeInput();
EscapeInput.Initialize();
SpawnMenu = new SpawnMenuLocalization();
SpawnMenu.Initialize();
SpawnCatalog = new SpawnMenuCatalog();
LevelMenu = new LevelMenuLocalization();
LevelMenu.Initialize();
harmony = new Harmony("ryuka.sulfur.dev_mode_qol");
harmony.PatchAll(typeof(DevToolsManagerPatches));
harmony.PatchAll(typeof(TextInputPatches));
PatchLevelButtons();
Log.LogInfo((object)"Dev Mode QoL v1.2.0 loaded.");
}
private void OnDestroy()
{
if (harmony != null)
{
harmony.UnpatchSelf();
harmony = null;
}
if (EscapeInput != null)
{
EscapeInput.Dispose();
EscapeInput = null;
}
if (SpawnMenu != null)
{
SpawnMenu.Dispose();
SpawnMenu = null;
}
if (SpawnCatalog != null)
{
SpawnCatalog.Dispose();
SpawnCatalog = null;
}
if (LevelMenu != null)
{
LevelMenu.Dispose();
LevelMenu = null;
}
GameLanguage.Detach();
Orientation = null;
}
private void PatchLevelButtons()
{
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
//IL_0044: Expected O, but got Unknown
MethodInfo methodInfo = ChapterPanelPatches.FindLevelButtonBuilder();
if (methodInfo == null)
{
Log.LogWarning((object)"The level list builder was not found in this game build; level buttons keep the game's identifier line only.");
}
else
{
harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(ChapterPanelPatches), "LevelButtonsCreatedPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
}
}
private void BindConfig()
{
InheritOrientationOnEnter = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - Free Camera", "InheritOrientationOnEnter", true, "Entering the developer free camera (F3) keeps the direction the player was looking at.");
InheritOrientationOnExit = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - Free Camera", "InheritOrientationOnExit", true, "Leaving the developer free camera hands the camera direction back to the player, but only when the player was teleported (T) during that free camera session. Without a teleport the player returns to the original spot and keeps the original view direction.");
CloseWithEscape = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Menu", "CloseWithEscape", true, "Escape closes the developer overlay. While the spawn menu is open Escape closes that menu first, and while the level select list is open the game's own cancel handling closes the list first. While the search box has focus Escape leaves the search box first. Backspace never closes anything; the game binds it to the same cancel, but it stays plain text editing.");
ShowEveryItem = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Menu", "ShowEveryItem", true, "The spawn menu lists every item in the game's item registry. Vanilla builds the list from the items tagged for it and then drops the ones whose data still says they are not part of the early access build, which hides items that have long since shipped (Dinner Jacket among them). Items the game leaves out on purpose, such as weapons flagged as not usable by the player, stay out. Turn this off for the vanilla list. The menu is built once per game session, so a change applies from the next session.");
LocalizeItemNames = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Menu", "LocalizeItemNames", true, "Spawn menu item buttons show the item name in the language the game is set to, the same name the inventory shows. Turn this off to see the authoring names instead.");
SearchEveryLanguage = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Menu", "SearchEveryLanguage", true, "The spawn menu search finds an item by its name in any language the game has loaded, as well as by its internal asset name. Turn this off to search the visible names only.");
PinyinSearch = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Menu", "PinyinSearch", true, "The spawn menu search also matches Chinese names by how they are pronounced, typed the way a Chinese keyboard is typed: full syllables (shouqiang), initials (sq), the two mixed (shouq), and characters with several readings match on any of them. Only applies to a query typed in latin letters.");
ShowLevelNames = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Menu", "ShowLevelNames", true, "Level select buttons get a second line with the level name the player sees in game, in the language the game is set to. The first line keeps the level identifier.");
VerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Diagnostics", "VerboseLogging", false, "Log every camera hand-over, overlay close and localization pass. Diagnostics only - does not change behaviour.");
}
internal static void LogVerbose(string message)
{
if (VerboseLogging != null && VerboseLogging.Value && Log != null)
{
Log.LogInfo((object)message);
}
}
}
internal sealed class SpawnMenuCatalog : IDisposable
{
private readonly List<ItemDefinition> lifted = new List<ItemDefinition>();
public void OnPopulatingItems(DevToolsManager manager)
{
RestoreEarlyAccessFlags();
if (Plugin.ShowEveryItem.Value && !((Object)(object)manager == (Object)null))
{
Dictionary<string, Dictionary<ItemId, ItemDefinition>> itemLists = DevToolsInternals.GetItemLists(manager);
if (itemLists != null)
{
int num = AddItemsTheLabelPassMissed(manager, itemLists);
int num2 = LiftEarlyAccessFilter(itemLists);
Plugin.LogVerbose("Spawn menu catalog: " + CountItems(itemLists) + " item(s) listed, " + num + " of them added from the item database, " + num2 + " shown past the early access filter.");
}
}
}
public void OnItemsPopulated()
{
RestoreEarlyAccessFlags();
}
private static int AddItemsTheLabelPassMissed(DevToolsManager manager, Dictionary<string, Dictionary<ItemId, ItemDefinition>> lists)
{
AsyncAssetLoading instance = StaticInstance<AsyncAssetLoading>.Instance;
ItemDatabase val = (((Object)(object)instance == (Object)null) ? null : instance.itemDatabase);
if ((Object)(object)val == (Object)null)
{
return 0;
}
List<ItemDefinition> rawList = val.GetRawList();
if (rawList == null)
{
return 0;
}
HashSet<ItemDefinition> hashSet = CollectItems(lists);
int count = hashSet.Count;
for (int i = 0; i < rawList.Count; i++)
{
ItemDefinition val2 = rawList[i];
if (!((Object)(object)val2 == (Object)null) && !hashSet.Contains(val2))
{
DevToolsInternals.AddToItemList(manager, val2);
}
}
return CountItems(lists) - count;
}
private int LiftEarlyAccessFilter(Dictionary<string, Dictionary<ItemId, ItemDefinition>> lists)
{
AsyncAssetLoading instance = StaticInstance<AsyncAssetLoading>.Instance;
if ((Object)(object)instance == (Object)null || !instance.IsFullGame)
{
return 0;
}
foreach (Dictionary<ItemId, ItemDefinition> value in lists.Values)
{
foreach (ItemDefinition value2 in value.Values)
{
if (!((Object)(object)value2 == (Object)null) && !value2.includedInEarlyAccess)
{
value2.includedInEarlyAccess = true;
lifted.Add(value2);
}
}
}
return lifted.Count;
}
private void RestoreEarlyAccessFlags()
{
for (int i = 0; i < lifted.Count; i++)
{
if ((Object)(object)lifted[i] != (Object)null)
{
lifted[i].includedInEarlyAccess = false;
}
}
lifted.Clear();
}
private static HashSet<ItemDefinition> CollectItems(Dictionary<string, Dictionary<ItemId, ItemDefinition>> lists)
{
HashSet<ItemDefinition> hashSet = new HashSet<ItemDefinition>();
foreach (Dictionary<ItemId, ItemDefinition> value in lists.Values)
{
foreach (ItemDefinition value2 in value.Values)
{
if ((Object)(object)value2 != (Object)null)
{
hashSet.Add(value2);
}
}
}
return hashSet;
}
private static int CountItems(Dictionary<string, Dictionary<ItemId, ItemDefinition>> lists)
{
return CollectItems(lists).Count;
}
public void Dispose()
{
RestoreEarlyAccessFlags();
}
}
}
namespace DevModeQoL.Patches
{
internal static class ChapterPanelPatches
{
private static FieldInfo panelField;
internal static MethodInfo FindLevelButtonBuilder()
{
Type[] nestedTypes = typeof(ChapterPanel).GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic);
for (int i = 0; i < nestedTypes.Length; i++)
{
MethodInfo[] methods = nestedTypes[i].GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
for (int j = 0; j < methods.Length; j++)
{
if (Matches(methods[j]))
{
panelField = FindPanelField(nestedTypes[i]);
if (!(panelField == null))
{
return methods[j];
}
}
}
}
return null;
}
internal static void LevelButtonsCreatedPostfix(object __instance, bool exists, WorldEnvironment environment)
{
if (!exists || Plugin.LevelMenu == null || panelField == null)
{
return;
}
try
{
object? value = panelField.GetValue(__instance);
ChapterPanel panel = (ChapterPanel)((value is ChapterPanel) ? value : null);
Plugin.LevelMenu.OnLevelButtonsCreated(panel, environment);
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Dev Mode QoL could not label the level buttons: " + ex));
}
}
private static bool Matches(MethodInfo method)
{
if (method.ReturnType != typeof(void))
{
return false;
}
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length == 2 && parameters[0].ParameterType == typeof(bool))
{
return parameters[1].ParameterType == typeof(WorldEnvironment);
}
return false;
}
private static FieldInfo FindPanelField(Type generatedType)
{
FieldInfo[] fields = generatedType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
for (int i = 0; i < fields.Length; i++)
{
if (fields[i].FieldType == typeof(ChapterPanel))
{
return fields[i];
}
}
return null;
}
}
[HarmonyPatch(typeof(DevToolsManager))]
internal static class DevToolsManagerPatches
{
[HarmonyPostfix]
[HarmonyPatch("TurnOn")]
private static void TurnOnPostfix(DevToolsManager __instance)
{
if (!((Object)(object)__instance == (Object)null) && __instance.shouldShow)
{
Run("TurnOn", delegate
{
Plugin.EscapeInput.OnFreeCameraStateChanged();
Plugin.Orientation.OnFreeCameraEnabled(__instance);
});
}
}
[HarmonyPostfix]
[HarmonyPatch("TurnOff")]
private static void TurnOffPostfix(DevToolsManager __instance)
{
if (!((Object)(object)__instance == (Object)null) && !__instance.shouldShow)
{
Run("TurnOff", delegate
{
Plugin.EscapeInput.OnFreeCameraStateChanged();
Plugin.Orientation.OnFreeCameraDisabled(__instance);
});
}
}
[HarmonyPostfix]
[HarmonyPatch("TeleportPlayer")]
private static void TeleportPlayerPostfix()
{
Run("TeleportPlayer", delegate
{
Plugin.Orientation.OnTeleportRequested();
});
}
[HarmonyPostfix]
[HarmonyPatch("ShowOptions")]
private static void ShowOptionsPostfix(DevToolsManager __instance)
{
Run("ShowOptions", delegate
{
Plugin.EscapeInput.OnSpawnMenuOpened(__instance);
});
}
[HarmonyPostfix]
[HarmonyPatch("HideOptions")]
private static void HideOptionsPostfix()
{
Run("HideOptions", delegate
{
Plugin.EscapeInput.OnSpawnMenuClosed();
});
}
[HarmonyPrefix]
[HarmonyPatch("PopulateSpawnMenuItems")]
private static void PopulateSpawnMenuItemsPrefix(DevToolsManager __instance)
{
Run("PopulateSpawnMenuItems", delegate
{
Plugin.SpawnCatalog.OnPopulatingItems(__instance);
});
}
[HarmonyFinalizer]
[HarmonyPatch("PopulateSpawnMenuItems")]
private static void PopulateSpawnMenuItemsFinalizer()
{
Run("PopulateSpawnMenuItems (finalizer)", delegate
{
Plugin.SpawnCatalog.OnItemsPopulated();
});
}
[HarmonyPostfix]
[HarmonyPatch("AfterListsPopulated")]
private static void AfterListsPopulatedPostfix(DevToolsManager __instance)
{
Run("AfterListsPopulated", delegate
{
Plugin.SpawnMenu.OnListsPopulated(__instance);
});
}
[HarmonyPostfix]
[HarmonyPatch("SetFilter")]
private static void SetFilterPostfix(string value)
{
Run("SetFilter", delegate
{
Plugin.SpawnMenu.OnFilterApplied(value);
});
}
[HarmonyPostfix]
[HarmonyPatch("SetSelectedItem")]
private static void SetSelectedItemPostfix(DevToolsManager __instance, ItemDefinition item)
{
Run("SetSelectedItem", delegate
{
Plugin.SpawnMenu.OnItemSelected(__instance, item);
});
}
[HarmonyPrefix]
[HarmonyPatch("SpecialCancel")]
private static void SpecialCancelPrefix(DevToolsManager __instance, bool byGamepad)
{
Run("SpecialCancel", delegate
{
Plugin.EscapeInput.OnCancel(__instance, byGamepad);
});
}
private static void Run(string hook, Action action)
{
if (Plugin.Orientation == null || Plugin.EscapeInput == null || Plugin.SpawnMenu == null || Plugin.LevelMenu == null || Plugin.SpawnCatalog == null)
{
return;
}
try
{
action();
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Dev Mode QoL hook '" + hook + "' failed: " + ex));
}
}
}
[HarmonyPatch(typeof(TMP_InputField))]
internal static class TextInputPatches
{
[HarmonyPrefix]
[HarmonyPatch("OnCancel")]
private static bool OnCancelPrefix(TMP_InputField __instance)
{
if (Plugin.EscapeInput == null)
{
return true;
}
try
{
return !Plugin.EscapeInput.IsBackspaceEditingText(__instance);
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Dev Mode QoL hook 'OnCancel' failed: " + ex));
return true;
}
}
}
}
namespace DevModeQoL.Localization
{
internal static class GameLanguage
{
private static AsyncAssetLoading attachedTo;
internal static event Action Changed;
internal static void EnsureAttached()
{
//IL_002b: Unknown result type (might be due to invalid IL or missing references)
//IL_0035: Expected O, but got Unknown
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Expected O, but got Unknown
AsyncAssetLoading instance = StaticInstance<AsyncAssetLoading>.Instance;
if ((Object)(object)instance == (Object)null || instance == attachedTo)
{
return;
}
Detach();
try
{
instance.onLanguageChange = (OnLanguageChange)Delegate.Combine((Delegate?)(object)instance.onLanguageChange, (Delegate?)new OnLanguageChange(OnLanguageChanged));
attachedTo = instance;
Plugin.LogVerbose("Listening for language changes.");
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not listen for language changes: " + ex.Message + " Developer overlay labels will only follow the language chosen before they were built."));
}
}
internal static void Detach()
{
//IL_002b: Unknown result type (might be due to invalid IL or missing references)
//IL_0035: Expected O, but got Unknown
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Expected O, but got Unknown
if ((Object)(object)attachedTo == (Object)null)
{
attachedTo = null;
return;
}
try
{
attachedTo.onLanguageChange = (OnLanguageChange)Delegate.Remove((Delegate?)(object)attachedTo.onLanguageChange, (Delegate?)new OnLanguageChange(OnLanguageChanged));
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not stop listening for language changes: " + ex.Message));
}
attachedTo = null;
}
private static void OnLanguageChanged()
{
Action changed = GameLanguage.Changed;
if (changed == null)
{
return;
}
try
{
changed();
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Relabelling the developer overlay after a language change failed: " + ex));
}
}
}
internal sealed class LevelMenuLocalization : IDisposable
{
private sealed class LevelLabel
{
internal TextMeshProUGUI Label { get; private set; }
internal string IdentifierLine { get; private set; }
internal WorldEnvironmentIds Environment { get; private set; }
internal int LevelNumber { get; private set; }
internal LevelLabel(TextMeshProUGUI label, string identifierLine, WorldEnvironmentIds environment, int levelNumber)
{
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
Label = label;
IdentifierLine = identifierLine;
Environment = environment;
LevelNumber = levelNumber;
}
}
private readonly List<LevelLabel> labels = new List<LevelLabel>();
private bool subscribed;
public void Initialize()
{
if (!subscribed)
{
subscribed = true;
GameLanguage.Changed += OnLanguageChanged;
Plugin.ShowLevelNames.SettingChanged += OnSettingChanged;
}
}
public void OnLevelButtonsCreated(ChapterPanel panel, WorldEnvironment environment)
{
//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)panel == (Object)null || (Object)(object)environment == (Object)null || environment.levels == null)
{
return;
}
int count = environment.levels.Count;
Transform transform = ((Component)panel).transform;
int num = transform.childCount - count;
if (count <= 0 || num < 0)
{
return;
}
GameLanguage.EnsureAttached();
for (int i = 0; i < count; i++)
{
TextMeshProUGUI componentInChildren = ((Component)transform.GetChild(num + i)).GetComponentInChildren<TextMeshProUGUI>();
if (!((Object)(object)componentInChildren == (Object)null))
{
string text = ((TMP_Text)componentInChildren).text;
string value = ((object)Unsafe.As<WorldEnvironmentIds, WorldEnvironmentIds>(ref environment.id)/*cast due to .constrained prefix*/).ToString() + " " + (i + 1) + ":";
if (text != null && text.StartsWith(value, StringComparison.Ordinal))
{
((TMP_Text)componentInChildren).overflowMode = (TextOverflowModes)0;
labels.Add(new LevelLabel(componentInChildren, text, environment.id, i + 1));
}
}
}
ApplyLabels();
}
private void ApplyLabels()
{
bool value = Plugin.ShowLevelNames.Value;
for (int num = labels.Count - 1; num >= 0; num--)
{
LevelLabel levelLabel = labels[num];
if ((Object)(object)levelLabel.Label == (Object)null)
{
labels.RemoveAt(num);
}
else
{
string text = (value ? BuildLevelName(levelLabel) : null);
string text2 = (string.IsNullOrEmpty(text) ? levelLabel.IdentifierLine : (levelLabel.IdentifierLine + "\n" + text));
((TMP_Text)levelLabel.Label).text = text2;
OverlayFonts.EnsureCanDisplay(levelLabel.Label, text2);
}
}
}
private static string BuildLevelName(LevelLabel label)
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
string text = TranslationCatalog.Translate("Environments/" + ((object)label.Environment/*cast due to .constrained prefix*/).ToString());
if (string.IsNullOrEmpty(text))
{
return null;
}
return text + " " + Helpers.ToRoman(label.LevelNumber);
}
private void OnLanguageChanged()
{
ApplyLabels();
}
private void OnSettingChanged(object sender, EventArgs arguments)
{
ApplyLabels();
}
public void Dispose()
{
if (subscribed)
{
GameLanguage.Changed -= OnLanguageChanged;
Plugin.ShowLevelNames.SettingChanged -= OnSettingChanged;
subscribed = false;
}
labels.Clear();
}
}
internal static class OverlayFonts
{
private const FontStyle PreferredStyle = (FontStyle)0;
private static bool warned;
private static bool reported;
internal static void EnsureCanDisplay(TextMeshProUGUI label, string text)
{
if ((Object)(object)label == (Object)null || string.IsNullOrEmpty(text))
{
return;
}
try
{
if (CanDisplay(((TMP_Text)label).font, text))
{
return;
}
TMP_FontAsset val = FindFontFor(text);
if ((Object)(object)val == (Object)null)
{
WarnOnce("The game exposes no font for the current language.");
}
else if (!((Object)(object)val == (Object)(object)((TMP_Text)label).font))
{
if (!reported)
{
reported = true;
Plugin.LogVerbose("Developer overlay labels switched to the current language's font '" + ((Object)val).name + "'.");
}
((TMP_Text)label).font = val;
}
}
catch (Exception ex)
{
WarnOnce("Could not give a developer overlay label a font for its language: " + ex.Message);
}
}
private static bool CanDisplay(TMP_FontAsset font, string text)
{
if ((Object)(object)font == (Object)null)
{
return false;
}
uint[] array = default(uint[]);
return font.HasCharacters(text, ref array, true, false);
}
private static TMP_FontAsset FindFontFor(string text)
{
TMP_FontAsset[] fontCache = GetFontCache();
if (fontCache == null)
{
return null;
}
for (int i = 0; i < fontCache.Length; i++)
{
if (CanDisplay(fontCache[i], text))
{
return fontCache[i];
}
}
int num = 0;
if (num >= fontCache.Length)
{
return null;
}
return fontCache[num];
}
private static TMP_FontAsset[] GetFontCache()
{
AsyncAssetLoading instance = StaticInstance<AsyncAssetLoading>.Instance;
if ((Object)(object)instance == (Object)null)
{
return null;
}
TMP_FontAsset[] fontCache = instance.fontCache;
if (fontCache == null || fontCache.Length == 0)
{
return null;
}
if ((Object)(object)fontCache[0] == (Object)null)
{
FontLocalizer.LoadFontCache();
fontCache = instance.fontCache;
}
return fontCache;
}
private static void WarnOnce(string message)
{
if (!warned)
{
warned = true;
Plugin.Log.LogWarning((object)(message + " Translated names may draw blank."));
}
}
}
internal sealed class PinyinName
{
private const int MaxLength = 64;
private readonly string text;
private readonly string[][] readings;
private PinyinName(string text, string[][] readings)
{
this.text = text;
this.readings = readings;
}
internal static PinyinName Create(string text)
{
if (string.IsNullOrEmpty(text) || !PinyinTable.IsAvailable)
{
return null;
}
if (text.Length > 64)
{
text = text.Substring(0, 64);
}
string[][] array = null;
for (int i = 0; i < text.Length; i++)
{
string[] array2 = PinyinTable.GetReadings(text[i]);
if (array2.Length != 0)
{
if (array == null)
{
array = new string[text.Length][];
}
array[i] = array2;
}
}
if (array != null)
{
return new PinyinName(text, array);
}
return null;
}
internal bool Matches(string query)
{
if (string.IsNullOrEmpty(query))
{
return true;
}
for (int i = 0; i < text.Length; i++)
{
if (MatchFrom(i, query, 0))
{
return true;
}
}
return false;
}
private bool MatchFrom(int index, string query, int queryIndex)
{
if (queryIndex >= query.Length)
{
return true;
}
if (index >= this.text.Length)
{
return false;
}
string[] array = readings[index];
if (array == null)
{
char c = this.text[index];
if (c == query[queryIndex] && MatchFrom(index + 1, query, queryIndex + 1))
{
return true;
}
if (!char.IsLetterOrDigit(c))
{
return MatchFrom(index + 1, query, queryIndex);
}
return false;
}
foreach (string text in array)
{
int j;
for (j = 0; j < text.Length && queryIndex + j < query.Length && text[j] == query[queryIndex + j]; j++)
{
}
if (j != 0)
{
if (queryIndex + j >= query.Length)
{
return true;
}
if (j == text.Length && MatchFrom(index + 1, query, queryIndex + j))
{
return true;
}
if (MatchFrom(index + 1, query, queryIndex + 1))
{
return true;
}
if (j >= 2 && IsRetroflex(text) && MatchFrom(index + 1, query, queryIndex + 2))
{
return true;
}
}
}
return false;
}
private static bool IsRetroflex(string reading)
{
if (reading.Length < 2 || reading[1] != 'h')
{
return false;
}
char c = reading[0];
if (c != 'z' && c != 'c')
{
return c == 's';
}
return true;
}
}
internal static class PinyinTable
{
private const string ResourceName = "DevModeQoL.Localization.PinyinTable.bin";
private static readonly string[] Empty = new string[0];
private static readonly Dictionary<char, string[]> Cache = new Dictionary<char, string[]>();
private static string[] syllables;
private static ushort[] starts;
private static ushort[] readingIds;
private static char rangeStart;
private static char rangeEnd;
private static bool loaded;
internal static bool IsAvailable
{
get
{
Load();
return syllables != null;
}
}
internal static int CoveredCharacters
{
get
{
Load();
if (readingIds != null)
{
return readingIds.Length;
}
return 0;
}
}
internal static bool IsHan(char character)
{
Load();
if (syllables != null && character >= rangeStart)
{
return character <= rangeEnd;
}
return false;
}
internal static string[] GetReadings(char character)
{
if (!IsHan(character))
{
return Empty;
}
if (Cache.TryGetValue(character, out var value))
{
return value;
}
int num = character - rangeStart;
int num2 = starts[num];
int num3 = starts[num + 1] - num2;
string[] array = Empty;
if (num3 > 0)
{
array = new string[num3];
for (int i = 0; i < num3; i++)
{
array[i] = syllables[readingIds[num2 + i]];
}
}
Cache[character] = array;
return array;
}
private static void Load()
{
if (loaded)
{
return;
}
loaded = true;
try
{
using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("DevModeQoL.Localization.PinyinTable.bin");
if (stream == null)
{
Plugin.Log.LogWarning((object)"The pinyin table is missing from this build; pinyin search is off.");
}
else
{
Read(stream);
}
}
catch (Exception ex)
{
syllables = null;
starts = null;
readingIds = null;
Plugin.Log.LogWarning((object)("Could not read the pinyin table: " + ex.Message + " Pinyin search is off."));
}
}
private static void Read(Stream stream)
{
using BinaryReader binaryReader = new BinaryReader(stream, Encoding.UTF8);
int num = binaryReader.ReadUInt16();
int count = (int)binaryReader.ReadUInt32();
syllables = Encoding.UTF8.GetString(binaryReader.ReadBytes(count)).Split(new char[1] { '\n' });
if (syllables.Length != num)
{
throw new InvalidDataException("expected " + num + " syllables but found " + syllables.Length);
}
rangeStart = (char)binaryReader.ReadUInt16();
rangeEnd = (char)binaryReader.ReadUInt16();
int num2 = rangeEnd - rangeStart + 1;
starts = new ushort[num2 + 1];
List<ushort> list = new List<ushort>(num2 + num2 / 2);
for (int i = 0; i < num2; i++)
{
starts[i] = (ushort)list.Count;
int num3 = binaryReader.ReadByte();
for (int j = 0; j < num3; j++)
{
list.Add(binaryReader.ReadUInt16());
}
}
starts[num2] = (ushort)list.Count;
readingIds = list.ToArray();
}
}
internal sealed class SpawnMenuLocalization : IDisposable
{
private sealed class ItemEntry
{
internal SelectorButton Button { get; private set; }
internal TextMeshProUGUI Label { get; private set; }
internal ItemDefinition Item { get; private set; }
private string[] SearchableText { get; set; }
private PinyinName[] SpokenNames { get; set; }
internal ItemEntry(SelectorButton button, TextMeshProUGUI label, ItemDefinition item, string[] searchableText, PinyinName[] spokenNames)
{
Button = button;
Label = label;
Item = item;
SearchableText = searchableText;
SpokenNames = spokenNames;
}
internal bool Matches(string lowerCaseFilter, bool usePinyin)
{
for (int i = 0; i < SearchableText.Length; i++)
{
if (SearchableText[i].IndexOf(lowerCaseFilter, StringComparison.Ordinal) >= 0)
{
return true;
}
}
if (usePinyin)
{
for (int j = 0; j < SpokenNames.Length; j++)
{
if (SpokenNames[j].Matches(lowerCaseFilter))
{
return true;
}
}
}
return false;
}
}
private static readonly PinyinName[] NoSpokenNames = new PinyinName[0];
private readonly List<ItemEntry> entries = new List<ItemEntry>();
private bool subscribed;
public void Initialize()
{
if (!subscribed)
{
subscribed = true;
GameLanguage.Changed += OnLanguageChanged;
Plugin.LocalizeItemNames.SettingChanged += OnSettingChanged;
}
}
public void OnListsPopulated(DevToolsManager manager)
{
entries.Clear();
List<SelectorButton> selectorButtons = DevToolsInternals.GetSelectorButtons(manager);
if (selectorButtons == null)
{
return;
}
for (int i = 0; i < selectorButtons.Count; i++)
{
SelectorButton val = selectorButtons[i];
if (!((Object)(object)val == (Object)null))
{
ItemDefinition selectorItem = DevToolsInternals.GetSelectorItem(val);
if (!((Object)(object)selectorItem == (Object)null))
{
string[] searchableText = BuildSearchableText(selectorItem);
entries.Add(new ItemEntry(val, DevToolsInternals.GetSelectorLabel(val), selectorItem, searchableText, BuildSpokenNames(searchableText)));
}
}
}
GameLanguage.EnsureAttached();
ApplyNames();
Plugin.LogVerbose("Indexed " + entries.Count + " spawn menu items across " + TranslationCatalog.LanguageCount + " language(s); current language is " + (TranslationCatalog.CurrentLanguage ?? "unknown") + "; pinyin search " + (PinyinTable.IsAvailable ? "ready" : "unavailable") + ".");
}
public void OnFilterApplied(string filter)
{
if (!Plugin.SearchEveryLanguage.Value || string.IsNullOrEmpty(filter))
{
return;
}
string text = filter.ToLowerInvariant();
bool usePinyin = Plugin.PinyinSearch.Value && IsLatin(text);
for (int i = 0; i < entries.Count; i++)
{
ItemEntry itemEntry = entries[i];
if ((Object)(object)itemEntry.Button == (Object)null)
{
continue;
}
bool flag = itemEntry.Matches(text, usePinyin);
if (flag != ((Component)itemEntry.Button).gameObject.activeSelf)
{
if (flag)
{
itemEntry.Button.Show();
}
else
{
itemEntry.Button.Hide();
}
}
}
}
public void OnItemSelected(DevToolsManager manager, ItemDefinition item)
{
if (!Plugin.LocalizeItemNames.Value || (Object)(object)item == (Object)null)
{
return;
}
TextMeshProUGUI selectedLabel = DevToolsInternals.GetSelectedLabel(manager);
if (!((Object)(object)selectedLabel == (Object)null))
{
string localizedDisplayName = item.LocalizedDisplayName;
if (!string.IsNullOrEmpty(localizedDisplayName))
{
((TMP_Text)selectedLabel).text = localizedDisplayName;
OverlayFonts.EnsureCanDisplay(selectedLabel, localizedDisplayName);
}
}
}
private void ApplyNames()
{
bool value = Plugin.LocalizeItemNames.Value;
for (int i = 0; i < entries.Count; i++)
{
ItemEntry itemEntry = entries[i];
if ((Object)(object)itemEntry.Button == (Object)null || (Object)(object)itemEntry.Item == (Object)null)
{
continue;
}
string text = (value ? itemEntry.Item.LocalizedDisplayName : itemEntry.Item.displayName);
if (!string.IsNullOrEmpty(text))
{
if ((Object)(object)itemEntry.Label != (Object)null)
{
((TMP_Text)itemEntry.Label).text = text;
OverlayFonts.EnsureCanDisplay(itemEntry.Label, text);
}
DevToolsInternals.SetSelectorSearchName(itemEntry.Button, text);
}
}
}
private static string[] BuildSearchableText(ItemDefinition item)
{
HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
Add(hashSet, ((Object)item).name);
Add(hashSet, item.displayName);
TranslationCatalog.CollectSearchableText("Items/" + ((Object)item).name, hashSet);
string[] array = new string[hashSet.Count];
hashSet.CopyTo(array);
return array;
}
private static PinyinName[] BuildSpokenNames(string[] searchableText)
{
List<PinyinName> list = null;
for (int i = 0; i < searchableText.Length; i++)
{
PinyinName pinyinName = PinyinName.Create(searchableText[i]);
if (pinyinName != null)
{
if (list == null)
{
list = new List<PinyinName>(2);
}
list.Add(pinyinName);
}
}
if (list != null)
{
return list.ToArray();
}
return NoSpokenNames;
}
private static bool IsLatin(string filter)
{
for (int i = 0; i < filter.Length; i++)
{
if (filter[i] > 'z' || filter[i] < 'a')
{
return false;
}
}
return true;
}
private static void Add(HashSet<string> searchable, string text)
{
if (!string.IsNullOrEmpty(text))
{
searchable.Add(text.ToLowerInvariant());
}
}
private void OnLanguageChanged()
{
ApplyNames();
}
private void OnSettingChanged(object sender, EventArgs arguments)
{
ApplyNames();
}
public void Dispose()
{
if (subscribed)
{
GameLanguage.Changed -= OnLanguageChanged;
Plugin.LocalizeItemNames.SettingChanged -= OnSettingChanged;
subscribed = false;
}
entries.Clear();
}
}
internal static class TranslationCatalog
{
private static bool warned;
internal static string CurrentLanguage
{
get
{
try
{
return LocalizationManager.CurrentLanguage;
}
catch (Exception ex)
{
WarnOnce("Could not read the current language: " + ex.Message);
return null;
}
}
}
internal static int LanguageCount
{
get
{
try
{
return LocalizationManager.GetAllLanguages(true)?.Count ?? 0;
}
catch (Exception ex)
{
WarnOnce("Could not count the installed languages: " + ex.Message);
return 0;
}
}
}
internal static string Translate(string term)
{
if (string.IsNullOrEmpty(term))
{
return null;
}
try
{
string text = default(string);
if (LocalizationManager.TryGetTranslation(term, ref text, true, 0, true, false, (GameObject)null, (string)null, true) && !string.IsNullOrEmpty(text))
{
return text;
}
}
catch (Exception ex)
{
WarnOnce("Could not translate '" + term + "': " + ex.Message);
}
return null;
}
internal static void CollectSearchableText(string term, ICollection<string> into)
{
if (string.IsNullOrEmpty(term) || into == null)
{
return;
}
try
{
List<LanguageSourceData> sources = LocalizationManager.Sources;
if (sources == null)
{
return;
}
for (int i = 0; i < sources.Count; i++)
{
LanguageSourceData val = sources[i];
if (val == null)
{
continue;
}
TermData termData = val.GetTermData(term, false);
if (termData == null || termData.Languages == null)
{
continue;
}
for (int j = 0; j < termData.Languages.Length; j++)
{
string translation = termData.GetTranslation(j, (string)null, false);
if (!string.IsNullOrEmpty(translation))
{
into.Add(translation.ToLowerInvariant());
}
}
}
}
catch (Exception ex)
{
WarnOnce("Could not read every language for '" + term + "': " + ex.Message);
}
}
private static void WarnOnce(string message)
{
if (!warned)
{
warned = true;
Plugin.Log.LogWarning((object)(message + " Developer overlay labels stay untranslated."));
}
}
}
}