HeavenStudio/Assets/Scripts/Games/PlayerActionEvent.cs
Rapandrasmus 98835c3936
Title Screen (#454)
* Barebones title screen prefab added

* logo and stuff

* cool

* Added sfx to title screen

* The logo now bops to the beat

* epic reveal

* Fixed something

* put some of the stuff into the main menu

* other logo bop

* Implemented logobop2 and starbop

* added scrolling bg, tweaked positioning and wip splash text for play button

* more menu

* ooops

* Expand implemented

* cool

* Made stars spawn in in the opening

* make UI elements look nicer on different aspect ratios

* add sound while hovering over logo

* add settings menu to title screen

make the title screen properly play after the opening

* swap out title screen hover sound

remove the old config path warning

* every button works, some play mode fixes

* fix issues with beataction/multisound and pausing

* fix dropdown menus not working in certain screens

* fix particles rotating when camera controls are used

* touch style pause menu items only trigger if cursor is over an item

* various changes

make playback (unpausing) more reliable
only apply changes to advanced audio settings on launch
fix title screen visuals
add opening music
continue past opening by pressing a key
update credits

* almost forgot this

* lol

* initial flow mems

* user-taggable fonts in textboxes

* alt materials for kurokane

* assets prep

* plan out judgement screen layout

change sound encodings

* start sequencing judgement

* judgement screen sequence

* full game loop

* fix major issue with pooled sound objects

rebalance ranking audio
fix issues with some effects in play mode

* new graphics

* particles

* make certain uses of the beat never go below 0

fix loop of superb music

* make markers non clamped

lockstep frees rendertextures when unloading

* lockstep creates its own rendertextures

swapped button order on title screen
added null checks to animation helpers
disabled controller auto-search for now

* enable particles on OK rank

* play mode info panel

* let play mode handle its own fade out

* fix that alignment bug in controller settings

* more safety here

* Update PauseMenu.cs

* settable (one-liner) rating screen text

* address minigame loading crashes

* don't do this twice

* wav converter for mp3

* Update Minigames.cs

* don't double-embed the converted audio

* studio dance bugfixing spree

* import redone sprites for studio dance

* update jukebox

prep epilogue screen

* epilogue screen

* studio dance inkling shuffle test

* new studio dance choreo system

* markers upgrade

* fix deleting volume changes and markers

prep category markers

* Update Editor.unity

* new rating / epilogue settings look

* update to use new tooltip system

mark certain editor components as blocking

* finish category system

* dedicated tempo / volume marker dialogs

* swing prep

* open properties dialog if mapper hasn't opened it prior for this chart

fix memory copy bug when making new chart

* fix ctrl + s

* return to title screen button

* make graphy work everywhere

studio dance selector
membillion mems

* make sure riq cache is clear when loading chart

* lol

* fix the stupid

* bring back tempo and volume change scrolling

* new look for panels

* adjust alignment

* round tooltip

* alignment of chart property prefab

* change scale factor of mem

* adjust open captions material

no dotted BG in results commentary (only epilogue)
bugfix for tempo / volume scroll

* format line 2 of judgement a bit better

update font

* oops

* adjust look of judgement score bar

* new rating bar

* judgement size adjustment

* fix timing window scaling with song pitch

* proper clamping in dialogs

better sync conductor to dsptime (experiment)

* disable timeline pitch change when song is paused

enable perfect challenge if no marker is set to do so

* new app icon

* timing window values are actually double now

* improve deferred timekeep even more

* re-generate font atlases

new app icon in credits

* default epilogue images

* more timing window adjustment

* fix timing display when pitched

* use proper terminology here

* new logo on titlescreen

* remove wip from play

update credits

* adjust spacing of play mode panel

* redo button spacing

* can pass title screen with any controller

fix issues with controller auto-search

* button scale fixes

* controller title screen nav

* remove song genre from properties editor

* disable circle cursor when not using touch style

* proper selection graphic

remove refs
re-add heart to the opening

* controller support in opening

---------

Co-authored-by: ev <85412919+evdial@users.noreply.github.com>
Co-authored-by: minenice55 <star.elementa@gmail.com>
Co-authored-by: ThatZeoMan <67521686+ThatZeoMan@users.noreply.github.com>
2023-12-26 05:22:51 +00:00

350 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
using HeavenStudio.Common;
namespace HeavenStudio.Games
{
public class PlayerActionEvent : MonoBehaviour
{
static List<PlayerActionEvent> allEvents = new List<PlayerActionEvent>();
public static bool EnableAutoplayCheat = true;
public delegate void ActionEventCallback(PlayerActionEvent caller);
public delegate void ActionEventCallbackState(PlayerActionEvent caller, float state);
public delegate bool ActionEventHittableQuery();
public ActionEventCallbackState OnHit; //Function to trigger when an input has been done perfectly
public ActionEventCallback OnMiss; //Function to trigger when an input has been missed
public ActionEventCallback OnBlank; //Function to trigger when an input has been recorded while this is pending
public ActionEventHittableQuery IsHittable; //Checks if an input can be hit. Returning false will skip button checks.
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;
public float weight = 1f;
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
public bool enabled = true; //Indicates if the PlayerActionEvent is enabled. If set to false, it'll not trigger any events and destroy itself AFTER it's not relevant anymore
public bool triggersAutoplay = true;
bool lockedByEvent = false;
bool markForDeletion = false;
float pitchWhenHit = 1f;
public bool autoplayOnly = false; //Indicates if the input event only triggers when it's autoplay. If set to true, NO Miss or Blank events will be triggered when you're not autoplaying.
public bool noAutoplay = false; //Indicates if this PlayerActionEvent is recognized by the autoplay. /!\ Overrides autoPlayOnly /!\
public InputType inputType; //The type of input. Check the InputType class to see a list of all of them
public bool perfectOnly = false; //Indicates that the input only recognize perfect inputs.
public bool countsForAccuracy = true; //Indicates if the input counts for the accuracy or not. If set to false, it'll not be counted in the accuracy calculation
public void setHitCallback(ActionEventCallbackState OnHit)
{
this.OnHit = OnHit;
}
public void setMissCallback(ActionEventCallback OnMiss)
{
this.OnMiss = OnMiss;
}
public void setHittableQuery(ActionEventHittableQuery IsHittable)
{
this.IsHittable = IsHittable;
}
public void Enable() { enabled = true; }
public void Disable() { enabled = false; }
public void QueueDeletion() { markForDeletion = true; }
public bool IsCorrectInput(out double dt)
{
dt = 0;
if (InputAction != null)
{
return PlayerInput.GetIsAction(InputAction, out dt);
}
return false;
}
public void CanHit(bool canHit)
{
this.canHit = canHit;
}
public void Start()
{
allEvents.Add(this);
}
public void Update()
{
Conductor cond = Conductor.instance;
if (markForDeletion) CleanUp();
if (!cond.NotStopped()) CleanUp(); // If the song is stopped entirely in the editor, destroy itself as we don't want duplicates
if (noAutoplay && autoplayOnly) autoplayOnly = false;
if (noAutoplay && triggersAutoplay) triggersAutoplay = false;
if (!enabled) return;
double normalizedTime = GetNormalizedTime();
if (GameManager.instance.autoplay)
{
AutoplayInput(normalizedTime);
return;
}
//BUGFIX: ActionEvents destroyed too early
if (normalizedTime > Minigame.NgLateTime(cond.SongPitch)) Miss();
if (lockedByEvent)
{
return;
}
if (!CheckEventLock())
{
return;
}
if (!autoplayOnly && (IsHittable == null || IsHittable != null && IsHittable()) && IsCorrectInput(out double dt))
{
normalizedTime -= dt;
if (IsExpectingInputNow())
{
double stateProg = ((normalizedTime - Minigame.JustEarlyTime()) / (Minigame.JustLateTime() - Minigame.JustEarlyTime()) - 0.5f) * 2;
Hit(stateProg, normalizedTime);
}
else
{
Blank();
}
}
}
public void LateUpdate()
{
if (markForDeletion)
{
allEvents.Remove(this);
OnDestroy(this);
Destroy(this.gameObject);
}
foreach (PlayerActionEvent evt in allEvents)
{
evt.lockedByEvent = false;
}
}
private bool CheckEventLock()
{
foreach (PlayerActionEvent toCompare in allEvents)
{
if (toCompare == this) continue;
if (toCompare.autoplayOnly) continue;
if (InputAction != null)
{
if (toCompare.InputAction == null) continue;
int catIdx = (int)PlayerInput.CurrentControlStyle;
if (toCompare.InputAction != null
&& toCompare.InputAction.inputLockCategory[catIdx] != InputAction.inputLockCategory[catIdx]) 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;
double songPos = Conductor.instance.songPositionInBeatsAsDouble;
// compare distance between current time and the events
// events that happen at the exact same time with the exact same inputs will return true
if (Math.Abs(t1 - songPos) > Math.Abs(t2 - songPos))
return false;
else if (t1 != t2) // if they are the same time, we don't want to lock the event
toCompare.lockedByEvent = true;
}
return true;
}
private void AutoplayInput(double normalizedTime, bool autoPlay = false)
{
if (triggersAutoplay && (GameManager.instance.autoplay || autoPlay) && GameManager.instance.canInput && normalizedTime >= 1f - (Time.deltaTime * 0.5f))
{
AutoplayEvent();
if (!autoPlay)
TimelineAutoplay();
}
}
// TODO: move this to timeline code instead
private void TimelineAutoplay()
{
if (Editor.Editor.instance == null) return;
if (Editor.Track.Timeline.instance != null && !Editor.Editor.instance.fullscreen)
{
Editor.Track.Timeline.instance.AutoplayBTN.GetComponent<Animator>().Play("Ace", 0, 0);
}
}
public bool IsExpectingInputNow()
{
if (IsHittable != null)
{
if (!IsHittable()) return false;
}
if (!enabled) return false;
if (!isEligible) return false;
double normalizedBeat = GetNormalizedTime();
return normalizedBeat > Minigame.NgEarlyTime() && normalizedBeat < Minigame.NgLateTime();
}
double GetNormalizedTime()
{
var cond = Conductor.instance;
double currTime = cond.songPositionAsDouble;
double targetTime = cond.GetSongPosFromBeat(startBeat + timer);
// HS timing window uses 1 as the middle point instead of 0
return 1 + (currTime - targetTime);
}
//For the Autoplay
public void AutoplayEvent()
{
if (EnableAutoplayCheat)
{
Hit(0f, 1f);
}
else
{
double normalizedBeat = GetNormalizedTime();
double stateProg = ((normalizedBeat - Minigame.JustEarlyTime()) / (Minigame.JustLateTime() - Minigame.JustEarlyTime()) - 0.5f) * 2;
Hit(stateProg, normalizedBeat);
}
}
//The state parameter is either -1 -> Early, 0 -> Perfect, 1 -> Late
public void Hit(double state, double time)
{
if (OnHit != null && enabled)
{
if (canHit)
{
CleanUp();
pitchWhenHit = Conductor.instance.SongPitch;
double normalized = time - 1f;
int offset = Mathf.CeilToInt((float)normalized * 1000);
GameManager.instance.AvgInputOffset = offset;
state = System.Math.Max(-1.0, System.Math.Min(1.0, state));
if (countsForAccuracy && !(noAutoplay || autoplayOnly) && isEligible)
{
GameManager.instance.ScoreInputAccuracy(startBeat + timer, TimeToAccuracy(time, pitchWhenHit), time > 1.0, time, weight, true);
if (state >= 1f || state <= -1f)
{
GoForAPerfect.instance.Miss();
SectionMedalsManager.instance.MakeIneligible();
}
else
{
GoForAPerfect.instance.Hit();
}
}
OnHit(this, (float)state);
}
else
{
Blank();
}
}
}
double TimeToAccuracy(double time, float pitch = -1)
{
if (pitch < 0) pitch = pitchWhenHit;
if (time >= Minigame.AceEarlyTime(pitch) && time <= Minigame.AceLateTime(pitch))
{
// Ace
return 1.0;
}
double state = 0;
if (time >= Minigame.JustEarlyTime(pitch) && time <= Minigame.JustLateTime(pitch))
{
// 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 - Minigame.rankHiThreshold;
state += Minigame.rankHiThreshold;
}
else
{
//early half of timing window
state = ((time - Minigame.JustEarlyTime(pitch)) / (Minigame.AceEarlyTime(pitch) - Minigame.JustEarlyTime(pitch)));
state *= 1.0 - Minigame.rankHiThreshold;
state += Minigame.rankHiThreshold;
}
}
else
{
if (time > 1.0)
{
// late half of timing window
state = 1.0 - ((time - Minigame.JustLateTime(pitch)) / (Minigame.NgLateTime(pitch) - Minigame.JustLateTime(pitch)));
state *= Minigame.rankOkThreshold;
}
else
{
//early half of timing window
state = ((time - Minigame.JustEarlyTime(pitch)) / (Minigame.AceEarlyTime(pitch) - Minigame.JustEarlyTime(pitch)));
state *= Minigame.rankOkThreshold;
}
}
return state;
}
public void Miss()
{
CleanUp();
if (OnMiss != null && enabled && !autoplayOnly)
{
OnMiss(this);
}
if (countsForAccuracy && !(noAutoplay || autoplayOnly))
{
GameManager.instance.ScoreInputAccuracy(startBeat + timer, 0, true, 2.0, weight, false);
GoForAPerfect.instance.Miss();
SectionMedalsManager.instance.MakeIneligible();
}
}
public void Blank()
{
if (OnBlank != null && enabled && !autoplayOnly)
{
OnBlank(this);
}
}
public void CleanUp()
{
if (markForDeletion) return;
markForDeletion = true;
}
}
}