using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; namespace BizHawk.Emulation.Common { /// /// Defines the schema for all the currently available controls for an IEmulator instance /// /// public class ControllerDefinition { public ControllerDefinition() { BoolButtons = new List(); FloatControls = new List(); FloatRanges = new List(); AxisConstraints = new List(); CategoryLabels = new Dictionary(); } public ControllerDefinition(ControllerDefinition source) : this() { Name = source.Name; BoolButtons.AddRange(source.BoolButtons); FloatControls.AddRange(source.FloatControls); FloatRanges.AddRange(source.FloatRanges); AxisConstraints.AddRange(source.AxisConstraints); CategoryLabels = source.CategoryLabels; } /// /// Gets or sets the name of the controller definition /// public string Name { get; set; } /// /// Gets or sets a list of all button types that have a boolean (on/off) value /// public List BoolButtons { get; set; } /// /// Gets a list of all non-boolean types, that can be represented by a numerical value (such as analog controls, stylus coordinates, etc /// public List FloatControls { get; } /// /// Gets a list of all float ranges for each float control (must be one to one with FloatControls) /// FloatRanges include the min/max/default values /// public List FloatRanges { get; } /// /// Gets the axis constraints that apply artificial constraints to float values /// For instance, a N64 controller's analog range is actually larger than the amount allowed by the plastic that artificially constrains it to lower values /// Axis constraints provide a way to technically allow the full range but have a user option to constrain down to typical values that a real control would have /// public List AxisConstraints { get; } /// /// Gets the category labels. These labels provide a means of categorizing controls in various controller display and config screens /// public Dictionary CategoryLabels { get; } public void ApplyAxisConstraints(string constraintClass, IDictionary floatButtons) { if (AxisConstraints == null) { return; } foreach (var constraint in AxisConstraints) { if (constraint.Class != constraintClass) { continue; } switch (constraint.Type) { case AxisConstraintType.Circular: { string xaxis = constraint.Params[0] as string; string yaxis = constraint.Params[1] as string; float range = (float)constraint.Params[2]; double xval = floatButtons[xaxis]; double yval = floatButtons[yaxis]; double length = Math.Sqrt((xval * xval) + (yval * yval)); if (length > range) { double ratio = range / length; xval *= ratio; yval *= ratio; } floatButtons[xaxis] = (float)xval; floatButtons[yaxis] = (float)yval; break; } } } } public struct FloatRange { public readonly float Min; public readonly float Max; /// /// default position /// public readonly float Mid; public FloatRange(float min, float mid, float max) { Min = min; Mid = mid; Max = max; } // for terse construction public static implicit operator FloatRange(float[] f) { if (f.Length != 3) { throw new ArgumentException(); } return new FloatRange(f[0], f[1], f[2]); } /// /// Gets maximum decimal digits analog input can occupy. Discards negative sign and possible fractional part (analog devices don't use floats anyway). /// public int MaxDigits() { return Math.Max( Math.Abs((int)Min).ToString().Length, Math.Abs((int)Max).ToString().Length); } } public enum AxisConstraintType { Circular } public struct AxisConstraint { public string Class; public AxisConstraintType Type; public object[] Params; } /// /// Gets a list of controls put in a logical order such as by controller number, /// This is a default implementation that should work most of the time /// public virtual IEnumerable> ControlsOrdered { get { List list = new List(FloatControls); list.AddRange(BoolButtons); List[] ret = new List[9]; for (int i = 0; i < ret.Length; i++) { ret[i] = new List(); } foreach (string btn in list) { ret[PlayerNumber(btn)].Add(btn); } return ret; } } public int PlayerNumber(string buttonName) { int player = 0; if (buttonName.Length > 3 && buttonName.StartsWith("P") && char.IsNumber(buttonName[1])) { player = buttonName[1] - '0'; } return player; } private static readonly Regex PlayerRegex = new Regex("^P(\\d) "); public int PlayerCount { get { var allNames = FloatControls.Concat(BoolButtons).ToList(); var player = allNames .Select(s => PlayerRegex.Match(s).Groups[1]) .Where(group => group.Success) .Select(group => group.Value[0] - '0') .DefaultIfEmpty(0) .Max(); if (player > 0) { return player; } // Hack for things like gameboy/ti-83 as opposed to genesis with no controllers plugged in if (allNames.Any(b => b.StartsWith("Up"))) { return 1; } return 0; } } public bool Any() { return BoolButtons.Any() || FloatControls.Any(); } } }