3002e48350
* add mouse controller * support different control styles in options deprecate old input check methods * fully functional input actions system * btsds InputAction * blue bear InputAction * more games fix bugs with some input related systems * coin toss re-toss * cheer readers touch * dog ninja touch * multiple games * last of the easy games' touch * more specialized games * specialized games 2 * finish ktb games * remove legacy settings disclaimer * "only" two games left * karate man touch * rockers touch still needs fixes and bad judge strum * DSGuy flicking animation * playstyle chart property * improve performance of minigame preloading * improve look of cursor make assetbundles use chunk-based compression refactor assetbundle loading methods a bit * prime conductor stream playback to stabilize seeking operations * fix air rally swing on pad release * use virtual mouse pointer * add UniTask * make BeatAction use UniTask * implement UniTask to replace some coroutines * add touch style UI elements and effects games now support the ability to define two cursor colours if they need split screen touch inputs * update plugins and buildscript * implement thresholded pointer position clipping * fix clamping * instant show / hide fix discord game SDK crashes
460 lines
17 KiB
C#
460 lines
17 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
using HeavenStudio.Util;
|
|
using HeavenStudio.Common;
|
|
using HeavenStudio.InputSystem;
|
|
using System;
|
|
|
|
namespace HeavenStudio.Games
|
|
{
|
|
public class Minigame : MonoBehaviour
|
|
{
|
|
public static double ngEarlyTime = 0.085f, justEarlyTime = 0.06f, aceEarlyTime = 0.01f, aceLateTime = 0.01f, justLateTime = 0.06f, ngLateTime = 0.085f;
|
|
public static float rankHiThreshold = 0.8f, rankOkThreshold = 0.6f;
|
|
[SerializeField] public SoundSequence.SequenceKeyValue[] SoundSequences;
|
|
|
|
#region Premade Input Actions
|
|
protected const int IAEmptyCat = -1;
|
|
protected const int IAPressCat = 0;
|
|
protected const int IAReleaseCat = 1;
|
|
protected const int IAPressingCat = 2;
|
|
protected const int IAFlickCat = 3;
|
|
protected const int IAMAXCAT = 4;
|
|
|
|
protected static bool IA_Empty(out double dt)
|
|
{
|
|
dt = 0;
|
|
return false;
|
|
}
|
|
|
|
protected static bool IA_PadBasicPress(out double dt)
|
|
{
|
|
return PlayerInput.GetPadDown(InputController.ActionsPad.East, out dt);
|
|
}
|
|
protected static bool IA_TouchBasicPress(out double dt)
|
|
{
|
|
return PlayerInput.GetTouchDown(InputController.ActionsTouch.Tap, out dt);
|
|
}
|
|
protected static bool IA_BatonBasicPress(out double dt)
|
|
{
|
|
return PlayerInput.GetBatonDown(InputController.ActionsBaton.Face, out dt);
|
|
}
|
|
|
|
protected static bool IA_PadBasicRelease(out double dt)
|
|
{
|
|
return PlayerInput.GetPadUp(InputController.ActionsPad.East, out dt);
|
|
}
|
|
protected static bool IA_TouchBasicRelease(out double dt)
|
|
{
|
|
return PlayerInput.GetTouchUp(InputController.ActionsTouch.Tap, out dt) && !PlayerInput.GetFlick(out _);
|
|
}
|
|
protected static bool IA_BatonBasicRelease(out double dt)
|
|
{
|
|
return PlayerInput.GetBatonUp(InputController.ActionsBaton.Face, out dt);
|
|
}
|
|
|
|
protected static bool IA_PadBasicPressing(out double dt)
|
|
{
|
|
dt = 0;
|
|
return PlayerInput.GetPad(InputController.ActionsPad.East);
|
|
}
|
|
protected static bool IA_TouchBasicPressing(out double dt)
|
|
{
|
|
dt = 0;
|
|
return PlayerInput.GetTouch(InputController.ActionsTouch.Tap);
|
|
}
|
|
protected static bool IA_BatonBasicPressing(out double dt)
|
|
{
|
|
dt = 0;
|
|
return PlayerInput.GetBaton(InputController.ActionsBaton.Face);
|
|
}
|
|
|
|
protected static bool IA_TouchFlick(out double dt)
|
|
{
|
|
return PlayerInput.GetFlick(out dt);
|
|
}
|
|
|
|
public static PlayerInput.InputAction InputAction_BasicPress =
|
|
new("BasicPress", new int[] { IAPressCat, IAPressCat, IAPressCat },
|
|
IA_PadBasicPress, IA_TouchBasicPress, IA_BatonBasicPress);
|
|
public static PlayerInput.InputAction InputAction_BasicRelease =
|
|
new("BasicRelease", new int[] { IAReleaseCat, IAReleaseCat, IAReleaseCat },
|
|
IA_PadBasicRelease, IA_TouchBasicRelease, IA_BatonBasicRelease);
|
|
public static PlayerInput.InputAction InputAction_BasicPressing =
|
|
new("BasicRelease", new int[] { IAReleaseCat, IAReleaseCat, IAReleaseCat },
|
|
IA_PadBasicPressing, IA_TouchBasicPressing, IA_BatonBasicPressing);
|
|
|
|
public static PlayerInput.InputAction InputAction_FlickPress =
|
|
new("FlickPress", new int[] { IAPressCat, IAFlickCat, IAPressCat },
|
|
IA_PadBasicPress, IA_TouchFlick, IA_BatonBasicPress);
|
|
public static PlayerInput.InputAction InputAction_FlickRelease =
|
|
new("FlickRelease", new int[] { IAReleaseCat, IAFlickCat, IAReleaseCat },
|
|
IA_PadBasicRelease, IA_TouchFlick, IA_BatonBasicRelease);
|
|
#endregion
|
|
|
|
public List<PlayerActionEvent> scheduledInputs = new List<PlayerActionEvent>();
|
|
|
|
/// <summary>
|
|
/// Schedule an Input for a later time in the minigame. Executes the methods put in parameters
|
|
/// </summary>
|
|
/// <param name="startBeat">When the scheduling started (in beats)</param>
|
|
/// <param name="timer">How many beats later should the input be expected</param>
|
|
/// <param name="inputAction">The input action that's expected</param>
|
|
/// <param name="OnHit">Method to run if the Input has been Hit</param>
|
|
/// <param name="OnMiss">Method to run if the Input has been Missed</param>
|
|
/// <param name="OnBlank">Method to run whenever there's an Input while this is Scheduled (Shouldn't be used too much)</param>
|
|
/// <returns></returns>
|
|
public PlayerActionEvent ScheduleInput(
|
|
double startBeat,
|
|
double timer,
|
|
PlayerInput.InputAction inputAction,
|
|
PlayerActionEvent.ActionEventCallbackState OnHit,
|
|
PlayerActionEvent.ActionEventCallback OnMiss,
|
|
PlayerActionEvent.ActionEventCallback OnBlank,
|
|
PlayerActionEvent.ActionEventHittableQuery HittableQuery = null
|
|
)
|
|
{
|
|
|
|
GameObject evtObj = new("ActionEvent" + (startBeat + timer));
|
|
|
|
PlayerActionEvent evt = evtObj.AddComponent<PlayerActionEvent>();
|
|
|
|
evt.startBeat = startBeat;
|
|
evt.timer = timer;
|
|
evt.InputAction = inputAction;
|
|
evt.OnHit = OnHit;
|
|
evt.OnMiss = OnMiss;
|
|
evt.OnBlank = OnBlank;
|
|
evt.IsHittable = HittableQuery;
|
|
|
|
evt.OnDestroy = RemoveScheduledInput;
|
|
|
|
evt.canHit = true;
|
|
evt.enabled = true;
|
|
|
|
evt.transform.parent = this.transform.parent;
|
|
|
|
evtObj.SetActive(true);
|
|
|
|
scheduledInputs.Add(evt);
|
|
|
|
return evt;
|
|
}
|
|
|
|
public PlayerActionEvent ScheduleAutoplayInput(double startBeat,
|
|
double timer,
|
|
PlayerInput.InputAction inputAction,
|
|
PlayerActionEvent.ActionEventCallbackState OnHit,
|
|
PlayerActionEvent.ActionEventCallback OnMiss,
|
|
PlayerActionEvent.ActionEventCallback OnBlank)
|
|
{
|
|
PlayerActionEvent evt = ScheduleInput(startBeat, timer, inputAction, OnHit, OnMiss, OnBlank);
|
|
evt.autoplayOnly = true;
|
|
return evt;
|
|
}
|
|
|
|
public PlayerActionEvent ScheduleUserInput(double startBeat,
|
|
double timer,
|
|
PlayerInput.InputAction inputAction,
|
|
PlayerActionEvent.ActionEventCallbackState OnHit,
|
|
PlayerActionEvent.ActionEventCallback OnMiss,
|
|
PlayerActionEvent.ActionEventCallback OnBlank,
|
|
PlayerActionEvent.ActionEventHittableQuery HittableQuery = null)
|
|
{
|
|
PlayerActionEvent evt = ScheduleInput(startBeat, timer, inputAction, OnHit, OnMiss, OnBlank, HittableQuery);
|
|
evt.noAutoplay = true;
|
|
return evt;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Schedule an Input for a later time in the minigame. Executes the methods put in parameters
|
|
/// </summary>
|
|
/// <param name="startBeat">When the scheduling started (in beats)</param>
|
|
/// <param name="timer">How many beats later should the input be expected</param>
|
|
/// <param name="inputType">The type of the input that's expected (Press, Release, A, B, Directions>)</param>
|
|
/// <param name="OnHit">Method to run if the Input has been Hit</param>
|
|
/// <param name="OnMiss">Method to run if the Input has been Missed</param>
|
|
/// <param name="OnBlank">Method to run whenever there's an Input while this is Scheduled (Shouldn't be used too much)</param>
|
|
/// <returns></returns>
|
|
[Obsolete("Use Input Action ScheduleInput instead")]
|
|
public PlayerActionEvent ScheduleInput(
|
|
double startBeat,
|
|
double timer,
|
|
InputType inputType,
|
|
PlayerActionEvent.ActionEventCallbackState OnHit,
|
|
PlayerActionEvent.ActionEventCallback OnMiss,
|
|
PlayerActionEvent.ActionEventCallback OnBlank,
|
|
PlayerActionEvent.ActionEventHittableQuery HittableQuery = null
|
|
)
|
|
{
|
|
|
|
GameObject evtObj = new GameObject("ActionEvent" + (startBeat + timer));
|
|
evtObj.AddComponent<PlayerActionEvent>();
|
|
|
|
PlayerActionEvent evt = evtObj.GetComponent<PlayerActionEvent>();
|
|
|
|
evt.startBeat = startBeat;
|
|
evt.timer = timer;
|
|
evt.inputType = inputType;
|
|
evt.OnHit = OnHit;
|
|
evt.OnMiss = OnMiss;
|
|
evt.OnBlank = OnBlank;
|
|
evt.IsHittable = HittableQuery;
|
|
|
|
evt.OnDestroy = RemoveScheduledInput;
|
|
|
|
evt.canHit = true;
|
|
evt.enabled = true;
|
|
|
|
evt.transform.parent = this.transform.parent;
|
|
|
|
evtObj.SetActive(true);
|
|
|
|
scheduledInputs.Add(evt);
|
|
|
|
return evt;
|
|
}
|
|
|
|
[Obsolete("Use Input Action ScheduleInput instead")]
|
|
public PlayerActionEvent ScheduleAutoplayInput(double startBeat,
|
|
double timer,
|
|
InputType inputType,
|
|
PlayerActionEvent.ActionEventCallbackState OnHit,
|
|
PlayerActionEvent.ActionEventCallback OnMiss,
|
|
PlayerActionEvent.ActionEventCallback OnBlank)
|
|
{
|
|
PlayerActionEvent evt = ScheduleInput(startBeat, timer, inputType, OnHit, OnMiss, OnBlank);
|
|
evt.autoplayOnly = true;
|
|
return evt;
|
|
}
|
|
|
|
[Obsolete("Use Input Action ScheduleInput instead")]
|
|
public PlayerActionEvent ScheduleUserInput(double startBeat,
|
|
double timer,
|
|
InputType inputType,
|
|
PlayerActionEvent.ActionEventCallbackState OnHit,
|
|
PlayerActionEvent.ActionEventCallback OnMiss,
|
|
PlayerActionEvent.ActionEventCallback OnBlank,
|
|
PlayerActionEvent.ActionEventHittableQuery HittableQuery = null)
|
|
{
|
|
PlayerActionEvent evt = ScheduleInput(startBeat, timer, inputType, OnHit, OnMiss, OnBlank, HittableQuery);
|
|
evt.noAutoplay = true;
|
|
return evt;
|
|
}
|
|
|
|
//Clean up method used whenever a PlayerActionEvent has finished
|
|
public void RemoveScheduledInput(PlayerActionEvent evt)
|
|
{
|
|
scheduledInputs.Remove(evt);
|
|
}
|
|
|
|
//Get the scheduled input that should happen the **Soonest**
|
|
//Can return null if there's no scheduled inputs
|
|
// remark: need a check for specific button(s)
|
|
[Obsolete("Use GetClosestScheduledInput InputAction or InputAction category instead")]
|
|
public PlayerActionEvent GetClosestScheduledInput(InputType input = InputType.ANY)
|
|
{
|
|
PlayerActionEvent closest = null;
|
|
|
|
foreach (PlayerActionEvent toCompare in scheduledInputs)
|
|
{
|
|
// ignore inputs that are for sequencing in autoplay
|
|
if (toCompare.autoplayOnly) continue;
|
|
|
|
if (closest == null)
|
|
{
|
|
if (input == InputType.ANY || (toCompare.inputType & input) != 0)
|
|
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 (input == InputType.ANY || (toCompare.inputType & input) != 0)
|
|
closest = toCompare;
|
|
}
|
|
}
|
|
}
|
|
|
|
return closest;
|
|
}
|
|
|
|
public PlayerActionEvent GetClosestScheduledInput(int[] actionCats)
|
|
{
|
|
int catIdx = (int)PlayerInput.CurrentControlStyle;
|
|
int cat = actionCats[catIdx];
|
|
PlayerActionEvent closest = null;
|
|
|
|
foreach (PlayerActionEvent toCompare in scheduledInputs)
|
|
{
|
|
// ignore inputs that are for sequencing in autoplay
|
|
if (toCompare.autoplayOnly) continue;
|
|
if (toCompare.InputAction == null) continue;
|
|
|
|
if (closest == null)
|
|
{
|
|
if (toCompare.InputAction.inputLockCategory[catIdx] == cat)
|
|
closest = toCompare;
|
|
}
|
|
else
|
|
{
|
|
double t1 = closest.startBeat + closest.timer;
|
|
double t2 = toCompare.startBeat + toCompare.timer;
|
|
|
|
if (t2 < t1)
|
|
{
|
|
if (toCompare.InputAction.inputLockCategory[catIdx] == cat)
|
|
closest = toCompare;
|
|
}
|
|
}
|
|
}
|
|
|
|
return closest;
|
|
}
|
|
|
|
public PlayerActionEvent GetClosestScheduledInput(PlayerInput.InputAction action)
|
|
{
|
|
return GetClosestScheduledInput(action.inputLockCategory);
|
|
}
|
|
|
|
//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
|
|
[Obsolete("Use IsExpectingInputNow InputAction or InputAction category instead")]
|
|
public bool IsExpectingInputNow(InputType wantInput = InputType.ANY)
|
|
{
|
|
PlayerActionEvent input = GetClosestScheduledInput(wantInput);
|
|
if (input == null) return false;
|
|
return input.IsExpectingInputNow();
|
|
}
|
|
|
|
public bool IsExpectingInputNow(int[] wantActionCategory)
|
|
{
|
|
PlayerActionEvent input = GetClosestScheduledInput(wantActionCategory);
|
|
if (input == null) return false;
|
|
return input.IsExpectingInputNow();
|
|
}
|
|
|
|
public bool IsExpectingInputNow(PlayerInput.InputAction wantAction)
|
|
{
|
|
return IsExpectingInputNow(wantAction.inputLockCategory);
|
|
}
|
|
|
|
// now should fix the fast bpm problem
|
|
public static double NgEarlyTime()
|
|
{
|
|
return 1f - ngEarlyTime;
|
|
}
|
|
|
|
public static double JustEarlyTime()
|
|
{
|
|
return 1f - justEarlyTime;
|
|
}
|
|
|
|
public static double JustLateTime()
|
|
{
|
|
return 1f + justLateTime;
|
|
}
|
|
|
|
public static double NgLateTime()
|
|
{
|
|
return 1f + ngLateTime;
|
|
}
|
|
|
|
public static double AceEarlyTime()
|
|
{
|
|
return 1f - aceEarlyTime;
|
|
}
|
|
|
|
public static double AceLateTime()
|
|
{
|
|
return 1f + aceLateTime;
|
|
}
|
|
|
|
public virtual void OnGameSwitch(double beat)
|
|
{
|
|
//Below is a template that can be used for handling previous entities.
|
|
//section below is if you only want to look at entities that overlap the game switch
|
|
/*
|
|
List<RiqEntity> prevEntities = GameManager.instance.Beatmap.Entities.FindAll(c => c.beat <= beat && c.datamodel.Split(0) == [insert game name]);
|
|
foreach(RiqEntity entity in prevEntities)
|
|
{
|
|
if(entity.beat + entity.length >= beat)
|
|
{
|
|
EventCaller.instance.CallEvent(entity, true);
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
public virtual void OnTimeChange()
|
|
{
|
|
|
|
}
|
|
|
|
public virtual void OnPlay(double beat)
|
|
{
|
|
|
|
}
|
|
|
|
public virtual void OnStop(double beat)
|
|
{
|
|
foreach (var evt in scheduledInputs)
|
|
{
|
|
evt.Disable();
|
|
}
|
|
}
|
|
|
|
public static MultiSound PlaySoundSequence(string game, string name, double startBeat, params SoundSequence.SequenceParams[] args)
|
|
{
|
|
Minigames.Minigame gameInfo = GameManager.instance.GetGameInfo(game);
|
|
foreach (SoundSequence.SequenceKeyValue pair in gameInfo.LoadedSoundSequences)
|
|
{
|
|
if (pair.name == name)
|
|
{
|
|
Debug.Log($"Playing sound sequence {pair.name} at beat {startBeat}");
|
|
return pair.sequence.Play(startBeat);
|
|
}
|
|
}
|
|
Debug.LogWarning($"Sound sequence {name} not found in game {game} (did you build AssetBundles?)");
|
|
return null;
|
|
}
|
|
|
|
public void ScoreMiss(double weight = 1f)
|
|
{
|
|
GameManager.instance.ScoreInputAccuracy(0, true, NgLateTime(), weight, false);
|
|
if (weight > 0)
|
|
{
|
|
GoForAPerfect.instance.Miss();
|
|
SectionMedalsManager.instance.MakeIneligible();
|
|
}
|
|
}
|
|
|
|
public void ToggleSplitColoursDisplay(bool on)
|
|
{
|
|
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
foreach (var evt in scheduledInputs)
|
|
{
|
|
evt.Disable();
|
|
}
|
|
}
|
|
|
|
protected void OnDrawGizmos()
|
|
{
|
|
Gizmos.color = Color.magenta;
|
|
Gizmos.DrawWireCube(Vector3.zero, new Vector3(17.77695f, 10, 0));
|
|
}
|
|
}
|
|
}
|