Gameplay: Timing windows use a "smart" BPM scaling system (#86)

* Game: Pajama Party

* Pajama Party: bg setup, Mako jump

* Pajama Party: mako jumping

* Pajama Party: prep landing anim references

* Pajama Party: several anims, improved 3d scene

* Pajama Party: bg cleanup

* Pajama Party: Mako sleeping anims

* Pajama Party: All Mako anims, add sounds to fs

* Pajama Party: prep monkey prefab

* Pajama Party: thrown pillow, prep sequences

* Pajama Party: make embarrassed catch not loop

whoops

* Pajama Party: sound adjust, prefab work

* Pajama Party: monkey spawning, basic jumping

* Pajama Party: jump sequence

no input detection or landing yet

* Pajama Party: anims override

* Pajama Party: jump cue functional

comes with improvements and bugfixes to PlayerActionEvent

* Pajama Party: sleep cue functional

* Pajama Party: some notes

* PlayerActionEvent: more bugfixes

* Pajama Party: fully functional throw cue

* Pajama Party: start animating sleep cue

* Pajama Party: feature-complete

* Pajama Party: unfuck layering

* Pajama Party: icon

also adds Fan Club's concept icon

* Pajama Party: cues while unloaded

* inverse-scale timing windows based on speed

* Fan Club: move to new input system

* Fan Club: allow forced animations during calls

* Crop Stomp: fix camera shake regression
This commit is contained in:
minenice55 2022-06-03 23:15:56 -04:00 committed by GitHub
parent 9b9bfea627
commit 057bdf7fe5
4 changed files with 156 additions and 191 deletions

View file

@ -292,9 +292,8 @@ namespace HeavenStudio.Games
if (shakeTween != null) if (shakeTween != null)
shakeTween.Kill(true); shakeTween.Kill(true);
var camTrans = GameCamera.instance.transform; DOTween.Punch(() => GameCamera.additionalPosition, x => GameCamera.additionalPosition = x, new Vector3(0, 0.75f, 0),
camTrans.localPosition = new Vector3(camTrans.localPosition.x, 0.75f, camTrans.localPosition.z); Conductor.instance.pitchedSecPerBeat*0.5f, 18, 1f);
camTrans.DOLocalMoveY(0f, 0.5f).SetEase(Ease.OutElastic, 1f);
isStepping = true; isStepping = true;
} }

View file

@ -83,7 +83,8 @@ namespace HeavenStudio.Games
BigCall, BigCall,
Squat, Squat,
Wink, Wink,
Dab Dab,
None
} }
public enum KamoneResponseType { public enum KamoneResponseType {
Through, Through,
@ -127,6 +128,7 @@ namespace HeavenStudio.Games
public GameEvent specBop = new GameEvent(); public GameEvent specBop = new GameEvent();
public GameEvent noBop = new GameEvent(); public GameEvent noBop = new GameEvent();
public GameEvent noResponse = new GameEvent(); public GameEvent noResponse = new GameEvent();
public GameEvent noCall = new GameEvent();
public GameEvent noSpecBop = new GameEvent(); public GameEvent noSpecBop = new GameEvent();
private static int performanceType = (int) IdolPerformanceType.Normal; private static int performanceType = (int) IdolPerformanceType.Normal;
@ -174,7 +176,6 @@ namespace HeavenStudio.Games
Player.player = true; Player.player = true;
} }
Spectators.Add(mobj); Spectators.Add(mobj);
fan.Init();
//prepare spawn point of next spectator //prepare spawn point of next spectator
spawnPos.x += RADIUS * 2; spawnPos.x += RADIUS * 2;
@ -313,6 +314,12 @@ namespace HeavenStudio.Games
noResponse.startBeat = beat; noResponse.startBeat = beat;
} }
private void DisableCall(float beat, float length)
{
noCall.length = length;
noCall.startBeat = beat;
}
private void DisableSpecBop(float beat, float length) private void DisableSpecBop(float beat, float length)
{ {
float bt = Conductor.instance.songPositionInBeats; float bt = Conductor.instance.songPositionInBeats;
@ -335,6 +342,7 @@ namespace HeavenStudio.Games
idolJumpStartTime = Single.MinValue; idolJumpStartTime = Single.MinValue;
DisableResponse(beat, length + 0.5f); DisableResponse(beat, length + 0.5f);
DisableBop(beat, length + 0.5f); DisableBop(beat, length + 0.5f);
DisableCall(beat, length + 0.5f);
switch (type) switch (type)
{ {
@ -388,6 +396,7 @@ namespace HeavenStudio.Games
idolAnimator.Play("IdolDab" + GetPerformanceSuffix(), -1, 0); idolAnimator.Play("IdolDab" + GetPerformanceSuffix(), -1, 0);
Jukebox.PlayOneShotGame("fanClub/arisa_dab"); Jukebox.PlayOneShotGame("fanClub/arisa_dab");
break; break;
default: break;
} }
} }
@ -429,6 +438,17 @@ namespace HeavenStudio.Games
} }
} }
private void DoIdolPeace(bool sync = true)
{
if (!(Conductor.instance.songPositionInBeats >= noCall.startBeat && Conductor.instance.songPositionInBeats < noCall.startBeat + noCall.length))
{
if (sync)
idolAnimator.Play("IdolPeace" + GetPerformanceSuffix(), -1, 0);
else
idolAnimator.Play("IdolPeaceNoSync" + GetPerformanceSuffix(), -1, 0);
}
}
private void DoIdolResponse() private void DoIdolResponse()
{ {
if (responseToggle) if (responseToggle)
@ -438,6 +458,21 @@ namespace HeavenStudio.Games
} }
} }
private void DoIdolCall(int part = 0, bool big = false)
{
if (!(Conductor.instance.songPositionInBeats >= noCall.startBeat && Conductor.instance.songPositionInBeats < noCall.startBeat + noCall.length))
{
if (big)
{
idolAnimator.Play("IdolBigCall" + part + GetPerformanceSuffix(), -1, 0);
}
else
{
idolAnimator.Play("IdolCall" + part + GetPerformanceSuffix(), -1, 0);
}
}
}
const float HAIS_LENGTH = 4.5f; const float HAIS_LENGTH = 4.5f;
public void CallHai(float beat, bool noSound = false, int type = 0) public void CallHai(float beat, bool noSound = false, int type = 0)
{ {
@ -458,11 +493,11 @@ namespace HeavenStudio.Games
BeatAction.New(Arisa, new List<BeatAction.Action>() BeatAction.New(Arisa, new List<BeatAction.Action>()
{ {
new BeatAction.Action(beat, delegate { Arisa.GetComponent<Animator>().Play("IdolPeace" + GetPerformanceSuffix(), -1, 0);}), new BeatAction.Action(beat, delegate { DoIdolPeace();}),
new BeatAction.Action(beat + 1f, delegate { Arisa.GetComponent<Animator>().Play("IdolPeace" + GetPerformanceSuffix(), -1, 0);}), new BeatAction.Action(beat + 1f, delegate { DoIdolPeace();}),
new BeatAction.Action(beat + 2f, delegate { Arisa.GetComponent<Animator>().Play("IdolPeace" + GetPerformanceSuffix(), -1, 0);}), new BeatAction.Action(beat + 2f, delegate { DoIdolPeace();}),
new BeatAction.Action(beat + 2.5f, delegate { DisableSpecBop(beat + 2.5f, 5f);}), new BeatAction.Action(beat + 2.5f, delegate { DisableSpecBop(beat + 2.5f, 5f);}),
new BeatAction.Action(beat + 3f, delegate { Arisa.GetComponent<Animator>().Play("IdolPeaceNoSync" + GetPerformanceSuffix()); PlayPrepare(); }), new BeatAction.Action(beat + 3f, delegate { DoIdolPeace(false); PlayPrepare(); }),
new BeatAction.Action(beat + 4f, delegate { PlayOneClap(beat + 4f); DoIdolClaps();}), new BeatAction.Action(beat + 4f, delegate { PlayOneClap(beat + 4f); DoIdolClaps();}),
new BeatAction.Action(beat + 5f, delegate { PlayOneClap(beat + 5f); DoIdolClaps();}), new BeatAction.Action(beat + 5f, delegate { PlayOneClap(beat + 5f); DoIdolClaps();}),
@ -498,14 +533,10 @@ namespace HeavenStudio.Games
public void CallKamone(float beat, bool noSound = false, int type = 0, int responseType = (int) KamoneResponseType.Through) public void CallKamone(float beat, bool noSound = false, int type = 0, int responseType = (int) KamoneResponseType.Through)
{ {
bool doJump = (responseType == (int) KamoneResponseType.Jump || responseType == (int) KamoneResponseType.JumpFast); bool doJump = (responseType == (int) KamoneResponseType.Jump || responseType == (int) KamoneResponseType.JumpFast);
BeatAction.Action call0; bool isBig = (responseType == (int) KamoneResponseType.ThroughFast || responseType == (int) KamoneResponseType.JumpFast);
BeatAction.Action call1;
DisableResponse(beat, 2f); DisableResponse(beat, 2f);
if (responseType == (int) KamoneResponseType.ThroughFast || responseType == (int) KamoneResponseType.JumpFast) if (isBig)
{ {
call0 = new BeatAction.Action(beat, delegate { Arisa.GetComponent<Animator>().Play("IdolBigCall0" + GetPerformanceSuffix(), -1, 0); });
call1 = new BeatAction.Action(beat + 1f, delegate { Arisa.GetComponent<Animator>().Play("IdolBigCall1" + GetPerformanceSuffix(), -1, 0); });
if (!noSound) if (!noSound)
{ {
MultiSound.Play(new MultiSound.Sound[] { MultiSound.Play(new MultiSound.Sound[] {
@ -517,9 +548,6 @@ namespace HeavenStudio.Games
} }
else else
{ {
call0 = new BeatAction.Action(beat, delegate { Arisa.GetComponent<Animator>().Play("IdolCall0" + GetPerformanceSuffix(), -1, 0); });
call1 = new BeatAction.Action(beat + 0.75f, delegate { Arisa.GetComponent<Animator>().Play("IdolCall1" + GetPerformanceSuffix(), -1, 0); });
if (!noSound) if (!noSound)
{ {
MultiSound.Play(new MultiSound.Sound[] { MultiSound.Play(new MultiSound.Sound[] {
@ -534,15 +562,15 @@ namespace HeavenStudio.Games
DisableBop(beat, (doJump) ? 6.25f : 5.25f); DisableBop(beat, (doJump) ? 6.25f : 5.25f);
DisableSpecBop(beat + 0.5f, 6f); DisableSpecBop(beat + 0.5f, 6f);
Prepare(beat + 1f); Prepare(beat + 1f, 3);
Prepare(beat + 2.5f); Prepare(beat + 2.5f);
Prepare(beat + 3f, 2); Prepare(beat + 3f, 2);
Prepare(beat + 4f, 1); Prepare(beat + 4f, 1);
BeatAction.New(Arisa, new List<BeatAction.Action>() BeatAction.New(Arisa, new List<BeatAction.Action>()
{ {
call0, new BeatAction.Action(beat, delegate { DoIdolCall(0, isBig); }),
call1, new BeatAction.Action(beat + (isBig ? 1f : 0.75f), delegate { DoIdolCall(1, isBig); }),
new BeatAction.Action(beat + 1f, delegate { PlayPrepare(); }), new BeatAction.Action(beat + 1f, delegate { PlayPrepare(); }),
new BeatAction.Action(beat + 2f, delegate { PlayLongClap(beat + 2f); DoIdolResponse(); }), new BeatAction.Action(beat + 2f, delegate { PlayLongClap(beat + 2f); DoIdolResponse(); }),
@ -648,11 +676,11 @@ namespace HeavenStudio.Games
{ {
string clipName = Spectators[i].GetComponent<Animator>().GetCurrentAnimatorClipInfo(0)[0].clip.name; string clipName = Spectators[i].GetComponent<Animator>().GetCurrentAnimatorClipInfo(0)[0].clip.name;
if (clipName == "FanBeat" || clipName == "NoPose") if (clipName == "FanBeat" || clipName == "NoPose")
Spectators[i].GetComponent<Animator>().Play(anim); Spectators[i].GetComponent<Animator>().Play(anim, -1, 0);
} }
continue; continue;
} }
Spectators[i].GetComponent<Animator>().Play(anim); Spectators[i].GetComponent<Animator>().Play(anim, -1, 0);
} }
} }

View file

@ -21,146 +21,85 @@ namespace HeavenStudio.Games.Scripts_FanClub
[Header("Properties")] [Header("Properties")]
[NonSerialized] public bool player = false; [NonSerialized] public bool player = false;
[NonSerialized] public bool hitValid = false;
public float jumpStartTime = Single.MinValue; public float jumpStartTime = Single.MinValue;
bool stopBeat = false; bool stopBeat = false;
bool stopCharge = false; bool stopCharge = false;
bool hasJumped = false; bool hasJumped = false;
float clappingStartTime = 0f; float clappingStartTime = Single.MinValue;
public SortedList<float, int> upcomingHits;
public float startBeat;
public int type;
public bool doCharge = false;
private bool inputHit = false;
private bool hasHit = false;
public void Init()
{
if (player)
upcomingHits = new SortedList<float, int>(); // beat, type
inputHit = true;
hasHit = true;
}
public override void OnAce()
{
Hit(true, type, true);
}
public void AddHit(float beat, int type) public void AddHit(float beat, int type)
{ {
inputHit = false; if (player)
try
{ {
upcomingHits.Add(beat, type); if (type == 0) // normal clap
FanClub.instance.ScheduleInput(beat, 1f, InputType.STANDARD_DOWN, ClapJust, ClapThrough, Out);
else if (type == 1) // jump
FanClub.instance.ScheduleInput(beat, 1f, InputType.STANDARD_UP, JumpJust, JumpThrough, JumpOut);
else if (type == 2) //"kamone" charge
FanClub.instance.ScheduleInput(beat, 1f, InputType.STANDARD_DOWN, ChargeClapJust, ClapThrough, Out);
else //"kamone" long clap (first)
FanClub.instance.ScheduleInput(beat, 1f, InputType.STANDARD_DOWN, LongClapJust, ClapThrough, Out);
} }
catch (ArgumentException)
{}
} }
public void Hit(bool _hit, int type = 0, bool fromAutoplay = false) public void ClapJust(PlayerActionEvent caller, float state)
{ {
if (player && !hasHit) bool auto = GameManager.instance.autoplay;
{ ClapStart(true, false, auto ? 0.25f : 0f);
if (type == 0) }
ClapStart(_hit, true, doCharge, fromAutoplay);
else if (type == 1)
JumpStart(_hit, true, fromAutoplay);
hasHit = true; public void ChargeClapJust(PlayerActionEvent caller, float state)
{
bool auto = GameManager.instance.autoplay;
ClapStart(true, true, auto ? 1f : 0f);
}
public void LongClapJust(PlayerActionEvent caller, float state)
{
bool auto = GameManager.instance.autoplay;
ClapStart(true, false, auto ? 1f : 0f);
}
public void JumpJust(PlayerActionEvent caller, float state)
{
JumpStart(true);
}
public void ClapThrough(PlayerActionEvent caller) {
FanClub.instance.AngerOnMiss();
}
public void JumpThrough(PlayerActionEvent caller) {
FanClub.instance.AngerOnMiss();
}
public void Out(PlayerActionEvent caller) {}
public void JumpOut(PlayerActionEvent caller) {
var cond = Conductor.instance;
if (stopCharge)
{
caller.CanHit(false);
} }
} }
private void Update() private void Update()
{ {
var cond = Conductor.instance; var cond = Conductor.instance;
// read cue queue and pop when needed
if (hasHit)
{
if (upcomingHits?.Count > 0)
{
var k = upcomingHits.Keys[0];
var v = upcomingHits[k];
startBeat = k;
type = v == 2 ? 0 : v;
doCharge = (v == 2);
// reset our shit to prepare for next hit
hasHit = false;
ResetState();
upcomingHits.Remove(k);
}
else if (Conductor.instance.GetPositionFromBeat(startBeat, 1) >= Minigame.EndTime())
{
startBeat = Single.MinValue;
type = 0;
doCharge = false;
// DO NOT RESET, wait for future cues
}
}
// no input?
if (!hasHit && Conductor.instance.GetPositionFromBeat(startBeat, 1f) >= Minigame.EndTime())
{
FanClub.instance.AngerOnMiss();
hasHit = true;
}
// dunno what this is for
if (!inputHit && Conductor.instance.GetPositionFromBeat(startBeat, 1) >= Minigame.EndTime())
{
inputHit = true;
}
if (!hasHit)
{
float normalizedBeat = Conductor.instance.GetPositionFromBeat(startBeat, 1);
StateCheck(normalizedBeat);
}
if (player) if (player)
{ {
if (PlayerInput.Pressed() && type == 0)
{
if (state.perfect)
{
Hit(true);
} else if (state.notPerfect())
{
Hit(false);
}
}
if (PlayerInput.PressedUp() && type == 1)
{
if (state.perfect)
{
Hit(true, type);
} else if (state.notPerfect())
{
Hit(false, type);
}
}
if (PlayerInput.Pressed()) if (PlayerInput.Pressed())
{ {
if (!hasHit || (upcomingHits?.Count == 0 && startBeat == Single.MinValue)) if (!FanClub.instance.IsExpectingInputNow())
FanClub.instance.AngerOnMiss(); {
ClapStart(false);
hasJumped = false; }
stopBeat = true;
jumpStartTime = -99f;
animator.Play("FanClap", -1, 0);
Jukebox.PlayOneShotGame("fanClub/play_clap");
Jukebox.PlayOneShotGame("fanClub/crap_impact");
clappingStartTime = cond.songPositionInBeats;
} }
if (PlayerInput.Pressing()) if (PlayerInput.Pressing())
{ {
if (cond.songPositionInBeats > clappingStartTime + 1.5f && !stopCharge) if (clappingStartTime != Single.MinValue && cond.songPositionInBeats > clappingStartTime + 2f && !stopCharge)
{ {
animator.Play("FanClapCharge", -1, 0); animator.Play("FanClapCharge", -1, 0);
stopCharge = true; stopCharge = true;
@ -170,13 +109,10 @@ namespace HeavenStudio.Games.Scripts_FanClub
{ {
if (stopCharge) if (stopCharge)
{ {
if (!hasHit || (upcomingHits?.Count == 0 && startBeat == Single.MinValue)) if (!FanClub.instance.IsExpectingInputNow())
FanClub.instance.AngerOnMiss(); {
JumpStart(false);
animator.Play("FanJump", -1, 0); }
Jukebox.PlayOneShotGame("fanClub/play_jump");
jumpStartTime = cond.songPositionInBeats;
stopCharge = false;
} }
else else
{ {
@ -212,41 +148,39 @@ namespace HeavenStudio.Games.Scripts_FanClub
} }
} }
public void ClapStart(bool hit, bool force = false, bool doCharge = false, bool fromAutoplay = false) public void ClapStart(bool hit, bool doCharge = false, float autoplayRelease = 0f)
{ {
var cond = Conductor.instance; if (!hit)
if (hit)
{
if (doCharge)
BeatAction.New(this.gameObject, new List<BeatAction.Action>()
{
new BeatAction.Action(cond.songPositionInBeats + 0.1f, delegate {
if (PlayerInput.Pressing() || fromAutoplay)
{
animator.Play("FanClapCharge", -1, 0);
stopCharge = true;
}
}),
});
}
else
{ {
FanClub.instance.AngerOnMiss(); FanClub.instance.AngerOnMiss();
} }
if (fromAutoplay || !force)
{ var cond = Conductor.instance;
hasJumped = false;
stopBeat = true; stopBeat = true;
jumpStartTime = -99f; jumpStartTime = -99f;
animator.Play("FanClap", -1, 0); animator.Play("FanClap", -1, 0);
Jukebox.PlayOneShotGame("fanClub/play_clap"); Jukebox.PlayOneShotGame("fanClub/play_clap");
Jukebox.PlayOneShotGame("fanClub/crap_impact"); Jukebox.PlayOneShotGame("fanClub/crap_impact");
clappingStartTime = cond.songPositionInBeats; clappingStartTime = cond.songPositionInBeats;
}
if (fromAutoplay && !doCharge) if (doCharge)
{
BeatAction.New(this.gameObject, new List<BeatAction.Action>() BeatAction.New(this.gameObject, new List<BeatAction.Action>()
{ {
new BeatAction.Action(cond.songPositionInBeats + 0.1f, delegate { new BeatAction.Action(cond.songPositionInBeats + 0.1f, delegate {
if (PlayerInput.Pressing() || autoplayRelease > 0f)
{
animator.Play("FanClapCharge", -1, 0);
stopCharge = true;
}
}),
});
if (autoplayRelease > 0f && !doCharge)
{
BeatAction.New(this.gameObject, new List<BeatAction.Action>()
{
new BeatAction.Action(cond.songPositionInBeats + autoplayRelease, delegate {
animator.Play("FanFree", -1, 0); animator.Play("FanFree", -1, 0);
stopBeat = false; stopBeat = false;
}), }),
@ -255,23 +189,19 @@ namespace HeavenStudio.Games.Scripts_FanClub
} }
} }
public void JumpStart(bool hit, bool force = false, bool fromAutoplay = false) public void JumpStart(bool hit)
{ {
var cond = Conductor.instance; if (!hit)
if (hit)
{}
else
{ {
FanClub.instance.AngerOnMiss(); FanClub.instance.AngerOnMiss();
} }
if (fromAutoplay || !force)
{ var cond = Conductor.instance;
animator.Play("FanJump", -1, 0); animator.Play("FanJump", -1, 0);
Jukebox.PlayOneShotGame("fanClub/play_jump"); Jukebox.PlayOneShotGame("fanClub/play_jump");
jumpStartTime = cond.songPositionInBeats; jumpStartTime = cond.songPositionInBeats;
stopCharge = false; stopCharge = false;
} }
}
public bool IsJumping() public bool IsJumping()
{ {

View file

@ -6,7 +6,7 @@ namespace HeavenStudio.Games
{ {
public class Minigame : MonoBehaviour public class Minigame : MonoBehaviour
{ {
public static float earlyTime = (1f - 0.1f), perfectTime = (1f - 0.06f), lateTime = (1f + 0.06f), endTime = (1f + 0.1f); public static float earlyTime = 0.1f, perfectTime = 0.06f, lateTime = 0.06f, endTime = 0.1f;
public List<Minigame.Eligible> EligibleHits = new List<Minigame.Eligible>(); public List<Minigame.Eligible> EligibleHits = new List<Minigame.Eligible>();
[System.Serializable] [System.Serializable]
@ -136,27 +136,35 @@ namespace HeavenStudio.Games
return input.IsExpectingInputNow(); return input.IsExpectingInputNow();
} }
// now should fix the fast bpm problem
// hopefully these will fix the lowbpm problem
public static float EarlyTime() public static float EarlyTime()
{ {
return earlyTime; return 1f - ScaleTimingMargin(earlyTime);
} }
public static float PerfectTime() public static float PerfectTime()
{ {
return perfectTime; return 1f - ScaleTimingMargin(perfectTime);
} }
public static float LateTime() public static float LateTime()
{ {
return lateTime; return 1f + ScaleTimingMargin(lateTime);
} }
public static float EndTime() public static float EndTime()
{ {
return endTime; return 1f + ScaleTimingMargin(endTime);
}
//scales timing windows to the BPM in an ""intelligent"" manner
static float ScaleTimingMargin(float f)
{
float bpm = Conductor.instance.songBpm * Conductor.instance.musicSource.pitch;
float a = bpm / 120f;
float b = (Mathf.Log(a) + 2f) * 0.5f;
float r = Mathf.Lerp(a, b, 0.25f);
return r * f;
} }
public int firstEnable = 0; public int firstEnable = 0;