using HeavenStudio.Util; using System; using System.Collections.Generic; using UnityEngine; using DG.Tweening; using Jukebox; namespace HeavenStudio.Games.Loaders { using static Minigames; public static class RvlTambourineLoader { public static Minigame AddGame(EventCaller eventCaller) { return new Minigame("tambourine", "Tambourine", "388cd0", false, false, new List() { new GameAction("beat intervals", "Start Interval") { preFunction = delegate {var e = eventCaller.currentEntity; Tambourine.PreInterval(e.beat, e.length, e["auto"]); }, defaultLength = 8f, resizable = true, parameters = new List() { new Param("auto", true, "Auto Pass Turn") } }, new GameAction("shake", "Shake") { defaultLength = 0.5f, }, new GameAction("hit", "Hit") { defaultLength = 0.5f, }, new GameAction("pass turn", "Pass Turn") { preFunction = delegate { Tambourine.PrePassTurn(eventCaller.currentEntity.beat); }, defaultLength = 1f, preFunctionLength = 1f }, new GameAction("bop", "Bop") { function = delegate {var e = eventCaller.currentEntity; Tambourine.instance.Bop(e.beat, e.length, e["whoBops"], e["whoBopsAuto"]); }, parameters = new List() { new Param("whoBops", Tambourine.WhoBops.Both, "Who Bops", "Who will bop."), new Param("whoBopsAuto", Tambourine.WhoBops.None, "Who Bops (Auto)", "Who will auto bop."), }, resizable = true, priority = 4 }, new GameAction("success", "Success") { function = delegate {var e = eventCaller.currentEntity; Tambourine.instance.SuccessFace(e.beat); }, defaultLength = 1f, priority = 4, }, new GameAction("fade background", "Background Color") { function = delegate {var e = eventCaller.currentEntity; Tambourine.instance.FadeBackgroundColor(e["colorA"], e["colorB"], e.length, e["instant"]); }, defaultLength = 4f, resizable = true, parameters = new List() { new Param("colorA", Color.white, "Start Color", "The starting color of the fade."), new Param("colorB", Tambourine.defaultBGColor, "End Color", "The ending color of the fade."), new Param("instant", false, "Instant", "Instantly set the color of the background to the start color?") } }, //backwards-compatibility new GameAction("set background color", "Background Color") { function = delegate {var e = eventCaller.currentEntity; Tambourine.instance.ChangeBackgroundColor(e["colorA"], 0f); }, defaultLength = 0.5f, parameters = new List() { new Param("colorA", Tambourine.defaultBGColor, "Background Color", "The background color to change to.") }, hidden = true }, }, new List() {"rvl", "repeat"}, "rvldrum", "en", new List() {} ); } } } namespace HeavenStudio.Games { public class Tambourine : Minigame { private static Color _defaultBGColor; public static Color defaultBGColor { get { ColorUtility.TryParseHtmlString("#388cd0", out _defaultBGColor); return _defaultBGColor; } } [Header("Components")] [SerializeField] Animator handsAnimator; [SerializeField] SpriteRenderer bg; [SerializeField] Animator monkeyAnimator; [SerializeField] ParticleSystem flowerParticles; [SerializeField] GameObject happyFace; [SerializeField] GameObject sadFace; [SerializeField] Animator sweatAnimator; [SerializeField] Animator frogAnimator; [Header("Variables")] float misses; bool frogPresent; bool monkeyGoBop; bool handsGoBop; Tween bgColorTween; public GameEvent bop = new GameEvent(); public enum WhoBops { Monkey, Player, Both, None } public static Tambourine instance; void Awake() { instance = this; sweatAnimator.Play("NoSweat", 0, 0); frogAnimator.Play("FrogExited", 0, 0); handsAnimator.Play("Idle", 0, 0); monkeyAnimator.Play("MonkeyIdle", 0, 0); } void Update() { if (Conductor.instance.ReportBeat(ref bop.lastReportedBeat, bop.startBeat % 1)) { if (monkeyGoBop) { monkeyAnimator.Play("MonkeyBop", 0, 0); } if (handsGoBop) { handsAnimator.Play("Bop", 0, 0); } } if (PlayerInput.Pressed() && !IsExpectingInputNow(InputType.STANDARD_DOWN)) { handsAnimator.Play("Shake", 0, 0); SoundByte.PlayOneShotGame($"tambourine/player/shake/{UnityEngine.Random.Range(1, 6)}"); sweatAnimator.Play("Sweating", 0, 0); SummonFrog(); ScoreMiss(); if (!IntervalIsGoingOn()) { sadFace.SetActive(true); } } else if (PlayerInput.AltPressed() && !IsExpectingInputNow(InputType.STANDARD_ALT_DOWN)) { handsAnimator.Play("Smack", 0, 0); SoundByte.PlayOneShotGame($"tambourine/player/hit/{UnityEngine.Random.Range(1, 6)}"); sweatAnimator.Play("Sweating", 0, 0); SummonFrog(); ScoreMiss(); if (!IntervalIsGoingOn()) { sadFace.SetActive(true); } } if (passedTurns.Count > 0) { foreach (var pass in passedTurns) { PassTurnStandalone(pass); } passedTurns.Clear(); } } private bool IntervalIsGoingOn() { double beat = Conductor.instance.songPositionInBeats; return EventCaller.GetAllInGameManagerList("tambourine", new string[] { "beat intervals" }).Find(x => beat >= x.beat && beat < x.beat + x.length) != null; } private List GetAllInputsBetweenBeat(double beat, double endBeat) { return EventCaller.GetAllInGameManagerList("tambourine", new string[] { "shake", "hit" }).FindAll(x => x.beat >= beat && x.beat < endBeat); } public static void PreInterval(double beat, float interval, bool autoPassTurn) { if (GameManager.instance.currentGame == "tambourine") { instance.StartInterval(beat, interval, beat, autoPassTurn); } else { queuedIntervals.Add(new QueuedInterval() { beat = beat, interval = interval, autoPassTurn = autoPassTurn }); } } public override void OnGameSwitch(double beat) { if (queuedIntervals.Count > 0) { foreach (var interval in queuedIntervals) { StartInterval(interval.beat, interval.interval, beat, interval.autoPassTurn); } queuedIntervals.Clear(); } } private struct QueuedInterval { public double beat; public float interval; public bool autoPassTurn; } private static List queuedIntervals = new(); private void StartInterval(double beat, float interval, double gameSwitchBeat, bool autoPassTurn) { List actions = new() { new BeatAction.Action(beat, delegate { DesummonFrog(); sadFace.SetActive(false); }) }; var relevantInputs = GetAllInputsBetweenBeat(beat, beat + interval); relevantInputs.Sort((x, y) => x.beat.CompareTo(y.beat)); for (int i = 0; i < relevantInputs.Count; i++) { bool isHit = relevantInputs[i].datamodel == "tambourine/hit"; double inputBeat = relevantInputs[i].beat; if (inputBeat >= gameSwitchBeat) { actions.Add(new BeatAction.Action(inputBeat, delegate { MonkeyInput(inputBeat, isHit); })); } } BeatAction.New(gameObject, actions); if (autoPassTurn) { PassTurn(beat + interval, beat, interval); } } public void MonkeyInput(double beat, bool hit) { if (hit) { monkeyAnimator.DoScaledAnimationAsync("MonkeySmack", 0.5f); SoundByte.PlayOneShotGame($"tambourine/monkey/hit/{UnityEngine.Random.Range(1, 6)}"); } else { monkeyAnimator.DoScaledAnimationAsync("MonkeyShake", 0.5f); SoundByte.PlayOneShotGame($"tambourine/monkey/shake/{UnityEngine.Random.Range(1, 6)}"); } } private RiqEntity GetLastIntervalBeforeBeat(double beat) { return EventCaller.GetAllInGameManagerList("tambourine", new string[] { "beat intervals" }).FindLast(x => x.beat <= beat); } public static void PrePassTurn(double beat) { if (GameManager.instance.currentGame == "tambourine") { instance.PassTurnStandalone(beat); } else { passedTurns.Add(beat); } } private static List passedTurns = new(); private void PassTurnStandalone(double beat) { var lastInterval = GetLastIntervalBeforeBeat(beat); if (lastInterval != null) PassTurn(beat, lastInterval.beat, lastInterval.length); } private void PassTurn(double beat, double intervalBeat, float intervalLength) { SoundByte.PlayOneShotGame($"tambourine/monkey/turnPass/{UnityEngine.Random.Range(1, 6)}", beat); List actions = new() { new BeatAction.Action(beat, delegate { monkeyAnimator.DoScaledAnimationAsync("MonkeyPassTurn", 0.5f); happyFace.SetActive(true); }), new BeatAction.Action(beat + 0.3f, delegate { happyFace.SetActive(false); }) }; var relevantInputs = GetAllInputsBetweenBeat(intervalBeat, intervalBeat + intervalLength); relevantInputs.Sort((x, y) => x.beat.CompareTo(y.beat)); for (int i = 0; i < relevantInputs.Count; i++) { bool isHit = relevantInputs[i].datamodel == "tambourine/hit"; double inputBeat = relevantInputs[i].beat - intervalBeat; actions.Add(new BeatAction.Action(inputBeat, delegate { if (isHit) ScheduleInput(beat + 1, inputBeat, InputType.STANDARD_ALT_DOWN, JustHit, Miss, Nothing); else ScheduleInput(beat + 1, inputBeat, InputType.STANDARD_DOWN, JustShake, Miss, Nothing); Bop(beat + 1 + inputBeat, 1, (int)WhoBops.Monkey, (int)WhoBops.None); })); } BeatAction.New(gameObject, actions); } public void Bop(double beat, float length, int whoBops, int whoBopsAuto) { monkeyGoBop = whoBopsAuto == (int)WhoBops.Monkey || whoBopsAuto == (int)WhoBops.Both; handsGoBop = whoBopsAuto == (int)WhoBops.Player || whoBopsAuto == (int)WhoBops.Both; for (int i = 0; i < length; i++) { BeatAction.New(instance.gameObject, new List() { new BeatAction.Action(beat + i, delegate { switch (whoBops) { case (int) WhoBops.Monkey: monkeyAnimator.DoScaledAnimationAsync("MonkeyBop", 0.5f); break; case (int) WhoBops.Player: handsAnimator.DoScaledAnimationAsync("Bop", 0.5f); break; case (int) WhoBops.Both: monkeyAnimator.DoScaledAnimationAsync("MonkeyBop", 0.5f); handsAnimator.DoScaledAnimationAsync("Bop", 0.5f); break; default: break; } }) }); } } public void SuccessFace(double beat) { DesummonFrog(); if (misses > 0) return; flowerParticles.Play(); SoundByte.PlayOneShotGame($"tambourine/player/turnPass/sweep"); MultiSound.Play(new MultiSound.Sound[] { new MultiSound.Sound("tambourine/player/turnPass/note1", beat), new MultiSound.Sound("tambourine/player/turnPass/note2", beat + 0.1f), new MultiSound.Sound("tambourine/player/turnPass/note3", beat + 0.2f), new MultiSound.Sound("tambourine/player/turnPass/note3", beat + 0.3f), }, forcePlay: true); happyFace.SetActive(true); BeatAction.New(instance.gameObject, new List() { new BeatAction.Action(beat + 1, delegate { happyFace.SetActive(false); }), }); } public void JustHit(PlayerActionEvent caller, float state) { if (state >= 1f || state <= -1f) { handsAnimator.DoScaledAnimationAsync("Smack", 0.5f); SoundByte.PlayOneShotGame($"tambourine/player/hit/{UnityEngine.Random.Range(1, 6)}"); SoundByte.PlayOneShotGame("tambourine/miss"); sweatAnimator.DoScaledAnimationAsync("Sweating", 0.5f); misses++; if (!IntervalIsGoingOn()) { sadFace.SetActive(true); } return; } Success(true); } public void JustShake(PlayerActionEvent caller, float state) { if (state >= 1f || state <= -1f) { handsAnimator.DoScaledAnimationAsync("Shake", 0.5f); SoundByte.PlayOneShotGame($"tambourine/player/shake/{UnityEngine.Random.Range(1, 6)}"); SoundByte.PlayOneShotGame("tambourine/miss"); sweatAnimator.DoScaledAnimationAsync("Sweating", 0.5f); misses++; if (!IntervalIsGoingOn()) { sadFace.SetActive(true); } return; } Success(false); } public void Success(bool hit) { sadFace.SetActive(false); if (hit) { handsAnimator.DoScaledAnimationAsync("Smack", 0.5f); SoundByte.PlayOneShotGame($"tambourine/player/hit/{UnityEngine.Random.Range(1, 6)}"); } else { handsAnimator.DoScaledAnimationAsync("Shake", 0.5f); SoundByte.PlayOneShotGame($"tambourine/player/shake/{UnityEngine.Random.Range(1, 6)}"); } } public void Miss(PlayerActionEvent caller) { SummonFrog(); sweatAnimator.DoScaledAnimationAsync("Sweating", 0.5f); misses++; if (!IntervalIsGoingOn()) { sadFace.SetActive(true); } } public void ChangeBackgroundColor(Color color, float beats) { var seconds = Conductor.instance.secPerBeat * beats; if (bgColorTween != null) bgColorTween.Kill(true); if (seconds == 0) { bg.color = color; } else { bgColorTween = bg.DOColor(color, seconds); } } public void FadeBackgroundColor(Color start, Color end, float beats, bool instant) { ChangeBackgroundColor(start, 0f); if (!instant) ChangeBackgroundColor(end, beats); } public void SummonFrog() { if (frogPresent) return; SoundByte.PlayOneShotGame("tambourine/frog"); frogAnimator.Play("FrogEnter", 0, 0); frogPresent = true; } public void DesummonFrog() { if (!frogPresent) return; frogAnimator.Play("FrogExit", 0, 0); frogPresent = false; } public void Nothing(PlayerActionEvent caller) {} } }