Configurable Event Priority & Bugfixes (#209)

* add event priority

fix crop stomp queuing inputs while chart is paused
fix rhythm tweezers not killing queued inputs when switching veggies

* file cleanup

* remove debug print

* remove more files
This commit is contained in:
minenice55
2023-01-18 21:31:08 -05:00
committed by GitHub
parent 8d707ecd4e
commit c037901d34
53 changed files with 77 additions and 1489 deletions

View File

@ -1,140 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace HeavenStudio
{
public class AudioDspTimeKeeper : MonoBehaviour
{
[SerializeField] private List<double> xValuesL = new List<double>();
[SerializeField] private List<double> yValuesL = new List<double>();
private double coeff1, coeff2;
private double audioDspStartTime;
private AudioSource audioSource;
public double currentSmoothedDSPTime;
public double dspTime;
public float audioTime;
private double musicDrift;
private Conductor conductor;
public float latencyAdjustment;
public void Play()
{
audioSource.PlayScheduled(audioDspStartTime);
audioDspStartTime = AudioSettings.dspTime;
}
private void Start()
{
conductor = GetComponent<Conductor>();
audioSource = conductor.musicSource;
}
private void Update()
{
if (!audioSource.isPlaying) return;
float currentGameTime = Time.realtimeSinceStartup;
double currentDspTime = AudioSettings.dspTime;
// Update our linear regression model by adding another data point.
UpdateLinearRegression(currentGameTime, currentDspTime);
CheckForDrift();
dspTime = GetCurrentTimeInSong();
audioTime = audioSource.time;
}
public double SmoothedDSPTime()
{
double result = Time.unscaledTimeAsDouble * coeff1 + coeff2;
if (result > currentSmoothedDSPTime)
{
currentSmoothedDSPTime = result;
}
return currentSmoothedDSPTime;
}
public double GetCurrentTimeInSong()
{
return this.SmoothedDSPTime() - audioDspStartTime - latencyAdjustment;
}
private void CheckForDrift()
{
double timeFromDSP = this.SmoothedDSPTime() - audioDspStartTime;
double timeFromAudioSource = audioSource.timeSamples / (float)audioSource.clip.frequency;
double drift = timeFromDSP - timeFromAudioSource;
musicDrift = drift;
if (Mathf.Abs((float)drift) > 0.05)
{
Debug.LogWarningFormat("Music drift of {0} detected, resyncing!", musicDrift);
audioDspStartTime += musicDrift;
}
}
private void UpdateLinearRegression(float currentGameTime, double currentDspTime)
{
if (xValuesL.Count > 3000)
{
xValuesL.RemoveRange(0, 2000);
yValuesL.RemoveRange(0, 2000);
}
xValuesL.Add((double)currentGameTime);
var xVals = xValuesL.ToArray();
yValuesL.Add((double)currentDspTime);
var yVals = yValuesL.ToArray();
if (xVals.Length != yVals.Length)
{
throw new Exception("Input values should be with the same length.");
}
double sumOfX = 0;
double sumOfY = 0;
double sumOfXSq = 0;
double sumOfYSq = 0;
double sumCodeviates = 0;
for (var i = 0; i < xVals.Length; i++)
{
var x = xVals[i];
var y = yVals[i];
sumCodeviates += x * y;
sumOfX += x;
sumOfY += y;
sumOfXSq += x * x;
sumOfYSq += y * y;
}
var count = xVals.Length;
var ssX = sumOfXSq - ((sumOfX * sumOfX) / count);
var ssY = sumOfYSq - ((sumOfY * sumOfY) / count);
var rNumerator = (count * sumCodeviates) - (sumOfX * sumOfY);
var rDenom = (count * sumOfXSq - (sumOfX * sumOfX)) * (count * sumOfYSq - (sumOfY * sumOfY));
var sCo = sumCodeviates - ((sumOfX * sumOfY) / count);
var meanX = sumOfX / count;
var meanY = sumOfY / count;
var dblR = rNumerator / Math.Sqrt(rDenom);
// coeff1 = dblR * dblR;
coeff2 = meanY - ((sCo / ssX) * meanX);
coeff1 = sCo / ssX;
}
}
}

View File

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

View File

@ -190,7 +190,7 @@ namespace HeavenStudio
}
}
public void SeekAheadAndPreload(float start, float seekTime = 8f)
public void SeekAheadAndPreload(double start, float seekTime = 8f)
{
//seek ahead to preload games that have assetbundles
//check game switches first
@ -217,6 +217,7 @@ namespace HeavenStudio
if (start + seekTime >= entities[currentPreEvent])
{
var entitiesAtSameBeat = Beatmap.entities.FindAll(c => c.beat == Beatmap.entities[currentPreEvent].beat && !EventCaller.FXOnlyGames().Contains(EventCaller.instance.GetMinigame(c.datamodel.Split('/')[0])));
SortEventsByPriority(entitiesAtSameBeat);
foreach (DynamicBeatmap.DynamicEntity entity in entitiesAtSameBeat)
{
string gameName = entity.datamodel.Split('/')[0];
@ -233,7 +234,7 @@ namespace HeavenStudio
}
}
public void SeekAheadAndDoPreEvent(float start, float seekTime = 2f)
public void SeekAheadAndDoPreEvent(double start, float seekTime = 2f)
{
List<float> entities = Beatmap.entities.Select(c => c.beat).ToList();
if (currentPreSequence < Beatmap.entities.Count && currentPreSequence >= 0)
@ -242,8 +243,10 @@ namespace HeavenStudio
{
float beat = Beatmap.entities[currentPreSequence].beat;
var entitiesAtSameBeat = Beatmap.entities.FindAll(c => c.beat == Beatmap.entities[currentPreSequence].beat);
SortEventsByPriority(entitiesAtSameBeat);
foreach (DynamicBeatmap.DynamicEntity entity in entitiesAtSameBeat)
{
currentPreSequence++;
string gameName = entity.datamodel.Split('/')[0];
var inf = GetGameInfo(gameName);
if (inf.usesAssetBundle && inf.AssetsLoaded && !inf.SequencesPreloaded)
@ -251,8 +254,10 @@ namespace HeavenStudio
Debug.Log($"Preloading game {gameName}");
PreloadGameSequences(gameName);
}
eventCaller.CallPreEvent(entity);
currentPreSequence++;
else
{
eventCaller.CallPreEvent(entity);
}
}
}
}
@ -274,7 +279,7 @@ namespace HeavenStudio
List<float> tempoChanges = Beatmap.tempoChanges.Select(c => c.beat).ToList();
if (currentTempoEvent < Beatmap.tempoChanges.Count && currentTempoEvent >= 0)
{
if (Conductor.instance.songPositionInBeats >= tempoChanges[currentTempoEvent])
if (Conductor.instance.songPositionInBeatsAsDouble >= tempoChanges[currentTempoEvent])
{
Conductor.instance.SetBpm(Beatmap.tempoChanges[currentTempoEvent].tempo);
currentTempoEvent++;
@ -284,7 +289,7 @@ namespace HeavenStudio
List<float> volumeChanges = Beatmap.volumeChanges.Select(c => c.beat).ToList();
if (currentVolumeEvent < Beatmap.volumeChanges.Count && currentVolumeEvent >= 0)
{
if (Conductor.instance.songPositionInBeats >= volumeChanges[currentVolumeEvent])
if (Conductor.instance.songPositionInBeatsAsDouble >= volumeChanges[currentVolumeEvent])
{
Conductor.instance.SetVolume(Beatmap.volumeChanges[currentVolumeEvent].volume);
currentVolumeEvent++;
@ -294,7 +299,7 @@ namespace HeavenStudio
List<float> chartSections = Beatmap.beatmapSections.Select(c => c.beat).ToList();
if (currentSectionEvent < Beatmap.beatmapSections.Count && currentSectionEvent >= 0)
{
if (Conductor.instance.songPositionInBeats >= chartSections[currentSectionEvent])
if (Conductor.instance.songPositionInBeatsAsDouble >= chartSections[currentSectionEvent])
{
Debug.Log("Section " + Beatmap.beatmapSections[currentSectionEvent].sectionName + " started");
currentSection = Beatmap.beatmapSections[currentSectionEvent];
@ -309,18 +314,21 @@ namespace HeavenStudio
float seekTime = 8f;
//seek ahead to preload games that have assetbundles
SeekAheadAndPreload(Conductor.instance.songPositionInBeats, seekTime);
SeekAheadAndPreload(Conductor.instance.songPositionInBeatsAsDouble, seekTime);
SeekAheadAndDoPreEvent(Conductor.instance.songPositionInBeats, 2f);
SeekAheadAndDoPreEvent(Conductor.instance.songPositionInBeatsAsDouble, 2f);
if (currentEvent < Beatmap.entities.Count && currentEvent >= 0)
{
if (Conductor.instance.songPositionInBeats >= entities[currentEvent] /*&& SongPosLessThanClipLength(Conductor.instance.songPositionInBeats)*/)
if (Conductor.instance.songPositionInBeatsAsDouble >= entities[currentEvent])
{
// allows for multiple events on the same beat to be executed on the same frame, so no more 1-frame delay
var entitiesAtSameBeat = Beatmap.entities.FindAll(c => c.beat == Beatmap.entities[currentEvent].beat && !EventCaller.FXOnlyGames().Contains(EventCaller.instance.GetMinigame(c.datamodel.Split('/')[0])));
var fxEntities = Beatmap.entities.FindAll(c => c.beat == Beatmap.entities[currentEvent].beat && EventCaller.FXOnlyGames().Contains(EventCaller.instance.GetMinigame(c.datamodel.Split('/')[0])));
SortEventsByPriority(fxEntities);
SortEventsByPriority(entitiesAtSameBeat);
// FX entities should ALWAYS execute before gameplay entities
for (int i = 0; i < fxEntities.Count; i++)
{
@ -427,6 +435,19 @@ namespace HeavenStudio
Beatmap.volumeChanges.Sort((x, y) => x.beat.CompareTo(y.beat));
}
void SortEventsByPriority(List<DynamicBeatmap.DynamicEntity> entities)
{
entities.Sort((x, y) => {
Minigames.Minigame xGame = EventCaller.instance.GetMinigame(x.datamodel.Split(0));
Minigames.GameAction xAction = EventCaller.instance.GetGameAction(xGame, x.datamodel.Split(1));
Minigames.Minigame yGame = EventCaller.instance.GetMinigame(y.datamodel.Split(0));
Minigames.GameAction yAction = EventCaller.instance.GetGameAction(yGame, y.datamodel.Split(1));
return yAction.priority.CompareTo(xAction.priority);
});
}
public void SetCurrentEventToClosest(float beat)
{
SortEventsList();

View File

@ -25,7 +25,7 @@ namespace HeavenStudio.Games.Scripts_CropStomp
return;
Conductor cond = Conductor.instance;
if (stomp == null)
if (stomp == null && cond.isPlaying)
{
if (GameManager.instance.currentGame == "cropStomp")
stomp = game.ScheduleUserInput(nextStompBeat - 1f, 1f, InputType.STANDARD_DOWN, Just, Miss, Out);

View File

@ -37,7 +37,9 @@ namespace HeavenStudio.Games.Scripts_CropStomp
public void Init()
{
game = CropStomp.instance;
game.ScheduleInput(targetBeat - 1, 1f, InputType.STANDARD_DOWN, StompJust, StompMiss, Out);
if (Conductor.instance.isPlaying)
game.ScheduleInput(targetBeat - 1, 1f, InputType.STANDARD_DOWN, StompJust, StompMiss, Out);
if (!isMole)
{

View File

@ -15,7 +15,8 @@ namespace HeavenStudio.Games.Loaders
{
function = delegate { FirstContact.instance.SetIntervalStart(eventCaller.currentEntity.beat, eventCaller.currentEntity.length); },
defaultLength = 4f,
resizable = true
resizable = true,
priority = 1,
},
new GameAction("alien speak", "Alien Speak")
{

View File

@ -194,7 +194,7 @@ namespace HeavenStudio.Games.Scripts_ForkLifter
private void Miss(PlayerActionEvent caller)
{
Jukebox.PlayOneShot("audience/disappointed");
Jukebox.PlayOneShot("forkLifter/disappointed");
BeatAction.New(game.gameObject, new List<BeatAction.Action>()
{
new BeatAction.Action(startBeat+ 2.45f, delegate {

View File

@ -15,6 +15,8 @@ namespace HeavenStudio.Games.Scripts_RhythmTweezers
private Tweezers tweezers;
private bool plucked;
PlayerActionEvent pluckEvent;
private void Awake()
{
game = RhythmTweezers.instance;
@ -22,7 +24,7 @@ namespace HeavenStudio.Games.Scripts_RhythmTweezers
}
private void Start() {
game.ScheduleInput(createBeat, game.tweezerBeatOffset + game.beatInterval, InputType.STANDARD_DOWN | InputType.DIRECTION_DOWN, Just, Miss, Out);
pluckEvent = game.ScheduleInput(createBeat, game.tweezerBeatOffset + game.beatInterval, InputType.STANDARD_DOWN | InputType.DIRECTION_DOWN, Just, Miss, Out);
}
private void Update()
@ -58,5 +60,11 @@ namespace HeavenStudio.Games.Scripts_RhythmTweezers
}
private void Out(PlayerActionEvent caller) {}
void OnDestroy()
{
if (pluckEvent != null)
pluckEvent.Disable();
}
}
}

View File

@ -21,6 +21,7 @@ namespace HeavenStudio.Games.Scripts_RhythmTweezers
private Sound pullSound;
PlayerActionEvent pluckEvent;
PlayerActionEvent endEvent;
InputType endInput;
@ -122,5 +123,13 @@ namespace HeavenStudio.Games.Scripts_RhythmTweezers
}
EndAce();
}
void OnDestroy()
{
if (pluckEvent != null)
pluckEvent.Disable();
if (endEvent != null)
endEvent.Disable();
}
}
}

View File

@ -19,7 +19,8 @@ namespace HeavenStudio.Games.Loaders
{
function = delegate { RhythmTweezers.instance.SetIntervalStart(eventCaller.currentEntity.beat, eventCaller.currentEntity.length); },
defaultLength = 4f,
resizable = true
resizable = true,
priority = 1
},
new GameAction("short hair", "Short Hair")
{
@ -41,7 +42,8 @@ namespace HeavenStudio.Games.Loaders
new Param("type", RhythmTweezers.VegetableType.Onion, "Type", "The vegetable to switch to"),
new Param("colorA", RhythmTweezers.defaultOnionColor, "Onion Color", "The color of the onion"),
new Param("colorB", RhythmTweezers.defaultPotatoColor, "Potato Color", "The color of the potato")
}
},
priority = 3
},
new GameAction("change vegetable", "Change Vegetable (Instant)")
{
@ -57,12 +59,14 @@ namespace HeavenStudio.Games.Loaders
new GameAction("set tweezer delay", "Offset Tweezer")
{
function = delegate { RhythmTweezers.instance.tweezerBeatOffset = eventCaller.currentEntity.length; },
resizable = true
resizable = true,
priority = 2
},
new GameAction("reset tweezer delay", "Reset Tweezer Offset")
{
function = delegate { RhythmTweezers.instance.tweezerBeatOffset = 0f; },
defaultLength = 0.5f
defaultLength = 0.5f,
priority = 2
},
new GameAction("set background color", "Background Colour")
{
@ -350,7 +354,10 @@ namespace HeavenStudio.Games
if (transitioning)
{
if (transitionTween != null)
transitionTween.Kill(true);
{
transitionTween.Complete(true);
transitionTween.Kill();
}
}
}
}

View File

@ -19,7 +19,8 @@ namespace HeavenStudio.Games.Loaders
{
function = delegate { WizardsWaltz.instance.SetIntervalStart(eventCaller.currentEntity.beat, eventCaller.currentEntity.length); },
defaultLength = 4f,
resizable = true
resizable = true,
priority = 1
},
new GameAction("plant", "Plant")
{

View File

@ -152,6 +152,7 @@ namespace HeavenStudio
public bool resizable = false;
public List<Param> parameters = null;
public bool hidden = false;
public int priority = 0;
public EventCallback inactiveFunction = delegate { };
public EventCallback preFunction = delegate { };
@ -169,7 +170,9 @@ namespace HeavenStudio
/// <param name="inactiveFunction">What the block does when read while the game it's associated with isn't loaded.</param>
/// <param name="prescheduleFunction">What the block does when the GameManager seeks to this cue for pre-scheduling.</param>
/// <param name="hidden">Prevents the block from being shown in the game list. Block will still function normally if it is in the timeline.</param>
public GameAction(string actionName, string displayName, float defaultLength = 1, bool resizable = false, List<Param> parameters = null, EventCallback function = null, EventCallback inactiveFunction = null, EventCallback prescheduleFunction = null, bool hidden = false)
/// <param name="preFunction">Runs two beats before this event is reached.</param>
/// <param name="priority">Priority of this event. Higher priority events will be run first.</param>
public GameAction(string actionName, string displayName, float defaultLength = 1, bool resizable = false, List<Param> parameters = null, EventCallback function = null, EventCallback inactiveFunction = null, EventCallback prescheduleFunction = null, bool hidden = false, EventCallback preFunction = null, int priority = 0)
{
this.actionName = actionName;
if (displayName == String.Empty) this.displayName = actionName;
@ -182,6 +185,8 @@ namespace HeavenStudio
this.function = function ?? delegate { };
this.inactiveFunction = inactiveFunction ?? delegate { };
this.preFunction = prescheduleFunction ?? delegate { };
this.priority = priority;
//todo: converting to new versions of GameActions
}

View File

@ -1,12 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
namespace HeavenStudio.Util
{
public class Audio
{
}
}

View File

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

View File

@ -1,21 +0,0 @@
using UnityEngine;
// this is a script for testing
using HeavenStudio.Editor;
namespace HeavenStudio.Tests
{
public class WTF : MonoBehaviour
{
public GameObject test;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Z))
{
GetComponent<CommandManager>().Execute(new TestCommand(test, new Vector3(Random.Range(-8f, 8f), Random.Range(-6f, 6f))));
}
}
}
}

View File

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