3002e48350
* add mouse controller * support different control styles in options deprecate old input check methods * fully functional input actions system * btsds InputAction * blue bear InputAction * more games fix bugs with some input related systems * coin toss re-toss * cheer readers touch * dog ninja touch * multiple games * last of the easy games' touch * more specialized games * specialized games 2 * finish ktb games * remove legacy settings disclaimer * "only" two games left * karate man touch * rockers touch still needs fixes and bad judge strum * DSGuy flicking animation * playstyle chart property * improve performance of minigame preloading * improve look of cursor make assetbundles use chunk-based compression refactor assetbundle loading methods a bit * prime conductor stream playback to stabilize seeking operations * fix air rally swing on pad release * use virtual mouse pointer * add UniTask * make BeatAction use UniTask * implement UniTask to replace some coroutines * add touch style UI elements and effects games now support the ability to define two cursor colours if they need split screen touch inputs * update plugins and buildscript * implement thresholded pointer position clipping * fix clamping * instant show / hide fix discord game SDK crashes
466 lines
17 KiB
C#
466 lines
17 KiB
C#
using DG.Tweening;
|
|
using NaughtyBezierCurves;
|
|
using HeavenStudio.Util;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using Jukebox;
|
|
|
|
namespace HeavenStudio.Games.Loaders
|
|
{
|
|
using static Minigames;
|
|
public static class NtrCropLoader
|
|
{
|
|
public static Minigame AddGame(EventCaller eventCaller) {
|
|
return new Minigame("cropStomp", "Crop Stomp", "c0f0b8", false, false, new List<GameAction>()
|
|
{
|
|
new GameAction("start marching", "Start Marching")
|
|
{
|
|
function = delegate { CropStomp.instance.StartMarching(eventCaller.currentEntity.beat); },
|
|
defaultLength = 2f,
|
|
inactiveFunction = delegate { CropStomp.MarchInactive(eventCaller.currentEntity.beat); }
|
|
},
|
|
new GameAction("veggies", "Veggies")
|
|
{
|
|
defaultLength = 4f,
|
|
resizable = true
|
|
},
|
|
new GameAction("mole", "Mole")
|
|
{
|
|
preFunction = delegate
|
|
{
|
|
if (eventCaller.currentEntity["mute"]) return;
|
|
CropStomp.MoleSound(eventCaller.currentEntity.beat);
|
|
},
|
|
defaultLength = 2f,
|
|
parameters = new List<Param>()
|
|
{
|
|
new Param("mute", false, "Mute", "Should the mole laugh sound be muted?")
|
|
},
|
|
preFunctionLength = 6
|
|
},
|
|
new GameAction("end", "End")
|
|
{
|
|
parameters = new List<Param>()
|
|
{
|
|
new Param("mute", true, "Mute Humming?")
|
|
}
|
|
},
|
|
new GameAction("plantCollect", "Veggie Collection Values")
|
|
{
|
|
function = delegate { var e = eventCaller.currentEntity;
|
|
CropStomp.instance.SetCollectThresholds(e["threshold"], e["limit"], e["force"], e["forceAmount"]); },
|
|
defaultLength = 0.5f,
|
|
parameters = new List<Param>()
|
|
{
|
|
new Param("threshold", new EntityTypes.Integer(1, 80, 8), "Threshold", "For each time the threshold is met a new plant will appear in the veggie bag."),
|
|
new Param("limit", new EntityTypes.Integer(1, 1000, 80), "Limit", "What is the limit for plants collected?"),
|
|
new Param("force", false, "Force Amount of Collected Plants", "", new List<Param.CollapseParam>()
|
|
{
|
|
new Param.CollapseParam((x, _) => (bool)x, new string[] { "forceAmount" })
|
|
}),
|
|
new Param("forceAmount", new EntityTypes.Integer(0, 1000, 0), "Force Amount")
|
|
}
|
|
}
|
|
},
|
|
new List<string>() {"ntr", "keep"},
|
|
"ntrstomp", "en",
|
|
new List<string>() {}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace HeavenStudio.Games
|
|
{
|
|
using Scripts_CropStomp;
|
|
|
|
public class CropStomp : Minigame
|
|
{
|
|
const float stepDistance = 2.115f;
|
|
//public static float[] moleSoundOffsets = new float[]{ 0.134f, 0.05f, 0.061f };
|
|
|
|
float scrollRate => stepDistance / (Conductor.instance.pitchedSecPerBeat * 2f);
|
|
float grassWidth;
|
|
float dotsWidth = 19.2f;
|
|
|
|
private double newBeat = -1f; // So that marching can happen on beat 0.
|
|
private double marchStartBeat = -1f;
|
|
private double marchEndBeat = double.MaxValue;
|
|
private bool willNotHum = true;
|
|
private double marchOffset;
|
|
private int currentMarchBeat;
|
|
private int stepCount;
|
|
private bool isStepping;
|
|
|
|
private static double inactiveStart = -1f;
|
|
|
|
public bool isMarching => marchStartBeat != -1f;
|
|
|
|
[NonSerialized] public bool isFlicking;
|
|
|
|
public GameObject baseVeggie;
|
|
public GameObject baseMole;
|
|
public Animator legsAnim;
|
|
public Animator bodyAnim;
|
|
public Transform farmerTrans;
|
|
public SpriteRenderer grass;
|
|
public Transform grassTrans;
|
|
public Transform dotsTrans;
|
|
public Transform scrollingHolder;
|
|
public Transform veggieHolder;
|
|
public Farmer farmer;
|
|
public BezierCurve3D pickCurve;
|
|
public BezierCurve3D moleCurve;
|
|
|
|
private Tween shakeTween;
|
|
|
|
public ParticleSystem hitParticle;
|
|
|
|
public static CropStomp instance;
|
|
|
|
public static PlayerInput.InputAction InputAction_Flick =
|
|
new("NtrStompFlick", new int[] { IAEmptyCat, IAFlickCat, IAEmptyCat },
|
|
IA_Empty, IA_TouchFlick, IA_Empty);
|
|
|
|
private void Awake()
|
|
{
|
|
instance = this;// Finding grass sprite width for grass scrolling.
|
|
farmer.Init();
|
|
var grassSprite = grass.sprite;
|
|
var borderLeft = grassSprite.rect.xMin + grassSprite.border.x;
|
|
var borderRight = grassSprite.rect.xMax - grassSprite.border.z;
|
|
var borderWidthPixels = borderRight - borderLeft;
|
|
grassWidth = borderWidthPixels / grassSprite.pixelsPerUnit;
|
|
|
|
legsAnim.Play("LiftFront", 0, 1); // Start with leg up.
|
|
|
|
// Initialize vegetables.
|
|
var cond = Conductor.instance;
|
|
var entities = GameManager.instance.Beatmap.Entities;
|
|
|
|
double startBeat = cond.songPositionInBeatsAsDouble;
|
|
double endBeat = double.MaxValue;
|
|
|
|
if (inactiveStart == -1f)
|
|
{
|
|
// Find the beat of the closest "start marching" event.
|
|
var marchStarts = entities.FindAll(m => m.datamodel == "cropStomp/start marching");
|
|
for (int i = 0; i < marchStarts.Count; i++)
|
|
{
|
|
var sampleBeat = marchStarts[i].beat;
|
|
if (cond.songPositionInBeatsAsDouble <= sampleBeat + 0.25f) // 0.25-beat buffer in case the start marching event is directly next to the game switch event.
|
|
{
|
|
startBeat = sampleBeat;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Find the beat of the next step, assuming marching started at inactiveStart.
|
|
int stepsPassed = 0;
|
|
|
|
while (inactiveStart + (stepsPassed * 2f) < cond.songPositionInBeatsAsDouble)
|
|
{
|
|
stepsPassed++;
|
|
|
|
if (stepsPassed > 1000)
|
|
{
|
|
Debug.Log("Loop broke!");
|
|
return;
|
|
}
|
|
}
|
|
|
|
startBeat = inactiveStart + (stepsPassed * 2f);
|
|
|
|
// Cue the marching proper to begin when applicable.
|
|
BeatAction.New(this, new List<BeatAction.Action>()
|
|
{
|
|
new BeatAction.Action(startBeat - 0.25f, delegate { StartMarching(startBeat); })
|
|
});
|
|
|
|
inactiveStart = -1f;
|
|
}
|
|
|
|
// find out when the next game switch (or remix end) happens
|
|
var allEnds = EventCaller.GetAllInGameManagerList("gameManager", new string[] { "switchGame", "end" });
|
|
if (allEnds.Count == 0)
|
|
{
|
|
endBeat = double.MaxValue;
|
|
}
|
|
else
|
|
{
|
|
allEnds.Sort((x, y) => x.beat.CompareTo(y.beat));
|
|
|
|
//get the beat of the closest end event
|
|
foreach (var end in allEnds)
|
|
{
|
|
if (end.datamodel != "gameManager/end" && end.datamodel.Split(2) == "cropStomp") continue;
|
|
if (end.beat > startBeat)
|
|
{
|
|
endBeat = end.beat;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Veggie and mole events.
|
|
var vegEvents = entities.FindAll(v => v.datamodel == "cropStomp/veggies");
|
|
var moleEvents = entities.FindAll(m => m.datamodel == "cropStomp/mole");
|
|
|
|
// Spawn veggies.
|
|
for (int i = 0; i < vegEvents.Count; i++)
|
|
{
|
|
var vegBeat = vegEvents[i].beat;
|
|
var vegLength = vegEvents[i].length;
|
|
|
|
// Only consider veggie events that aren't past the start point.
|
|
if (startBeat <= vegBeat + vegLength)
|
|
{
|
|
int veggiesInEvent = Mathf.CeilToInt(vegLength + 1) / 2;
|
|
|
|
for (int b = 0; b < veggiesInEvent; b++)
|
|
{
|
|
var targetVeggieBeat = vegBeat + 2f * b;
|
|
if (startBeat <= targetVeggieBeat && targetVeggieBeat < endBeat)
|
|
{
|
|
SpawnVeggie(targetVeggieBeat, startBeat, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Spawn moles.
|
|
for (int i = 0; i < moleEvents.Count; i++)
|
|
{
|
|
var moleBeat = moleEvents[i].beat;
|
|
|
|
if (startBeat <= moleBeat && moleBeat < endBeat)
|
|
{
|
|
SpawnVeggie(moleBeat, startBeat, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
List<RiqEntity> cuedMoleSounds = new List<RiqEntity>();
|
|
|
|
public override void OnGameSwitch(double beat)
|
|
{
|
|
SetInitTresholds(beat);
|
|
SetMarchEndBeat(beat);
|
|
}
|
|
|
|
public override void OnPlay(double beat)
|
|
{
|
|
SetInitTresholds(beat);
|
|
SetMarchEndBeat(beat);
|
|
}
|
|
|
|
private void SetMarchEndBeat(double beat)
|
|
{
|
|
double nextEndBeat = double.MaxValue;
|
|
var nextEnd = EventCaller.GetAllInGameManagerList("gameManager", new string[] { "switchGame", "end" }).Find(e => e.beat > beat);
|
|
if (nextEnd != null) nextEndBeat = nextEnd.beat;
|
|
|
|
var allEnds = EventCaller.GetAllInGameManagerList("cropStomp", new string[] { "end" });
|
|
var tempEnds = allEnds.FindAll(x => x.beat >= beat && x.beat < nextEndBeat);
|
|
if (tempEnds.Count == 0) return;
|
|
|
|
marchEndBeat = tempEnds[0].beat;
|
|
willNotHum = tempEnds[0]["mute"];
|
|
}
|
|
|
|
public static void MoleSound(double beat)
|
|
{
|
|
MultiSound.Play(new MultiSound.Sound[]
|
|
{
|
|
new MultiSound.Sound("cropStomp/moleNyeh", beat - 2, 1, 1, false, 0.134),
|
|
new MultiSound.Sound("cropStomp/moleHeh1", beat - 1.5, 1, 1, false, 0.05),
|
|
new MultiSound.Sound("cropStomp/moleHeh2", beat - 1, 1, 1, false, 0.061)
|
|
}, forcePlay: true);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
var cond = Conductor.instance;
|
|
|
|
if (!cond.isPlaying)
|
|
return;
|
|
|
|
if (!isMarching)
|
|
return;
|
|
// Debug.Log(newBeat);
|
|
|
|
bool cameraLocked = cond.songPositionInBeats >= marchEndBeat;
|
|
bool isHumming = !(cameraLocked && willNotHum);
|
|
|
|
if (cond.ReportBeat(ref newBeat, marchOffset, true))
|
|
{
|
|
currentMarchBeat += 1;
|
|
|
|
PlayAnims();
|
|
if (currentMarchBeat % 2 != 0 && isHumming) //step sound
|
|
{
|
|
MultiSound.Play(new MultiSound.Sound[] {new MultiSound.Sound("cropStomp/hmm", newBeat + marchOffset)});
|
|
}
|
|
}
|
|
|
|
if (PlayerInput.GetIsAction(InputAction_BasicRelease) && !IsExpectingInputNow(InputAction_BasicRelease))
|
|
{
|
|
bodyAnim.Play("Raise");
|
|
}
|
|
if (PlayerInput.GetIsAction(InputAction_Flick) && !IsExpectingInputNow(InputAction_FlickRelease))
|
|
{
|
|
bodyAnim.Play("Pick");
|
|
}
|
|
|
|
if (cameraLocked) return;
|
|
|
|
// Object scroll.
|
|
var scrollPos = scrollingHolder.localPosition;
|
|
var newScrollX = scrollPos.x + (scrollRate * Time.deltaTime);
|
|
scrollingHolder.localPosition = new Vector3(newScrollX, scrollPos.y, scrollPos.z);
|
|
|
|
// Grass scroll.
|
|
var grassPos = grassTrans.localPosition;
|
|
|
|
var newGrassX = grassPos.x + (scrollRate * Time.deltaTime);
|
|
newGrassX = (newGrassX % (grassWidth * 4.5f));
|
|
|
|
grassTrans.localPosition = new Vector3(newGrassX, grassPos.y, grassPos.z);
|
|
|
|
// Dots scroll
|
|
var dotsPos = dotsTrans.localPosition;
|
|
|
|
var newDotsX = dotsPos.x + (scrollRate * Time.deltaTime);
|
|
newDotsX = (newDotsX % dotsWidth);
|
|
|
|
dotsTrans.localPosition = new Vector3(newDotsX, dotsPos.y, dotsPos.z);
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
if (!isMarching)
|
|
return;
|
|
|
|
isFlicking = false;
|
|
}
|
|
|
|
public void SetCollectThresholds(int thresholdEvolve, int limit, bool force, int forceAmount)
|
|
{
|
|
farmer.plantThreshold = thresholdEvolve;
|
|
farmer.plantLimit = limit;
|
|
if (force) Farmer.collectedPlants = forceAmount;
|
|
farmer.UpdatePlants();
|
|
}
|
|
|
|
private void SetInitTresholds(double beat)
|
|
{
|
|
var allCollects = EventCaller.GetAllInGameManagerList("cropStomp", new string[] { "plantCollect" });
|
|
if (allCollects.Count == 0) return;
|
|
|
|
var tempCollect = allCollects.FindLast(x => x.beat < beat);
|
|
if (tempCollect == null) return;
|
|
SetCollectThresholds(tempCollect["threshold"], tempCollect["limit"], tempCollect["force"], tempCollect["forceAmount"]);
|
|
}
|
|
|
|
public void CollectPlant(int veggieType)
|
|
{
|
|
farmer.CollectPlant(veggieType);
|
|
}
|
|
|
|
private void PlayAnims()
|
|
{
|
|
// Step.
|
|
if (currentMarchBeat % 2 != 0)
|
|
{
|
|
// Don't step if already stomped.
|
|
if (!isStepping)
|
|
{
|
|
stepCount += 1;
|
|
var stepAnim = (stepCount % 2 != 0 ? "StepFront" : "StepBack");
|
|
|
|
legsAnim.Play(stepAnim, 0, 0);
|
|
|
|
isStepping = true;
|
|
}
|
|
|
|
}
|
|
// Lift.
|
|
else
|
|
{
|
|
var liftAnim = (stepCount % 2 != 0 ? "LiftBack" : "LiftFront");
|
|
legsAnim.Play(liftAnim, 0, 0);
|
|
|
|
var farmerPos = farmerTrans.localPosition;
|
|
farmerTrans.localPosition = new Vector3(farmerPos.x - stepDistance, farmerPos.y, farmerPos.z);
|
|
|
|
isStepping = false;
|
|
}
|
|
}
|
|
|
|
public void StartMarching(double beat)
|
|
{
|
|
marchStartBeat = beat;
|
|
marchOffset = marchStartBeat % 1;
|
|
currentMarchBeat = 0;
|
|
stepCount = 0;
|
|
|
|
farmer.nextStompBeat = beat;
|
|
}
|
|
|
|
public void Stomp()
|
|
{
|
|
// Don't increment step counter if autostep stepped already.
|
|
if (!isStepping)
|
|
stepCount += 1;
|
|
|
|
var stompAnim = (stepCount % 2 != 0 ? "StompFront" : "StompBack");
|
|
|
|
legsAnim.Play(stompAnim, 0, 0);
|
|
|
|
SoundByte.PlayOneShotGame("cropStomp/stomp");
|
|
|
|
if (shakeTween != null)
|
|
shakeTween.Kill(true);
|
|
|
|
DOTween.Punch(() => GameCamera.additionalPosition, x => GameCamera.additionalPosition = x, new Vector3(0, 0.75f, 0),
|
|
Conductor.instance.pitchedSecPerBeat*0.5f, 18, 1f);
|
|
|
|
isStepping = true;
|
|
}
|
|
|
|
private void SpawnVeggie(double beat, double startBeat, bool isMole)
|
|
{
|
|
var newVeggie = GameObject.Instantiate(isMole ? baseMole : baseVeggie, veggieHolder).GetComponent<Veggie>();
|
|
|
|
newVeggie.targetBeat = beat;
|
|
|
|
var veggieX = (beat - startBeat) * -stepDistance / 2f;
|
|
newVeggie.transform.localPosition = new Vector3((float)veggieX, 0f, 0f);
|
|
newVeggie.Init();
|
|
newVeggie.gameObject.SetActive(true);
|
|
}
|
|
|
|
public static void MarchInactive(double beat)
|
|
{
|
|
if (GameManager.instance.currentGame == "cropStomp") //this function is only meant for making march sounds while the game is inactive
|
|
{
|
|
return;
|
|
}
|
|
inactiveStart = beat;
|
|
RiqEntity gameSwitch = GameManager.instance.Beatmap.Entities.Find(c => c.beat >= beat && c.datamodel == "gameManager/switchGame/cropStomp");
|
|
if (gameSwitch == null)
|
|
return;
|
|
int length = (int)Math.Ceiling((gameSwitch.beat - beat)/2);
|
|
MultiSound.Sound[] sounds = new MultiSound.Sound[length];
|
|
for(int i = 0; i < length; i++)
|
|
{
|
|
sounds[i] = new MultiSound.Sound("cropStomp/hmm", beat + i*2);
|
|
}
|
|
MultiSound.Play(sounds, forcePlay:true);
|
|
}
|
|
}
|
|
}
|