diff --git a/OWML.Common/IModInputHandler.cs b/OWML.Common/IModInputHandler.cs index e4a67087..73386ce5 100644 --- a/OWML.Common/IModInputHandler.cs +++ b/OWML.Common/IModInputHandler.cs @@ -2,6 +2,8 @@ { public interface IModInputHandler { + IModInputTextures Textures { get; } + IModInputCombination RegisterCombination(IModBehaviour mod, string name, string combination); void UnregisterCombination(IModInputCombination combo); bool IsPressedExact(IModInputCombination combo); diff --git a/OWML.Common/IModInputTextures.cs b/OWML.Common/IModInputTextures.cs new file mode 100644 index 00000000..715146fc --- /dev/null +++ b/OWML.Common/IModInputTextures.cs @@ -0,0 +1,10 @@ +using UnityEngine; + +namespace OWML.Common +{ + public interface IModInputTextures + { + Texture2D KeyTexture(string key); + Texture2D KeyTexture(KeyCode key); + } +} diff --git a/OWML.Common/OWML.Common.csproj b/OWML.Common/OWML.Common.csproj index fda96cf7..5c1c9ad7 100644 --- a/OWML.Common/OWML.Common.csproj +++ b/OWML.Common/OWML.Common.csproj @@ -39,6 +39,7 @@ + diff --git a/OWML.Launcher/App.cs b/OWML.Launcher/App.cs index ae0b4861..e04ed595 100644 --- a/OWML.Launcher/App.cs +++ b/OWML.Launcher/App.cs @@ -21,6 +21,8 @@ public class App private readonly OWPatcher _owPatcher; private readonly VRPatcher _vrPatcher; + private const string VrArgument = " -vrmode openvr"; + public App(IOwmlConfig owmlConfig, IModManifest owmlManifest, IModConsole writer, IModFinder modFinder, OutputListener listener, PathFinder pathFinder, OWPatcher owPatcher, VRPatcher vrPatcher) { @@ -55,9 +57,10 @@ public void Run(string[] args) ShowModList(mods); - PatchGame(mods); + var hasVrMod = HasVrMod(mods); + PatchGame(hasVrMod); - StartGame(args); + StartGame(args, hasVrMod); if (hasPortArgument) { @@ -142,13 +145,18 @@ private void OnOutput(string s) } } - private void PatchGame(IList mods) + private bool HasVrMod(IList mods) { - _owPatcher.PatchGame(); - var vrMod = mods.FirstOrDefault(x => x.Config.RequireVR && x.Config.Enabled); - var enableVR = vrMod != null; - _writer.WriteLine(enableVR ? $"{vrMod.Manifest.UniqueName} requires VR." : "No mods require VR."); + var hasVrMod = vrMod != null; + _writer.WriteLine(hasVrMod ? $"{vrMod.Manifest.UniqueName} requires VR." : "No mods require VR."); + return hasVrMod; + } + + private void PatchGame(bool enableVR) + { + _owPatcher.PatchGame(); + _vrPatcher.PatchVR(enableVR); try { _vrPatcher.PatchVR(enableVR); @@ -159,12 +167,17 @@ private void PatchGame(IList mods) } } - private void StartGame(string[] args) + private void StartGame(string[] args, bool enableVR) { _writer.WriteLine("Starting game..."); try { - Process.Start($"{_owmlConfig.GamePath}/OuterWilds.exe", string.Join(" ", args)); + var gameArgs = string.Join(" ", args); + if (enableVR) + { + gameArgs += VrArgument; + } + Process.Start($"{_owmlConfig.GamePath}/OuterWilds.exe", gameArgs); } catch (Exception ex) { diff --git a/OWML.Launcher/OWML.Config.json b/OWML.Launcher/OWML.Config.json index 49b33d21..67380528 100644 --- a/OWML.Launcher/OWML.Config.json +++ b/OWML.Launcher/OWML.Config.json @@ -1,5 +1,5 @@ { "gamePath": "C:/Program Files/Epic Games/OuterWilds", "verbose": false, - "combinationsBlockInput" : false + "combinationsBlockInput": false } diff --git a/OWML.Launcher/OWML.Manifest.json b/OWML.Launcher/OWML.Manifest.json index 93c8391d..db34c053 100644 --- a/OWML.Launcher/OWML.Manifest.json +++ b/OWML.Launcher/OWML.Manifest.json @@ -2,6 +2,6 @@ "author": "Alek", "name": "OWML", "uniqueName": "Alek.OWML", - "version": "0.3.57", + "version": "0.4.0", "description": "The mod loader and mod framework for Outer Wilds" } diff --git a/OWML.ModHelper.Assets/ModAssets.cs b/OWML.ModHelper.Assets/ModAssets.cs index 80ca911e..e2324746 100644 --- a/OWML.ModHelper.Assets/ModAssets.cs +++ b/OWML.ModHelper.Assets/ModAssets.cs @@ -93,7 +93,7 @@ private IEnumerator LoadMesh(ObjectAsset modAsset, string objectPath) var meshFilter = modAsset.AddComponent(); meshFilter.mesh = mesh; yield return new WaitForEndOfFrame(); - modAsset.SetAsset(modAsset.gameObject); + modAsset.SetMeshFilter(meshFilter); } private IEnumerator LoadTexture(ObjectAsset modAsset, string imagePath) @@ -111,6 +111,7 @@ private IEnumerator LoadTexture(ObjectAsset modAsset, string imagePath) } var meshRenderer = modAsset.AddComponent(); meshRenderer.material.mainTexture = texture; + modAsset.SetMeshRenderer(meshRenderer); } private IEnumerator LoadMesh(MeshAsset modAsset, string objectPath) diff --git a/OWML.ModHelper.Assets/ObjectAsset.cs b/OWML.ModHelper.Assets/ObjectAsset.cs index 659010f3..349e8d90 100644 --- a/OWML.ModHelper.Assets/ObjectAsset.cs +++ b/OWML.ModHelper.Assets/ObjectAsset.cs @@ -4,5 +4,28 @@ namespace OWML.ModHelper.Assets { public class ObjectAsset : ModAsset { + private MeshRenderer _meshRenderer; + private MeshFilter _meshFilter; + + public void SetMeshRenderer(MeshRenderer meshRenderer) + { + _meshRenderer = meshRenderer; + SetAssetIfComplete(); + } + + public void SetMeshFilter(MeshFilter meshFilter) + { + _meshFilter = meshFilter; + SetAssetIfComplete(); + } + + private void SetAssetIfComplete() + { + if (_meshFilter != null && _meshRenderer != null) + { + SetAsset(gameObject); + } + } + } } diff --git a/OWML.ModHelper.Events/HarmonyHelper.cs b/OWML.ModHelper.Events/HarmonyHelper.cs index 5f8dc4aa..0f4c1ffe 100644 --- a/OWML.ModHelper.Events/HarmonyHelper.cs +++ b/OWML.ModHelper.Events/HarmonyHelper.cs @@ -42,16 +42,21 @@ private HarmonyInstance CreateInstance() private MethodInfo GetMethod(string methodName) { var targetType = typeof(T); + MethodInfo result = null; try { _logger.Log($"Getting method {methodName} of {targetType.Name}"); - return targetType.GetAnyMethod(methodName); + result = targetType.GetAnyMethod(methodName); } catch (Exception ex) { _console.WriteLine($"Exception while getting method {methodName} of {targetType.Name}: {ex}"); - return null; } + if (result == null) + { + _console.WriteLine($"Error: Original method {methodName} of class {targetType} not found"); + } + return result; } public void AddPrefix(string methodName, Type patchType, string patchMethodName) diff --git a/OWML.ModHelper.Events/ModEvents.cs b/OWML.ModHelper.Events/ModEvents.cs index fa84a336..8256a67e 100644 --- a/OWML.ModHelper.Events/ModEvents.cs +++ b/OWML.ModHelper.Events/ModEvents.cs @@ -56,7 +56,7 @@ private void SubscribeToEvent(Common.Events ev) var type = typeof(T); if (IsSubscribedTo(type, ev)) { - _console.WriteLine($"Warning: already subscribed to {ev} of {type.Name}"); + _logger.Log($"Already subscribed to {ev} of {type.Name}"); return; } AddToEventList(_subscribedEvents, type, ev); diff --git a/OWML.ModHelper.Input/BindingChangeListener.cs b/OWML.ModHelper.Input/BindingChangeListener.cs index e467cf19..1af6c104 100644 --- a/OWML.ModHelper.Input/BindingChangeListener.cs +++ b/OWML.ModHelper.Input/BindingChangeListener.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.ObjectModel; -using System.Collections.Generic; -using OWML.Common; +using OWML.Common; using UnityEngine; namespace OWML.ModHelper.Input @@ -14,13 +11,13 @@ public class BindingChangeListener : MonoBehaviour internal void Initialize(ModInputHandler inputHandler, IModEvents events) { _inputHandler = inputHandler; - events.Subscribe(Common.Events.AfterStart); + events.Subscribe(Events.AfterStart); events.OnEvent += OnEvent; } - private void OnEvent(MonoBehaviour behaviour, Common.Events ev) + private void OnEvent(MonoBehaviour behaviour, Events ev) { - if (behaviour.GetType() == typeof(TitleScreenManager) && ev == Common.Events.AfterStart) + if (behaviour.GetType() == typeof(TitleScreenManager) && ev == Events.AfterStart) { _updateInputsNext = true; } @@ -29,7 +26,7 @@ private void OnEvent(MonoBehaviour behaviour, Common.Events ev) private void Start() { DontDestroyOnLoad(gameObject); - GlobalMessenger.AddListener("KeyBindingsChanged", new Callback(PrepareForUpdate)); + GlobalMessenger.AddListener("KeyBindingsChanged", PrepareForUpdate); } private void PrepareForUpdate() diff --git a/OWML.ModHelper.Input/ModInputCombination.cs b/OWML.ModHelper.Input/ModInputCombination.cs index 6ca5bc96..6bf3213d 100644 --- a/OWML.ModHelper.Input/ModInputCombination.cs +++ b/OWML.ModHelper.Input/ModInputCombination.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.Collections.Generic; using OWML.Common; using UnityEngine; @@ -8,11 +7,6 @@ namespace OWML.ModHelper.Input { public class ModInputCombination : IModInputCombination { - private const int GamePadKeyDiff = 20; - private const int MaxUsefulKey = 350; - private const int MaxComboLength = 7; - private const string XboxPrefix = "xbox_"; - public float LastPressedMoment { get; private set; } public bool IsFirst { get; private set; } public float PressDuration => LastPressedMoment - _firstPressedMoment; @@ -24,8 +18,8 @@ public class ModInputCombination : IModInputCombination private bool _isPressed; private float _firstPressedMoment; - private List _singles = new List(); - private List _hashes = new List(); + private readonly List _singles = new List(); + private readonly List _hashes; internal ModInputCombination(IModManifest mod, string name, string combination) { @@ -34,104 +28,12 @@ internal ModInputCombination(IModManifest mod, string name, string combination) _hashes = StringToHashes(combination); } - private KeyCode StringToKeyCodeKeyboard(string keyboardKey) - { - if (keyboardKey == "control" || keyboardKey == "ctrl") - { - return KeyCode.LeftControl; - } - if (keyboardKey == "shift") - { - return KeyCode.LeftShift; - } - if (keyboardKey == "alt") - { - return KeyCode.LeftAlt; - } - var code = (KeyCode)Enum.Parse(typeof(KeyCode), keyboardKey, true); - return Enum.IsDefined(typeof(KeyCode), code) ? code : KeyCode.None; - } - - private KeyCode StringToKeyCodeGamepad(string gamepadKey) - { - var gamepadcodeCode = (JoystickButton)Enum.Parse(typeof(JoystickButton), gamepadKey, true); - return (Enum.IsDefined(typeof(JoystickButton), gamepadcodeCode)) ? - InputTranslator.GetButtonKeyCode(gamepadcodeCode) : KeyCode.None; - } - - private KeyCode StringToKeyCodeXbox(string xboxKey) - { - switch (xboxKey[0]) - { - case 'A': - return InputTranslator.GetButtonKeyCode(JoystickButton.FaceDown); - case 'B': - return InputTranslator.GetButtonKeyCode(JoystickButton.FaceRight); - case 'X': - return InputTranslator.GetButtonKeyCode(JoystickButton.FaceLeft); - case 'Y': - return InputTranslator.GetButtonKeyCode(JoystickButton.FaceUp); - default: - return StringToKeyCodeGamepad(xboxKey); - } - } - - private KeyCode StringToKeyCode(string key) - { - var trimmedKey = key.Trim(); - return trimmedKey.Contains(XboxPrefix) ? StringToKeyCodeXbox(trimmedKey.Substring(XboxPrefix.Length)) - : StringToKeyCodeKeyboard(trimmedKey); - } - - private int[] StringToKeyArray(string stringCombination) - { - var keyCombination = new int[MaxComboLength]; - var i = 0; - foreach (var key in stringCombination.Trim().ToLower().Split('+')) - { - var code = StringToKeyCode(key); - if ((int)code >= MaxUsefulKey) - { - code -= (((int)code - MaxUsefulKey + GamePadKeyDiff) / GamePadKeyDiff) * GamePadKeyDiff; - } - if (code == KeyCode.None) - { - keyCombination[0] = (int)RegistrationCode.InvalidCombination; - return keyCombination; - } - if (i >= MaxComboLength) - { - keyCombination[0] = (int)RegistrationCode.CombinationTooLong; - return keyCombination; - } - keyCombination[i] = (int)code; - i++; - } - Array.Sort(keyCombination); - return keyCombination; - } - - private long StringToHash(string stringCombination) - { - var keyCombination = StringToKeyArray(stringCombination); - if (keyCombination[0] < 0) - { - return keyCombination[0]; - } - long hash = 0; - for (var i = 0; i < MaxComboLength; i++) - { - hash = hash * MaxUsefulKey + keyCombination[i]; - } - return hash; - } - private List StringToHashes(string combinations) { var hashes = new List(); foreach (var combo in combinations.Split('/')) { - var hash = StringToHash(combo); + var hash = ModInputLibrary.StringToHash(combo); if (hash <= 0) { hashes.Clear(); @@ -139,10 +41,10 @@ private List StringToHashes(string combinations) return hashes; } hashes.Add(hash); - if (hash < MaxUsefulKey) + if (hash < ModInputLibrary.MaxUsefulKey) { _singles.Add((KeyCode)hash); - } + } } return hashes; } @@ -161,4 +63,4 @@ public void InternalSetPressed(bool isPressed = true) _isPressed = isPressed; } } -} +} \ No newline at end of file diff --git a/OWML.ModHelper.Input/ModInputHandler.cs b/OWML.ModHelper.Input/ModInputHandler.cs index b3a1f1eb..4148c496 100644 --- a/OWML.ModHelper.Input/ModInputHandler.cs +++ b/OWML.ModHelper.Input/ModInputHandler.cs @@ -5,7 +5,6 @@ using System.Linq; using OWML.Common; using UnityEngine; -using System.Security.Policy; namespace OWML.ModHelper.Input { @@ -13,28 +12,30 @@ public class ModInputHandler : IModInputHandler { private const float Cooldown = 0.05f; private const float TapDuration = 0.1f; - private const int MinUsefulKey = 8; - private const int MaxUsefulKey = 350; - private const int MaxComboLength = 7; - private const int GamePadKeyDiff = 20; private const BindingFlags NonPublic = BindingFlags.NonPublic | BindingFlags.Instance; internal static ModInputHandler Instance { get; private set; } - private HashSet _singlesPressed = new HashSet(); - private Dictionary _comboRegistry = new Dictionary(); - private HashSet _gameBindingRegistry = new HashSet(); - private HashSet _toResetOnNextFrame = new HashSet(); - private float[] _timeout = new float[MaxUsefulKey]; - private int[] _gameBindingCounter = new int[MaxUsefulKey]; + private readonly HashSet _singlesPressed = new HashSet(); + private readonly Dictionary _comboRegistry = new Dictionary(); + private readonly HashSet _gameBindingRegistry = new HashSet(); + private readonly HashSet _toResetOnNextFrame = new HashSet(); + private readonly float[] _timeout = new float[ModInputLibrary.MaxUsefulKey]; + private readonly int[] _gameBindingCounter = new int[ModInputLibrary.MaxUsefulKey]; private IModInputCombination _currentCombination; private int _lastSingleUpdate; private int _lastCombinationUpdate; private readonly IModLogger _logger; private readonly IModConsole _console; + public IModInputTextures Textures { get; } + public ModInputHandler(IModLogger logger, IModConsole console, IHarmonyHelper patcher, IOwmlConfig owmlConfig, IModEvents events) { + var textures = new ModInputTextures(); + textures.FillTextureLibrary(); + Textures = textures; + _console = console; _logger = logger; @@ -50,15 +51,13 @@ public ModInputHandler(IModLogger logger, IModConsole console, IHarmonyHelper pa Instance = this; } - internal bool IsPressedAndIgnored(KeyCode code) + internal bool IsPressedAndIgnored(KeyCode key) { UpdateCurrentCombination(); - var intKey = (int)code; - if ((int)code >= MaxUsefulKey) - { - intKey -= ((intKey - MaxUsefulKey + GamePadKeyDiff) / GamePadKeyDiff) * GamePadKeyDiff; - } - return UnityEngine.Input.GetKey(code) && _currentCombination != null && Time.realtimeSinceStartup - _timeout[intKey] < Cooldown; + var cleanKey = ModInputLibrary.NormalizeKeyCode(key); + return UnityEngine.Input.GetKey(cleanKey) && + _currentCombination != null && + Time.realtimeSinceStartup - _timeout[(int)cleanKey] < Cooldown; } private long? HashFromKeyboard() @@ -66,18 +65,18 @@ internal bool IsPressedAndIgnored(KeyCode code) long hash = 0; var keysCount = 0; var countdownTrigger = true; - for (var code = MinUsefulKey; code < MaxUsefulKey; code++) + for (var code = ModInputLibrary.MinUsefulKey; code < ModInputLibrary.MaxUsefulKey; code++) { if (!(Enum.IsDefined(typeof(KeyCode), (KeyCode)code) && UnityEngine.Input.GetKey((KeyCode)code))) { continue; } keysCount++; - if (keysCount > MaxComboLength) + if (keysCount > ModInputLibrary.MaxComboLength) { return null; } - hash = hash * MaxUsefulKey + code; + hash = hash * ModInputLibrary.MaxUsefulKey + code; if (Time.realtimeSinceStartup - _timeout[code] > Cooldown) { countdownTrigger = false; @@ -94,7 +93,7 @@ private IModInputCombination CombinationFromKeyboard() { return null; } - long hash = (long)nullableHash; + var hash = (long)nullableHash; if (hash < 0) { countdownTrigger = true; @@ -106,19 +105,19 @@ private IModInputCombination CombinationFromKeyboard() } var combination = _comboRegistry[hash]; - if (!(combination == _currentCombination) && countdownTrigger) + if (combination != _currentCombination && countdownTrigger) { return null; } - if (hash < MaxUsefulKey) + if (hash < ModInputLibrary.MaxUsefulKey) { return combination; } while (hash > 0) { - _timeout[hash % MaxUsefulKey] = Time.realtimeSinceStartup; - hash /= MaxUsefulKey; + _timeout[hash % ModInputLibrary.MaxUsefulKey] = Time.realtimeSinceStartup; + hash /= ModInputLibrary.MaxUsefulKey; } return combination; } @@ -162,31 +161,24 @@ public bool IsPressedExact(IModInputCombination combination) public bool IsNewlyPressedExact(IModInputCombination combination) { - if (combination == null) - { - return false; - } - return IsPressedExact(combination) && combination.IsFirst; + return combination != null && + IsPressedExact(combination) && + combination.IsFirst; } public bool WasTappedExact(IModInputCombination combination) { - if (combination == null) - { - return false; - } - return !IsPressedExact(combination) - && (combination.PressDuration < TapDuration) - && combination.IsFirst; + return combination != null && + !IsPressedExact(combination) && + combination.PressDuration < TapDuration && + combination.IsFirst; } public bool WasNewlyReleasedExact(IModInputCombination combination) { - if (combination == null) - { - return false; - } - return !IsPressedExact(combination) && combination.IsFirst; + return combination != null && + !IsPressedExact(combination) && + combination.IsFirst; } private void UpdateSinglesPressed() @@ -222,93 +214,78 @@ private bool IsPressedSingle(IModInputCombination combination) { return true; } - foreach (var key in combination.Singles) + var single = combination.Singles.FirstOrDefault(key => UnityEngine.Input.GetKey(key) && !IsPressedAndIgnored(key)); + if (single == 0) { - if (UnityEngine.Input.GetKey(key) && !IsPressedAndIgnored(key)) - { - _singlesPressed.Add(combination); - combination.InternalSetPressed(); - return true; - } + return false; } - return false; + _singlesPressed.Add(combination); + combination.InternalSetPressed(); + return true; } public bool IsPressed(IModInputCombination combination) { - if (combination == null) - { - return false; - } - return IsPressedExact(combination) || IsPressedSingle(combination); + return combination != null && + (IsPressedExact(combination) || IsPressedSingle(combination)); } public bool IsNewlyPressed(IModInputCombination combination) { - if (combination == null) - { - return false; - } - return IsPressed(combination) && combination.IsFirst; + return combination != null && + IsPressed(combination) && + combination.IsFirst; } public bool WasTapped(IModInputCombination combination) { - if (combination == null) - { - return false; - } - return (!IsPressed(combination)) && (combination.PressDuration < TapDuration) - && combination.IsFirst; + return combination != null && + !IsPressed(combination) && + combination.PressDuration < TapDuration && + combination.IsFirst; } public bool WasNewlyReleased(IModInputCombination combination) { - if (combination == null) - { - return false; - } - return (!IsPressed(combination)) && combination.IsFirst; + return combination != null && + !IsPressed(combination) && + combination.IsFirst; } private RegistrationCode SwapCombination(IModInputCombination combination, bool toUnregister) { - bool taken = false; + var taken = false; if (combination.Hashes[0] <= 0) { return (RegistrationCode)combination.Hashes[0]; } - foreach (long hash in combination.Hashes) + foreach (var hash in combination.Hashes) { if (toUnregister) { _comboRegistry.Remove(hash); continue; } - if (_comboRegistry.ContainsKey(hash) || (hash < MaxUsefulKey && _gameBindingCounter[hash] > 0)) + if (_comboRegistry.ContainsKey(hash) || hash < ModInputLibrary.MaxUsefulKey && _gameBindingCounter[hash] > 0) { taken = true; continue; } _comboRegistry.Add(hash, combination); } - if (taken) - { - return RegistrationCode.CombinationTaken; - } - return RegistrationCode.AllNormal; + return taken ? RegistrationCode.CombinationTaken : RegistrationCode.AllNormal; } private List GetCollisions(ReadOnlyCollection hashes) { - List combos = new List(); - foreach (long hash in hashes) + var combos = new List(); + foreach (var hash in hashes) { if (_comboRegistry.ContainsKey(hash)) { combos.Add(_comboRegistry[hash].FullName); } - if (hash < MaxUsefulKey && _gameBindingCounter[hash] > 0) + if (hash < ModInputLibrary.MaxUsefulKey && _gameBindingCounter[hash] > 0) { combos.Add("Outer Wilds." + Enum.GetName(typeof(KeyCode), (KeyCode)hash)); } @@ -316,6 +293,21 @@ private List GetCollisions(ReadOnlyCollection hashes) return combos; } + public List GetCollisions(string combinations) + { + var hashes = new List(); + foreach (var combo in combinations.Split('/')) + { + var hash = ModInputLibrary.StringToHash(combo); + if (hash <= 0) + { + return new List { ((RegistrationCode)(-hash)).ToString() }; + } + hashes.Add(hash); + } + return GetCollisions(hashes.AsReadOnly()); + } + public IModInputCombination RegisterCombination(IModBehaviour mod, string name, string combination) { var combo = new ModInputCombination(mod.ModHelper.Manifest, name, combination); @@ -330,7 +322,7 @@ public IModInputCombination RegisterCombination(IModBehaviour mod, string name, case RegistrationCode.CombinationTaken: _console.WriteLine($"Failed to register \"{combo.FullName}\": already in use by following mods:"); var collisions = GetCollisions(combo.Hashes); - foreach (string collision in collisions) + foreach (var collision in collisions) { _console.WriteLine($"\"{collision}\""); } @@ -358,7 +350,7 @@ public void UnregisterCombination(IModInputCombination combination) _console.WriteLine($"Failed to unregister \"{combination.FullName}\": too long!"); return; case RegistrationCode.AllNormal: - _logger.Log($"succesfully unregistered \"{combination.FullName}\""); + _logger.Log($"Successfully unregistered \"{combination.FullName}\""); return; default: return; @@ -367,29 +359,20 @@ public void UnregisterCombination(IModInputCombination combination) internal void SwapGamesBinding(InputCommand binding, bool toUnregister) { - if ((_gameBindingRegistry.Contains(binding) ^ toUnregister) || binding == null) + if (_gameBindingRegistry.Contains(binding) ^ toUnregister || binding == null) { return; } var fields = binding is SingleAxisCommand ? - typeof(SingleAxisCommand).GetFields(NonPublic) : typeof(DoubleAxisCommand).GetFields(NonPublic); - foreach (var field in fields) + typeof(SingleAxisCommand).GetFields(NonPublic) : + typeof(DoubleAxisCommand).GetFields(NonPublic); + foreach (var field in fields.Where(x => x.FieldType == typeof(List))) { - if (field.FieldType == typeof(List)) + var keys = (List)field.GetValue(binding); + foreach (var key in keys.Where(x => x != KeyCode.None)) { - var keys = (List)(field.GetValue(binding)); - foreach (var key in keys) - { - if (key != KeyCode.None) - { - var intKey = (int)key; - if ((int)key >= MaxUsefulKey) - { - intKey -= ((intKey - MaxUsefulKey + GamePadKeyDiff) / GamePadKeyDiff) * GamePadKeyDiff; - } - _gameBindingCounter[intKey] += toUnregister ? -1 : 1; - } - } + var intKey = (int)ModInputLibrary.NormalizeKeyCode(key); + _gameBindingCounter[intKey] += toUnregister ? -1 : 1; } } if (toUnregister) @@ -414,10 +397,9 @@ internal void UnregisterGamesBinding(InputCommand binding) internal void UpdateGamesBindings() { - Array.ForEach(_gameBindingCounter, x => x = 0); _gameBindingRegistry.Clear(); var inputCommands = typeof(InputLibrary).GetFields(BindingFlags.Public | BindingFlags.Static); - Array.ForEach(inputCommands, field => RegisterGamesBinding(field.GetValue(null) as InputCommand)); + inputCommands.ToList().ForEach(field => RegisterGamesBinding(field.GetValue(null) as InputCommand)); } } -} +} \ No newline at end of file diff --git a/OWML.ModHelper.Input/ModInputLibrary.cs b/OWML.ModHelper.Input/ModInputLibrary.cs new file mode 100644 index 00000000..63cb2577 --- /dev/null +++ b/OWML.ModHelper.Input/ModInputLibrary.cs @@ -0,0 +1,139 @@ +using System; +using UnityEngine; + +namespace OWML.ModHelper.Input +{ + public static class ModInputLibrary + { + public const float ScaleDown = 0.75f; + public const string XboxPrefix = "xbox_"; + public const int MinUsefulKey = 8; + public const int MinGamepadKey = 330; + public const int MaxUsefulKey = 350; + public const int MaxComboLength = 7; + public const int GamePadKeyDiff = 20; + + public static KeyCode NormalizeKeyCode(KeyCode key) + { + if ((int)key >= MaxUsefulKey) + { + key -= ((int)key - MaxUsefulKey + GamePadKeyDiff) / GamePadKeyDiff * GamePadKeyDiff; + } + return key; + } + + public static JoystickButton XboxButtonToJoystickButton(string xboxKey) + { + switch (xboxKey[0]) + { + case 'a': + return JoystickButton.FaceDown; + case 'b': + return JoystickButton.FaceRight; + case 'x': + return JoystickButton.FaceLeft; + case 'y': + return JoystickButton.FaceUp; + default: + var code = (JoystickButton)Enum.Parse(typeof(JoystickButton), xboxKey); + return Enum.IsDefined(typeof(JoystickButton), code) ? code : JoystickButton.None; + } + } + + public static string JoystickButtonToXboxButton(JoystickButton key) + { + switch (key) + { + case JoystickButton.FaceDown: + return "a"; + case JoystickButton.FaceRight: + return "b"; + case JoystickButton.FaceLeft: + return "x"; + case JoystickButton.FaceUp: + return "y"; + default: + return key.ToString(); + } + } + + private static KeyCode StringToKeyCodeKeyboard(string keyboardKey) + { + switch (keyboardKey) + { + case "control": + case "ctrl": + return KeyCode.LeftControl; + case "shift": + return KeyCode.LeftShift; + case "alt": + return KeyCode.LeftAlt; + default: + var code = (KeyCode)Enum.Parse(typeof(KeyCode), keyboardKey, true); + return Enum.IsDefined(typeof(KeyCode), code) ? code : KeyCode.None; + } + } + + private static KeyCode StringToKeyCodeGamepad(string xboxKey) + { + var gamepadCode = XboxButtonToJoystickButton(xboxKey); + return gamepadCode == JoystickButton.None ? KeyCode.None : NormalizeKeyCode(InputTranslator.GetButtonKeyCode(gamepadCode)); + } + + public static KeyCode StringToKeyCode(string key) + { + var trimmedKey = key.Trim(); + return trimmedKey.Contains(XboxPrefix) ? + StringToKeyCodeGamepad(trimmedKey.Substring(XboxPrefix.Length)) : + StringToKeyCodeKeyboard(trimmedKey); + } + + private static int[] StringToKeyArray(string stringCombination) + { + var keyCombination = new int[MaxComboLength]; + var i = 0; + foreach (var key in stringCombination.Trim().ToLower().Split('+')) + { + var code = StringToKeyCode(key); + if (code == KeyCode.None) + { + keyCombination[0] = (int)RegistrationCode.InvalidCombination; + return keyCombination; + } + if (i >= MaxComboLength) + { + keyCombination[0] = (int)RegistrationCode.CombinationTooLong; + return keyCombination; + } + keyCombination[i] = (int)code; + i++; + } + Array.Sort(keyCombination); + return keyCombination; + } + + internal static long StringToHash(string stringCombination) + { + var keyCombination = StringToKeyArray(stringCombination); + if (keyCombination[0] < 0) + { + return keyCombination[0]; + } + long hash = 0; + for (var i = 0; i < MaxComboLength; i++) + { + hash = hash * MaxUsefulKey + keyCombination[i]; + } + return hash; + } + + public static string KeyCodeToString(KeyCode key) + { + var config = OWInput.GetActivePadConfig() ?? InputUtil.GamePadConfig_Xbox; + key = NormalizeKeyCode(key); + return (int)key >= MinGamepadKey ? + XboxPrefix + JoystickButtonToXboxButton(InputTranslator.ConvertKeyCodeToButton(key, config)) : + key.ToString(); + } + } +} \ No newline at end of file diff --git a/OWML.ModHelper.Input/ModInputTextures.cs b/OWML.ModHelper.Input/ModInputTextures.cs new file mode 100644 index 00000000..a97dccb1 --- /dev/null +++ b/OWML.ModHelper.Input/ModInputTextures.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using OWML.Common; + +namespace OWML.ModHelper.Input +{ + public class ModInputTextures : IModInputTextures + { + private Dictionary _loadedTextures; + + internal void FillTextureLibrary() + { + _loadedTextures = new Dictionary(); + var config = OWInput.GetActivePadConfig() ?? InputUtil.GamePadConfig_Xbox; + for (var code = ModInputLibrary.MinUsefulKey; code < ModInputLibrary.MaxUsefulKey; code++) + { + var key = (KeyCode)code; + if (!Enum.IsDefined(typeof(KeyCode), key)) + { + continue; + } + var keyName = ModInputLibrary.KeyCodeToString(key); + if (_loadedTextures.ContainsKey(keyName)) + { + continue; + } + var toStore = (int)key >= ModInputLibrary.MinGamepadKey ? + ButtonPromptLibrary.SharedInstance.GetButtonTexture(InputTranslator.ConvertKeyCodeToButton(key, config)) : + ButtonPromptLibrary.SharedInstance.GetButtonTexture(key); + _loadedTextures.Add(keyName, toStore); + } + } + + public Texture2D KeyTexture(string key) + { + return KeyTexture(ModInputLibrary.StringToKeyCode(key)); + } + + public Texture2D KeyTexture(KeyCode key) + { + if (_loadedTextures == null) + { + FillTextureLibrary(); + } + var keyName = ModInputLibrary.KeyCodeToString(key); + if (_loadedTextures.ContainsKey(keyName)) + { + return _loadedTextures[keyName]; + } + var config = OWInput.GetActivePadConfig() ?? InputUtil.GamePadConfig_Xbox; + var toStore = (int)key >= ModInputLibrary.MinGamepadKey ? + ButtonPromptLibrary.SharedInstance.GetButtonTexture(InputTranslator.ConvertKeyCodeToButton(key, config)) : + ButtonPromptLibrary.SharedInstance.GetButtonTexture(key); + _loadedTextures.Add(keyName, toStore); + return toStore; + } + } +} \ No newline at end of file diff --git a/OWML.ModHelper.Input/OWML.ModHelper.Input.csproj b/OWML.ModHelper.Input/OWML.ModHelper.Input.csproj index 7d44b013..8e0df88b 100644 --- a/OWML.ModHelper.Input/OWML.ModHelper.Input.csproj +++ b/OWML.ModHelper.Input/OWML.ModHelper.Input.csproj @@ -76,6 +76,8 @@ + + diff --git a/OWML.ModHelper.Interaction/InterfaceProxyBuilder.cs b/OWML.ModHelper.Interaction/InterfaceProxyBuilder.cs index 1a255a11..9340eb47 100644 --- a/OWML.ModHelper.Interaction/InterfaceProxyBuilder.cs +++ b/OWML.ModHelper.Interaction/InterfaceProxyBuilder.cs @@ -9,16 +9,19 @@ namespace OWML.ModHelper.Interaction { internal class InterfaceProxyBuilder { - private readonly Type TargetType; - - private readonly Type ProxyType; + private readonly Type _targetType; + private readonly Type _proxyType; public InterfaceProxyBuilder(string name, ModuleBuilder moduleBuilder, Type interfaceType, Type targetType) { if (name == null) + { throw new ArgumentNullException(nameof(name)); + } if (targetType == null) + { throw new ArgumentNullException(nameof(targetType)); + } var proxyBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Class); proxyBuilder.AddInterfaceImplementation(interfaceType); @@ -31,20 +34,23 @@ public InterfaceProxyBuilder(string name, ModuleBuilder moduleBuilder, Type inte { var targetMethod = targetType.GetMethod(proxyMethod.Name, proxyMethod.GetParameters().Select(a => a.ParameterType).ToArray()); if (targetMethod == null) + { throw new InvalidOperationException($"The {interfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API."); - - this.ProxyMethod(proxyBuilder, targetMethod, targetField); + } + ProxyMethod(proxyBuilder, targetMethod, targetField); } - this.TargetType = targetType; - this.ProxyType = proxyBuilder.CreateType(); + _targetType = targetType; + _proxyType = proxyBuilder.CreateType(); } public object CreateInstance(object targetInstance) { - var constructor = this.ProxyType.GetConstructor(new[] { this.TargetType }); + var constructor = _proxyType.GetConstructor(new[] { _targetType }); if (constructor == null) - throw new InvalidOperationException($"Couldn't find the constructor for generated proxy type '{this.ProxyType.Name}'."); // should never happen + { + throw new InvalidOperationException($"Couldn't find the constructor for generated proxy type '{_proxyType.Name}'."); // should never happen + } return constructor.Invoke(new[] { targetInstance }); } @@ -68,8 +74,10 @@ private void CreateProxyMethodBody(MethodBuilder methodBuilder, MethodInfo targe il.Emit(OpCodes.Ldfld, instanceField); // invoke target method on instance - for (int i = 0; i < argTypes.Length; i++) + for (var i = 0; i < argTypes.Length; i++) + { il.Emit(OpCodes.Ldarg, i + 1); + } il.Emit(OpCodes.Call, target); // return result diff --git a/OWML.ModHelper.Interaction/InterfaceProxyFactory.cs b/OWML.ModHelper.Interaction/InterfaceProxyFactory.cs index e83b5d2b..eb30e045 100644 --- a/OWML.ModHelper.Interaction/InterfaceProxyFactory.cs +++ b/OWML.ModHelper.Interaction/InterfaceProxyFactory.cs @@ -9,29 +9,34 @@ namespace OWML.ModHelper.Interaction { public class InterfaceProxyFactory { - private readonly ModuleBuilder ModuleBuilder; - - private readonly IDictionary Builders = new Dictionary(); + private readonly ModuleBuilder _moduleBuilder; + private readonly IDictionary _builders = new Dictionary(); public InterfaceProxyFactory() { - var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName($"OWMLInteraction.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run); - this.ModuleBuilder = assemblyBuilder.DefineDynamicModule("OWMLInteraction.Proxies"); + var assemblyName = new AssemblyName($"OWMLInteraction.Proxies, Version={GetType().Assembly.GetName().Version}, Culture=neutral"); + var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + _moduleBuilder = assemblyBuilder.DefineDynamicModule("OWMLInteraction.Proxies"); } - public TInterface CreateProxy(object instance, string sourceModID, string targetModID) where TInterface : class + public TInterface CreateProxy(object instance, string sourceModName, string targetModName) where TInterface : class { if (instance == null) + { throw new InvalidOperationException("Can't proxy access to a null API."); + } if (!typeof(TInterface).IsInterface) + { throw new InvalidOperationException("The proxy type must be an interface, not a class."); + } var targetType = instance.GetType(); - var proxyTypeName = $"OWMLInteraction.Proxies.From<{sourceModID}_{typeof(TInterface).FullName}>_To<{targetModID}_{targetType.FullName}>"; - if (!this.Builders.TryGetValue(proxyTypeName, out InterfaceProxyBuilder builder)) + + var proxyTypeName = $"OWMLInteraction.Proxies.From<{sourceModName}_{typeof(TInterface).FullName}>_To<{targetModName}_{targetType.FullName}>"; + if (!_builders.TryGetValue(proxyTypeName, out InterfaceProxyBuilder builder)) { - builder = new InterfaceProxyBuilder(proxyTypeName, this.ModuleBuilder, typeof(TInterface), targetType); - this.Builders[proxyTypeName] = builder; + builder = new InterfaceProxyBuilder(proxyTypeName, _moduleBuilder, typeof(TInterface), targetType); + _builders[proxyTypeName] = builder; } return (TInterface)builder.CreateInstance(instance); diff --git a/OWML.ModHelper.Interaction/ModInteraction.cs b/OWML.ModHelper.Interaction/ModInteraction.cs index aed103e1..fa4b6280 100644 --- a/OWML.ModHelper.Interaction/ModInteraction.cs +++ b/OWML.ModHelper.Interaction/ModInteraction.cs @@ -7,13 +7,9 @@ namespace OWML.ModHelper.Interaction public class ModInteraction : IModInteraction { private readonly IList _modList; - private readonly InterfaceProxyFactory _proxyFactory; - private readonly IModManifest _manifest; - private Dictionary> _dependantDict = new Dictionary>(); - private Dictionary> _dependencyDict = new Dictionary>(); public ModInteraction(IList list, InterfaceProxyFactory proxyFactory, IModManifest manifest) diff --git a/OWML.ModHelper.Menus/ModComboInput.cs b/OWML.ModHelper.Menus/ModComboInput.cs index 208c533a..779b907f 100644 --- a/OWML.ModHelper.Menus/ModComboInput.cs +++ b/OWML.ModHelper.Menus/ModComboInput.cs @@ -1,25 +1,25 @@ using OWML.Common.Menus; using OWML.ModHelper.Events; +using OWML.ModHelper.Input; using UnityEngine; using UnityEngine.UI; -using System; +using OWML.Common; namespace OWML.ModHelper.Menus { public class ModComboInput : ModInput, IModComboInput { - private const float ScaleDown = 0.75f; - private const string XboxPrefix = "xbox_"; - public IModLayoutButton Button { get; } protected readonly IModInputMenu InputMenu; protected readonly TwoButtonToggleElement ToggleElement; private string _value; - private HorizontalLayoutGroup _layoutGroup; + private readonly HorizontalLayoutGroup _layoutGroup; + private readonly IModInputHandler _inputHandler; - public ModComboInput(TwoButtonToggleElement element, IModMenu menu, IModInputMenu inputMenu) : base(element, menu) + public ModComboInput(TwoButtonToggleElement element, IModMenu menu, IModInputMenu inputMenu, IModInputHandler inputHandler) : base(element, menu) { + _inputHandler = inputHandler; ToggleElement = element; InputMenu = inputMenu; Button = new ModLayoutButton(element.GetValue