diff --git a/src/BizHawk.Client.Common/Controller.cs b/src/BizHawk.Client.Common/Controller.cs index 367e0328b6..493527fa67 100644 --- a/src/BizHawk.Client.Common/Controller.cs +++ b/src/BizHawk.Client.Common/Controller.cs @@ -17,6 +17,7 @@ namespace BizHawk.Client.Common _axes[kvp.Key] = kvp.Value.Neutral; _axisRanges[kvp.Key] = kvp.Value; } + foreach (var channel in Definition.HapticsChannels) _haptics[channel] = 0; } public ControllerDefinition Definition { get; private set; } @@ -25,11 +26,17 @@ namespace BizHawk.Client.Common public int AxisValue(string name) => _axes[name]; + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() + => _haptics.Select(kvp => (kvp.Key, kvp.Value)).ToArray(); + + public void SetHapticChannelStrength(string name, int strength) => _haptics[name] = strength; + private readonly WorkingDictionary> _bindings = new WorkingDictionary>(); private readonly WorkingDictionary _buttons = new WorkingDictionary(); private readonly WorkingDictionary _axes = new WorkingDictionary(); private readonly Dictionary _axisRanges = new WorkingDictionary(); private readonly Dictionary _axisBindings = new Dictionary(); + private readonly Dictionary _haptics = new WorkingDictionary(); private readonly Dictionary _feedbackBindings = new Dictionary(); /// don't do this @@ -95,11 +102,14 @@ namespace BizHawk.Client.Common } } - public void PrepareHapticsForHost(SimpleController finalHostController, int debug) + public void PrepareHapticsForHost(SimpleController finalHostController) { foreach (var kvp in _feedbackBindings) { - finalHostController.SetHapticChannelStrength(kvp.Value.GamepadPrefix + kvp.Value.Channel, (int) ((double) debug * kvp.Value.Prescale)); + if (_haptics.TryGetValue(kvp.Key, out var strength)) + { + finalHostController.SetHapticChannelStrength(kvp.Value.GamepadPrefix + kvp.Value.Channel, (int) ((double) strength * kvp.Value.Prescale)); + } } } diff --git a/src/BizHawk.Client.Common/controllers/AutofireController.cs b/src/BizHawk.Client.Common/controllers/AutofireController.cs index cf6b3eac73..df00b8978d 100644 --- a/src/BizHawk.Client.Common/controllers/AutofireController.cs +++ b/src/BizHawk.Client.Common/controllers/AutofireController.cs @@ -44,6 +44,10 @@ namespace BizHawk.Client.Common throw new NotImplementedException(); } + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Array.Empty<(string, int)>(); + + public void SetHapticChannelStrength(string name, int strength) {} + /// /// uses the bindings to latch our own logical button state from the source controller's button state (which are assumed to be the physical side of the binding). /// this will clobber any existing data (use OR_* or other functions to layer in additional input sources) diff --git a/src/BizHawk.Client.Common/controllers/ClickyVirtualPadController.cs b/src/BizHawk.Client.Common/controllers/ClickyVirtualPadController.cs index 0a34b38747..cb9636d0d9 100644 --- a/src/BizHawk.Client.Common/controllers/ClickyVirtualPadController.cs +++ b/src/BizHawk.Client.Common/controllers/ClickyVirtualPadController.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using BizHawk.Emulation.Common; namespace BizHawk.Client.Common @@ -18,6 +19,10 @@ namespace BizHawk.Client.Common public int AxisValue(string name) => 0; + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Array.Empty<(string, int)>(); + + public void SetHapticChannelStrength(string name, int strength) {} + /// /// Call this once per frame to do the timekeeping for the hold and release /// diff --git a/src/BizHawk.Client.Common/inputAdapters/BitwiseAdapters.cs b/src/BizHawk.Client.Common/inputAdapters/BitwiseAdapters.cs index 114f9ede6b..6eddac0f04 100644 --- a/src/BizHawk.Client.Common/inputAdapters/BitwiseAdapters.cs +++ b/src/BizHawk.Client.Common/inputAdapters/BitwiseAdapters.cs @@ -1,4 +1,6 @@ -using BizHawk.Emulation.Common; +using System.Collections.Generic; + +using BizHawk.Emulation.Common; namespace BizHawk.Client.Common { @@ -20,6 +22,10 @@ namespace BizHawk.Client.Common // this works in the code because SourceOr is the autofire controller public int AxisValue(string name) => Source.AxisValue(name); + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Source.GetHapticsSnapshot(); + + public void SetHapticChannelStrength(string name, int strength) => Source.SetHapticChannelStrength(name, strength); + public IController Source { get; set; } internal IController SourceAnd { get; set; } } @@ -42,6 +48,10 @@ namespace BizHawk.Client.Common // this works in the code because SourceOr is the autofire controller public int AxisValue(string name) => Source.AxisValue(name); + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Source.GetHapticsSnapshot(); + + public void SetHapticChannelStrength(string name, int strength) => Source.SetHapticChannelStrength(name, strength); + public IController Source { get; set; } internal IController SourceXor { get; set; } } @@ -60,6 +70,10 @@ namespace BizHawk.Client.Common // this works in the code because SourceOr is the autofire controller public int AxisValue(string name) => Source.AxisValue(name); + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Source.GetHapticsSnapshot(); + + public void SetHapticChannelStrength(string name, int strength) => Source.SetHapticChannelStrength(name, strength); + public IController Source { get; set; } internal IController SourceOr { get; set; } } diff --git a/src/BizHawk.Client.Common/inputAdapters/CopyController.cs b/src/BizHawk.Client.Common/inputAdapters/CopyController.cs index 8ce44963c0..7385fbec11 100644 --- a/src/BizHawk.Client.Common/inputAdapters/CopyController.cs +++ b/src/BizHawk.Client.Common/inputAdapters/CopyController.cs @@ -1,4 +1,6 @@ -using BizHawk.Emulation.Common; +using System.Collections.Generic; + +using BizHawk.Emulation.Common; namespace BizHawk.Client.Common { @@ -13,6 +15,10 @@ namespace BizHawk.Client.Common public int AxisValue(string name) => Curr.AxisValue(name); + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Curr.GetHapticsSnapshot(); + + public void SetHapticChannelStrength(string name, int strength) => Curr.SetHapticChannelStrength(name, strength); + public IController Source { get; set; } private IController Curr => Source ?? NullController.Instance; diff --git a/src/BizHawk.Client.Common/inputAdapters/InputManager.cs b/src/BizHawk.Client.Common/inputAdapters/InputManager.cs index 949743f35d..f319b9cd30 100644 --- a/src/BizHawk.Client.Common/inputAdapters/InputManager.cs +++ b/src/BizHawk.Client.Common/inputAdapters/InputManager.cs @@ -117,8 +117,7 @@ namespace BizHawk.Client.Common if (feedbackBinds.TryGetValue(def.Name, out var fBinds)) { - const string channel = "Debug"; -// foreach (var channel in def.HapticsChannels) + foreach (var channel in def.HapticsChannels) { if (fBinds.TryGetValue(channel, out var bind)) ret.BindFeedbackChannel(channel, bind); } diff --git a/src/BizHawk.Client.Common/inputAdapters/OverrideAdapter.cs b/src/BizHawk.Client.Common/inputAdapters/OverrideAdapter.cs index 2785a0ddc8..02cd96d7ca 100644 --- a/src/BizHawk.Client.Common/inputAdapters/OverrideAdapter.cs +++ b/src/BizHawk.Client.Common/inputAdapters/OverrideAdapter.cs @@ -33,6 +33,10 @@ namespace BizHawk.Client.Common ? _axisOverrides[name] : 0; + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => throw new NotImplementedException(); // no idea --yoshi + + public void SetHapticChannelStrength(string name, int strength) => throw new NotImplementedException(); // no idea --yoshi + public IEnumerable Overrides => _overrides.Select(kvp => kvp.Key); public IEnumerable AxisOverrides => _axisOverrides.Select(kvp => kvp.Key); diff --git a/src/BizHawk.Client.Common/inputAdapters/StickyAdapters.cs b/src/BizHawk.Client.Common/inputAdapters/StickyAdapters.cs index f1b417a32a..a12aa2de0b 100644 --- a/src/BizHawk.Client.Common/inputAdapters/StickyAdapters.cs +++ b/src/BizHawk.Client.Common/inputAdapters/StickyAdapters.cs @@ -39,6 +39,10 @@ namespace BizHawk.Client.Common return Source.AxisValue(name); } + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Source.GetHapticsSnapshot(); + + public void SetHapticChannelStrength(string name, int strength) => Source.SetHapticChannelStrength(name, strength); + public IController Source { get; set; } private List _justPressed = new List(); @@ -141,6 +145,10 @@ namespace BizHawk.Client.Common return Source.AxisValue(name); } + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Source.GetHapticsSnapshot(); + + public void SetHapticChannelStrength(string name, int strength) => Source.SetHapticChannelStrength(name, strength); + // TODO: Change the AutoHold adapter to be one of these, with an 'Off' value of 0? // Probably would have slightly lower performance, but it seems weird to have such a similar class that is only used once. private int _on; diff --git a/src/BizHawk.Client.Common/inputAdapters/UDLRController.cs b/src/BizHawk.Client.Common/inputAdapters/UDLRController.cs index 305d171696..d2d1c188ab 100644 --- a/src/BizHawk.Client.Common/inputAdapters/UDLRController.cs +++ b/src/BizHawk.Client.Common/inputAdapters/UDLRController.cs @@ -1,4 +1,6 @@ -using BizHawk.Common.StringExtensions; +using System.Collections.Generic; + +using BizHawk.Common.StringExtensions; using BizHawk.Emulation.Common; namespace BizHawk.Client.Common @@ -62,5 +64,9 @@ namespace BizHawk.Client.Common // The float format implies no U+D and no L+R no matter what, so just passthru public int AxisValue(string name) => Source.AxisValue(name); + + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Source.GetHapticsSnapshot(); + + public void SetHapticChannelStrength(string name, int strength) => Source.SetHapticChannelStrength(name, strength); } } diff --git a/src/BizHawk.Client.Common/movie/bk2/Bk2Controller.cs b/src/BizHawk.Client.Common/movie/bk2/Bk2Controller.cs index 15dcd334ec..281fc23bcd 100644 --- a/src/BizHawk.Client.Common/movie/bk2/Bk2Controller.cs +++ b/src/BizHawk.Client.Common/movie/bk2/Bk2Controller.cs @@ -46,6 +46,10 @@ namespace BizHawk.Client.Common public bool IsPressed(string button) => _myBoolButtons[button]; public int AxisValue(string name) => _myAxisControls[name]; + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => throw new NotImplementedException(); // no idea --yoshi + + public void SetHapticChannelStrength(string name, int strength) => throw new NotImplementedException(); // no idea --yoshi + public void SetFrom(IController source) { foreach (var button in Definition.BoolButtons) diff --git a/src/BizHawk.Client.Common/movie/import/bkm/BkmControllerAdapter.cs b/src/BizHawk.Client.Common/movie/import/bkm/BkmControllerAdapter.cs index 462e4a6433..ff0e4adceb 100644 --- a/src/BizHawk.Client.Common/movie/import/bkm/BkmControllerAdapter.cs +++ b/src/BizHawk.Client.Common/movie/import/bkm/BkmControllerAdapter.cs @@ -1,4 +1,7 @@ -using BizHawk.Common; +using System; +using System.Collections.Generic; + +using BizHawk.Common; using BizHawk.Emulation.Common; namespace BizHawk.Client.Common @@ -45,6 +48,10 @@ namespace BizHawk.Client.Common return _myAxisControls[name]; } + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => throw new NotImplementedException(); // no idea --yoshi + + public void SetHapticChannelStrength(string name, int strength) => throw new NotImplementedException(); // no idea --yoshi + /// /// latches all buttons from the supplied mnemonic string /// diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 09bf1d7cb8..487481cbc5 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -703,9 +703,8 @@ namespace BizHawk.Client.EmuHawk // ...but prepare haptics first, those get read in ProcessInput var finalHostController = (ControllerInputCoalescer) InputManager.ControllerInputCoalescer; // for now, vibrate the first gamepad when the Fast Forward hotkey is held, using the value from the previous (host) frame - InputManager.ActiveController.PrepareHapticsForHost( - finalHostController, - debug: InputManager.ClientControls.IsPressed("Fast Forward") ? int.MaxValue : 0); + InputManager.ActiveController.SetHapticChannelStrength("Debug", InputManager.ClientControls.IsPressed("Fast Forward") ? int.MaxValue : 0); + InputManager.ActiveController.PrepareHapticsForHost(finalHostController); ProcessInput( _hotkeyCoalescer, finalHostController, diff --git a/src/BizHawk.Client.EmuHawk/config/ControllerConfig.cs b/src/BizHawk.Client.EmuHawk/config/ControllerConfig.cs index 1a14efb2f2..47bc008bab 100644 --- a/src/BizHawk.Client.EmuHawk/config/ControllerConfig.cs +++ b/src/BizHawk.Client.EmuHawk/config/ControllerConfig.cs @@ -239,7 +239,7 @@ namespace BizHawk.Client.EmuHawk LoadToPanel( FeedbacksTab, _emulator.ControllerDefinition.Name, - /*_emulator.ControllerDefinition.HapticsChannels*/new[] { "Debug" }, + _emulator.ControllerDefinition.HapticsChannels, _emulator.ControllerDefinition.CategoryLabels, haptics, new(string.Empty, string.Empty, 1.0f), diff --git a/src/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs b/src/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs index f7a37f7e85..ae64d084dd 100644 --- a/src/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs +++ b/src/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs @@ -10,9 +10,7 @@ namespace BizHawk.Emulation.Common /// public class ControllerDefinition { - public ControllerDefinition() - { - } + public ControllerDefinition() => HapticsChannels.Add("Debug"); public ControllerDefinition(ControllerDefinition source) : this() @@ -35,6 +33,9 @@ namespace BizHawk.Emulation.Common public readonly AxisDict Axes = new AxisDict(); + /// Contains names of virtual haptic feedback channels, e.g. { "P1 Mono" }, { "P2 Left", "P2 Right" }. + public List HapticsChannels { get; } = new(); + /// /// Gets the category labels. These labels provide a means of categorizing controls in various controller display and config screens /// diff --git a/src/BizHawk.Emulation.Common/Base Implementations/NullController.cs b/src/BizHawk.Emulation.Common/Base Implementations/NullController.cs index d1b56318d8..ac22d2f96e 100644 --- a/src/BizHawk.Emulation.Common/Base Implementations/NullController.cs +++ b/src/BizHawk.Emulation.Common/Base Implementations/NullController.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace BizHawk.Emulation.Common { @@ -18,6 +19,10 @@ namespace BizHawk.Emulation.Common public int AxisValue(string name) => 0; + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Array.Empty<(string, int)>(); + + public void SetHapticChannelStrength(string name, int strength) {} + public static readonly NullController Instance = new NullController(); } } \ No newline at end of file diff --git a/src/BizHawk.Emulation.Common/ControllerDefinitionMerger.cs b/src/BizHawk.Emulation.Common/ControllerDefinitionMerger.cs index 4c1a533c89..a6378109a0 100644 --- a/src/BizHawk.Emulation.Common/ControllerDefinitionMerger.cs +++ b/src/BizHawk.Emulation.Common/ControllerDefinitionMerger.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace BizHawk.Emulation.Common { @@ -31,24 +32,32 @@ namespace BizHawk.Emulation.Common int playerNext = 1; foreach (var def in controllers) { - var remaps = new Dictionary(); + Dictionary buttonAxisRemaps = new(); + Dictionary feedbackRemaps = new(); foreach (string s in def.BoolButtons) { string r = Allocate(s, ref plr, ref playerNext); ret.BoolButtons.Add(r); - remaps[s] = r; + buttonAxisRemaps[s] = r; } foreach (var kvp in def.Axes) { string r = Allocate(kvp.Key, ref plr, ref playerNext); ret.Axes.Add(r, kvp.Value); - remaps[kvp.Key] = r; + buttonAxisRemaps[kvp.Key] = r; + } + + foreach (var s in def.HapticsChannels) + { + string r = Allocate(s, ref plr, ref playerNext); + ret.HapticsChannels.Add(r); + feedbackRemaps[s] = r; } plr = playerNext; - unmergers.Add(new ControlDefUnMerger(remaps)); + unmergers.Add(new ControlDefUnMerger(buttonAxisRemaps, feedbackRemaps)); } return ret; @@ -57,22 +66,24 @@ namespace BizHawk.Emulation.Common public class ControlDefUnMerger { - private readonly Dictionary _remaps; - - public ControlDefUnMerger(Dictionary remaps) - { - _remaps = remaps; - } - private class DummyController : IController { - private readonly IController _src; - private readonly Dictionary _remaps; + /// + private readonly IReadOnlyDictionary _buttonAxisRemaps; - public DummyController(IController src, Dictionary remaps) + /// + private readonly IReadOnlyDictionary _feedbackRemaps; + + private readonly IController _src; + + public DummyController( + IController src, + IReadOnlyDictionary buttonAxisRemaps, + IReadOnlyDictionary feedbackRemaps) { _src = src; - _remaps = remaps; + _buttonAxisRemaps = buttonAxisRemaps; + _feedbackRemaps = feedbackRemaps; } /// always @@ -80,18 +91,36 @@ namespace BizHawk.Emulation.Common public bool IsPressed(string button) { - return _src.IsPressed(_remaps[button]); + return _src.IsPressed(_buttonAxisRemaps[button]); } public int AxisValue(string name) { - return _src.AxisValue(_remaps[name]); + return _src.AxisValue(_buttonAxisRemaps[name]); } + + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() + => _src.GetHapticsSnapshot() + .Select(hapticsEntry => (_feedbackRemaps.First(kvpRemap => kvpRemap.Value == hapticsEntry.Name).Value, hapticsEntry.Strength)) // reverse lookup + .ToArray(); + + public void SetHapticChannelStrength(string name, int strength) => _src.SetHapticChannelStrength(_feedbackRemaps[name], strength); } - public IController UnMerge(IController c) + /// these need to be separate because it's expected that "P1 Left" will appear in both + private readonly IReadOnlyDictionary _buttonAxisRemaps; + + /// + private readonly IReadOnlyDictionary _feedbackRemaps; + + public ControlDefUnMerger( + IReadOnlyDictionary buttonAxisRemaps, + IReadOnlyDictionary feedbackRemaps) { - return new DummyController(c, _remaps); + _buttonAxisRemaps = buttonAxisRemaps; + _feedbackRemaps = feedbackRemaps; } + + public IController UnMerge(IController c) => new DummyController(c, _buttonAxisRemaps, _feedbackRemaps); } } diff --git a/src/BizHawk.Emulation.Common/Interfaces/IController.cs b/src/BizHawk.Emulation.Common/Interfaces/IController.cs index 5f2977af37..a28fe345dd 100644 --- a/src/BizHawk.Emulation.Common/Interfaces/IController.cs +++ b/src/BizHawk.Emulation.Common/Interfaces/IController.cs @@ -1,4 +1,6 @@ -namespace BizHawk.Emulation.Common +using System.Collections.Generic; + +namespace BizHawk.Emulation.Common { public interface IController { @@ -7,6 +9,9 @@ /// ControllerDefinition Definition { get; } + /// + IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot(); + /// /// Returns the current state of a boolean control /// @@ -16,5 +21,9 @@ /// Returns the state of an axis control /// int AxisValue(string name); + + /// haptic channel name e.g. "P1 Mono", "P2 Left" + /// 0.. + void SetHapticChannelStrength(string name, int strength); } } diff --git a/src/BizHawk.Emulation.Common/SaveController.cs b/src/BizHawk.Emulation.Common/SaveController.cs index 01340c79cd..f29360e340 100644 --- a/src/BizHawk.Emulation.Common/SaveController.cs +++ b/src/BizHawk.Emulation.Common/SaveController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; @@ -95,5 +96,9 @@ namespace BizHawk.Emulation.Common { return _buttons[name]; } + + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Array.Empty<(string, int)>(); + + public void SetHapticChannelStrength(string name, int strength) {} } }