2014-11-30 23:05:00 +00:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
2017-04-27 22:28:55 +00:00
using System.Text.RegularExpressions ;
2014-11-30 23:05:00 +00:00
namespace BizHawk.Emulation.Common
{
2016-12-19 16:50:03 +00:00
/// <summary>
/// Defines the schema for all the currently available controls for an IEmulator instance
/// </summary>
/// <seealso cref="IEmulator" />
2014-11-30 23:05:00 +00:00
public class ControllerDefinition
{
2016-12-19 16:50:03 +00:00
public ControllerDefinition ( )
{
BoolButtons = new List < string > ( ) ;
FloatControls = new List < string > ( ) ;
FloatRanges = new List < FloatRange > ( ) ;
AxisConstraints = new List < AxisConstraint > ( ) ;
CategoryLabels = new Dictionary < string , string > ( ) ;
}
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 ;
}
/// <summary>
2017-04-24 12:41:55 +00:00
/// Gets or sets the name of the controller definition
2016-12-19 16:50:03 +00:00
/// </summary>
public string Name { get ; set ; }
/// <summary>
2017-04-24 12:41:55 +00:00
/// Gets or sets a list of all button types that have a boolean (on/off) value
2016-12-19 16:50:03 +00:00
/// </summary>
public List < string > BoolButtons { get ; set ; }
/// <summary>
2017-04-24 12:41:55 +00:00
/// Gets a list of all non-boolean types, that can be represented by a numerical value (such as analog controls, stylus coordinates, etc
2016-12-19 16:50:03 +00:00
/// </summary>
2017-04-24 12:41:55 +00:00
public List < string > FloatControls { get ; }
2016-12-19 16:50:03 +00:00
/// <summary>
2017-04-24 12:41:55 +00:00
/// Gets a list of all float ranges for each float control (must be one to one with FloatControls)
2016-12-19 16:50:03 +00:00
/// FloatRanges include the min/max/default values
/// </summary>
2017-04-24 12:41:55 +00:00
public List < FloatRange > FloatRanges { get ; }
2016-12-19 16:50:03 +00:00
/// <summary>
2017-04-24 12:41:55 +00:00
/// 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
2016-12-19 16:50:03 +00:00
/// </summary>
2017-04-24 12:41:55 +00:00
public List < AxisConstraint > AxisConstraints { get ; }
2016-12-19 16:50:03 +00:00
/// <summary>
2017-04-24 12:41:55 +00:00
/// Gets the category labels. These labels provide a means of categorizing controls in various controller display and config screens
2016-12-19 16:50:03 +00:00
/// </summary>
2017-04-24 12:41:55 +00:00
public Dictionary < string , string > CategoryLabels { get ; }
2016-12-19 16:50:03 +00:00
2014-11-30 23:05:00 +00:00
public void ApplyAxisConstraints ( string constraintClass , IDictionary < string , float > floatButtons )
{
2017-04-14 17:28:23 +00:00
if ( AxisConstraints = = null )
{
return ;
}
2014-11-30 23:05:00 +00:00
foreach ( var constraint in AxisConstraints )
{
if ( constraint . Class ! = constraintClass )
2017-04-14 17:28:23 +00:00
{
2014-11-30 23:05:00 +00:00
continue ;
2017-04-14 17:28:23 +00:00
}
2014-11-30 23:05:00 +00:00
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 ] ;
2017-07-29 18:57:50 +00:00
if ( ! floatButtons . ContainsKey ( xaxis ) ) break ;
if ( ! floatButtons . ContainsKey ( yaxis ) ) break ;
2014-11-30 23:05:00 +00:00
double xval = floatButtons [ xaxis ] ;
double yval = floatButtons [ yaxis ] ;
2017-04-27 13:24:21 +00:00
double length = Math . Sqrt ( ( xval * xval ) + ( yval * yval ) ) ;
2014-11-30 23:05:00 +00:00
if ( length > range )
{
double ratio = range / length ;
xval * = ratio ;
yval * = ratio ;
}
2017-04-24 12:41:55 +00:00
2014-11-30 23:05:00 +00:00
floatButtons [ xaxis ] = ( float ) xval ;
floatButtons [ yaxis ] = ( float ) yval ;
break ;
}
}
}
}
public struct FloatRange
{
public readonly float Min ;
public readonly float Max ;
/// <summary>
/// default position
/// </summary>
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 ] ) ;
}
2016-11-12 12:30:09 +00:00
/// <summary>
/// Gets maximum decimal digits analog input can occupy. Discards negative sign and possible fractional part (analog devices don't use floats anyway).
/// </summary>
public int MaxDigits ( )
{
return Math . Max (
Math . Abs ( ( int ) Min ) . ToString ( ) . Length ,
Math . Abs ( ( int ) Max ) . ToString ( ) . Length ) ;
}
2014-11-30 23:05:00 +00:00
}
public enum AxisConstraintType
{
Circular
}
public struct AxisConstraint
{
public string Class ;
public AxisConstraintType Type ;
public object [ ] Params ;
}
/// <summary>
2017-04-27 13:24:21 +00:00
/// Gets a list of controls put in a logical order such as by controller number,
2014-11-30 23:05:00 +00:00
/// This is a default implementation that should work most of the time
/// </summary>
public virtual IEnumerable < IEnumerable < string > > ControlsOrdered
{
get
{
2015-03-23 21:16:13 +00:00
List < string > list = new List < string > ( FloatControls ) ;
list . AddRange ( BoolButtons ) ;
2014-11-30 23:05:00 +00:00
2017-06-11 14:49:22 +00:00
// starts with console buttons, then each plasyer's buttons individually
List < string > [ ] ret = new List < string > [ PlayerCount + 1 ] ;
2015-03-23 21:16:13 +00:00
for ( int i = 0 ; i < ret . Length ; i + + )
2016-02-29 00:03:01 +00:00
{
2015-03-23 21:16:13 +00:00
ret [ i ] = new List < string > ( ) ;
2016-02-29 00:03:01 +00:00
}
2015-01-11 15:54:01 +00:00
2017-04-27 13:24:21 +00:00
foreach ( string btn in list )
2016-02-29 00:03:01 +00:00
{
2017-04-27 13:24:21 +00:00
ret [ PlayerNumber ( btn ) ] . Add ( btn ) ;
2016-02-29 00:03:01 +00:00
}
2015-01-11 15:54:01 +00:00
2015-03-23 21:16:13 +00:00
return ret ;
2014-11-30 23:05:00 +00:00
}
}
2015-07-23 17:55:22 +00:00
public int PlayerNumber ( string buttonName )
{
2017-06-11 14:49:22 +00:00
var match = PlayerRegex . Match ( buttonName ) ;
if ( match . Success )
2016-02-29 00:03:01 +00:00
{
2017-06-11 14:49:22 +00:00
return int . Parse ( match . Groups [ 1 ] . Value ) ;
}
else
{
return 0 ;
2016-02-29 00:03:01 +00:00
}
2015-07-23 17:55:22 +00:00
}
2017-06-11 14:49:22 +00:00
private static readonly Regex PlayerRegex = new Regex ( "^P(\\d+) " ) ;
2017-04-27 22:28:55 +00:00
2016-12-19 16:50:03 +00:00
public int PlayerCount
2014-11-30 23:05:00 +00:00
{
get
{
2017-04-27 22:28:55 +00:00
var allNames = FloatControls . Concat ( BoolButtons ) . ToList ( ) ;
var player = allNames
2017-06-11 14:49:22 +00:00
. Select ( PlayerNumber )
2017-04-27 22:28:55 +00:00
. DefaultIfEmpty ( 0 )
. Max ( ) ;
if ( player > 0 )
{
return player ;
}
2017-04-27 17:45:33 +00:00
// Hack for things like gameboy/ti-83 as opposed to genesis with no controllers plugged in
2017-04-27 22:28:55 +00:00
if ( allNames . Any ( b = > b . StartsWith ( "Up" ) ) )
2017-04-27 17:45:33 +00:00
{
return 1 ;
}
2014-11-30 23:05:00 +00:00
return 0 ;
}
}
2016-12-19 16:50:03 +00:00
public bool Any ( )
2014-11-30 23:05:00 +00:00
{
return BoolButtons . Any ( ) | | FloatControls . Any ( ) ;
}
}
}