Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Scroll Speed Event #2409

Merged
merged 9 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 72 additions & 1 deletion source/funkin/play/PlayState.hx
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,11 @@ class PlayState extends MusicBeatSubState
*/
public var cameraZoomTween:FlxTween;

/**
* An FlxTween that changes the additive speed to the desired amount.
*/
public var scrollSpeedTweens:Array<FlxTween> = [];

/**
* The camera follow point from the last stage.
* Used to persist the position of the `cameraFollowPosition` between levels.
Expand Down Expand Up @@ -822,6 +827,8 @@ class PlayState extends MusicBeatSubState
{
if (!assertChartExists()) return;

prevScrollTargets = [];

dispatchEvent(new ScriptEvent(SONG_RETRY));

resetCamera();
Expand Down Expand Up @@ -1204,6 +1211,15 @@ class PlayState extends MusicBeatSubState
cameraTweensPausedBySubState.add(cameraZoomTween);
}

for (tween in scrollSpeedTweens)
{
if (tween != null && tween.active)
{
tween.active = false;
cameraTweensPausedBySubState.add(tween);
}
}

// Pause the countdown.
Countdown.pauseCountdown();
}
Expand Down Expand Up @@ -3042,8 +3058,9 @@ class PlayState extends MusicBeatSubState
// Stop camera zooming on beat.
cameraZoomRate = 0;

// Cancel camera tweening if it's active.
// Cancel camera and scroll tweening if it's active.
cancelAllCameraTweens();
cancelScrollSpeedTweens();

// If the opponent is GF, zoom in on the opponent.
// Else, if there is no GF, zoom in on BF.
Expand Down Expand Up @@ -3263,6 +3280,60 @@ class PlayState extends MusicBeatSubState
cancelCameraZoomTween();
}

var prevScrollTargets:Array<Dynamic> = []; // used to snap scroll speed when things go unruely

/**
* The magical function that shall tween the scroll speed.
*/
public function tweenScrollSpeed(?speed:Float, ?duration:Float, ?ease:Null<Float->Float>, strumlines:Array<String>):Void
{
// Cancel the current tween if it's active.
cancelScrollSpeedTweens();

// Snap to previous event value to prevent the tween breaking when another event cancels the previous tween.
for (i in prevScrollTargets)
{
var value:Float = i[0];
var strum:Strumline = Reflect.getProperty(this, i[1]);
strum.scrollSpeed = value;
}

// for next event, clean array.
prevScrollTargets = [];

for (i in strumlines)
{
var value:Float = speed;
var strum:Strumline = Reflect.getProperty(this, i);

if (duration == 0)
{
strum.scrollSpeed = value;
}
else
{
scrollSpeedTweens.push(FlxTween.tween(strum,
{
'scrollSpeed': value
}, duration, {ease: ease}));
}
// make sure charts dont break if the charter is dumb and stupid
prevScrollTargets.push([value, i]);
}
}

public function cancelScrollSpeedTweens()
{
for (tween in scrollSpeedTweens)
{
if (tween != null)
{
tween.cancel();
}
}
scrollSpeedTweens = [];
}

#if (debug || FORCE_DEBUG_VERSION)
/**
* Jumps forward or backward a number of sections in the song.
Expand Down
172 changes: 172 additions & 0 deletions source/funkin/play/event/ScrollSpeedEvent.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package funkin.play.event;

import flixel.tweens.FlxTween;
import flixel.FlxCamera;
import flixel.tweens.FlxEase;
// Data from the chart
import funkin.data.song.SongData;
import funkin.data.song.SongData.SongEventData;
// Data from the event schema
import funkin.play.event.SongEvent;
import funkin.data.event.SongEventSchema;
import funkin.data.event.SongEventSchema.SongEventFieldType;

/**
* This class represents a handler for scroll speed events.
*
* Example: Scroll speed change of both strums from 1x to 1.3x:
* ```
* {
* 'e': 'ScrollSpeed',
* "v": {
* "scroll": "1.3",
* "duration": "4",
* "ease": "linear"
* }
* }
* ```
*/
class ScrollSpeedEvent extends SongEvent
{
public function new()
{
super('ScrollSpeed');
}

static final DEFAULT_SCROLL:Float = 1;
static final DEFAULT_DURATION:Float = 4.0;
static final DEFAULT_EASE:String = 'linear';
static final DEFAULT_ABSOLUTE:Bool = false;
static final DEFAULT_STRUMLINE:String = 'both'; // my special little trick

public override function handleEvent(data:SongEventData):Void
{
// Does nothing if there is no PlayState.
if (PlayState.instance == null) return;

var scroll:Float = data.getFloat('scroll') ?? DEFAULT_SCROLL;

var duration:Float = data.getFloat('duration') ?? DEFAULT_DURATION;

var ease:String = data.getString('ease') ?? DEFAULT_EASE;

var strumline:String = data.getString('strumline') ?? DEFAULT_STRUMLINE;

var absolute:Bool = data.getBool('absolute') ?? DEFAULT_ABSOLUTE;

var strumlineNames:Array<String> = [];

if (!absolute)
{
// If absolute is set to false, do the awesome multiplicative thing
scroll = scroll * (PlayState.instance?.currentChart?.scrollSpeed ?? 1.0);
}

switch (strumline)
{
case 'both':
strumlineNames = ['playerStrumline', 'opponentStrumline'];
default:
strumlineNames = [strumline + 'Strumline'];
}
// If it's a string, check the value.
switch (ease)
{
case 'INSTANT':
PlayState.instance.tweenScrollSpeed(scroll, 0, null, strumlineNames);
default:
var durSeconds = Conductor.instance.stepLengthMs * duration / 1000;
var easeFunction:Null<Float->Float> = Reflect.field(FlxEase, ease);
if (easeFunction == null)
{
trace('Invalid ease function: $ease');
return;
}

PlayState.instance.tweenScrollSpeed(scroll, durSeconds, easeFunction, strumlineNames);
}
}

public override function getTitle():String
{
return 'Scroll Speed';
}

/**
* ```
* {
* 'scroll': FLOAT, // Target scroll level.
* 'duration': FLOAT, // Duration in steps.
* 'ease': ENUM, // Easing function.
* }
* @return SongEventSchema
*/
public override function getEventSchema():SongEventSchema
{
return new SongEventSchema([
{
name: 'scroll',
title: 'Target Value',
defaultValue: 1.0,
step: 0.1,
type: SongEventFieldType.FLOAT,
units: 'x'
},
{
name: 'duration',
title: 'Duration',
defaultValue: 4.0,
step: 0.5,
type: SongEventFieldType.FLOAT,
units: 'steps'
},
{
name: 'ease',
title: 'Easing Type',
defaultValue: 'linear',
type: SongEventFieldType.ENUM,
keys: [
'Linear' => 'linear',
'Instant (Ignores Duration)' => 'INSTANT',
'Sine In' => 'sineIn',
'Sine Out' => 'sineOut',
'Sine In/Out' => 'sineInOut',
'Quad In' => 'quadIn',
'Quad Out' => 'quadOut',
'Quad In/Out' => 'quadInOut',
'Cube In' => 'cubeIn',
'Cube Out' => 'cubeOut',
'Cube In/Out' => 'cubeInOut',
'Quart In' => 'quartIn',
'Quart Out' => 'quartOut',
'Quart In/Out' => 'quartInOut',
'Quint In' => 'quintIn',
'Quint Out' => 'quintOut',
'Quint In/Out' => 'quintInOut',
'Expo In' => 'expoIn',
'Expo Out' => 'expoOut',
'Expo In/Out' => 'expoInOut',
'Smooth Step In' => 'smoothStepIn',
'Smooth Step Out' => 'smoothStepOut',
'Smooth Step In/Out' => 'smoothStepInOut',
'Elastic In' => 'elasticIn',
'Elastic Out' => 'elasticOut',
'Elastic In/Out' => 'elasticInOut'
]
},
{
name: 'strumline',
title: 'Target Strumline',
defaultValue: 'both',
type: SongEventFieldType.ENUM,
keys: ['Both' => 'both', 'Player' => 'player', 'Opponent' => 'opponent']
},
{
name: 'absolute',
title: 'Absolute',
defaultValue: false,
type: SongEventFieldType.BOOL,
}
]);
}
}
12 changes: 11 additions & 1 deletion source/funkin/play/notes/Strumline.hx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ class Strumline extends FlxSpriteGroup
*/
public var conductorInUse(get, set):Conductor;

// Used in-game to control the scroll speed within a song
public var scrollSpeed:Float = 1.0;

public function resetScrollSpeed():Void
{
scrollSpeed = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0;
}

var _conductorInUse:Null<Conductor>;

function get_conductorInUse():Conductor
Expand Down Expand Up @@ -134,6 +142,7 @@ class Strumline extends FlxSpriteGroup
this.refresh();

this.onNoteIncoming = new FlxTypedSignal<NoteSprite->Void>();
resetScrollSpeed();

for (i in 0...KEY_COUNT)
{
Expand Down Expand Up @@ -283,7 +292,6 @@ class Strumline extends FlxSpriteGroup
// var vwoosh:Float = (strumTime < Conductor.songPosition) && vwoosh ? 2.0 : 1.0;
// ^^^ commented this out... do NOT make it move faster as it moves offscreen!
var vwoosh:Float = 1.0;
var scrollSpeed:Float = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0;

return
Constants.PIXELS_PER_MS * (conductorInUse.songPosition - strumTime - Conductor.instance.inputOffset) * scrollSpeed * vwoosh * (Preferences.downscroll ? 1 : -1);
Expand Down Expand Up @@ -539,6 +547,7 @@ class Strumline extends FlxSpriteGroup
{
playStatic(dir);
}
resetScrollSpeed();
}

public function applyNoteData(data:Array<SongNoteData>):Void
Expand Down Expand Up @@ -705,6 +714,7 @@ class Strumline extends FlxSpriteGroup

if (holdNoteSprite != null)
{
holdNoteSprite.parentStrumline = this;
holdNoteSprite.noteData = note;
holdNoteSprite.strumTime = note.time;
holdNoteSprite.noteDirection = note.getDirection();
Expand Down
31 changes: 24 additions & 7 deletions source/funkin/play/notes/SustainTrail.hx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class SustainTrail extends FlxSprite
public var sustainLength(default, set):Float = 0; // millis
public var fullSustainLength:Float = 0;
public var noteData:Null<SongNoteData>;
public var parentStrumline:Strumline;

public var cover:NoteHoldCover = null;

Expand Down Expand Up @@ -119,7 +120,7 @@ class SustainTrail extends FlxSprite

// CALCULATE SIZE
graphicWidth = graphic.width / 8 * zoom; // amount of notes * 2
graphicHeight = sustainHeight(sustainLength, getScrollSpeed());
graphicHeight = sustainHeight(sustainLength, parentStrumline?.scrollSpeed ?? 1.0);
// instead of scrollSpeed, PlayState.SONG.speed

flipY = Preferences.downscroll;
Expand All @@ -135,9 +136,21 @@ class SustainTrail extends FlxSprite
this.active = true; // This NEEDS to be true for the note to be drawn!
}

function getScrollSpeed():Float
function getBaseScrollSpeed()
{
return PlayState?.instance?.currentChart?.scrollSpeed ?? 1.0;
return (PlayState.instance?.currentChart?.scrollSpeed ?? 1.0);
}

var previousScrollSpeed:Float = 1;

override function update(elapsed)
{
super.update(elapsed);
if (previousScrollSpeed != (parentStrumline?.scrollSpeed ?? 1.0))
{
triggerRedraw();
}
previousScrollSpeed = parentStrumline?.scrollSpeed ?? 1.0;
}

/**
Expand All @@ -155,12 +168,16 @@ class SustainTrail extends FlxSprite
if (s < 0.0) s = 0.0;

if (sustainLength == s) return s;

graphicHeight = sustainHeight(s, getScrollSpeed());
this.sustainLength = s;
triggerRedraw();
return this.sustainLength;
}

function triggerRedraw()
{
graphicHeight = sustainHeight(sustainLength, parentStrumline?.scrollSpeed ?? 1.0);
updateClipping();
updateHitbox();
return this.sustainLength;
}

public override function updateHitbox():Void
Expand All @@ -178,7 +195,7 @@ class SustainTrail extends FlxSprite
*/
public function updateClipping(songTime:Float = 0):Void
{
var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), getScrollSpeed()), 0, graphicHeight);
var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), parentStrumline?.scrollSpeed ?? 1.0), 0, graphicHeight);
if (clipHeight <= 0.1)
{
visible = false;
Expand Down
Loading
Loading