536 lines
21 KiB
C#
536 lines
21 KiB
C#
using HeavenStudio.Util;
|
|
using HeavenStudio.InputSystem;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using System.Linq;
|
|
|
|
namespace HeavenStudio.Games.Loaders
|
|
{
|
|
using static Minigames;
|
|
public static class PcoMeatLoader
|
|
{
|
|
public static Minigame AddGame(EventCaller eventCaller)
|
|
{
|
|
List<Param> reactionParams = new() {
|
|
new Param("tackReaction", MeatGrinder.TackExpressions.None, "Tack Reaction", "If this is hit, what expression should tack do?", new List<Param.CollapseParam>() {
|
|
new((x, y) => (int)x != (int)MeatGrinder.TackExpressions.None, new string[] { "tackReactionBeats" }),
|
|
}),
|
|
new Param("tackReactionBeats", new EntityTypes.Float(0.5f, 10, 1), "Tack React After", "The amount of beats to wait until tack reacts"),
|
|
new Param("bossReaction", MeatGrinder.BossExpressions.None, "Boss Reaction", "If this is hit, what expression should boss do?", new List<Param.CollapseParam>() {
|
|
new((x, y) => (int)x != (int)MeatGrinder.BossExpressions.None, new string[] { "bossReactionBeats" }),
|
|
}),
|
|
new Param("bossReactionBeats", new EntityTypes.Float(0, 10, 0), "Boss React After", "The amount of beats to wait until boss reacts"),
|
|
};
|
|
|
|
return new Minigame("meatGrinder", "Meat Grinder", "501d18", false, false, new List<GameAction>()
|
|
{
|
|
new GameAction("bop", "Bop")
|
|
{
|
|
function = delegate {
|
|
var e = eventCaller.currentEntity;
|
|
MeatGrinder.instance.Bop(e.beat, e.length, e["bop"], e["bossBop"]);
|
|
},
|
|
parameters = new List<Param>()
|
|
{
|
|
new Param("bop", true, "Bop", "Toggle if Boss should bop for the duration of this event."),
|
|
new Param("bossBop", false, "Bop (Auto)", "Toggle if Boss should automatically bop until another Bop event is reached."),
|
|
},
|
|
resizable = true,
|
|
priority = 1,
|
|
},
|
|
new GameAction("MeatToss", "Meat Toss")
|
|
{
|
|
function = delegate {
|
|
var e = eventCaller.currentEntity;
|
|
SoundByte.PlayOneShotGame("meatGrinder/toss", forcePlay: true);
|
|
MeatGrinder.instance.MeatToss(e.beat, e["bacon"], e["tackReaction"], e["tackReactionBeats"], e["bossReaction"], e["bossReactionBeats"]);
|
|
},
|
|
inactiveFunction = delegate {
|
|
SoundByte.PlayOneShotGame("meatGrinder/toss", forcePlay: true);
|
|
MeatGrinder.QueueMeatToss(eventCaller.currentEntity);
|
|
},
|
|
defaultLength = 2f,
|
|
priority = 2,
|
|
parameters = new List<Param>()
|
|
{
|
|
new Param("bacon", false, "Bacon Ball", "Toggle if a bacon ball should be thrown instead of the typical dark meat"),
|
|
}.Concat(reactionParams).ToList(), // doing this because i want these params to always be the same
|
|
},
|
|
new GameAction("StartInterval", "Start Interval")
|
|
{
|
|
defaultLength = 4f,
|
|
resizable = true,
|
|
preFunction = delegate
|
|
{
|
|
var e = eventCaller.currentEntity;
|
|
MeatGrinder.PreInterval(e.beat, e.length, e["auto"]);
|
|
},
|
|
parameters = new List<Param>()
|
|
{
|
|
new Param("auto", true, "Auto Pass Turn", "Toggle if the turn should be passed automatically at the end of the start interval.")
|
|
},
|
|
preFunctionLength = 1
|
|
},
|
|
new GameAction("MeatCall", "Meat Call")
|
|
{
|
|
inactiveFunction = delegate { SoundByte.PlayOneShotGame("meatGrinder/signal", forcePlay: true); },
|
|
defaultLength = 0.5f,
|
|
priority = 2,
|
|
preFunctionLength = 1f,
|
|
parameters = reactionParams,
|
|
},
|
|
new GameAction("passTurn", "Pass Turn")
|
|
{
|
|
preFunction = delegate { MeatGrinder.PrePassTurn(eventCaller.currentEntity.beat); },
|
|
preFunctionLength = 1
|
|
},
|
|
new GameAction("expressions", "Expressions")
|
|
{
|
|
function = delegate {
|
|
var e = eventCaller.currentEntity;
|
|
MeatGrinder.instance.DoExpressions(e["tackExpression"], e["bossExpression"]);
|
|
},
|
|
parameters = new List<Param>() {
|
|
new Param("tackExpression", MeatGrinder.TackExpressions.Content, "Tack", "Set the expression Tack will display."),
|
|
new Param("bossExpression", MeatGrinder.BossExpressions.None, "Boss", "Set the expression Boss will display."),
|
|
}
|
|
},
|
|
new GameAction("cartGuy", "Cart Guy")
|
|
{
|
|
function = delegate {
|
|
var e = eventCaller.currentEntity;
|
|
MeatGrinder.instance.CartGuy(e.beat, e.length, e["spider"], e["direction"], e["ease"]);
|
|
},
|
|
resizable = true,
|
|
defaultLength = 16,
|
|
parameters = new List<Param>() {
|
|
new Param("spider", false, "On Phone", "Toggle if Cart Guy should be on his phone."),
|
|
new Param("direction", MeatGrinder.CartGuyDirection.Right, "Direction", "Set the direction the cart will be moved to."),
|
|
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action."),
|
|
}
|
|
},
|
|
new GameAction("gears", "Gears")
|
|
{
|
|
function = delegate {
|
|
var e = eventCaller.currentEntity;
|
|
MeatGrinder.instance.ChangeGears(e.beat, e.length, e["ease"], e["speed"]);
|
|
},
|
|
resizable = true,
|
|
defaultLength = 1,
|
|
parameters = new List<Param>() {
|
|
new Param("speed", new EntityTypes.Float(0, 10, 1), "Speed", "Set the speed of the gears."),
|
|
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action"),
|
|
}
|
|
},
|
|
},
|
|
new List<string>() { "pco", "normal", "repeat" },
|
|
"pcomeat", "en",
|
|
new List<string>() { }
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace HeavenStudio.Games
|
|
{
|
|
using Jukebox;
|
|
using Scripts_MeatGrinder;
|
|
|
|
public class MeatGrinder : Minigame
|
|
{
|
|
private static List<QueuedInterval> queuedIntervals = new();
|
|
|
|
private struct QueuedInterval
|
|
{
|
|
public double beat;
|
|
public float length;
|
|
public bool autoPassTurn;
|
|
}
|
|
|
|
public struct UpdateEasing
|
|
{
|
|
public double beat;
|
|
public float length;
|
|
public Util.EasingFunction.Ease ease;
|
|
}
|
|
|
|
public struct Reaction
|
|
{
|
|
public int expression;
|
|
public double beat;
|
|
public Reaction(int expression, double beat)
|
|
{
|
|
this.expression = expression;
|
|
this.beat = beat;
|
|
}
|
|
}
|
|
|
|
// private static List<RiqEntity> queuedMeats = new();
|
|
|
|
[Header("Objects")]
|
|
public GameObject MeatBase;
|
|
public ParticleSystem MeatSplash;
|
|
[SerializeField] Transform[] Gears;
|
|
|
|
[Header("Animators")]
|
|
public Animator BossAnim;
|
|
public Animator TackAnim;
|
|
[SerializeField] Animator CartGuyParentAnim;
|
|
[SerializeField] Animator CartGuyAnim;
|
|
|
|
[Header("Variables")]
|
|
private bool bossBop = true;
|
|
public bool bossAnnoyed = false;
|
|
private UpdateEasing cartEase;
|
|
private bool cartPhone = false;
|
|
private string cartDir = "Left";
|
|
private UpdateEasing gearEase;
|
|
private float oldGearSpeed = 1;
|
|
private float newGearSpeed = 1;
|
|
private const string sfxName = "meatGrinder/";
|
|
|
|
public static MeatGrinder instance;
|
|
|
|
public enum TackExpressions
|
|
{
|
|
None,
|
|
Content,
|
|
Smug,
|
|
Wonder,
|
|
}
|
|
|
|
public enum BossExpressions
|
|
{
|
|
None,
|
|
Eyebrow,
|
|
Scared,
|
|
}
|
|
|
|
public enum CartGuyDirection
|
|
{
|
|
Right,
|
|
Left,
|
|
}
|
|
|
|
protected static bool IA_PadAny(out double dt)
|
|
{
|
|
return PlayerInput.GetPadDown(InputController.ActionsPad.East, out dt)
|
|
|| PlayerInput.GetPadDown(InputController.ActionsPad.Up, out dt)
|
|
|| PlayerInput.GetPadDown(InputController.ActionsPad.Down, out dt)
|
|
|| PlayerInput.GetPadDown(InputController.ActionsPad.Left, out dt)
|
|
|| PlayerInput.GetPadDown(InputController.ActionsPad.Right, out dt);
|
|
}
|
|
|
|
public static PlayerInput.InputAction InputAction_Press =
|
|
new("PcoMeatPress", new int[] { IAPressCat, IAPressCat, IAPressCat },
|
|
IA_PadAny, IA_TouchBasicPress, IA_BatonBasicPress);
|
|
|
|
private void Awake()
|
|
{
|
|
instance = this;
|
|
SetupBopRegion("meatGrinder", "bop", "bossBop");
|
|
MeatBase.SetActive(false);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
var cond = Conductor.instance;
|
|
if (PlayerInput.GetIsAction(InputAction_Press) && !IsExpectingInputNow(InputAction_Press))
|
|
{
|
|
TackAnim.DoScaledAnimationAsync("TackEmptyHit", 0.5f);
|
|
TackAnim.SetBool("tackMeated", false);
|
|
SoundByte.PlayOneShotGame("meatGrinder/whiff");
|
|
bossAnnoyed = false;
|
|
}
|
|
|
|
if (bossAnnoyed) BossAnim.SetBool("bossAnnoyed", true);
|
|
|
|
if (passedTurns.Count > 0)
|
|
{
|
|
foreach (double pass in passedTurns)
|
|
{
|
|
PassTurnStandalone(pass);
|
|
}
|
|
passedTurns.Clear();
|
|
}
|
|
|
|
var currentGearSpeed = newGearSpeed;
|
|
if (gearEase.length != 0)
|
|
{
|
|
float normalizedBeat = cond.GetPositionFromBeat(gearEase.beat, gearEase.length);
|
|
Util.EasingFunction.Function func = Util.EasingFunction.GetEasingFunction(gearEase.ease);
|
|
currentGearSpeed = func(oldGearSpeed, newGearSpeed, normalizedBeat);
|
|
if (normalizedBeat >= 1) gearEase.length = 0;
|
|
}
|
|
|
|
if (cartEase.length != 0)
|
|
{
|
|
float normalizedBeat = cond.GetPositionFromBeat(cartEase.beat, cartEase.length);
|
|
Util.EasingFunction.Function func = Util.EasingFunction.GetEasingFunction(cartEase.ease);
|
|
float newPos = func(0f, 1f, normalizedBeat);
|
|
CartGuyParentAnim.DoNormalizedAnimation($"Move{cartDir}", newPos);
|
|
if (normalizedBeat >= 1) cartEase.length = 0;
|
|
}
|
|
|
|
CartGuyParentAnim.gameObject.SetActive(cartEase.length != 0);
|
|
|
|
if (cond.isPlaying && !cond.isPaused)
|
|
{
|
|
foreach (Transform gear in Gears)
|
|
{
|
|
double newZ = Time.deltaTime * currentGearSpeed * 50 * (gear.name == "Big" ? -1 : 1) / cond.pitchedSecPerBeat;
|
|
gear.Rotate(new Vector3(0, 0, (float)newZ));
|
|
}
|
|
}
|
|
|
|
if (cond.isPlaying)
|
|
{
|
|
MeatSplash.Play();
|
|
}
|
|
else if (cond.isPaused)
|
|
{
|
|
MeatSplash.Pause();
|
|
}
|
|
else
|
|
{
|
|
MeatSplash.Stop();
|
|
}
|
|
}
|
|
|
|
public override void OnLateBeatPulse(double beat)
|
|
{
|
|
if (!BossAnim.IsPlayingAnimationNames("BossCall", "BossSignal", "BossScared") && BeatIsInBopRegion(beat))
|
|
{
|
|
BossAnim.DoScaledAnimationAsync(bossAnnoyed ? "BossMiss" : "Bop", 0.5f);
|
|
}
|
|
|
|
if (CartGuyParentAnim.gameObject.activeSelf)
|
|
{
|
|
// Debug.Log(cartPhone ? "PhoneBop" : "Bop");
|
|
if (cartPhone)
|
|
{
|
|
CartGuyAnim.DoScaledAnimationAsync("PhoneBop", 0.5f);
|
|
}
|
|
else
|
|
{
|
|
CartGuyAnim.DoScaledAnimationAsync("Bop", 0.5f);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void OnGameSwitch(double beat)
|
|
{
|
|
if (queuedIntervals.Count > 0)
|
|
{
|
|
foreach (var interval in queuedIntervals) StartInterval(interval.beat, interval.length, beat, interval.autoPassTurn);
|
|
queuedIntervals.Clear();
|
|
}
|
|
// if (queuedMeats.Count > 0)
|
|
// {
|
|
// foreach (var meat in queuedMeats) MeatToss(meat.beat, meat["bacon"], meat["tackReaction"], meat["tackReactionBeats"], meat["bossReaction"], meat["bossReactionBeats"]);
|
|
// queuedMeats.Clear();
|
|
// }
|
|
OnPlay(beat);
|
|
}
|
|
|
|
public override void OnPlay(double beat)
|
|
{
|
|
List<RiqEntity> allEntities = GameManager.instance.Beatmap.Entities.FindAll(c => c.datamodel.Split('/')[0] == "meatGrinder");
|
|
RiqEntity cg = allEntities.Find(c => c.datamodel == "meatGrinder/cartGuy");
|
|
if (cg != null)
|
|
{
|
|
CartGuy(cg.beat, cg.length, cg["spider"], cg["direction"], cg["ease"]);
|
|
}
|
|
RiqEntity gr = allEntities.Find(c => c.datamodel == "meatGrinder/gears");
|
|
if (gr != null)
|
|
{
|
|
ChangeGears(gr.beat, gr.length, gr["ease"], gr["speed"]);
|
|
}
|
|
List<RiqEntity> meats = allEntities.FindAll(c => c.datamodel == "meatGrinder/MeatToss" && beat > c.beat && beat < c.beat + 1);
|
|
foreach (var meat in meats)
|
|
{
|
|
MeatToss(meat.beat, meat["bacon"], meat["tackReaction"], meat["tackReactionBeats"], meat["bossReaction"], meat["bossReactionBeats"]);
|
|
}
|
|
}
|
|
|
|
private List<RiqEntity> GetRelevantMeatCallsBetweenBeat(double beat, double endBeat)
|
|
{
|
|
return EventCaller.GetAllInGameManagerList("meatGrinder", new string[] { "MeatCall" }).FindAll(x => x.beat >= beat && x.beat < endBeat);
|
|
}
|
|
|
|
public void Bop(double beat, double length, bool doesBop, bool autoBop)
|
|
{
|
|
bossBop = autoBop;
|
|
if (doesBop)
|
|
{
|
|
var actions = new List<BeatAction.Action>();
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
actions.Add(new BeatAction.Action(beat + i, delegate
|
|
{
|
|
if (!BossAnim.IsPlayingAnimationNames("BossCall", "BossSignal"))
|
|
{
|
|
BossAnim.DoScaledAnimationAsync(bossAnnoyed ? "BossMiss" : "Bop", 0.5f);
|
|
}
|
|
}));
|
|
}
|
|
BeatAction.New(instance, actions);
|
|
}
|
|
}
|
|
|
|
public void DoExpressions(int tackExpression, int bossExpression = 0)
|
|
{
|
|
if (tackExpression != (int)TackExpressions.None)
|
|
{
|
|
string tackAnim = ((TackExpressions)tackExpression).ToString();
|
|
TackAnim.DoScaledAnimationAsync("Tack" + tackAnim, 0.5f);
|
|
}
|
|
if (bossExpression != (int)BossExpressions.None)
|
|
{
|
|
string bossAnim = ((BossExpressions)bossExpression).ToString();
|
|
BossAnim.DoScaledAnimationAsync("Boss" + bossAnim, 0.5f);
|
|
}
|
|
}
|
|
|
|
public void CartGuy(double beat, float length, bool spider, int direction, int ease)
|
|
{
|
|
cartPhone = spider;
|
|
cartDir = direction == 0 ? "Right" : "Left";
|
|
if (cartPhone)
|
|
{
|
|
CartGuyAnim.Play("Phone", 0, 0);
|
|
}
|
|
cartEase = new()
|
|
{
|
|
beat = beat,
|
|
length = length,
|
|
ease = (Util.EasingFunction.Ease)ease,
|
|
};
|
|
}
|
|
|
|
public void ChangeGears(double beat, float length, int ease, float speed)
|
|
{
|
|
gearEase = new()
|
|
{
|
|
beat = beat,
|
|
length = length,
|
|
ease = (Util.EasingFunction.Ease)ease,
|
|
};
|
|
|
|
oldGearSpeed = newGearSpeed;
|
|
newGearSpeed = speed;
|
|
}
|
|
|
|
public static void PreInterval(double beat, float length, bool autoPassTurn)
|
|
{
|
|
SoundByte.PlayOneShotGame("meatGrinder/startSignal", beat - 1, forcePlay: true);
|
|
|
|
if (GameManager.instance.currentGame == "meatGrinder")
|
|
{
|
|
instance.StartInterval(beat, length, beat, autoPassTurn);
|
|
}
|
|
else
|
|
{
|
|
queuedIntervals.Add(new QueuedInterval()
|
|
{
|
|
beat = beat,
|
|
length = length,
|
|
autoPassTurn = autoPassTurn
|
|
});
|
|
}
|
|
}
|
|
|
|
public void StartInterval(double beat, float length, double gameSwitchBeat, bool autoPassTurn)
|
|
{
|
|
List<BeatAction.Action> actions = new() {
|
|
new(beat - 1, delegate { if (Conductor.instance.songPositionInBeatsAsDouble < beat) BossAnim.DoScaledAnimationFromBeatAsync("BossSignal", 0.5f, beat - 1); }),
|
|
};
|
|
|
|
var allCallEvents = GetRelevantMeatCallsBetweenBeat(beat, beat + length);
|
|
allCallEvents.Sort((x, y) => x.beat.CompareTo(y.beat));
|
|
for (int i = 0; i < allCallEvents.Count; i++)
|
|
{
|
|
double eventBeat = allCallEvents[i].beat;
|
|
|
|
if (eventBeat >= gameSwitchBeat)
|
|
{
|
|
actions.Add(new BeatAction.Action(eventBeat, delegate
|
|
{
|
|
BossAnim.DoScaledAnimationAsync("BossCall", 0.5f);
|
|
SoundByte.PlayOneShotGame("meatGrinder/signal");
|
|
}));
|
|
}
|
|
}
|
|
|
|
BeatAction.New(this, actions);
|
|
|
|
if (autoPassTurn)
|
|
{
|
|
PassTurn(beat + length, beat, length, allCallEvents);
|
|
}
|
|
}
|
|
|
|
public static void QueueMeatToss(RiqEntity entity)
|
|
{
|
|
// queuedMeats.Add(entity);
|
|
}
|
|
|
|
public void MeatToss(double beat, bool bacon, int tackReaction, float tackReactionBeats, int bossReaction, float bossReactionBeats)
|
|
{
|
|
Meat meat = Instantiate(MeatBase, transform).GetComponent<Meat>();
|
|
meat.gameObject.SetActive(true);
|
|
meat.startBeat = beat;
|
|
meat.meatType = bacon ? Meat.MeatType.BaconBall : Meat.MeatType.DarkMeat;
|
|
meat.tackReaction = new Reaction(tackReaction, tackReactionBeats);
|
|
meat.bossReaction = new Reaction(bossReaction, bossReactionBeats);
|
|
// meat.reaction = reaction;
|
|
}
|
|
|
|
public static void PrePassTurn(double beat)
|
|
{
|
|
if (GameManager.instance.currentGame == "meatGrinder")
|
|
{
|
|
instance.PassTurnStandalone(beat);
|
|
}
|
|
else
|
|
{
|
|
passedTurns.Add(beat);
|
|
}
|
|
}
|
|
|
|
private static List<double> passedTurns = new();
|
|
|
|
private void PassTurnStandalone(double beat)
|
|
{
|
|
var lastInterval = EventCaller.GetAllInGameManagerList("meatGrinder", new string[] { "StartInterval" }).FindLast(x => x.beat <= beat);
|
|
if (lastInterval != null) PassTurn(beat, lastInterval.beat, lastInterval.length);
|
|
}
|
|
|
|
private void PassTurn(double beat, double intervalBeat, float intervalLength, List<RiqEntity> allCallEvents = null)
|
|
{
|
|
if (allCallEvents == null)
|
|
{
|
|
allCallEvents = GetRelevantMeatCallsBetweenBeat(intervalBeat, intervalBeat + intervalLength);
|
|
allCallEvents.Sort((x, y) => x.beat.CompareTo(y.beat));
|
|
}
|
|
List<BeatAction.Action> meatCalls = new();
|
|
for (int i = 0; i < allCallEvents.Count; i++)
|
|
{
|
|
double relativeBeat = allCallEvents[i].beat - intervalBeat;
|
|
var tackReaction = new Reaction(allCallEvents[i]["tackReaction"], allCallEvents[i]["tackReactionBeats"]);
|
|
var bossReaction = new Reaction(allCallEvents[i]["bossReaction"], allCallEvents[i]["bossReactionBeats"]);
|
|
meatCalls.Add(new BeatAction.Action(beat + relativeBeat - 1, delegate
|
|
{
|
|
Meat meat = Instantiate(MeatBase, transform).GetComponent<Meat>();
|
|
meat.gameObject.SetActive(true);
|
|
meat.startBeat = beat + relativeBeat - 1;
|
|
meat.meatType = Meat.MeatType.LightMeat;
|
|
meat.tackReaction = tackReaction;
|
|
meat.bossReaction = bossReaction;
|
|
}));
|
|
}
|
|
BeatAction.New(this, meatCalls);
|
|
}
|
|
}
|
|
} |