BizHawk/BizHawk.Emulation.Cores/Consoles/Sega/Saturn/SaturnusControllerDeck.cs

676 lines
14 KiB
C#

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<IDevice>()
.ToArray();
_data = new byte[count * DataSize];
List<ControlDefUnMerger> 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(Key)",
"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(Key)",
"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(Key)": 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(Key)": 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;
}
}
}
}
}