BizHawk/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesControllerDeck.cs

436 lines
9.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using BizHawk.Emulation.Common;
using BizHawk.Common.NumberExtensions;
namespace BizHawk.Emulation.Cores.Nintendo.SNES
{
public class LibsnesControllerDeck
{
public enum ControllerType
{
Unplugged,
Gamepad,
Multitap,
Mouse,
SuperScope,
Justifier,
Payload
}
private static ILibsnesController Factory(ControllerType t, LibsnesCore.SnesSyncSettings ss)
{
switch (t)
{
case ControllerType.Unplugged:
return new SnesUnpluggedController();
case ControllerType.Gamepad:
return new SnesController();
case ControllerType.Multitap:
return new SnesMultitapController();
case ControllerType.Payload:
return new SnesPayloadController();
case ControllerType.Mouse:
return new SnesMouseController
{
LimitAnalogChangeSensitivity = ss.LimitAnalogChangeSensitivity
};
case ControllerType.SuperScope:
return new SnesSuperScopeController();
case ControllerType.Justifier:
return new SnesJustifierController();
default:
throw new InvalidOperationException();
}
}
private readonly ILibsnesController[] _ports;
private readonly ControlDefUnMerger[] _mergers;
public ControllerDefinition Definition { get; private set; }
public LibsnesControllerDeck(LibsnesCore.SnesSyncSettings ss)
{
_ports = new[]
{
Factory(ss.LeftPort, ss),
Factory(ss.RightPort, ss)
};
List<ControlDefUnMerger> tmp;
Definition = ControllerDefinitionMerger.GetMerged(_ports.Select(p => p.Definition), out tmp);
_mergers = tmp.ToArray();
// add buttons that the core itself will handle
Definition.BoolButtons.Add("Reset");
Definition.BoolButtons.Add("Power");
Definition.Name = "SNES Controller";
}
public void NativeInit(LibsnesApi api)
{
for (int i = 0; i < 2; i++)
{
api.SetInputPortBeforeInit(i, _ports[i].PortType);
}
}
public short CoreInputState(IController controller, int port, int device, int index, int id)
{
return _ports[port].GetState(_mergers[port].UnMerge(controller), index, id);
}
}
public interface ILibsnesController
{
/// <summary>
/// the type to pass back to the native init
/// </summary>
LibsnesApi.SNES_INPUT_PORT PortType { get; }
/// <summary>
/// respond to a native core poll
/// </summary>
/// <param name="controller">controller input from user, remapped</param>
/// <param name="index">libsnes specific value, sometimes multitap number</param>
/// <param name="id">libsnes specific value, sometimes button number</param>
/// <returns></returns>
short GetState(IController controller, int index, int id);
ControllerDefinition Definition { get; }
// due to the way things are implemented, right now, all of the ILibsnesControllers are stateless
// but if one needed state, that would be doable
// void SyncState(Serializer ser);
}
public class SnesController : ILibsnesController
{
public LibsnesApi.SNES_INPUT_PORT PortType { get; } = LibsnesApi.SNES_INPUT_PORT.Joypad;
private static readonly string[] Buttons =
{
"0B",
"0Y",
"0Select",
"0Start",
"0Up",
"0Down",
"0Left",
"0Right",
"0A",
"0X",
"0L",
"0R"
};
private static int ButtonOrder(string btn)
{
var order = new Dictionary<string, int>
{
["0Up"] = 0,
["0Down"] = 1,
["0Left"] = 2,
["0Right"] = 3,
["0Select"] = 4,
["0Start"] = 5,
["0Y"] = 6,
["0B"] = 7,
["0X"] = 8,
["0A"] = 9,
["0L"] = 10,
["0R"] = 11
};
return order[btn];
}
private static readonly ControllerDefinition _definition = new ControllerDefinition
{
BoolButtons = Buttons.OrderBy(ButtonOrder).ToList()
};
public ControllerDefinition Definition { get; } = _definition;
public short GetState(IController controller, int index, int id)
{
if (id >= 12)
{
return 0;
}
return (short)(controller.IsPressed(Buttons[id]) ? 1 : 0);
}
}
public class SnesMultitapController : ILibsnesController
{
public LibsnesApi.SNES_INPUT_PORT PortType { get; } = LibsnesApi.SNES_INPUT_PORT.Multitap;
private static readonly string[] Buttons =
{
"B",
"Y",
"Select",
"Start",
"Up",
"Down",
"Left",
"Right",
"A",
"X",
"L",
"R"
};
private static int ButtonOrder(string btn)
{
var order = new Dictionary<string, int>
{
["Up"] = 0,
["Down"] = 1,
["Left"] = 2,
["Right"] = 3,
["Select"] = 4,
["Start"] = 5,
["Y"] = 6,
["B"] = 7,
["X"] = 8,
["A"] = 9,
["L"] = 10,
["R"] = 11
};
return order[btn];
}
private static readonly ControllerDefinition _definition = new ControllerDefinition
{
BoolButtons = Enumerable.Range(0, 4)
.SelectMany(i => Buttons
.OrderBy(ButtonOrder)
.Select(b => i + b))
.Concat(new[] { "0Toggle Multitap" })
.ToList()
};
public ControllerDefinition Definition { get; } = _definition;
public short GetState(IController controller, int index, int id)
{
if (id == 16)
{
return (short)(controller.IsPressed("0Toggle Multitap") ? 1 : 0);
}
if (id >= 12)
{
return 0;
}
return (short)(controller.IsPressed(index + Buttons[id]) ? 1 : 0);
}
}
public class SnesPayloadController : ILibsnesController
{
public LibsnesApi.SNES_INPUT_PORT PortType { get; } = LibsnesApi.SNES_INPUT_PORT.Multitap;
private static readonly ControllerDefinition _definition = new ControllerDefinition
{
BoolButtons = Enumerable.Range(0, 32).Select(i => "0B" + i).ToList()
};
public ControllerDefinition Definition { get; } = _definition;
public short GetState(IController controller, int index, int id)
{
return (short)(controller.IsPressed("0B" + (index << 4 & 16 | id)) ? 1 : 0);
}
}
public class SnesUnpluggedController : ILibsnesController
{
public LibsnesApi.SNES_INPUT_PORT PortType { get; } = LibsnesApi.SNES_INPUT_PORT.None;
private static readonly ControllerDefinition _definition = new ControllerDefinition();
public ControllerDefinition Definition { get; } = _definition;
public short GetState(IController controller, int index, int id)
{
return 0;
}
}
public class SnesMouseController : ILibsnesController
{
public LibsnesApi.SNES_INPUT_PORT PortType => LibsnesApi.SNES_INPUT_PORT.Mouse;
private static readonly ControllerDefinition _definition = new ControllerDefinition
{
BoolButtons = new List<string>
{
"0Mouse Left",
"0Mouse Right"
},
FloatControls =
{
"0Mouse X",
"0Mouse Y"
},
FloatRanges =
{
new[] { -127f, 0f, 127f },
new[] { -127f, 0f, 127f }
}
};
public ControllerDefinition Definition => _definition;
public bool LimitAnalogChangeSensitivity { get; set; } = true;
public short GetState(IController controller, int index, int id)
{
switch (id)
{
default:
return 0;
case 0:
var x = (int)controller.GetFloat("0Mouse X");
if (LimitAnalogChangeSensitivity)
{
x = x.Clamp(-10, 10);
}
return (short)x;
case 1:
var y = (int)controller.GetFloat("0Mouse Y");
if (LimitAnalogChangeSensitivity)
{
y = y.Clamp(-10, 10);
}
return (short)y;
case 2:
return (short)(controller.IsPressed("0Mouse Left") ? 1 : 0);
case 3:
return (short)(controller.IsPressed("0Mouse Right") ? 1 : 0);
}
}
}
public class SnesSuperScopeController : ILibsnesController
{
public LibsnesApi.SNES_INPUT_PORT PortType => LibsnesApi.SNES_INPUT_PORT.SuperScope;
private static readonly ControllerDefinition _definition = new ControllerDefinition
{
BoolButtons = new List<string>
{
"0Trigger",
"0Cursor",
"0Turbo",
"0Pause"
},
FloatControls =
{
"0Scope X",
"0Scope Y"
},
FloatRanges =
{
// problem: when you're in 240 line mode, the limit on Y needs to be 240.
// when you're in 224 mode, it needs to be 224. perhaps the deck needs to account for this...
new[] { 0f, 128f, 256f },
new[] { 0f, 0f, 240f }
}
};
public ControllerDefinition Definition => _definition;
public short GetState(IController controller, int index, int id)
{
switch (id)
{
default:
return 0;
case 0:
var x = (int)controller.GetFloat("0Scope X");
return (short)x;
case 1:
var y = (int)controller.GetFloat("0Scope Y");
return (short)y;
case 2:
return (short)(controller.IsPressed("0Trigger") ? 1 : 0);
case 3:
return (short)(controller.IsPressed("0Cursor") ? 1 : 0);
case 4:
return (short)(controller.IsPressed("0Turbo") ? 1 : 0);
case 5:
return (short)(controller.IsPressed("0Pause") ? 1 : 0);
}
}
}
public class SnesJustifierController : ILibsnesController
{
public LibsnesApi.SNES_INPUT_PORT PortType => LibsnesApi.SNES_INPUT_PORT.Justifier;
private static readonly ControllerDefinition _definition = new ControllerDefinition
{
BoolButtons = new List<string>
{
"0Trigger",
"0Start",
"1Trigger",
"1Start"
},
FloatControls =
{
"0Justifier X",
"0Justifier Y",
"1Justifier X",
"1Justifier Y",
},
FloatRanges =
{
// problem: when you're in 240 line mode, the limit on Y needs to be 240.
// when you're in 224 mode, it needs to be 224. perhaps the deck needs to account for this...
new[] { 0f, 128f, 256f },
new[] { 0f, 0f, 240f },
new[] { 0f, 128f, 256f },
new[] { 0f, 0f, 240f }
}
};
public ControllerDefinition Definition => _definition;
public short GetState(IController controller, int index, int id)
{
switch (id)
{
default:
return 0;
case 0:
var x = (int)controller.GetFloat(index + "Justifier X");
return (short)x;
case 1:
var y = (int)controller.GetFloat(index + "Justifier Y");
return (short)y;
case 2:
return (short)(controller.IsPressed(index + "Trigger") ? 1 : 0);
case 3:
return (short)(controller.IsPressed(index + "Start") ? 1 : 0);
}
}
}
}