add mouse controller

This commit is contained in:
minenice55 2023-09-15 17:59:00 -04:00
parent dd461216d9
commit 4a41a331d3
12 changed files with 2034 additions and 131 deletions

View file

@ -50,7 +50,7 @@ namespace HeavenStudio.Games
)
{
GameObject evtObj = new GameObject("ActionEvent" + (startBeat+timer));
GameObject evtObj = new GameObject("ActionEvent" + (startBeat + timer));
evtObj.AddComponent<PlayerActionEvent>();
PlayerActionEvent evt = evtObj.GetComponent<PlayerActionEvent>();
@ -115,16 +115,17 @@ namespace HeavenStudio.Games
{
PlayerActionEvent closest = null;
foreach(PlayerActionEvent toCompare in scheduledInputs)
foreach (PlayerActionEvent toCompare in scheduledInputs)
{
// ignore inputs that are for sequencing in autoplay
if (toCompare.autoplayOnly) continue;
if(closest == null)
if (closest == null)
{
if (input == InputType.ANY || (toCompare.inputType & input) != 0)
closest = toCompare;
} else
}
else
{
double t1 = closest.startBeat + closest.timer;
double t2 = toCompare.startBeat + toCompare.timer;
@ -142,6 +143,40 @@ namespace HeavenStudio.Games
return closest;
}
public PlayerActionEvent GetClosestScheduledInput(string action)
{
if (action == null) return null;
PlayerActionEvent closest = null;
foreach (PlayerActionEvent toCompare in scheduledInputs)
{
// ignore inputs that are for sequencing in autoplay
if (toCompare.autoplayOnly) continue;
if (closest == null)
{
if (toCompare.InputAction.name == action)
closest = toCompare;
}
else
{
double t1 = closest.startBeat + closest.timer;
double t2 = toCompare.startBeat + toCompare.timer;
// Debug.Log("t1=" + t1 + " -- t2=" + t2);
if (t2 < t1)
{
if (toCompare.InputAction.name == action)
closest = toCompare;
}
}
}
return closest;
}
//Hasn't been tested yet. *Should* work.
//Can be used to detect if the user is expected to input something now or not
//Useful for strict call and responses games like Tambourine
@ -152,6 +187,13 @@ namespace HeavenStudio.Games
return input.IsExpectingInputNow();
}
public bool IsExpectingInputNow(string wantAction)
{
PlayerActionEvent input = GetClosestScheduledInput(wantAction);
if (input == null) return false;
return input.IsExpectingInputNow();
}
// now should fix the fast bpm problem
public static double NgEarlyTime()
{
@ -260,14 +302,16 @@ namespace HeavenStudio.Games
}
}
private void OnDestroy() {
private void OnDestroy()
{
foreach (var evt in scheduledInputs)
{
evt.Disable();
}
}
protected void OnDrawGizmos() {
protected void OnDrawGizmos()
{
Gizmos.color = Color.magenta;
Gizmos.DrawWireCube(Vector3.zero, new Vector3(17.77695f, 10, 0));
}

View file

@ -8,6 +8,7 @@ using HeavenStudio.Util;
using Starpelly;
using HeavenStudio.Common;
using HeavenStudio.InputSystem;
namespace HeavenStudio.Games
{
@ -27,6 +28,8 @@ namespace HeavenStudio.Games
public ActionEventCallback OnDestroy; //Function to trigger whenever this event gets destroyed. /!\ Shouldn't be used for a minigame! Use OnMiss instead /!\
public PlayerInput.InputAction InputAction;
public double startBeat;
public double timer;
@ -69,6 +72,10 @@ namespace HeavenStudio.Games
public bool IsCorrectInput(out double dt)
{
dt = 0;
if (InputAction != null)
{
return PlayerInput.GetIsAction(InputAction, out dt);
}
return (
//General inputs, both down and up
(PlayerInput.Pressed(out dt) && inputType.HasFlag(InputType.STANDARD_DOWN)) ||
@ -160,8 +167,16 @@ namespace HeavenStudio.Games
{
if (toCompare == this) continue;
if (toCompare.autoplayOnly) continue;
if ((toCompare.inputType & this.inputType) == 0) continue;
if (!toCompare.IsExpectingInputNow()) continue;
if (InputAction != null)
{
if (toCompare.InputAction == null) continue;
if (toCompare.InputAction != null && toCompare.InputAction == InputAction) continue;
}
else
{
if ((toCompare.inputType & this.inputType) == 0) continue;
if (!toCompare.IsExpectingInputNow()) continue;
}
double t1 = this.startBeat + this.timer;
double t2 = toCompare.startBeat + toCompare.timer;

View file

@ -11,7 +11,7 @@ namespace HeavenStudio.InputSystem.Loaders
{
public static class InputJoyshockInitializer
{
[LoadOrder(1)]
[LoadOrder(2)]
public static InputController[] Initialize()
{
InputJoyshock.joyshocks = new();
@ -120,7 +120,8 @@ namespace HeavenStudio.InputSystem
0x888888
};
static int[] defaultMappings {
static int[] defaultMappings
{
get
{
return new[]
@ -140,7 +141,8 @@ namespace HeavenStudio.InputSystem
};
}
}
static int[] defaultMappingsL {
static int[] defaultMappingsL
{
get
{
return new[]
@ -161,7 +163,8 @@ namespace HeavenStudio.InputSystem
}
}
static int[] defaultMappingsR {
static int[] defaultMappingsR
{
get
{
return new[]
@ -277,7 +280,7 @@ namespace HeavenStudio.InputSystem
"Mic",
};
static readonly float debounceTime = 1f/90f;
static readonly float debounceTime = 1f / 90f;
public static Dictionary<int, InputJoyshock> joyshocks;
@ -335,7 +338,7 @@ namespace HeavenStudio.InputSystem
if (action < 0 || action >= BINDS_MAX) return -1;
ControlBindings actionMap = currentBindings;
if (actionMap.Pad[action] > ButtonMaskSR) return -1;
return actionMap.Pad[action];
}
@ -362,6 +365,10 @@ namespace HeavenStudio.InputSystem
timestamp = (DateTime.Now - js.startTime).TotalSeconds,
input = state
});
js.joyImuStateCurrent = imuState;
js.joyImuStateLast = lastImuState;
}
public override void InitializeController()
@ -417,7 +424,7 @@ namespace HeavenStudio.InputSystem
buttonStates[i].isDelta = false;
}
foreach(TimestampedState state in lastInputStack)
foreach (TimestampedState state in lastInputStack)
{
joyBtStateCurrent = state.input;
@ -480,30 +487,28 @@ namespace HeavenStudio.InputSystem
xAxis = joyBtStateCurrent.stickLX;
yAxis = joyBtStateCurrent.stickLY;
}
directionStateLast = directionStateCurrent;
directionStateCurrent = 0;
directionStateCurrent |= ((yAxis >= stickDeadzone) ? (1 << ((int) InputDirection.Up)) : 0);
directionStateCurrent |= ((yAxis <= -stickDeadzone) ? (1 << ((int) InputDirection.Down)) : 0);
directionStateCurrent |= ((xAxis >= stickDeadzone) ? (1 << ((int) InputDirection.Right)) : 0);
directionStateCurrent |= ((xAxis <= -stickDeadzone) ? (1 << ((int) InputDirection.Left)) : 0);
directionStateCurrent |= ((yAxis >= stickDeadzone) ? (1 << ((int)InputDirection.Up)) : 0);
directionStateCurrent |= ((yAxis <= -stickDeadzone) ? (1 << ((int)InputDirection.Down)) : 0);
directionStateCurrent |= ((xAxis >= stickDeadzone) ? (1 << ((int)InputDirection.Right)) : 0);
directionStateCurrent |= ((xAxis <= -stickDeadzone) ? (1 << ((int)InputDirection.Left)) : 0);
//Debug.Log("stick direction: " + directionStateCurrent + "| x axis: " + xAxis + " y axis: " + yAxis);
lastInputStack.Clear();
}
public override void OnSelected()
{
{
Task.Run(() => SelectionVibrate());
}
async void SelectionVibrate()
{
JslSetRumbleFrequency(GetHandle(), 0.5f, 0.5f, 80f, 160f);
await Task.Delay(50);
JslSetRumbleFrequency(GetHandle(), 0.75f, 0.75f, 160f, 320f);
await Task.Delay(50);
JslSetRumbleFrequency(GetHandle(), 0f, 0f, 0f, 0f);
await Task.Delay(100);
JslSetRumbleFrequency(GetHandle(), 0f, 0f, 160f, 320f);
}
public override string GetDeviceName()
@ -651,7 +656,7 @@ namespace HeavenStudio.InputSystem
}
}
if (otherHalf != null)
{
{
return otherHalf.GetLastActionDown();
}
return -1;
@ -659,7 +664,7 @@ namespace HeavenStudio.InputSystem
public override bool GetAction(int button)
{
if (button == -1) {return false;}
if (button == -1) { return false; }
if (otherHalf != null)
{
return actionStates[button].pressed || otherHalf.actionStates[button].pressed;
@ -669,7 +674,7 @@ namespace HeavenStudio.InputSystem
public override bool GetActionDown(int button, out double dt)
{
if (button == -1) {dt = 0; return false;}
if (button == -1) { dt = 0; return false; }
if (otherHalf != null && otherHalf.GetActionDown(button, out dt))
{
return true;
@ -680,7 +685,7 @@ namespace HeavenStudio.InputSystem
public override bool GetActionUp(int button, out double dt)
{
if (button == -1) {dt = 0; return false;}
if (button == -1) { dt = 0; return false; }
if (otherHalf != null && otherHalf.GetActionUp(button, out dt))
{
return true;
@ -706,14 +711,37 @@ namespace HeavenStudio.InputSystem
case InputAxis.AxisRStickY:
return joyBtStateCurrent.stickRY;
case InputAxis.TouchpadX: //isn't updated for now, so always returns 0f
//return joyTouchStateCurrent.t0X;
//return joyTouchStateCurrent.t0X;
case InputAxis.TouchpadY:
//return joyTouchStateCurrent.t0Y;
//return joyTouchStateCurrent.t0Y;
default:
return 0f;
}
}
public override Vector3 GetVector(InputVector vec)
{
switch (vec)
{
case InputVector.LStick:
return new Vector3(joyBtStateCurrent.stickLX, joyBtStateCurrent.stickLY, 0f);
case InputVector.RStick:
return new Vector3(joyBtStateCurrent.stickRX, joyBtStateCurrent.stickRY, 0f);
case InputVector.Touchpad:
return new Vector3(joyTouchStateCurrent.t0X, joyTouchStateCurrent.t0Y, 0f);
case InputVector.Accelerometer:
return new Vector3(joyImuStateCurrent.accelX, joyImuStateCurrent.accelY, joyImuStateCurrent.accelZ);
case InputVector.Gyroscope:
return new Vector3(joyImuStateCurrent.gyroX, joyImuStateCurrent.gyroY, joyImuStateCurrent.gyroZ);
}
return Vector3.zero;
}
public override Vector2 GetPointer()
{
return Vector2.zero;
}
public override bool GetHatDirection(InputDirection direction)
{
int bt;
@ -736,9 +764,9 @@ namespace HeavenStudio.InputSystem
}
if (otherHalf != null)
{
return GetAction(bt) || BitwiseUtils.WantCurrent(otherHalf.directionStateCurrent, 1 << (int) direction) || BitwiseUtils.WantCurrent(directionStateCurrent, 1 << (int) direction);
return GetAction(bt) || BitwiseUtils.WantCurrent(otherHalf.directionStateCurrent, 1 << (int)direction) || BitwiseUtils.WantCurrent(directionStateCurrent, 1 << (int)direction);
}
return GetAction(bt) || BitwiseUtils.WantCurrent(directionStateCurrent, 1 << (int) direction);
return GetAction(bt) || BitwiseUtils.WantCurrent(directionStateCurrent, 1 << (int)direction);
}
public override bool GetHatDirectionDown(InputDirection direction, out double dt)
@ -768,7 +796,7 @@ namespace HeavenStudio.InputSystem
{
return btbool || BitwiseUtils.WantCurrentAndNotLast(otherHalf.directionStateCurrent, otherHalf.directionStateLast, 1 << (int)direction) || BitwiseUtils.WantCurrentAndNotLast(directionStateCurrent, directionStateLast, 1 << (int)direction);
}
return btbool || BitwiseUtils.WantCurrentAndNotLast(directionStateCurrent, directionStateLast, 1 << (int) direction);
return btbool || BitwiseUtils.WantCurrentAndNotLast(directionStateCurrent, directionStateLast, 1 << (int)direction);
}
public override bool GetHatDirectionUp(InputDirection direction, out double dt)
@ -796,9 +824,9 @@ namespace HeavenStudio.InputSystem
if (!btbool) dt = 0;
if (otherHalf != null)
{
return btbool || BitwiseUtils.WantNotCurrentAndLast(otherHalf.directionStateCurrent, otherHalf.directionStateLast, 1 << (int) direction) || BitwiseUtils.WantNotCurrentAndLast(directionStateCurrent, directionStateLast, 1 << (int) direction);
return btbool || BitwiseUtils.WantNotCurrentAndLast(otherHalf.directionStateCurrent, otherHalf.directionStateLast, 1 << (int)direction) || BitwiseUtils.WantNotCurrentAndLast(directionStateCurrent, directionStateLast, 1 << (int)direction);
}
return btbool || BitwiseUtils.WantNotCurrentAndLast(directionStateCurrent, directionStateLast, 1 << (int) direction);
return btbool || BitwiseUtils.WantNotCurrentAndLast(directionStateCurrent, directionStateLast, 1 << (int)direction);
}
public override void SetPlayer(int? playerNum)
@ -812,16 +840,16 @@ namespace HeavenStudio.InputSystem
return;
}
this.playerNum = playerNum;
int ledMask = (int) this.playerNum;
int ledMask = (int)this.playerNum;
if (type == TypeDualSense)
{
if (playerNum <= 5)
{
ledMask = DualSensePlayerMask[Math.Max((int) this.playerNum + 1, 1)];
ledMask = DualSensePlayerMask[Math.Max((int)this.playerNum + 1, 1)];
}
}
JslSetPlayerNumber(joyshockHandle, ledMask);
lightbarColour = GetLightbarColourForPlayer((int) this.playerNum);
lightbarColour = GetLightbarColourForPlayer((int)this.playerNum);
JslSetLightColour(joyshockHandle, lightbarColour);
}

View file

@ -4,8 +4,6 @@ using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static JSL;
namespace HeavenStudio.InputSystem.Loaders
{
public static class InputKeyboardInitializer
@ -180,7 +178,17 @@ namespace HeavenStudio.InputSystem
{
return 0;
}
public override Vector3 GetVector(InputVector vec)
{
return Vector3.zero;
}
public override Vector2 GetPointer()
{
return Vector2.zero;
}
//todo: directionals
public override bool GetHatDirection(InputDirection direction)
{

View file

@ -0,0 +1,257 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using HeavenStudio.Util;
namespace HeavenStudio.InputSystem.Loaders
{
namespace HeavenStudio.InputSystem.Loaders
{
public static class InputMouseInitializer
{
[LoadOrder(1)]
public static InputController[] Initialize()
{
PlayerInput.PlayerInputRefresh.Add(Refresh);
InputMouse mouse = new InputMouse();
mouse.SetPlayer(null);
mouse.InitializeController();
return new InputController[] { mouse };
}
public static InputController[] Refresh()
{
InputMouse mouse = new InputMouse();
mouse.SetPlayer(null);
mouse.InitializeController();
return new InputController[] { mouse };
}
}
}
}
namespace HeavenStudio.InputSystem
{
public class InputMouse : InputController
{
private static readonly KeyCode[] keyCodes = Enum.GetValues(typeof(KeyCode))
.Cast<KeyCode>()
.Where(k => ((int)k <= (int)KeyCode.Mouse6))
.ToArray();
static ControlBindings defaultBindings {
get
{
return new ControlBindings()
{
Touch = new int[]
{
-1,
(int)KeyCode.Mouse0,
(int)KeyCode.Mouse1,
(int)KeyCode.Z,
(int)KeyCode.X,
(int)KeyCode.Escape
},
};
}
}
public override void InitializeController()
{
}
public override void UpdateState()
{
// Update the state of the controller
}
public override void OnSelected()
{
}
public override string GetDeviceName()
{
return "Mouse";
}
public override string[] GetButtonNames()
{
string[] names = new string[(int)KeyCode.Mouse6];
for (int i = 0; i < keyCodes.Length; i++)
{
names[(int)keyCodes[i]] = keyCodes[i].ToString();
}
return names;
}
public override InputFeatures GetFeatures()
{
return InputFeatures.Readable_Pointer | InputFeatures.Style_Touch;
}
public override bool GetIsConnected()
{
return true;
}
public override bool GetIsPoorConnection()
{
return false;
}
public override ControlBindings GetDefaultBindings()
{
return defaultBindings;
}
public override void ResetBindings()
{
currentBindings = GetDefaultBindings();
}
public override ControlBindings GetCurrentBindings()
{
return currentBindings;
}
public override void SetCurrentBindings(ControlBindings newBinds)
{
currentBindings = newBinds;
}
public override bool GetIsActionUnbindable(int action, ControlStyles style)
{
return action is 0;
}
public override int GetLastButtonDown()
{
if (Input.anyKeyDown)
{
for (KeyCode i = keyCodes[1]; i <= KeyCode.Menu; i++)
{
if (Input.GetKeyDown(i))
return (int)i;
}
}
return (int)KeyCode.None;
}
public override int GetLastActionDown()
{
for (int i = 0; i < BINDS_MAX; i++)
{
if (Input.GetKeyDown((KeyCode)currentBindings.Pad[i]))
return i;
}
return -1;
}
public override bool GetAction(int button)
{
return Input.GetKey((KeyCode)currentBindings.Pad[button]);
}
public override bool GetActionDown(int button, out double dt)
{
dt = 0;
return Input.GetKeyDown((KeyCode)currentBindings.Pad[button]);
}
public override bool GetActionUp(int button, out double dt)
{
dt = 0;
return Input.GetKeyUp((KeyCode)currentBindings.Pad[button]);
}
public override float GetAxis(InputAxis axis)
{
return 0;
}
public override Vector3 GetVector(InputVector vec)
{
return Vector3.zero;
}
public override Vector2 GetPointer()
{
return Vector2.zero;
}
//todo: directionals
public override bool GetHatDirection(InputDirection direction)
{
switch (direction)
{
case InputDirection.Up:
return Input.GetKey((KeyCode)currentBindings.Pad[0]);
case InputDirection.Down:
return Input.GetKey((KeyCode)currentBindings.Pad[1]);
case InputDirection.Left:
return Input.GetKey((KeyCode)currentBindings.Pad[2]);
case InputDirection.Right:
return Input.GetKey((KeyCode)currentBindings.Pad[3]);
default:
return false;
}
}
public override bool GetHatDirectionDown(InputDirection direction, out double dt)
{
dt = 0;
switch (direction)
{
case InputDirection.Up:
return Input.GetKeyDown((KeyCode)currentBindings.Pad[0]);
case InputDirection.Down:
return Input.GetKeyDown((KeyCode)currentBindings.Pad[1]);
case InputDirection.Left:
return Input.GetKeyDown((KeyCode)currentBindings.Pad[2]);
case InputDirection.Right:
return Input.GetKeyDown((KeyCode)currentBindings.Pad[3]);
default:
return false;
}
}
public override bool GetHatDirectionUp(InputDirection direction, out double dt)
{
dt = 0;
switch (direction)
{
case InputDirection.Up:
return Input.GetKeyUp((KeyCode)currentBindings.Pad[0]);
case InputDirection.Down:
return Input.GetKeyUp((KeyCode)currentBindings.Pad[1]);
case InputDirection.Left:
return Input.GetKeyUp((KeyCode)currentBindings.Pad[2]);
case InputDirection.Right:
return Input.GetKeyUp((KeyCode)currentBindings.Pad[3]);
default:
return false;
}
}
public override void SetPlayer(int? playerNum)
{
if (playerNum == -1 || playerNum == null)
{
this.playerNum = null;
return;
}
this.playerNum = (int) playerNum;
}
public override int? GetPlayer()
{
return playerNum;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c9ea9f77f8f42db46b1a9c23820fd66a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -23,6 +23,15 @@ namespace HeavenStudio.InputSystem
TouchpadY = 7
}
public enum InputVector: int
{
LStick = 0,
RStick = 1,
Touchpad = 2,
Gyroscope = 3,
Accelerometer = 4,
}
//D-Pad directions, usable to adapt analogue sticks to cardinal directions
public enum InputDirection : int
{
@ -69,9 +78,9 @@ namespace HeavenStudio.InputSystem
public enum ControlStyles
{
Pad,
Baton,
Touch,
Move
Baton,
// Move
}
public const int BINDS_MAX = 12; //maximum number of binds per controller
@ -240,6 +249,10 @@ namespace HeavenStudio.InputSystem
/// <returns></returns>
public abstract float GetAxis(InputAxis axis);
public abstract Vector3 GetVector(InputVector vector);
public abstract Vector2 GetPointer();
/// <summary>
/// True if the current direction is active
/// </summary>

View file

@ -24,14 +24,31 @@ namespace HeavenStudio
{
public class PlayerInput
{
public class InputAction
{
public delegate bool ActionQuery(out double dt);
public string name;
public ActionQuery padAction, touchAction, batonAction;
public InputAction(string name, ActionQuery pad, ActionQuery touch, ActionQuery baton)
{
this.name = name;
padAction = pad;
touchAction = touch;
batonAction = baton;
}
}
//Clockwise
public const int UP = 0;
public const int RIGHT = 1;
public const int DOWN = 2;
public const int LEFT = 3;
public static InputController.ControlStyles CurrentControlStyle = InputController.ControlStyles.Pad;
static List<InputController> inputDevices;
static InputController.ControlStyles currentControlStyle = InputController.ControlStyles.Pad;
public delegate InputController[] InputControllerInitializer();
@ -188,13 +205,22 @@ namespace HeavenStudio
/* MAIN INPUT METHODS */
/*--------------------*/
public static bool GetIsAction(string action)
public static bool GetIsAction(InputAction action, out double dt)
{
dt = 0;
switch (CurrentControlStyle)
{
case InputController.ControlStyles.Pad:
return action.padAction(out dt);
case InputController.ControlStyles.Touch:
return action.touchAction(out dt);
case InputController.ControlStyles.Baton:
return action.batonAction(out dt);
}
return false;
}
// BUTTONS
//TODO: refactor for controller and custom binds, currently uses temporary button checks
public static bool Pressed(bool includeDPad = false)
{

View file

@ -501,16 +501,6 @@ namespace HeavenStudio
}
}
public class InputAction
{
public string name;
public List<InputActionEntry> mappings;
}
public class InputActionEntry
{
}
public delegate void EventCallback();
public delegate void ParamChangeCallback(string paramName, object paramValue, RiqEntity entity);

View file

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
@ -15,6 +16,8 @@ namespace HeavenStudio.Editor
{
public class ControllerSettings : TabsContent
{
[SerializeField] private TMP_Dropdown stylesDropdown;
[SerializeField] private TMP_Text numConnectedLabel;
[SerializeField] private TMP_Text currentControllerLabel;
[SerializeField] private TMP_Dropdown controllersDropdown;
@ -43,6 +46,7 @@ namespace HeavenStudio.Editor
private int currentBindingBt;
private void Start() {
PopulateStylesDropdown();
numConnectedLabel.text = "Connected: " + PlayerInput.GetNumControllersConnected();
currentControllerLabel.text = "Current Controller: " + PlayerInput.GetInputController(1).GetDeviceName();
PopulateControllersDropdown();
@ -262,6 +266,17 @@ namespace HeavenStudio.Editor
PopulateControllersDropdown();
}
public void PopulateStylesDropdown()
{
List<TMP_Dropdown.OptionData> dropDownData = new List<TMP_Dropdown.OptionData>();
var enumNames = Enum.GetNames(typeof(InputController.ControlStyles)).ToList();
controllersDropdown.ClearOptions();
stylesDropdown.AddOptions(enumNames);
stylesDropdown.value = (int) PlayerInput.CurrentControlStyle;
}
public void PopulateControllersDropdown()
{
List<TMP_Dropdown.OptionData> dropDownData = new List<TMP_Dropdown.OptionData>();

File diff suppressed because one or more lines are too long