using BizHawk.Emulation.Common; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using System.Threading.Tasks; using static BizHawk.Emulation.Common.ControllerDefinition; namespace BizHawk.Emulation.Cores.Consoles.Sega.Saturn { public class SaturnusControllerDeck { private const int DataSize = 32; private static readonly Type[] Implementors = { typeof(None), typeof(Gamepad), typeof(ThreeDeeGamepad), typeof(Mouse), typeof(Wheel), typeof(Mission), typeof(DualMission), typeof(Keyboard) }; private readonly IDevice[] _devices; private readonly ControlDefUnMerger[] _unmerger; private readonly byte[] _data; public ControllerDefinition Definition { get; } public SaturnusControllerDeck(bool[] multitap, Device[] devices, LibSaturnus core) { int count = 2 + multitap.Count(b => b) * 5; int[] dev = new int[12]; int[] mt = new int[2]; for (int i = 0; i < 12; i++) dev[i] = (int)(i < count ? devices[i] : Device.None); for (int i = 0; i < 2; i++) mt[i] = multitap[i] ? 1 : 0; core.SetupInput(dev, mt); _devices = dev.Take(count) .Select(i => Activator.CreateInstance(Implementors[i])) .Cast() .ToArray(); _data = new byte[count * DataSize]; List cdum; Definition = ControllerDefinitionMerger.GetMerged(_devices.Select(d => d.Definition), out cdum); _unmerger = cdum.ToArray(); } public byte[] Poll(IController controller) { for (int i = 0, offset = 0; i < _devices.Length; i++, offset += DataSize) _devices[i].Update(_unmerger[i].UnMerge(controller), _data, offset); return _data; } public enum Device { None, Gamepad, [Display(Name = "3D Pad")] ThreeDeePad, Mouse, [Display(Name = "Racing Controller")] Wheel, [Display(Name = "Mission Stick")] Mission, [Display(Name = "Two Mission Sticks")] DualMission, Keyboard } private interface IDevice { void Update(IController controller, byte[] dest, int offset); ControllerDefinition Definition { get; } } private class None : IDevice { private static readonly ControllerDefinition NoneDefition = new ControllerDefinition(); public ControllerDefinition Definition => NoneDefition; public void Update(IController controller, byte[] dest, int offset) { } } private abstract class ButtonedDevice : IDevice { private static readonly FloatRange AnalogFloatRange = new FloatRange(0, 128, 255); protected ButtonedDevice() { _bakedButtonNames = ButtonNames.Select(s => s != null ? "0" + s : null).ToArray(); _bakedAnalogNames = AnalogNames.Select(s => "0" + s).ToArray(); Definition = new ControllerDefinition { BoolButtons = _bakedButtonNames .Select((s, i) => new { s, i }) .Where(a => a.s != null) .OrderBy(a => ButtonOrdinal(ButtonNames[a.i])) .Select(a => a.s) .ToList(), }; Definition.FloatControls.AddRange(_bakedAnalogNames .Select((s, i) => new { s, i }) .OrderBy(a => AnalogOrdinal(AnalogNames[a.i])) .Select(a => a.s)); Definition.FloatRanges.AddRange(_bakedAnalogNames.Select(s => AnalogFloatRange)); } private readonly string[] _bakedButtonNames; private readonly string[] _bakedAnalogNames; protected virtual string[] ButtonNames { get; } = new string[0]; protected virtual int ButtonByteOffset { get; } = 0; protected virtual string[] AnalogNames { get; } = new string[0]; protected virtual int AnalogByteOffset => (ButtonNames.Length + 7) / 8; public ControllerDefinition Definition { get; } protected virtual int ButtonOrdinal(string name) { return 0; } protected virtual int AnalogOrdinal(string name) { return 0; } private void UpdateButtons(IController controller, byte[] dest, int offset) { int pos = offset + ButtonByteOffset; byte data = 0; int bit = 0; for (int i = 0; i < _bakedButtonNames.Length; i++) { if (_bakedButtonNames[i] != null && controller.IsPressed(_bakedButtonNames[i])) data |= (byte)(1 << bit); if (++bit == 8) { bit = 0; dest[pos++] = data; data = 0; } } if (bit != 0) dest[pos] = data; } private void UpdateAnalogs(IController controller, byte[] dest, int offset) { int pos = offset + AnalogByteOffset; for (int i = 0; i < _bakedAnalogNames.Length; i++) { var data = (byte)(int)controller.GetFloat(_bakedAnalogNames[i]); dest[pos++] = data; } } public virtual void Update(IController controller, byte[] dest, int offset) { UpdateButtons(controller, dest, offset); UpdateAnalogs(controller, dest, offset); } } private class Gamepad : ButtonedDevice { private static readonly string[] _buttonNames = { "Z", "Y", "X", "R", "Up", "Down", "Left", "Right", "B", "C", "A", "Start", null, null, null, "L" }; protected override string[] ButtonNames => _buttonNames; protected override int ButtonOrdinal(string name) { switch (name) { default: return 0; case "A": return 1; case "B": case "C": return 2; case "X": return 3; case "Y": return 4; case "Z": case "L": return 5; case "R": return 6; } } } private class ThreeDeeGamepad : ButtonedDevice { private static readonly string[] _buttonNames = { "Up", "Down", "Left", "Right", "B", "C", "A", "Start", "Z", "Y", "X" }; protected override string[] ButtonNames => _buttonNames; private static readonly string[] _analogNames = { "Stick Horizontal", "Stick Vertical", "Left Shoulder", "Right Shoulder" }; protected override string[] AnalogNames => _analogNames; public ThreeDeeGamepad() { Definition.FloatRanges[2] = new FloatRange(0, 0, 255); Definition.FloatRanges[3] = new FloatRange(0, 0, 255); } public override void Update(IController controller, byte[] dest, int offset) { base.Update(controller, dest, offset); // set the "Mode" button to analog at all times dest[offset + 1] |= 0x10; } protected override int ButtonOrdinal(string name) { switch (name) { default: return 0; case "A": return 1; case "B": case "C": return 2; case "X": return 3; case "Y": return 4; case "Z": return 5; } } } private class Mouse : ButtonedDevice { private static readonly string[] _buttonNames = { "Mouse Left", "Mouse Right", "Mouse Center", "Start" }; protected override string[] ButtonNames => _buttonNames; private static readonly string[] _analogNames = { "X", "Y" }; protected override string[] AnalogNames => _analogNames; protected override int ButtonOrdinal(string name) { switch (name) { default: case "Mouse Left": return 0; case "Mouse Center": return 1; case "Mouse Right": return 2; case "Start": return 3; } } } private class Wheel : ButtonedDevice { private static readonly string[] _buttonNames = { "Up", "Down", null, null, "B", "C", "A", "Start", "Z", "Y", "X" }; protected override string[] ButtonNames => _buttonNames; private static readonly string[] _analogNames = { "Wheel" }; protected override string[] AnalogNames => _analogNames; protected override int ButtonOrdinal(string name) { switch (name) { default: return 0; case "A": return 1; case "B": case "C": return 2; case "X": return 3; case "Y": return 4; case "Z": return 5; } } } private class Mission : ButtonedDevice { private static readonly string[] _buttonNames = { "B", "C", "A", "Start", "Z", "Y", "X", "R", null, null, null, "L" }; protected override string[] ButtonNames => _buttonNames; private static readonly string[] _analogNames = { "Stick Horizontal", "Stick Vertical", "Throttle" }; protected override string[] AnalogNames => _analogNames; protected override int AnalogByteOffset => 4; protected override int ButtonOrdinal(string name) { switch (name) { default: return 0; case "Start": return 1; case "A": return 2; case "B": case "C": return 3; case "X": return 4; case "Y": return 5; case "Z": case "L": return 6; case "R": return 7; } } } private class DualMission : Mission { private static readonly string[] _analogNames = { "Right Stick Horizontal", "Right Stick Vertical", "Right Throttle", "Left Stick Horizontal", "Left Stick Vertical", "Left Throttle" }; protected override string[] AnalogNames => _analogNames; } private class Keyboard : ButtonedDevice { // TODO: LEDs, which are actually data sent back by the core private static readonly string[] _buttonNames = { null, "F9", null, "F5", "F3", "F1", "F2", "F12", null, "F10", "F8", "F6", "F4", "Tab", "Grave`", null, null, "LeftAlt", "LeftShift", null, "LeftCtrl", "Q", "1(One)", "RightAlt", "RightCtrl", "KeypadEnter", "Z(Key)", "S", "A(Key)", "W", "2", null, null, "C(Key)", "X(Key)", "D", "E", "4", "3", null, null, "Space", "V", "F", "T", "R", "5", null, null, "N", "B(Key)", "H", "G", "Y(Key)", "6", null, null, null, "M", "J", "U", "7", "8", null, null, "Comma,", "K", "I", "O", "0(Zero)", "9", null, null, "Period.", "Slash/", "L", "Semicolon;", "P", "Minus-", null, null, null, "Quote'", null, "LeftBracket[", "Equals=", null, null, "CapsLock", "RightShift", "Enter", "RightBracket]", null, "Backslash\\", null, null, null, null, null, null, null, null, "Backspace", null, null, "KeypadEnd/1", null, "KeypadLeft/4", "KeypadHome/7", null, null, null, "KeypadInsert/0", "KeypadDelete", "KeypadDown/2", "KeypadCenter/5", "KeypadRight/6", "KeypadUp/8", "Escape", "NumLock", "F11", "KeypadPlus", "KeypadPagedown/3", "KeypadMinus", "KeypadAsterisk(Multiply)", "KeypadPageup/9", "ScrollLock", null, "KeypadSlash(Divide)", "Insert", "Pause", "F7", "PrintScreen", "Delete", "CursorLeft", "Home", "End", "Up", "Down", "PageUp", "PageDown", "Right", }; protected override string[] ButtonNames => _buttonNames; protected override int ButtonOrdinal(string name) { switch (name) { default: return 0; case "Escape": return 1; case "F1": return 2; case "F2": return 3; case "F3": return 4; case "F4": return 5; case "F5": return 6; case "F6": return 7; case "F7": return 8; case "F8": return 9; case "F9": return 10; case "F10": return 11; case "F11": return 12; case "F12": return 13; case "Grave`": return 100; case "1(One)": return 101; case "2": return 102; case "3": return 103; case "4": return 104; case "5": return 105; case "6": return 106; case "7": return 107; case "8": return 108; case "9": return 109; case "0(Zero)": return 110; case "Minus-": return 111; case "Equals=": return 112; case "Backslash\\": return 113; case "Backspace": return 114; case "Tab": return 200; case "Q": return 201; case "W": return 202; case "E": return 203; case "R": return 204; case "T": return 205; case "Y(Key)": return 206; case "U": return 207; case "I": return 208; case "O": return 209; case "P": return 210; case "LeftBracket[": return 211; case "RightBracket]": return 212; case "Enter": return 213; case "CapsLock": return 300; case "A(Key)": return 301; case "S": return 302; case "D": return 303; case "F": return 304; case "G": return 305; case "H": return 306; case "J": return 307; case "K": return 308; case "L": return 309; case "Semicolon;": return 310; case "Quote'": return 311; case "LeftShift": return 400; case "Z(Key)": return 401; case "X(Key)": return 402; case "C(Key)": return 403; case "V": return 404; case "B(Key)": return 405; case "N": return 406; case "M": return 407; case "Comma,": return 408; case "Period.": return 409; case "Slash/": return 410; case "RightShift": return 411; case "LeftCtrl": return 500; case "LeftAlt": return 501; case "Space": return 502; case "RightAlt": return 503; case "RightCtrl": return 504; case "PrintScreen": return 1000; case "ScrollLock": return 1001; case "Pause": return 1002; case "Insert": return 1100; case "Delete": return 1101; case "Home": return 1102; case "End": return 1103; case "PageUp": return 1104; case "PageDown": return 1105; case "Up": return 1200; case "Down": return 1201; case "CursorLeft": return 1202; case "Right": return 1203; case "NumLock": return 1300; case "KeypadSlash(Divide)": return 1301; case "KeypadAsterisk(Multiply)": return 1302; case "KeypadMinus": return 1303; case "KeypadHome/7": return 1304; case "KeypadUp/8": return 1305; case "KeypadPageup/9": return 1306; case "KeypadPlus": return 1307; case "KeypadLeft/4": return 1308; case "KeypadCenter/5": return 1309; case "KeypadRight/6": return 1310; case "KeypadEnd/1": return 1311; case "KeypadDown/2": return 1312; case "KeypadPagedown/3": return 1313; case "KeypadEnter": return 1314; case "KeypadInsert/0": return 1315; case "KeypadDelete": return 1316; } } } } }