mirror of
https://github.com/RHeavenStudio/HeavenStudio.git
synced 2025-06-12 10:37:37 +02:00
Meat Grinder Rework (#598)
* meat grinder has been entirely reworked * update MeatGrinder.cs, replace assets background is still temporary but this is all im doing til ev can help * Boss New Anims * New Tack Animations * edited anims * new bg * working out merge conflics (cries) * push for anims stuff why r unity animations so suck * Cart guy cart guy cart guy * cart guy :) * explodes 👼 * curves for meat trajectory * typo * cart guy is awesome. and tack reaction. 🐱 🐱 🐱 🐱 🐱 * I love unity animation * everything but the boss call cancel fix * bacon anim (+ remove interp on other meat anims) * lastbeatpulse + gears are the only thing left i was GONNA add both the beat based and constant gear animations but it just doesn't work out the way i want it to. OHHHH WELLLLL im gonna use straight up transforms anyways so i wont have to deal with unity animation (🤢) * truly, and dearly, finished? NOPE! UNITY ANIMATION BUG :D * mwuhahahaha disable ass buns * meatsplash start, fix riqentity find bug * fixes + particle improvements all that's left is the phone bop and arc/particle adjustments, im pretty sure * particle + arc adjustment, fix weird anim play on gameswitch * fixed phonebop oops i was playing the idle animation in update... * make particles beat based just waiting on mine to fix the offset bug * fix trajectory jank --------- Co-authored-by: Rapandrasmus <78219215+Rapandrasmus@users.noreply.github.com> Co-authored-by: Seanski2 <seanbenedit@gmail.com> Co-authored-by: ev <85412919+evdial@users.noreply.github.com> Co-authored-by: minenice55 <star.elementa@gmail.com>
This commit is contained in:
@ -644,6 +644,16 @@ namespace HeavenStudio
|
||||
private void LateUpdate()
|
||||
{
|
||||
OverlaysManager.instance.TogleOverlaysVisibility(Editor.Editor.instance == null || Editor.Editor.instance.fullscreen || ((PersistentDataManager.gameSettings.overlaysInEditor) && (!Editor.Editor.instance.fullscreen)) || HeavenStudio.Editor.GameSettings.InPreview);
|
||||
|
||||
if (!Conductor.instance.isPlaying)
|
||||
return;
|
||||
|
||||
if (Conductor.instance.songPositionInBeatsAsDouble >= Math.Ceiling(_playStartBeat) + _latePulseTally)
|
||||
{
|
||||
if (_currentMinigame != null) _currentMinigame.OnLateBeatPulse(Math.Ceiling(_playStartBeat) + _latePulseTally);
|
||||
onBeatPulse?.Invoke(Math.Ceiling(_playStartBeat) + _latePulseTally);
|
||||
_latePulseTally++;
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleInputs(bool inputs)
|
||||
@ -655,6 +665,7 @@ namespace HeavenStudio
|
||||
|
||||
private double _playStartBeat = 0;
|
||||
private int _pulseTally = 0;
|
||||
private int _latePulseTally = 0;
|
||||
|
||||
public void Play(double beat, float delay = 0f)
|
||||
{
|
||||
@ -662,6 +673,7 @@ namespace HeavenStudio
|
||||
Debug.Log("Playing at " + beat);
|
||||
_playStartBeat = beat;
|
||||
_pulseTally = 0;
|
||||
_latePulseTally = 0;
|
||||
canInput = true;
|
||||
if (!paused)
|
||||
{
|
||||
|
223
Assets/Scripts/Games/MeatGrinder/Meat.cs
Normal file
223
Assets/Scripts/Games/MeatGrinder/Meat.cs
Normal file
@ -0,0 +1,223 @@
|
||||
using HeavenStudio.Util;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace HeavenStudio.Games.Scripts_MeatGrinder
|
||||
{
|
||||
public class Meat : MonoBehaviour
|
||||
{
|
||||
[SerializeField] Transform startPosition;
|
||||
[SerializeField] Transform startPositionAlt;
|
||||
[SerializeField] Transform hitPosition;
|
||||
[SerializeField] Transform missPosition;
|
||||
[SerializeField] float meatFlyHeight = 1f;
|
||||
[SerializeField] float meatFlyHeightAlt = 1f;
|
||||
[SerializeField] bool showAltCurve;
|
||||
|
||||
[NonSerialized] public double startBeat;
|
||||
[NonSerialized] public MeatType meatType;
|
||||
[NonSerialized] public MeatGrinder.Reaction tackReaction;
|
||||
[NonSerialized] public MeatGrinder.Reaction bossReaction;
|
||||
|
||||
private bool isHit = false;
|
||||
|
||||
// const float meatStart = 0;
|
||||
// const float meatEnd = 3.43f;
|
||||
|
||||
[Header("Animators")]
|
||||
private Animator anim;
|
||||
private SpriteRenderer sr;
|
||||
[SerializeField] private Sprite[] meats;
|
||||
|
||||
public enum MeatType
|
||||
{
|
||||
DarkMeat,
|
||||
LightMeat,
|
||||
BaconBall,
|
||||
}
|
||||
|
||||
private MeatGrinder game;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
game = MeatGrinder.instance;
|
||||
anim = GetComponent<Animator>();
|
||||
sr = GetComponent<SpriteRenderer>();
|
||||
// anim.writeDefaultValuesOnDisable = false;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
sr.sprite = meats[(int)meatType];
|
||||
|
||||
game.ScheduleInput(startBeat, 1, MeatGrinder.InputAction_Press, Hit, Miss, Nothing);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
Conductor cond = Conductor.instance;
|
||||
if (!isHit)
|
||||
{
|
||||
double startTime = cond.GetSongPosFromBeat(startBeat);
|
||||
double hitTime = cond.GetSongPosFromBeat(startBeat + 1);
|
||||
double missTime = hitTime + MeatGrinder.ngLateTime;
|
||||
double currentTime = cond.songPositionAsDouble;
|
||||
Vector3 lastPos = transform.position;
|
||||
Vector3 startPos = meatType == MeatType.LightMeat ? startPositionAlt.position : startPosition.position;
|
||||
|
||||
float hitAlongMissRatio = Vector3.Dot((startPos - missPosition.position), (startPos - hitPosition.position));
|
||||
hitAlongMissRatio /= Vector3.Dot((startPos - missPosition.position), (startPos - missPosition.position));
|
||||
Vector3 hitOnMissPos = startPos + ((missPosition.position - startPos) * hitAlongMissRatio);
|
||||
|
||||
float prog;
|
||||
|
||||
if (currentTime >= hitTime)
|
||||
{
|
||||
prog = (float)((currentTime - hitTime) / (missTime - hitTime));
|
||||
transform.position = Vector3.Lerp(hitOnMissPos, missPosition.position, prog);
|
||||
|
||||
prog = (prog * (1 - hitAlongMissRatio)) + hitAlongMissRatio;
|
||||
}
|
||||
else
|
||||
{
|
||||
prog = (float)((currentTime - startTime) / (hitTime - startTime));
|
||||
transform.position = Vector3.Lerp(startPos, hitOnMissPos, prog);
|
||||
|
||||
prog *= hitAlongMissRatio;
|
||||
}
|
||||
float yMul = prog * 2f - 1f;
|
||||
float yWeight = -(yMul * yMul) + 1f;
|
||||
transform.position += (meatType == MeatType.LightMeat ? meatFlyHeightAlt : meatFlyHeight) * yWeight * Vector3.up;
|
||||
// point towards the next position
|
||||
if (cond.isPlaying)
|
||||
{
|
||||
transform.right = transform.position - lastPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Hit(PlayerActionEvent caller, float state)
|
||||
{
|
||||
anim.enabled = true;
|
||||
isHit = true;
|
||||
game.TackAnim.SetBool("tackMeated", false);
|
||||
anim.DoScaledAnimationAsync(meatType.ToString() + "Hit", 0.5f);
|
||||
|
||||
bool isBarely = state is >= 1f or <= -1f;
|
||||
|
||||
game.bossAnnoyed = isBarely;
|
||||
SoundByte.PlayOneShotGame("meatGrinder/" + (isBarely ? "tink" : "meatHit"));
|
||||
game.TackAnim.DoScaledAnimationAsync("TackHit" + (isBarely ? "Barely" : "Success"), 0.5f);
|
||||
|
||||
(float rangeStart, float rangeEnd) = meatType switch { // im not good enough at math to figure out how to make this an equation
|
||||
MeatType.DarkMeat => (0, 0.333f),
|
||||
MeatType.LightMeat => (0.334f, 0.666f),
|
||||
MeatType.BaconBall or _ => (0.667f, 1),
|
||||
};
|
||||
var sheetAnim = game.MeatSplash.textureSheetAnimation;
|
||||
var main = game.MeatSplash.main;
|
||||
sheetAnim.frameOverTime = new ParticleSystem.MinMaxCurve(rangeStart, rangeEnd);
|
||||
main.simulationSpeed = 0.5f / Conductor.instance.pitchedSecPerBeat;
|
||||
|
||||
// has a probability of zero normally so it's not played with Play() but this exposes it to the editor
|
||||
var emission = game.MeatSplash.emission;
|
||||
game.MeatSplash.Emit(new ParticleSystem.EmitParams(), (int)emission.GetBurst(0).count.constant);
|
||||
|
||||
if (tackReaction.expression > 0) {
|
||||
BeatAction.New(game, new List<BeatAction.Action>() {
|
||||
new(startBeat + tackReaction.beat + 1, delegate { game.DoExpressions(tackReaction.expression); }),
|
||||
});
|
||||
}
|
||||
if (bossReaction.expression > 0) {
|
||||
BeatAction.New(game, new List<BeatAction.Action>() {
|
||||
new(startBeat + bossReaction.beat + 1, delegate { game.DoExpressions(0, bossReaction.expression); }),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void Miss(PlayerActionEvent caller)
|
||||
{
|
||||
anim.enabled = true;
|
||||
game.bossAnnoyed = true;
|
||||
SoundByte.PlayOneShotGame("meatGrinder/miss");
|
||||
|
||||
game.TackAnim.DoScaledAnimationAsync("TackMiss" + meatType.ToString(), 0.5f);
|
||||
game.TackAnim.SetBool("tackMeated", true);
|
||||
game.BossAnim.DoScaledAnimationAsync("BossMiss", 0.5f);
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
private void Nothing(PlayerActionEvent caller) { }
|
||||
|
||||
public void SetSprite()
|
||||
{
|
||||
sr.sprite = meats[(int)meatType];
|
||||
}
|
||||
|
||||
public void DestroySelf()
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback to draw gizmos that are pickable and always drawn.
|
||||
/// </summary>
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
// draw a line showing the direction from the miss position to the hit position
|
||||
if (startPosition == null) return;
|
||||
if (startPositionAlt == null) return;
|
||||
|
||||
Vector3 startPos = showAltCurve ? startPositionAlt.position : startPosition.position;
|
||||
if (hitPosition != null && missPosition != null && startPos != null)
|
||||
{
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawLine(startPos, hitPosition.position);
|
||||
Gizmos.color = Color.blue;
|
||||
Gizmos.DrawLine(startPos, missPosition.position);
|
||||
Gizmos.color = Color.green;
|
||||
Vector3 direction = (hitPosition.position - missPosition.position).normalized;
|
||||
Gizmos.DrawRay(missPosition.position, direction * 10);
|
||||
|
||||
// project start position -> hit position onto start position -> miss position
|
||||
float hitAlongMissRatio = Vector3.Dot((startPos - missPosition.position), (startPos - hitPosition.position));
|
||||
hitAlongMissRatio /= Vector3.Dot((startPos - missPosition.position), (startPos - missPosition.position));
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawRay(startPos, (missPosition.position - startPos) * hitAlongMissRatio);
|
||||
|
||||
DrawCurveGizmo(startPos, missPosition.position, Vector3.up * (showAltCurve ? meatFlyHeightAlt : meatFlyHeight), Color.yellow, 0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawCurveGizmo(Vector3 start, Vector3 end, Vector3 height, Color colour, float interval)
|
||||
{
|
||||
Gizmos.color = colour;
|
||||
Vector3 lastPos = start;
|
||||
Vector3 pos;
|
||||
float t, yMul, yWeight;
|
||||
for (float i = 0; i < 1; i += interval)
|
||||
{
|
||||
t = i;
|
||||
pos = Vector3.Lerp(start, end, t);
|
||||
|
||||
yMul = t * 2f - 1f;
|
||||
yWeight = -(yMul * yMul) + 1f;
|
||||
|
||||
pos += height * yWeight;
|
||||
Gizmos.DrawLine(lastPos, pos);
|
||||
lastPos = pos;
|
||||
}
|
||||
t = 1f;
|
||||
pos = end;
|
||||
|
||||
yMul = t * 2f - 1f;
|
||||
yWeight = -(yMul * yMul) + 1f;
|
||||
|
||||
pos += height * yWeight;
|
||||
Gizmos.DrawLine(lastPos, pos);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,51 +3,28 @@ using HeavenStudio.InputSystem;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
using NaughtyBezierCurves;
|
||||
using System.Linq;
|
||||
|
||||
namespace HeavenStudio.Games.Loaders
|
||||
{
|
||||
using static Minigames;
|
||||
public static class pcoMeatLoader
|
||||
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("MeatToss", "Meat Toss")
|
||||
{
|
||||
function = delegate {
|
||||
var e = eventCaller.currentEntity;
|
||||
MeatGrinder.instance.MeatToss(e.beat);
|
||||
},
|
||||
defaultLength = 2f,
|
||||
priority = 2,
|
||||
},
|
||||
new GameAction("MeatCall", "Meat Call")
|
||||
{
|
||||
function = delegate {
|
||||
var e = eventCaller.currentEntity;
|
||||
MeatGrinder.instance.MeatCall(e.beat);
|
||||
},
|
||||
defaultLength = 0.5f,
|
||||
priority = 2,
|
||||
preFunctionLength = 1f,
|
||||
preFunction = delegate {
|
||||
var e = eventCaller.currentEntity;
|
||||
MeatGrinder.PreInterval(e.beat, 4f);
|
||||
},
|
||||
},
|
||||
new GameAction("StartInterval", "Start Interval")
|
||||
{
|
||||
defaultLength = 4f,
|
||||
resizable = true,
|
||||
priority = 5,
|
||||
preFunction = delegate {
|
||||
var e = eventCaller.currentEntity;
|
||||
MeatGrinder.PreInterval(e.beat, e.length);
|
||||
},
|
||||
},
|
||||
new GameAction("bop", "Bop")
|
||||
{
|
||||
function = delegate {
|
||||
@ -62,10 +39,95 @@ namespace HeavenStudio.Games.Loaders
|
||||
resizable = true,
|
||||
priority = 1,
|
||||
},
|
||||
},
|
||||
new List<string>() { "pco", "normal", "repeat" },
|
||||
"pcomeat", "en",
|
||||
new List<string>() { }
|
||||
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", "Throw a bacon ball instead of the typical 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")
|
||||
},
|
||||
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 Expression", "The expression Tack will display"),
|
||||
new Param("bossExpression", MeatGrinder.BossExpressions.None, "Boss Expression", "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", "Put a spider in the box?"),
|
||||
new Param("direction", MeatGrinder.CartGuyDirection.Right, "Direction", "The direction the cart will be carted to."),
|
||||
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease", "What ease will the cart use?"),
|
||||
}
|
||||
},
|
||||
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", "How fast will the gears go?"),
|
||||
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease", "What ease will the gears speed up/slow down with?"),
|
||||
}
|
||||
},
|
||||
}
|
||||
// ,
|
||||
// new List<string>() { "pco", "normal", "repeat" },
|
||||
// "pcomeat", "en",
|
||||
// new List<string>() { }
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -73,37 +135,82 @@ namespace HeavenStudio.Games.Loaders
|
||||
|
||||
namespace HeavenStudio.Games
|
||||
{
|
||||
using Jukebox;
|
||||
using Scripts_MeatGrinder;
|
||||
|
||||
public class MeatGrinder : Minigame
|
||||
{
|
||||
static List<double> queuedInputs = new();
|
||||
static List<QueuedInterval> queuedIntervals = new List<QueuedInterval>();
|
||||
struct QueuedInterval
|
||||
private static List<QueuedInterval> queuedIntervals = new();
|
||||
|
||||
private struct QueuedInterval
|
||||
{
|
||||
public double beat;
|
||||
public double length;
|
||||
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")]
|
||||
bool intervalStarted;
|
||||
double intervalStartBeat;
|
||||
public double beatInterval = 4f;
|
||||
private bool bossBop = true;
|
||||
public bool bossAnnoyed = false;
|
||||
const string sfxName = "meatGrinder/";
|
||||
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 MeatType
|
||||
public enum TackExpressions
|
||||
{
|
||||
Dark,
|
||||
Light,
|
||||
None,
|
||||
Content,
|
||||
Smug,
|
||||
Wonder,
|
||||
}
|
||||
|
||||
public enum BossExpressions
|
||||
{
|
||||
None,
|
||||
Eyebrow,
|
||||
Scared,
|
||||
}
|
||||
|
||||
public enum CartGuyDirection
|
||||
{
|
||||
Right,
|
||||
Left,
|
||||
}
|
||||
|
||||
protected static bool IA_PadAny(out double dt)
|
||||
@ -116,147 +223,292 @@ namespace HeavenStudio.Games
|
||||
}
|
||||
|
||||
public static PlayerInput.InputAction InputAction_Press =
|
||||
new("PcoMeatPress", new int[] { IAPressCat, IAFlickCat, IAPressCat },
|
||||
new("PcoMeatPress", new int[] { IAPressCat, IAPressCat, IAPressCat },
|
||||
IA_PadAny, IA_TouchBasicPress, IA_BatonBasicPress);
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
instance = this;
|
||||
SetupBopRegion("meatGrinder", "bop", "bossBop");
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (!Conductor.instance.isPlaying || Conductor.instance.isPaused)
|
||||
{
|
||||
if (queuedInputs.Count > 0) queuedInputs.Clear();
|
||||
if (queuedIntervals.Count > 0) queuedIntervals.Clear();
|
||||
intervalStarted = false;
|
||||
beatInterval = 4f;
|
||||
}
|
||||
foreach (var evt in scheduledInputs) evt.Disable();
|
||||
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(sfxName + "whiff");
|
||||
SoundByte.PlayOneShotGame("meatGrinder/whiff");
|
||||
bossAnnoyed = false;
|
||||
}
|
||||
|
||||
if (bossAnnoyed) BossAnim.SetBool("bossAnnoyed", true);
|
||||
|
||||
if (queuedIntervals.Count > 0)
|
||||
if (passedTurns.Count > 0)
|
||||
{
|
||||
foreach (var interval in queuedIntervals) StartInterval(interval.beat, interval.length);
|
||||
queuedIntervals.Clear();
|
||||
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) cartEase.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 OnBeatPulse(double beat)
|
||||
public override void OnLateBeatPulse(double beat)
|
||||
{
|
||||
if (!BossAnim.IsPlayingAnimationNames("BossCall")
|
||||
&& !BossAnim.IsPlayingAnimationNames("BossSignal")
|
||||
&& BeatIsInBopRegion(beat))
|
||||
if (!BossAnim.IsPlayingAnimationNames("BossCall", "BossSignal", "BossScared") && BeatIsInBopRegion(beat))
|
||||
{
|
||||
BossAnim.DoScaledAnimationAsync(bossAnnoyed ? "BossMiss" : "Bop", 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
public void Bop(double beat, double length, bool doesBop, bool autoBop)
|
||||
{
|
||||
if (doesBop)
|
||||
{
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
BeatAction.New(instance, new List<BeatAction.Action>() {
|
||||
new BeatAction.Action(beat + i, delegate {
|
||||
if (!BossAnim.IsPlayingAnimationNames("BossCall") && !BossAnim.IsPlayingAnimationNames("BossSignal")) {
|
||||
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 static void PreInterval(double beat, double length)
|
||||
public override void OnGameSwitch(double beat)
|
||||
{
|
||||
if (MeatGrinder.instance.intervalStarted || MeatGrinder.queuedIntervals.Count > 0) return;
|
||||
|
||||
MeatGrinder.queuedIntervals.Add(new QueuedInterval()
|
||||
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)
|
||||
{
|
||||
cartEase = new() {
|
||||
beat = beat,
|
||||
length = length,
|
||||
});
|
||||
ease = (Util.EasingFunction.Ease)ease,
|
||||
};
|
||||
cartPhone = spider;
|
||||
cartDir = direction == 0 ? "Right" : "Left";
|
||||
if (cartPhone) {
|
||||
CartGuyAnim.Play("Phone", 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
{
|
||||
BeatAction.New(MeatGrinder.instance, new List<BeatAction.Action>() {
|
||||
new BeatAction.Action(beat - 1, delegate {
|
||||
MeatGrinder.instance.BossAnim.DoScaledAnimationAsync("BossSignal", 0.5f);
|
||||
}),
|
||||
});
|
||||
instance.StartInterval(beat, length, beat, autoPassTurn);
|
||||
}
|
||||
}
|
||||
|
||||
public void StartInterval(double beat, double length)
|
||||
{
|
||||
if (MeatGrinder.instance.intervalStarted) return;
|
||||
|
||||
intervalStartBeat = beat;
|
||||
intervalStarted = true;
|
||||
beatInterval = length;
|
||||
|
||||
BeatAction.New(this, new List<BeatAction.Action>() {
|
||||
new BeatAction.Action(beat + length - 0.33f, delegate { PassTurn(beat); }),
|
||||
});
|
||||
}
|
||||
|
||||
public void MeatToss(double beat)
|
||||
{
|
||||
SoundByte.PlayOneShotGame(sfxName + "toss");
|
||||
|
||||
MeatToss Meat = Instantiate(MeatBase, gameObject.transform).GetComponent<MeatToss>();
|
||||
Meat.startBeat = beat;
|
||||
Meat.cueLength = 1f;
|
||||
Meat.cueBased = true;
|
||||
Meat.meatType = "DarkMeat";
|
||||
}
|
||||
|
||||
public void MeatCall(double beat)
|
||||
{
|
||||
BossAnim.DoScaledAnimationAsync("BossCall", 0.5f);
|
||||
SoundByte.PlayOneShotGame(sfxName + "signal");
|
||||
|
||||
StartInterval(beat, beatInterval);
|
||||
|
||||
queuedInputs.Add(beat - intervalStartBeat);
|
||||
}
|
||||
|
||||
public void PassTurn(double beat)
|
||||
{
|
||||
intervalStarted = false;
|
||||
foreach (var input in queuedInputs)
|
||||
else
|
||||
{
|
||||
BeatAction.New(instance, new List<BeatAction.Action>()
|
||||
queuedIntervals.Add(new QueuedInterval()
|
||||
{
|
||||
new BeatAction.Action(input + beatInterval , delegate {
|
||||
MeatToss Meat = Instantiate(MeatBase, gameObject.transform).GetComponent<MeatToss>();
|
||||
Meat.startBeat = beat;
|
||||
Meat.cueLength = beatInterval + input;
|
||||
Meat.cueBased = false;
|
||||
Meat.meatType = "LightMeat";
|
||||
}),
|
||||
beat = beat,
|
||||
length = length,
|
||||
autoPassTurn = autoPassTurn
|
||||
});
|
||||
|
||||
}
|
||||
queuedInputs.Clear();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
using HeavenStudio.Util;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
using NaughtyBezierCurves;
|
||||
|
||||
namespace HeavenStudio.Games.Scripts_MeatGrinder
|
||||
{
|
||||
public class MeatToss : MonoBehaviour
|
||||
{
|
||||
public double startBeat;
|
||||
public double cueLength;
|
||||
public bool cueBased;
|
||||
public string meatType;
|
||||
|
||||
[Header("Animators")]
|
||||
private Animator anim;
|
||||
|
||||
private MeatGrinder game;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
game = MeatGrinder.instance;
|
||||
anim = GetComponent<Animator>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
game.ScheduleInput(startBeat, cueLength, MeatGrinder.InputAction_Press, Hit, Miss, Nothing);
|
||||
|
||||
BeatAction.New(this, new List<BeatAction.Action>() {
|
||||
new BeatAction.Action(cueBased ? startBeat + 0.66f : cueLength + startBeat - 1 + 0.66f, delegate {
|
||||
anim.DoScaledAnimationAsync(meatType+"Thrown", 0.32f);
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (anim.IsPlayingAnimationNames("DarkIdle") || anim.IsPlayingAnimationNames("LightIdle")) GameObject.Destroy(gameObject);
|
||||
}
|
||||
|
||||
private void InputActions(bool annoyBoss, string whichSfx, string whichAnim)
|
||||
{
|
||||
game.bossAnnoyed = annoyBoss;
|
||||
SoundByte.PlayOneShotGame("meatGrinder/"+whichSfx);
|
||||
game.TackAnim.DoScaledAnimationAsync(whichAnim, 0.5f);
|
||||
}
|
||||
|
||||
private void Hit(PlayerActionEvent caller, float state)
|
||||
{
|
||||
game.TackAnim.SetBool("tackMeated", false);
|
||||
anim.DoScaledAnimationAsync(meatType+"Hit", 0.5f);
|
||||
|
||||
if (state >= 1f || state <= -1f) {
|
||||
InputActions(true, "tink", "TackHitBarely");
|
||||
} else {
|
||||
InputActions(false, "meatHit", "TackHitSuccess");
|
||||
}
|
||||
}
|
||||
|
||||
private void Miss(PlayerActionEvent caller)
|
||||
{
|
||||
GameObject.Destroy(gameObject);
|
||||
InputActions(true, "miss", "TackMiss"+meatType);
|
||||
game.BossAnim.DoScaledAnimationAsync("BossMiss", 0.5f);
|
||||
game.TackAnim.SetBool("tackMeated", true);
|
||||
}
|
||||
|
||||
private void Nothing(PlayerActionEvent caller) { }
|
||||
}
|
||||
}
|
@ -439,6 +439,13 @@ namespace HeavenStudio.Games
|
||||
|
||||
}
|
||||
|
||||
// added because OnBeatPulse had some animation issues going on
|
||||
// if your bopping overlaps with other animations, use this instead
|
||||
public virtual void OnLateBeatPulse(double beat)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static MultiSound PlaySoundSequence(string game, string name, double startBeat, params SoundSequence.SequenceParams[] args)
|
||||
{
|
||||
Minigames.Minigame gameInfo = GameManager.instance.GetGameInfo(game);
|
||||
|
Reference in New Issue
Block a user