using System; using System.Collections.Generic; using System.Text; using System.Xml; using System.IO; using SCJMapper_V2.SC; using SCJMapper_V2.Keyboard; using SCJMapper_V2.Mouse; using SCJMapper_V2.Gamepad; using SCJMapper_V2.Joystick; using System.Linq; namespace SCJMapper_V2 { /// /// Maintains an action - something like: /// /// /// /// /// /// /// AC1.0 /// /// /// /// /// /// AC1.1 /// /// /// /// /// AC2.0 /// /// // jsN, moN, kbN (gamepad ?) /// // jsN, moN, kbN (gamepad ?) still possible together with rebind? /// /// /// public class ActionCls { private static readonly log4net.ILog log = log4net.LogManager.GetLogger( System.Reflection.MethodBase.GetCurrentMethod( ).DeclaringType ); /// /// Device Enums /// public enum ActionDevice { AD_Unknown = -1, AD_Joystick = 0, AD_Gamepad, AD_Keyboard, AD_Mouse, // 20151220BM: add mouse device (from AC 2.0 defaultProfile usage) } #region Static Items /// /// Return the Device Enum from a DeviceClass string /// /// Device Class string /// Device Enum static public ActionDevice ADevice( string deviceClass ) { switch ( deviceClass.ToLower( ) ) { case KeyboardCls.DeviceClass: return ActionDevice.AD_Keyboard; case JoystickCls.DeviceClass: return ActionDevice.AD_Joystick; case GamepadCls.DeviceClass: return ActionDevice.AD_Gamepad; case MouseCls.DeviceClass: return ActionDevice.AD_Mouse; // 20151220BM: add mouse device (from AC 2.0 defaultProfile usage) case "ps3pad": return ActionDevice.AD_Gamepad; default: return ActionDevice.AD_Unknown; } } // Static items to have this mapping in only one place /// /// Returns the Device Tag i.e. the single letter to mark a device in Actions /// /// The device name from the defaultProfile /// The single UCase device Tag letter static public string DevTag( string device ) { switch ( device.ToLower( ) ) { case KeyboardCls.DeviceClass: return "K"; case JoystickCls.DeviceClass: return "J"; case GamepadCls.DeviceClass: return "X"; case MouseCls.DeviceClass: return "M"; // 20151220BM: add mouse device (from AC 2.0 defaultProfile usage) case "ps3pad": return "P"; default: return "Z"; } } /// /// Returns the Device name from the Device Tag /// /// The single UCase device Tag letter /// The device name from the defaultProfile static public string DeviceClassFromTag( string devTag ) { switch ( devTag ) { case "K": return KeyboardCls.DeviceClass; case "J": return JoystickCls.DeviceClass; case "X": return GamepadCls.DeviceClass; case "M": return MouseCls.DeviceClass; // 20151220BM: add mouse device (from AC 2.0 defaultProfile usage) case "P": return "ps3pad"; default: return "unknown"; } } /// /// Try to derive the device class from the devInput string (mo1_, kb1_, xi1_, jsN_) /// /// The input command string dev_input format /// A proper DeviceClass string static public string DeviceClassFromInput( string devInput ) { string deviceClass = DeviceCls.DeviceClass; deviceClass = JoystickCls.DeviceClassFromInput( devInput ); if ( !DeviceCls.IsUndefined( deviceClass ) ) return deviceClass; deviceClass = GamepadCls.DeviceClassFromInput( devInput ); if ( !DeviceCls.IsUndefined( deviceClass ) ) return deviceClass; deviceClass = KeyboardCls.DeviceClassFromInput( devInput ); if ( !DeviceCls.IsUndefined( deviceClass ) ) return deviceClass; deviceClass = MouseCls.DeviceClassFromInput( devInput ); if ( !DeviceCls.IsUndefined( deviceClass ) ) return deviceClass; // others.. return deviceClass; } /// /// Returns the ActionDevice from a deviceID (a trailing _ is added if not there) /// /// DeviceID /// The ActionDevice static public ActionDevice ADeviceFromDevID( string devID ) { string val = devID; if ( !devID.EndsWith( "_" ) ) val += "_"; return ADevice( DeviceClassFromInput( val ) ); } /// /// Returns the ActionDevice from the devInput string (mo1_, kb1_, xi1_, jsN_) /// /// The input command string dev_input format /// The ActionDevice static public ActionDevice ADeviceFromInput( string devInput ) { return ADevice( DeviceClassFromInput( devInput ) ); } /// /// Query the devices if the input is blended /// /// The input command /// True if blended input static public Boolean IsBlendedInput( string input ) { Boolean blendedInput = false; blendedInput = DeviceCls.IsBlendedInput( input ); // generic if ( blendedInput ) return blendedInput; blendedInput = JoystickCls.IsBlendedInput( input ); if ( blendedInput ) return blendedInput; blendedInput = GamepadCls.IsBlendedInput( input ); if ( blendedInput ) return blendedInput; blendedInput = KeyboardCls.IsBlendedInput( input ); if ( blendedInput ) return blendedInput; blendedInput = MouseCls.IsBlendedInput( input ); if ( blendedInput ) return blendedInput; // others.. return blendedInput; } /// /// Blend the input using the device specific format of the input is generic Blind /// /// An input (generic blend or a valid command) /// A valid device /// A device blend or the original input if it was not a blend static public string BlendInput( string input, ActionDevice aDevice ) { if ( DeviceCls.IsBlendedInput( input ) ) { // was generic blind switch ( aDevice ) { case ActionDevice.AD_Gamepad: return GamepadCls.BlendedInput; case ActionDevice.AD_Joystick: return JoystickCls.BlendedInput; case ActionDevice.AD_Keyboard: return KeyboardCls.BlendedInput; case ActionDevice.AD_Mouse: return MouseCls.BlendedInput; default: return ""; } } else { return input; // just return } } /// /// Extends the input to a device input if not already done /// /// An input /// The ActionDevice /// A valid devInput (dev_input) format static public string DevInput( string input, ActionDevice aDevice ) { switch ( aDevice ) { case ActionDevice.AD_Gamepad: return GamepadCls.DevInput( input ); case ActionDevice.AD_Joystick: return JoystickCls.DevInput( input ); case ActionDevice.AD_Keyboard: return KeyboardCls.DevInput( input ); case ActionDevice.AD_Mouse: return MouseCls.DevInput( input ); default: return input; } } /// /// Return the color of a device /// /// The devinput (determine JS colors) /// The ActionDevice /// The device color static public System.Drawing.Color DeviceColor( string devInput ) { // background is along the input ActionDevice aDevice = ADeviceFromInput( devInput); switch ( aDevice ) { case ActionDevice.AD_Gamepad: return GamepadCls.XiColor( ); case ActionDevice.AD_Joystick: { int jNum = JoystickCls.JSNum( devInput ); // need to know which JS return JoystickCls.JsNColor( jNum ); } case ActionDevice.AD_Keyboard: return KeyboardCls.KbdColor( ); case ActionDevice.AD_Mouse: return MouseCls.MouseColor( ); default: return MyColors.UnassignedColor; } } #endregion // **************** Class items ********************** public string key { get; set; } // the key is the "Daction" formatted item (as we can have the same name multiple times) public string name { get; set; } // the plain action name e.g. v_yaw public ActionDevice actionDevice { get; set; } // the enum of the device public string device { get; set; } // name of the device (uses DeviceClass) public string defBinding { get; set; } // the default binding public ActivationMode defActivationMode { get; set; } // the default binding ActivationMode public List inputList { get; set; } // regular bind is the 0-element, addbinds are added to the list /// /// Clone this object /// /// A deep Clone of this object private ActionCls MyClone( ) { ActionCls newAc = (ActionCls)this.MemberwiseClone(); // more objects to deep copy newAc.defActivationMode = ( ActivationMode )this.defActivationMode.Clone(); newAc.inputList = this.inputList.Select( x => ( ActionCommandCls )x.Clone( ) ).ToList( ); return newAc; } /// /// Copy return the action while reassigning the JsN Tag /// /// The JsN reassign list /// The action copy with reassigned input public ActionCls ReassignJsN( JsReassingList newJsList ) { ActionCls newAc = this.MyClone(); // creates a copy of the list with reassigned jsN devs newAc.inputList.Clear( ); // get rid of cloned list foreach ( ActionCommandCls acc in inputList ) { newAc.inputList.Add( acc.ReassignJsN( newJsList ) ); // creates the deep copy of the list } return newAc; } /// /// ctor /// public ActionCls( ) { key = ""; actionDevice = ActionDevice.AD_Unknown; device = JoystickCls.DeviceClass; name = ""; defBinding = ""; defActivationMode = ActivationMode.Default; inputList = new List( ); // empty list } /// /// Creates and adds the inputCommand list with given input string /// AC2 style input is used i.e. with device tag in front /// apply given ActivationMode - can be "~" to indicate DONT APPLY /// /// /// Returns the ActionCommand created public ActionCommandCls AddCommand( string devInput, ActivationMode activationMode ) { ActionCommandCls acc = new ActionCommandCls( devInput, inputList.Count - 1 ); // starts from -1 ... acc.ActivationMode = new ActivationMode( activationMode ); inputList.Add( acc ); return acc; } /// /// Add an ActionCommand with Input at nodeindex /// apply default ActivationMode /// /// /// /// public ActionCommandCls AddCommand( string devInput, int index ) { ActionCommandCls acc = new ActionCommandCls( devInput, index ); acc.ActivationMode = new ActivationMode( ActivationMode.Default ); inputList.Add( acc ); return acc; } public void DelCommand( int index ) { int removeIt = -1; for ( int i = 0; i < inputList.Count; i++ ) { if ( inputList[i].NodeIndex == index ) removeIt = i; if ( inputList[i].NodeIndex > index ) inputList[i].NodeIndex -= 1; // reorder trailing ones } if ( removeIt >= 0 ) inputList.RemoveAt( removeIt ); } /// /// Merge action is simply copying the new input control /// /// public void Merge( ActionCls newAc ) { this.inputList.Clear( ); foreach ( ActionCommandCls acc in newAc.inputList ) { this.inputList.Add( acc ); } } /// /// Updates an actionCommand with a new input (command) /// /// The input command public void UpdateCommandFromInput( string devInput, int accIndex ) // ActionCommandCls actionCmd ) { //log.Debug( "UpdateCommandFromInput - Entry" ); if ( accIndex < 0 ) return; // Apply the input to the ActionTree this.inputList[accIndex].DevInput = BlendInput( devInput, this.actionDevice ); if ( IsBlendedInput( this.inputList[accIndex].DevInput ) || string.IsNullOrEmpty(devInput) ) { this.inputList[accIndex].ActivationMode = new ActivationMode( ActivationMode.Default ); // reset activation mode if the input is empty } } /// /// Find an ActionCommand with input in an Action /// /// The input /// An actionCommand or null if not found public ActionCommandCls FindActionInputObject( string devInput ) { log.Debug( "FindActionInputObject - Entry" ); // Apply the input to the ActionTree ActionCommandCls acc = null; acc = this.inputList.Find( delegate ( ActionCommandCls _ACC ) { return _ACC.DevInput == devInput; } ); if ( acc == null ) { log.Error( "FindActionInputObject - Action Input not found in Action" ); return null; // ERROR - Action Input not found in tree } return acc; } /// /// Find an ActionCommand with index in an Action /// /// The input /// An actionCommand or null if not found public ActionCommandCls FindActionInputObject( int index ) { log.Debug( "FindActionInputObject - Entry" ); // Apply the input to the ActionTree ActionCommandCls acc = null; acc = this.inputList.Find( delegate ( ActionCommandCls _ACC ) { return _ACC.NodeIndex == index; } ); if ( acc == null ) { log.Error( "FindActionInputObject - Action Input not found in Action" ); return null; // ERROR - Action Input not found in tree } return acc; } /// /// Dump the action as partial XML nicely formatted /// /// the action as XML fragment public string toXML( ) { string r = ""; string bindCmd = "rebind"; if ( inputList.Count > 0 ) { if ( !string.IsNullOrEmpty( inputList[0].Input ) ) { r = string.Format( "\t\n", name ); foreach ( ActionCommandCls acc in inputList ) { if ( !string.IsNullOrEmpty( acc.Input ) ) { // r += string.Format( "\t\t\t<{0} device=\"{1}\" {2}", bindCmd, device, acc.toXML( ) ); // OLD style r += string.Format( "\t\t\t<{0} {1}", bindCmd, acc.toXML( ) ); // 20151220BM: format for AC2 style bindCmd = "addbind"; // switch to addbind } } r += string.Format( "\t\t\n" ); } } return r; } /// /// Read an action from XML - do some sanity check /// /// the XML action fragment /// True if an action was decoded public Boolean fromXML( string xml ) { XmlReaderSettings settings = new XmlReaderSettings( ); settings.ConformanceLevel = ConformanceLevel.Fragment; settings.IgnoreWhitespace = true; settings.IgnoreComments = true; XmlReader reader = XmlReader.Create( new StringReader( xml ), settings ); reader.Read( ); if ( reader.Name.ToLowerInvariant( ) == "action" ) { if ( reader.HasAttributes ) { name = reader["name"]; reader.ReadStartElement( "action" ); // Checks that the current content node is an element with the given Name and advances the reader to the next node } else { return false; } } do { // support AC2 and AC1 i.e. without and with device attribute if ( reader.Name.ToLowerInvariant( ) == "rebind" ) { if ( reader.HasAttributes ) { device = reader["device"]; string input = reader["input"]; if ( string.IsNullOrEmpty( input ) ) return false; // ERROR exit input = DeviceCls.fromXML( input ); // move from external to internal blend if ( string.IsNullOrEmpty( device ) ) { // AC2 style - derive the device (Device.DeviceClass) device = DeviceClassFromInput( input ); } else { // AC1 style - need to reformat mouse and keyboard according to AC2 style now if ( KeyboardCls.IsDeviceClass( device ) ) input = KeyboardCls.FromAC1( input ); else if ( MouseCls.IsDeviceClass( device ) ) input = MouseCls.FromAC1( input ); else if ( GamepadCls.IsDeviceClass( device ) ) input = GamepadCls.FromAC1( input ); } //first find an ActivationMode if there is - applies to all actions // this can be an Activation Mode OR a multitap // if there is an activationMode - copy the one from our List // if no ActivationMode is given, create one with multitap 1 or may be 2... string actModeName = reader["ActivationMode"]; ActivationMode actMode = null; if ( ! string.IsNullOrEmpty( actModeName ) ) { actMode = ActivationModes.Instance.ActivationModeByName( actModeName ); // should be a valid ActivationMode for this action } else { actMode = new ActivationMode( ActivationMode.Default ); // no specific name given, use default string multiTap = reader["multiTap"]; if ( !string.IsNullOrEmpty( multiTap ) ) { actMode.MultiTap = int.Parse(multiTap); // modify with given multiTap } } key = DevTag( device ) + name; // unique id of the action actionDevice = ADevice( device ); // get the enum of the input device AddCommand( input, actMode ); // advances the reader to the next node reader.ReadStartElement( "rebind" ); } } else if ( reader.Name.ToLowerInvariant( ) == "addbind" ) { if ( reader.HasAttributes ) { device = reader["device"]; string input = reader["input"]; if ( string.IsNullOrEmpty( input ) ) return false; // ERROR exit input = DeviceCls.fromXML( input ); // move from external to internal blend if ( string.IsNullOrEmpty( device ) ) { // AC2 style - derive the device (Device.DeviceClass) device = DeviceClassFromInput( input ); } else { // AC1 style - need to reformat according to AC2 style now if ( KeyboardCls.IsDeviceClass( device ) ) input = KeyboardCls.FromAC1( input ); else if ( MouseCls.IsDeviceClass( device ) ) input = MouseCls.FromAC1( input ); else if ( GamepadCls.IsDeviceClass( device ) ) input = GamepadCls.FromAC1( input ); } //first find an ActivationMode if there is - applies to all actions // this can be an Activation Mode OR a multitap // if there is an activationMode - copy the one from our List // if no ActivationMode is given, create one with multitap 1 or may be 2... string actModeName = reader["ActivationMode"]; ActivationMode actMode = null; if ( !string.IsNullOrEmpty( actModeName ) ) { actMode = ActivationModes.Instance.ActivationModeByName( actModeName ); // should be a valid ActivationMode for this action } else { actMode = new ActivationMode( ActivationMode.Default ); // no specific name given, use default string multiTap = reader["multiTap"]; if ( !string.IsNullOrEmpty( multiTap ) ) { actMode.MultiTap = int.Parse( multiTap ); // modify with given multiTap } } AddCommand( input, actMode ); // advances the reader to the next node reader.ReadStartElement( "addbind" ); } } else { return false; } } while ( reader.Name == "addbind" ); return true; } } }