371 lines
12 KiB
C#
371 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Rewired;
|
|
using RoR2.UI;
|
|
using UnityEngine;
|
|
|
|
namespace RoR2;
|
|
|
|
public class InputMapperHelper : IDisposable
|
|
{
|
|
private readonly MPEventSystem eventSystem;
|
|
|
|
private readonly List<InputMapper> inputMappers = new List<InputMapper>();
|
|
|
|
private ControllerMap[] maps = Array.Empty<ControllerMap>();
|
|
|
|
private SimpleDialogBox dialogBox;
|
|
|
|
public float timeout = 5f;
|
|
|
|
private float timer;
|
|
|
|
private Player currentPlayer;
|
|
|
|
private InputAction currentAction;
|
|
|
|
private AxisRange currentAxisRange;
|
|
|
|
public static bool anyRemapperListening = false;
|
|
|
|
public static float anyRemapperListeningStopTimestamp = -1f;
|
|
|
|
protected Player _player;
|
|
|
|
private Action<InputMapper.ConflictResponse> conflictResponseCallback;
|
|
|
|
private static readonly HashSet<string> forbiddenElements = new HashSet<string>
|
|
{
|
|
"Left Stick X",
|
|
"left stick x",
|
|
"Left Stick Y",
|
|
"left stick y",
|
|
"Right Stick X",
|
|
"right stick x",
|
|
"Right Stick Y",
|
|
"right stick y",
|
|
"Mouse Horizontal",
|
|
"Mouse Vertical",
|
|
"Menu",
|
|
"Menu Button",
|
|
"OPTIONS button",
|
|
"options button",
|
|
"Left SL",
|
|
"Left SR",
|
|
"Right SL",
|
|
"Right SR",
|
|
"+ Button",
|
|
Keyboard.GetKeyName(KeyCode.Escape),
|
|
Keyboard.GetKeyName(KeyCode.KeypadEnter),
|
|
Keyboard.GetKeyName(KeyCode.Return)
|
|
};
|
|
|
|
public bool isListening { get; private set; }
|
|
|
|
public InputMapperHelper(MPEventSystem eventSystem)
|
|
{
|
|
this.eventSystem = eventSystem;
|
|
}
|
|
|
|
private InputMapper AddInputMapper()
|
|
{
|
|
InputMapper inputMapper = new InputMapper();
|
|
inputMapper.ConflictFoundEvent += InputMapperOnConflictFoundEvent;
|
|
inputMapper.CanceledEvent += InputMapperOnCanceledEvent;
|
|
inputMapper.ErrorEvent += InputMapperOnErrorEvent;
|
|
inputMapper.InputMappedEvent += InputMapperOnInputMappedEvent;
|
|
inputMapper.StartedEvent += InputMapperOnStartedEvent;
|
|
inputMapper.StoppedEvent += InputMapperOnStoppedEvent;
|
|
inputMapper.TimedOutEvent += InputMapperOnTimedOutEvent;
|
|
inputMapper.options = new InputMapper.Options
|
|
{
|
|
allowAxes = true,
|
|
allowButtons = true,
|
|
allowKeyboardKeysWithModifiers = false,
|
|
allowKeyboardModifierKeyAsPrimary = true,
|
|
checkForConflicts = true,
|
|
checkForConflictsWithAllPlayers = false,
|
|
checkForConflictsWithPlayerIds = Array.Empty<int>(),
|
|
checkForConflictsWithSelf = true,
|
|
checkForConflictsWithSystemPlayer = false,
|
|
defaultActionWhenConflictFound = InputMapper.ConflictResponse.Add,
|
|
holdDurationToMapKeyboardModifierKeyAsPrimary = 0f,
|
|
ignoreMouseXAxis = true,
|
|
ignoreMouseYAxis = true,
|
|
timeout = float.PositiveInfinity
|
|
};
|
|
inputMappers.Add(inputMapper);
|
|
return inputMapper;
|
|
}
|
|
|
|
private void RemoveInputMapper(InputMapper inputMapper)
|
|
{
|
|
inputMapper.ConflictFoundEvent -= InputMapperOnConflictFoundEvent;
|
|
inputMapper.CanceledEvent -= InputMapperOnCanceledEvent;
|
|
inputMapper.ErrorEvent -= InputMapperOnErrorEvent;
|
|
inputMapper.InputMappedEvent -= InputMapperOnInputMappedEvent;
|
|
inputMapper.StartedEvent -= InputMapperOnStartedEvent;
|
|
inputMapper.StoppedEvent -= InputMapperOnStoppedEvent;
|
|
inputMapper.TimedOutEvent -= InputMapperOnTimedOutEvent;
|
|
inputMappers.Remove(inputMapper);
|
|
}
|
|
|
|
public void Start(Player player, IList<Controller> controllers, InputAction action, AxisRange axisRange)
|
|
{
|
|
Stop();
|
|
anyRemapperListening = true;
|
|
isListening = true;
|
|
currentPlayer = player;
|
|
currentAction = action;
|
|
currentAxisRange = axisRange;
|
|
maps = (from controller in controllers
|
|
select player.controllers.maps.GetFirstMapInCategory(controller, 0) into map
|
|
where map != null
|
|
select map).ToArray();
|
|
DefaultControllerMaps.RewiredDebugLog("InputMapperHelper: Found {0} control maps", maps.Length);
|
|
_player = player;
|
|
Controller lastActiveController = player.controllers.GetLastActiveController();
|
|
Controller lastActiveController2 = player.controllers.GetLastActiveController(ControllerType.Joystick);
|
|
DefaultControllerMaps.RewiredDebugLog("InputMapHelper.Start: active {0} ({1}), activeJoystick {2} ({3})", (lastActiveController != null) ? lastActiveController.type.ToString() : "**null**", (lastActiveController != null) ? lastActiveController.hardwareTypeGuid.ToString() : "**null**", (lastActiveController2 != null) ? lastActiveController2.type.ToString() : "**null**", (lastActiveController2 != null) ? lastActiveController2.hardwareTypeGuid.ToString() : "**null**");
|
|
if (lastActiveController2 != null && lastActiveController == lastActiveController2)
|
|
{
|
|
ControllerMap[] array = maps;
|
|
foreach (ControllerMap controllerMap in array)
|
|
{
|
|
DefaultControllerMaps.RewiredDebugLog("\tProcessing map: {0}", controllerMap.ToJsonString());
|
|
InputMapper.Context mappingContext = new InputMapper.Context
|
|
{
|
|
actionId = action.id,
|
|
controllerMap = controllerMap,
|
|
actionRange = currentAxisRange
|
|
};
|
|
AddInputMapper().Start(mappingContext);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ControllerMap[] array = maps;
|
|
foreach (ControllerMap controllerMap2 in array)
|
|
{
|
|
if (controllerMap2.controllerType != ControllerType.Joystick)
|
|
{
|
|
InputMapper.Context mappingContext2 = new InputMapper.Context
|
|
{
|
|
actionId = action.id,
|
|
controllerMap = controllerMap2,
|
|
actionRange = currentAxisRange
|
|
};
|
|
AddInputMapper().Start(mappingContext2);
|
|
}
|
|
}
|
|
}
|
|
dialogBox = SimpleDialogBox.Create(eventSystem);
|
|
timer = timeout;
|
|
UpdateDialogBoxString();
|
|
RoR2Application.onUpdate += Update;
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
if (isListening)
|
|
{
|
|
anyRemapperListening = false;
|
|
anyRemapperListeningStopTimestamp = Time.unscaledTime;
|
|
maps = Array.Empty<ControllerMap>();
|
|
currentPlayer = null;
|
|
currentAction = null;
|
|
for (int num = inputMappers.Count - 1; num >= 0; num--)
|
|
{
|
|
InputMapper inputMapper = inputMappers[num];
|
|
inputMapper.Stop();
|
|
RemoveInputMapper(inputMapper);
|
|
}
|
|
if ((bool)dialogBox)
|
|
{
|
|
UnityEngine.Object.Destroy(dialogBox.rootObject);
|
|
dialogBox = null;
|
|
}
|
|
isListening = false;
|
|
RoR2Application.onUpdate -= Update;
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
float unscaledDeltaTime = Time.unscaledDeltaTime;
|
|
if (isListening)
|
|
{
|
|
timer -= unscaledDeltaTime;
|
|
if (timer < 0f)
|
|
{
|
|
Stop();
|
|
}
|
|
else if (currentPlayer.GetButtonDown(25))
|
|
{
|
|
ShowCancledDialog();
|
|
}
|
|
else
|
|
{
|
|
UpdateDialogBoxString();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ShowCancledDialog()
|
|
{
|
|
Stop();
|
|
SimpleDialogBox simpleDialogBox = SimpleDialogBox.Create(eventSystem);
|
|
simpleDialogBox.headerToken = new SimpleDialogBox.TokenParamsPair("OPTION_REBIND_DIALOG_TITLE");
|
|
simpleDialogBox.descriptionToken = new SimpleDialogBox.TokenParamsPair("OPTION_REBIND_CANCELLED_DIALOG_DESCRIPTION");
|
|
simpleDialogBox.AddCancelButton(CommonLanguageTokens.ok);
|
|
}
|
|
|
|
private void UpdateDialogBoxString()
|
|
{
|
|
if ((bool)dialogBox && timer >= 0f)
|
|
{
|
|
string @string = Language.GetString(InputCatalog.GetActionNameToken(currentAction.name));
|
|
dialogBox.headerToken = new SimpleDialogBox.TokenParamsPair
|
|
{
|
|
token = CommonLanguageTokens.optionRebindDialogTitle,
|
|
formatParams = Array.Empty<object>()
|
|
};
|
|
dialogBox.descriptionToken = new SimpleDialogBox.TokenParamsPair
|
|
{
|
|
token = CommonLanguageTokens.optionRebindDialogDescription,
|
|
formatParams = new object[2] { @string, timer }
|
|
};
|
|
}
|
|
}
|
|
|
|
private void InputMapperOnTimedOutEvent(InputMapper.TimedOutEventData timedOutEventData)
|
|
{
|
|
}
|
|
|
|
private void InputMapperOnStoppedEvent(InputMapper.StoppedEventData stoppedEventData)
|
|
{
|
|
}
|
|
|
|
private void InputMapperOnStartedEvent(InputMapper.StartedEventData startedEventData)
|
|
{
|
|
}
|
|
|
|
private void InputMapperOnInputMappedEvent(InputMapper.InputMappedEventData inputMappedEventData)
|
|
{
|
|
ActionElementMap incomingActionElementMap = inputMappedEventData.actionElementMap;
|
|
int incomingActionId = inputMappedEventData.actionElementMap.actionId;
|
|
int incomingElementIndex = inputMappedEventData.actionElementMap.elementIndex;
|
|
ControllerElementType incomingElementType = inputMappedEventData.actionElementMap.elementType;
|
|
ControllerMap map = inputMappedEventData.actionElementMap.controllerMap;
|
|
Controller activeController = null;
|
|
ControllerMap defaultMap = null;
|
|
ControllerMap uiMap = null;
|
|
if (_player != null)
|
|
{
|
|
activeController = _player.controllers.GetLastActiveController();
|
|
if (activeController != null && activeController.type == ControllerType.Joystick)
|
|
{
|
|
if (!DefaultControllerMaps.HardwareGuidsMatch(activeController.hardwareTypeGuid, map.hardwareGuid))
|
|
{
|
|
DefaultControllerMaps.RewiredDebugLog($"Skipping hardware {map.hardwareGuid} (activeController {activeController.hardwareTypeGuid}");
|
|
return;
|
|
}
|
|
DefaultControllerMaps.RewiredDebugLog($"Hardware match: {map.hardwareGuid} (activeController {activeController.hardwareTypeGuid}");
|
|
}
|
|
}
|
|
if (activeController != null && activeController.type == ControllerType.Joystick)
|
|
{
|
|
defaultMap = ReInput.mapping.GetJoystickMapInstance(activeController.hardwareTypeGuid, 0, 0);
|
|
uiMap = ReInput.mapping.GetJoystickMapInstance(activeController.hardwareTypeGuid, 2, 0);
|
|
}
|
|
DefaultControllerMaps.RewiredDebugLog($"NEWMAP: elementIdentifierID: {inputMappedEventData.actionElementMap.elementIdentifierId} ({inputMappedEventData.actionElementMap.actionDescriptiveName})");
|
|
if (forbiddenElements.Contains(inputMappedEventData.actionElementMap.elementIdentifierName))
|
|
{
|
|
ShowCancledDialog();
|
|
return;
|
|
}
|
|
ControllerMap[] array = maps;
|
|
foreach (ControllerMap controllerMap in array)
|
|
{
|
|
if (controllerMap != map)
|
|
{
|
|
controllerMap.DeleteElementMapsWithAction(incomingActionId);
|
|
}
|
|
}
|
|
while (DeleteFirstConflictingElementMap())
|
|
{
|
|
}
|
|
if (activeController != null)
|
|
{
|
|
if (map is JoystickMap inJoystickMap)
|
|
{
|
|
eventSystem?.localUser?.userProfile.UpdateJoystickMap(activeController.hardwareTypeGuid, inJoystickMap);
|
|
}
|
|
else
|
|
{
|
|
DefaultControllerMaps.RewiredDebugWarn($"NOT A JOYSTICK MAP for controller {activeController.hardwareTypeGuid}");
|
|
}
|
|
}
|
|
eventSystem?.localUser?.userProfile.RequestEventualSave();
|
|
Stop();
|
|
if (InputBindingDisplayController.onBindingsChanged != null)
|
|
{
|
|
InputBindingDisplayController.onBindingsChanged();
|
|
}
|
|
bool ActionElementMapConflicts(ActionElementMap actionElementMap)
|
|
{
|
|
if (actionElementMap == incomingActionElementMap)
|
|
{
|
|
return false;
|
|
}
|
|
bool num = actionElementMap.elementIndex == incomingElementIndex && actionElementMap.elementType == incomingElementType;
|
|
bool flag = actionElementMap.actionId == incomingActionId && actionElementMap.axisContribution == incomingActionElementMap.axisContribution;
|
|
return num || flag;
|
|
}
|
|
bool DeleteFirstConflictingElementMap()
|
|
{
|
|
foreach (ActionElementMap allMap in map.AllMaps)
|
|
{
|
|
if ((defaultMap != null || uiMap != null) && allMap.controllerMap != null)
|
|
{
|
|
bool num2 = allMap.controllerMap == defaultMap;
|
|
bool flag2 = allMap.controllerMap != uiMap;
|
|
if (!num2 && !flag2)
|
|
{
|
|
DefaultControllerMaps.RewiredDebugLog("Skipping map as it is for wrong controller... active {0} vs checking {1}", activeController.hardwareTypeGuid, allMap.controllerMap.controller.hardwareTypeGuid);
|
|
continue;
|
|
}
|
|
}
|
|
if (ActionElementMapConflicts(allMap))
|
|
{
|
|
Debug.LogFormat("Deleting conflicting mapping {0}", allMap);
|
|
DefaultControllerMaps.RewiredDebugLog("***** Deleting conflicting mapping {0} ({1})", allMap, allMap.controllerMap.controller.hardwareTypeGuid);
|
|
map.DeleteElementMap(allMap.id);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private void InputMapperOnErrorEvent(InputMapper.ErrorEventData errorEventData)
|
|
{
|
|
}
|
|
|
|
private void InputMapperOnCanceledEvent(InputMapper.CanceledEventData canceledEventData)
|
|
{
|
|
}
|
|
|
|
private void InputMapperOnConflictFoundEvent(InputMapper.ConflictFoundEventData conflictFoundEventData)
|
|
{
|
|
InputMapper.ConflictResponse obj = ((!conflictFoundEventData.conflicts.Any((ElementAssignmentConflictInfo elementAssignmentConflictInfo) => forbiddenElements.Contains(elementAssignmentConflictInfo.elementIdentifier.name))) ? InputMapper.ConflictResponse.Add : InputMapper.ConflictResponse.Ignore);
|
|
conflictFoundEventData.responseCallback(obj);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Stop();
|
|
}
|
|
}
|