From 45d4483765d57bb3a4d512522640417da8251ab4 Mon Sep 17 00:00:00 2001 From: minenice55 Date: Sun, 12 May 2024 18:45:23 -0400 Subject: [PATCH] fix bugs related to timing windows add ability to adjust timing window size per-input made rockers suck less --- Assets/Scripts/GameManager.cs | 4 +- Assets/Scripts/Games/Minigame.cs | 76 ++++++++++++------- Assets/Scripts/Games/PlayerActionEvent.cs | 31 ++++---- Assets/Scripts/Games/Rockers/Rockers.cs | 17 ++--- Assets/Scripts/Games/Rockers/RockersInput.cs | 2 +- .../UI/Overlays/TimingAccuracyDisplay.cs | 20 ++--- 6 files changed, 86 insertions(+), 64 deletions(-) diff --git a/Assets/Scripts/GameManager.cs b/Assets/Scripts/GameManager.cs index 237e174f..8d241bc5 100644 --- a/Assets/Scripts/GameManager.cs +++ b/Assets/Scripts/GameManager.cs @@ -322,11 +322,11 @@ namespace HeavenStudio } } - public void ScoreInputAccuracy(double beat, double accuracy, bool late, double time, float weight = 1, bool doDisplay = true) + public void ScoreInputAccuracy(double beat, double accuracy, bool late, double time, float pitch = 1, double margin = 0, float weight = 1, bool doDisplay = true) { // push the hit event to the timing display if (doDisplay) - TimingAccuracyDisplay.instance.MakeAccuracyVfx(time, late); + TimingAccuracyDisplay.instance.MakeAccuracyVfx(time, pitch, margin, late); if (weight > 0 && MarkerWeight > 0) { diff --git a/Assets/Scripts/Games/Minigame.cs b/Assets/Scripts/Games/Minigame.cs index d3bde51b..440bd718 100644 --- a/Assets/Scripts/Games/Minigame.cs +++ b/Assets/Scripts/Games/Minigame.cs @@ -14,7 +14,10 @@ namespace HeavenStudio.Games { public class Minigame : MonoBehaviour { + // root timing window values public static double ngEarlyTimeBase = 0.1, justEarlyTimeBase = 0.05, aceEarlyTimeBase = 0.01, aceLateTimeBase = 0.01, justLateTimeBase = 0.05, ngLateTimeBase = 0.1; + // recommended added margin for release inputs + public static double releaseMargin = 0.01; public static double rankHiThreshold = 0.8, rankOkThreshold = 0.6; public static double ngEarlyTime => ngEarlyTimeBase * Conductor.instance?.SongPitch ?? 1; @@ -110,7 +113,7 @@ namespace HeavenStudio.Games IA_PadBasicRelease, IA_TouchFlick, IA_BatonBasicRelease); #endregion - public List scheduledInputs = new List(); + [NonSerialized] public List scheduledInputs = new List(); /// /// Schedule an Input for a later time in the minigame. Executes the methods put in parameters @@ -166,6 +169,22 @@ namespace HeavenStudio.Games return evt; } + public PlayerActionEvent ScheduleInput( + double startBeat, + double timer, + double margin, + 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.margin = margin; + return evt; + } + public PlayerActionEvent ScheduleAutoplayInput(double startBeat, double timer, PlayerInput.InputAction inputAction, @@ -236,7 +255,7 @@ namespace HeavenStudio.Games { PlayerActionEvent input = GetClosestScheduledInput(wantActionCategory); if (input == null) return false; - return input.IsExpectingInputNow(); + return input.IsExpectingInputNow(conductor); } public bool IsExpectingInputNow(PlayerInput.InputAction wantAction) @@ -245,46 +264,46 @@ namespace HeavenStudio.Games } // now should fix the fast bpm problem - public static double NgEarlyTime(float pitch = -1) + public static double NgEarlyTime(float pitch = -1, double margin = 0) { if (pitch < 0) - return 1 - ngEarlyTime; - return 1 - (ngEarlyTimeBase * pitch); + return 1 - (ngEarlyTime + margin); + return 1 - ((ngEarlyTime + margin) * pitch); } - public static double JustEarlyTime(float pitch = -1) + public static double NgLateTime(float pitch = -1, double margin = 0) { if (pitch < 0) - return 1 - justEarlyTime; - return 1 - (justEarlyTimeBase * pitch); + return 1 + (ngLateTime + margin); + return 1 + ((ngLateTime + margin) * pitch); } - public static double JustLateTime(float pitch = -1) + public static double JustEarlyTime(float pitch = -1, double margin = 0) { if (pitch < 0) - return 1 + justLateTime; - return 1 + (justLateTimeBase * pitch); + return 1 - (justEarlyTime + margin); + return 1 - ((justEarlyTime + margin) * pitch); } - public static double NgLateTime(float pitch = -1) + public static double JustLateTime(float pitch = -1, double margin = 0) { if (pitch < 0) - return 1 + ngLateTime; - return 1 + (ngLateTimeBase * pitch); + return 1 + (justLateTime + margin); + return 1 + ((justLateTime + margin) * pitch); } - public static double AceEarlyTime(float pitch = -1) + public static double AceEarlyTime(float pitch = -1, double margin = 0) { if (pitch < 0) - return 1 - aceEarlyTime; - return 1 - (aceEarlyTimeBase * pitch); + return 1 - (aceEarlyTime + margin); + return 1 - ((aceEarlyTime + margin) * pitch); } - public static double AceLateTime(float pitch = -1) + public static double AceLateTime(float pitch = -1, double margin = 0) { if (pitch < 0) - return 1 + aceLateTime; - return 1 + (aceLateTimeBase * pitch); + return 1 + (aceLateTime + margin); + return 1 + ((aceLateTime + margin) * pitch); } public virtual void OnGameSwitch(double beat) @@ -340,7 +359,7 @@ namespace HeavenStudio.Games public void ScoreMiss(float weight = 1f) { double beat = Conductor.instance?.songPositionInBeatsAsDouble ?? -1; - GameManager.instance.ScoreInputAccuracy(beat, 0, true, NgLateTime(), weight, false); + GameManager.instance.ScoreInputAccuracy(beat, 0, true, NgLateTime(), weight: weight, doDisplay: false); } public void ToggleSplitColoursDisplay(bool on) @@ -442,14 +461,17 @@ namespace HeavenStudio.Games public Color GetColor() => MakeNewColor(startBeat, length, startColor, endColor, easeFunc); public static Color MakeNewColor(double beat, float length, Color start, Color end, Util.EasingFunction.Function func) { - if (length != 0) { + if (length != 0) + { float normalizedBeat = length == 0 ? 1 : Mathf.Clamp01(Conductor.instance.GetPositionFromBeat(beat, length)); float newR = func(start.r, end.r, normalizedBeat); float newG = func(start.g, end.g, normalizedBeat); float newB = func(start.b, end.b, normalizedBeat); return new Color(newR, newG, newB); - } else { + } + else + { return end; } } @@ -471,19 +493,21 @@ namespace HeavenStudio.Games /// The ease to use to transition between and .
/// Should be derived from Util.EasingFunction.Ease, /// - public ColorEase(double startBeat, float length, Color startColor, Color endColor, int ease) { + public ColorEase(double startBeat, float length, Color startColor, Color endColor, int ease) + { this.startBeat = startBeat; this.length = length; (this.startColor, this.endColor) = (startColor, endColor); this.ease = (Util.EasingFunction.Ease)ease; this.easeFunc = Util.EasingFunction.GetEasingFunction(this.ease); } - + /// /// The constructor to use when initializing the ColorEase variable. /// /// The default color to initialize with. - public ColorEase(Color? defaultColor = null) { + public ColorEase(Color? defaultColor = null) + { startColor = endColor = defaultColor ?? Color.white; easeFunc = Util.EasingFunction.Instant; } diff --git a/Assets/Scripts/Games/PlayerActionEvent.cs b/Assets/Scripts/Games/PlayerActionEvent.cs index 5dd7bfc0..620cf8c7 100644 --- a/Assets/Scripts/Games/PlayerActionEvent.cs +++ b/Assets/Scripts/Games/PlayerActionEvent.cs @@ -27,6 +27,7 @@ namespace HeavenStudio.Games public double startBeat; public double timer; public float weight = 1f; + public double margin = 0; public bool isEligible = true; public bool canHit = true; //Indicates if you can still hit the cue or not. If set to false, it'll guarantee a miss @@ -108,7 +109,7 @@ namespace HeavenStudio.Games } //BUGFIX: ActionEvents destroyed too early - if (normalizedTime > Minigame.NgLateTime(cond.SongPitch)) Miss(); + if (normalizedTime > Minigame.NgLateTime(cond.SongPitch, margin)) Miss(); if (lockedByEvent) { @@ -122,10 +123,10 @@ namespace HeavenStudio.Games if (!autoplayOnly && (IsHittable == null || IsHittable != null && IsHittable()) && IsCorrectInput(out double dt)) { normalizedTime -= dt; - if (IsExpectingInputNow()) + if (IsExpectingInputNow(cond)) { - double stateProg = ((normalizedTime - Minigame.JustEarlyTime()) / (Minigame.JustLateTime() - Minigame.JustEarlyTime()) - 0.5f) * 2; - Hit(stateProg, normalizedTime); + double stateProg = ((normalizedTime - Minigame.JustEarlyTime(cond.SongPitch, margin)) / (Minigame.JustLateTime(cond.SongPitch, margin) - Minigame.JustEarlyTime(cond.SongPitch, margin)) - 0.5f) * 2; + Hit(stateProg, normalizedTime, cond.SongPitch); } else { @@ -198,7 +199,7 @@ namespace HeavenStudio.Games } } - public bool IsExpectingInputNow() + public bool IsExpectingInputNow(Conductor cond) { if (IsHittable != null) { @@ -208,7 +209,7 @@ namespace HeavenStudio.Games if (!isEligible) return false; double normalizedBeat = GetNormalizedTime(); - return normalizedBeat > Minigame.NgEarlyTime() && normalizedBeat < Minigame.NgLateTime(); + return normalizedBeat > Minigame.NgEarlyTime(cond.SongPitch, margin) && normalizedBeat < Minigame.NgLateTime(cond.SongPitch, margin); } double GetNormalizedTime() @@ -242,7 +243,7 @@ namespace HeavenStudio.Games } //The state parameter is either -1 -> Early, 0 -> Perfect, 1 -> Late - public void Hit(double state, double time) + public void Hit(double state, double time, float pitch = 1) { GameManager gm = GameManager.instance; if (OnHit != null && enabled) @@ -261,7 +262,7 @@ namespace HeavenStudio.Games if (countsForAccuracy && gm.canInput && !(noAutoplay || autoplayOnly) && isEligible) { - gm.ScoreInputAccuracy(startBeat + timer, TimeToAccuracy(time, pitchWhenHit), time > 1.0, time, weight, true); + gm.ScoreInputAccuracy(startBeat + timer, TimeToAccuracy(time, pitchWhenHit), time > 1.0, time, pitch, margin, weight, true); if (state >= 1f || state <= -1f) { GoForAPerfect.instance.Miss(); @@ -284,27 +285,27 @@ namespace HeavenStudio.Games double TimeToAccuracy(double time, float pitch = -1) { if (pitch < 0) pitch = pitchWhenHit; - if (time >= Minigame.AceEarlyTime(pitch) && time <= Minigame.AceLateTime(pitch)) + if (time >= Minigame.AceEarlyTime(pitch, margin) && time <= Minigame.AceLateTime(pitch, margin)) { // Ace return 1.0; } double state = 0; - if (time >= Minigame.JustEarlyTime(pitch) && time <= Minigame.JustLateTime(pitch)) + if (time >= Minigame.JustEarlyTime(pitch, margin) && time <= Minigame.JustLateTime(pitch, margin)) { // Good Hit if (time > 1.0) { // late half of timing window - state = 1.0 - ((time - Minigame.AceLateTime(pitch)) / (Minigame.JustLateTime(pitch) - Minigame.AceLateTime(pitch))); + state = 1.0 - ((time - Minigame.AceLateTime(pitch, margin)) / (Minigame.JustLateTime(pitch, margin) - Minigame.AceLateTime(pitch, margin))); state *= 1.0 - Minigame.rankHiThreshold; state += Minigame.rankHiThreshold; } else { //early half of timing window - state = ((time - Minigame.JustEarlyTime(pitch)) / (Minigame.AceEarlyTime(pitch) - Minigame.JustEarlyTime(pitch))); + state = (time - Minigame.JustEarlyTime(pitch, margin)) / (Minigame.AceEarlyTime(pitch, margin) - Minigame.JustEarlyTime(pitch, margin)); state *= 1.0 - Minigame.rankHiThreshold; state += Minigame.rankHiThreshold; } @@ -314,13 +315,13 @@ namespace HeavenStudio.Games if (time > 1.0) { // late half of timing window - state = 1.0 - ((time - Minigame.JustLateTime(pitch)) / (Minigame.NgLateTime(pitch) - Minigame.JustLateTime(pitch))); + state = 1.0 - ((time - Minigame.JustLateTime(pitch, margin)) / (Minigame.NgLateTime(pitch, margin) - Minigame.JustLateTime(pitch, margin))); state *= Minigame.rankOkThreshold; } else { //early half of timing window - state = ((time - Minigame.JustEarlyTime(pitch)) / (Minigame.AceEarlyTime(pitch) - Minigame.JustEarlyTime(pitch))); + state = (time - Minigame.JustEarlyTime(pitch, margin)) / (Minigame.AceEarlyTime(pitch, margin) - Minigame.JustEarlyTime(pitch, margin)); state *= Minigame.rankOkThreshold; } } @@ -338,7 +339,7 @@ namespace HeavenStudio.Games if (countsForAccuracy && !missable && gm.canInput && !(noAutoplay || autoplayOnly)) { - gm.ScoreInputAccuracy(startBeat + timer, 0, true, 2.0, weight, false); + gm.ScoreInputAccuracy(startBeat + timer, 0, true, 2.0, weight: weight, doDisplay: false); GoForAPerfect.instance.Miss(); SectionMedalsManager.instance.MakeIneligible(); } diff --git a/Assets/Scripts/Games/Rockers/Rockers.cs b/Assets/Scripts/Games/Rockers/Rockers.cs index 96b2265e..ab32c6fd 100644 --- a/Assets/Scripts/Games/Rockers/Rockers.cs +++ b/Assets/Scripts/Games/Rockers/Rockers.cs @@ -777,15 +777,15 @@ namespace HeavenStudio.Games }); RockersInput riffComp = Instantiate(rockerInputRef, transform); riffComp.Init(false, new int[6], beat, 3, GetSample(SoshiSamples[0]), SoshiPitches[0]); - ScheduleInput(beat, 3.5f, InputAction_TriggerDown, JustMute, MuteMiss, Empty); + ScheduleAutoplayInput(beat, 3.5f, InputAction_TriggerDown, JustMute, MuteMiss, Empty); RockersInput riffComp2 = Instantiate(rockerInputRef, transform); riffComp2.Init(false, new int[6], beat, 4.5f, GetSample(SoshiSamples[1]), SoshiPitches[1]); - ScheduleInput(beat, 5f, InputAction_TriggerDown, JustMute, MuteMiss, Empty); + ScheduleAutoplayInput(beat, 5f, InputAction_TriggerDown, JustMute, MuteMiss, Empty); RockersInput riffComp3 = Instantiate(rockerInputRef, transform); riffComp3.Init(false, new int[6], beat, 6, GetSample(SoshiSamples[2]), SoshiPitches[2]); - ScheduleInput(beat, 6.5f, InputAction_TriggerDown, JustMute, MuteMiss, Empty); + ScheduleAutoplayInput(beat, 6.5f, InputAction_TriggerDown, JustMute, MuteMiss, Empty); } public void DefaultCmon(double beat, int[] JJSamples, int[] JJPitches, int[] SoshiSamples, int[] SoshiPitches, bool moveCamera) @@ -848,7 +848,7 @@ namespace HeavenStudio.Games RockersInput riffComp3 = Instantiate(rockerInputRef, transform); riffComp3.Init(false, new int[6], beat, 6, GetSample(SoshiSamples[2]), SoshiPitches[2]); - ScheduleInput(beat, 6.5f, InputAction_BasicPress, JustMute, MuteMiss, Empty); + ScheduleAutoplayInput(beat, 6.5f, InputAction_BasicPress, JustMute, MuteMiss, Empty); RockersInput riffComp4 = Instantiate(rockerInputRef, transform); riffComp4.Init(false, new int[6], beat, 7, GetSample(SoshiSamples[3]), SoshiPitches[3], true); @@ -903,8 +903,7 @@ namespace HeavenStudio.Games RockersInput riffComp = Instantiate(rockerInputRef, transform); riffComp.Init(e["gcS"], new int[6] { e["1S"], e["2S"], e["3S"], e["4S"], e["5S"], e["6S"] }, beat, e.beat - beat, GetSample(e["sampleS"]), e["pitchSampleS"]); - if (e.length <= 0.5f) ScheduleInput(beat, e.beat - beat + e.length, InputAction_BasicPress, JustMute, MuteMiss, Empty); - else ScheduleAutoplayInput(beat, e.beat - beat + e.length, InputAction_BasicPress, JustMute, MuteMiss, Empty); + ScheduleAutoplayInput(beat, e.beat - beat + e.length, InputAction_BasicPress, JustMute, MuteMiss, Empty); } else { @@ -924,8 +923,7 @@ namespace HeavenStudio.Games RockersInput riffComp = Instantiate(rockerInputRef, transform); riffComp.Init(e["gcS"], new int[6] { e["1S"], e["2S"], e["3S"], e["4S"], e["5S"], e["6S"] }, beat, e.beat - beat, GetSample(e["sampleS"]), e["pitchSampleS"], true); - if (e.length <= 0.5f) ScheduleInput(beat, e.beat - beat + e.length, InputAction_BasicPress, JustMute, MuteMiss, Empty); - else ScheduleAutoplayInput(beat, e.beat - beat + e.length, InputAction_BasicPress, JustMute, MuteMiss, Empty); + ScheduleAutoplayInput(beat, e.beat - beat + e.length, InputAction_BasicPress, JustMute, MuteMiss, Empty); break; } } @@ -1139,8 +1137,7 @@ namespace HeavenStudio.Games RockersInput riffComp = Instantiate(rockerInputRef, transform); riffComp.Init(crEvent["gcS"], new int[6] { crEvent["1S"], crEvent["2S"], crEvent["3S"], crEvent["4S"], crEvent["5S"], crEvent["6S"] }, beat, relativeBeat, GetSample(crEvent["sampleS"]), crEvent["pitchSampleS"]); - if (crEvent.length > 0.5f) ScheduleAutoplayInput(beat, relativeBeat + crEvent.length, InputAction_BasicPress, JustMute, MuteMiss, Empty); - else ScheduleInput(beat, relativeBeat + crEvent.length, InputAction_BasicPress, JustMute, MuteMiss, Empty); + ScheduleAutoplayInput(beat, relativeBeat + crEvent.length, InputAction_BasicPress, JustMute, MuteMiss, Empty); } else { diff --git a/Assets/Scripts/Games/Rockers/RockersInput.cs b/Assets/Scripts/Games/Rockers/RockersInput.cs index 42548aaf..83d6687b 100644 --- a/Assets/Scripts/Games/Rockers/RockersInput.cs +++ b/Assets/Scripts/Games/Rockers/RockersInput.cs @@ -26,7 +26,7 @@ namespace HeavenStudio.Games.Scripts_Rockers this.sample = sample; this.sampleTones = sampleTones; this.jump = jump; - game.ScheduleInput(beat, length, Rockers.InputAction_FlickRelease, Just, Miss, Empty); + game.ScheduleInput(beat, length, Rockers.releaseMargin, Rockers.InputAction_FlickRelease, Just, Miss, Empty); } private void Just(PlayerActionEvent caller, float state) diff --git a/Assets/Scripts/UI/Overlays/TimingAccuracyDisplay.cs b/Assets/Scripts/UI/Overlays/TimingAccuracyDisplay.cs index 47341523..5c3c3b0d 100644 --- a/Assets/Scripts/UI/Overlays/TimingAccuracyDisplay.cs +++ b/Assets/Scripts/UI/Overlays/TimingAccuracyDisplay.cs @@ -70,7 +70,7 @@ namespace HeavenStudio.Common MetreAnim.Play("NoPose", -1, 0f); } - public void MakeAccuracyVfx(double time, bool late = false) + public void MakeAccuracyVfx(double time, float pitch, double margin, bool late = false) { if (!OverlaysManager.OverlaysEnabled) return; GameObject it; @@ -87,12 +87,12 @@ namespace HeavenStudio.Common // SetArrowPos(time); // no Clamp() because double - time = System.Math.Max(Minigame.NgEarlyTime(), System.Math.Min(Minigame.NgLateTime(), time)); + time = System.Math.Max(Minigame.NgEarlyTime(pitch, margin), System.Math.Min(Minigame.NgLateTime(pitch, margin), time)); - if (time >= Minigame.AceEarlyTime() && time <= Minigame.AceLateTime()) + if (time >= Minigame.AceEarlyTime(pitch, margin) && time <= Minigame.AceLateTime(pitch, margin)) { type = Rating.Just; - frac = (float)((time - Minigame.AceEarlyTime()) / (Minigame.AceLateTime() - Minigame.AceEarlyTime())); + frac = (float)((time - Minigame.AceEarlyTime(pitch, margin)) / (Minigame.AceLateTime(pitch, margin) - Minigame.AceEarlyTime(pitch, margin))); y = barJustTransform.localScale.y * frac - (barJustTransform.localScale.y * 0.5f); } else @@ -100,32 +100,32 @@ namespace HeavenStudio.Common if (time > 1.0) { // goes "down" - if (time <= Minigame.JustLateTime()) + if (time <= Minigame.JustLateTime(pitch, margin)) { type = Rating.OK; - frac = (float)((time - Minigame.AceLateTime()) / (Minigame.JustLateTime() - Minigame.AceLateTime())); + frac = (float)((time - Minigame.AceLateTime(pitch, margin)) / (Minigame.JustLateTime(pitch, margin) - Minigame.AceLateTime(pitch, margin))); y = ((barOKTransform.localScale.y - barJustTransform.localScale.y) * frac) + barJustTransform.localScale.y; } else { type = Rating.NG; - frac = (float)((time - Minigame.JustLateTime()) / (Minigame.NgLateTime() - Minigame.JustLateTime())); + frac = (float)((time - Minigame.JustLateTime(pitch, margin)) / (Minigame.NgLateTime(pitch, margin) - Minigame.JustLateTime(pitch, margin))); y = ((barNGTransform.localScale.y - barOKTransform.localScale.y) * frac) + barOKTransform.localScale.y; } } else { // goes "up" - if (time >= Minigame.JustEarlyTime()) + if (time >= Minigame.JustEarlyTime(pitch, margin)) { type = Rating.OK; - frac = (float)((time - Minigame.JustEarlyTime()) / (Minigame.AceEarlyTime() - Minigame.JustEarlyTime())); + frac = (float)((time - Minigame.JustEarlyTime(pitch, margin)) / (Minigame.AceEarlyTime(pitch, margin) - Minigame.JustEarlyTime(pitch, margin))); y = ((barOKTransform.localScale.y - barJustTransform.localScale.y) * -frac) - barJustTransform.localScale.y; } else { type = Rating.NG; - frac = (float)((time - Minigame.NgEarlyTime()) / (Minigame.JustEarlyTime() - Minigame.NgEarlyTime())); + frac = (float)((time - Minigame.NgEarlyTime(pitch, margin)) / (Minigame.JustEarlyTime(pitch, margin) - Minigame.NgEarlyTime(pitch, margin))); y = ((barNGTransform.localScale.y - barOKTransform.localScale.y) * -frac) - barOKTransform.localScale.y; } }