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

[ENHANCEMENT/BUGFIX] Bopper sync and bpm change events #3903

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
26 changes: 26 additions & 0 deletions source/funkin/Conductor.hx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,17 @@ class Conductor
*/
public var onStepHit(default, null):FlxSignal = new FlxSignal();

/**
* Signal fired when the current Conductor instance changes BPM.
*/
public static var bpmChange(default, null):FlxSignal = new FlxSignal();

/**
* Signal fired when THIS Conductor instance changes BPM.
* TODO: This naming sucks but we can't make a static and instance field with the same name!
*/
public var onBpmChange(default, null):FlxSignal = new FlxSignal();

/**
* The list of time changes in the song.
* There should be at least one time change (at the beginning of the song) to define the BPM.
Expand Down Expand Up @@ -328,13 +339,20 @@ class Conductor
Conductor.stepHit.dispatch();
}

static function dispatchBpmChange():Void
{
Conductor.bpmChange.dispatch();
}

static function setupSingleton(input:Conductor):Void
{
input.onMeasureHit.add(dispatchMeasureHit);

input.onBeatHit.add(dispatchBeatHit);

input.onStepHit.add(dispatchStepHit);

input.onBpmChange.add(dispatchBpmChange);
}

static function clearSingleton(input:Conductor):Void
Expand All @@ -344,6 +362,8 @@ class Conductor
input.onBeatHit.remove(dispatchBeatHit);

input.onStepHit.remove(dispatchStepHit);

input.onBpmChange.remove(dispatchBpmChange);
}

static function get_instance():Conductor
Expand Down Expand Up @@ -418,6 +438,7 @@ class Conductor
var oldMeasure:Float = this.currentMeasure;
var oldBeat:Float = this.currentBeat;
var oldStep:Float = this.currentStep;
var oldBpm:Float = this.bpm;

// If the song is playing, limit the song position to the length of the song or beginning of the song.
if (FlxG.sound.music != null && FlxG.sound.music.playing)
Expand Down Expand Up @@ -469,6 +490,11 @@ class Conductor
}

// FlxSignals are really cool.
if (bpm != oldBpm)
{
this.onBpmChange.dispatch();
}

if (currentStep != oldStep)
{
this.onStepHit.dispatch();
Expand Down
5 changes: 5 additions & 0 deletions source/funkin/modding/IScriptedClass.hx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ interface INoteScriptedClass extends IScriptedClass
*/
interface IBPMSyncedScriptedClass extends IScriptedClass
{
/**
* Called when the BPM changes.
*/
public function onBpmChange(event:SongTimeScriptEvent):Void;

/**
* Called once every step of the song.
*/
Expand Down
4 changes: 3 additions & 1 deletion source/funkin/modding/events/ScriptEventDispatcher.hx
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,11 @@ class ScriptEventDispatcher
case SONG_BEAT_HIT:
t.onBeatHit(cast event);
return;
case SONG_BPM_CHANGE:
t.onBpmChange(cast event);
return;
case SONG_STEP_HIT:
t.onStepHit(cast event);
return;
default: // Continue;
}
}
Expand Down
7 changes: 7 additions & 0 deletions source/funkin/modding/events/ScriptEventType.hx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ enum abstract ScriptEventType(String) from String to String
*/
var SONG_STEP_HIT = 'STEP_HIT';

/**
* Called when the BPM changes.
*
* This event is not cancelable.
*/
var SONG_BPM_CHANGE = 'BPM_CHANGE';

/**
* Called when a note comes on screen and starts approaching the strumline.
*
Expand Down
2 changes: 2 additions & 0 deletions source/funkin/modding/module/Module.hx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte

public function onNoteGhostMiss(event:GhostMissNoteScriptEvent) {}

public function onBpmChange(event:SongTimeScriptEvent) {}

public function onStepHit(event:SongTimeScriptEvent) {}

public function onBeatHit(event:SongTimeScriptEvent) {}
Expand Down
1 change: 1 addition & 0 deletions source/funkin/play/character/BaseCharacter.hx
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ class BaseCharacter extends Bopper
// super.onBeatHit handles the regular `dance()` calls.
}
FlxG.watch.addQuick('holdTimer-${characterId}', holdTimer);
FlxG.watch.addQuick('_realDanceEvery-${characterId}', _realDanceEvery);
}

public function isSinging():Bool
Expand Down
2 changes: 2 additions & 0 deletions source/funkin/play/song/Song.hx
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta

public function onSongEvent(event:SongEventScriptEvent):Void {};

public function onBpmChange(event:SongTimeScriptEvent):Void {};

public function onStepHit(event:SongTimeScriptEvent):Void {};

public function onBeatHit(event:SongTimeScriptEvent):Void {};
Expand Down
67 changes: 58 additions & 9 deletions source/funkin/play/stage/Bopper.hx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package funkin.play.stage;

import flixel.FlxSprite;
import flixel.FlxCamera;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import flixel.util.FlxTimer;
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
Expand All @@ -23,7 +24,16 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
* Supports up to 0.25 precision.
* @default 0.0 on props, 1.0 on characters
*/
public var danceEvery:Float = 0.0;
public var danceEvery(default, set):Float = 1;

var _realDanceEvery:Float = 1;

function set_danceEvery(value:Float):Float
{
this.danceEvery = value;
this.update_danceEvery();
return value;
}

/**
* Whether the bopper should dance left and right.
Expand All @@ -45,6 +55,13 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
*/
public var idleSuffix(default, set):String = '';

function set_idleSuffix(value:String):String
{
this.idleSuffix = value;
this.dance();
return value;
}

/**
* If this bopper is rendered with pixel art, disable anti-aliasing.
* @default `false`
Expand All @@ -63,13 +80,6 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
*/
public var shouldBop:Bool = true;

function set_idleSuffix(value:String):String
{
this.idleSuffix = value;
this.dance();
return value;
}

/**
* The offset of the character relative to the position specified by the stage.
*/
Expand Down Expand Up @@ -159,19 +169,58 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
this.shouldAlternate = hasAnimation('danceLeft');
}

/**
* Called when the BPM of the song changes.
*/
public function onBpmChange(event:SongTimeScriptEvent)
{
update_danceEvery();
}

/**
* Called once every step of the song.
*/
public function onStepHit(event:SongTimeScriptEvent)
{
if (danceEvery > 0 && (event.step % (danceEvery * Constants.STEPS_PER_BEAT)) == 0)
if (_realDanceEvery > 0 && (event.step % (_realDanceEvery * Constants.STEPS_PER_BEAT)) == 0)
{
dance(shouldBop);
}
}

public function onBeatHit(event:SongTimeScriptEvent):Void {}

function update_danceEvery():Void
{
if (danceEvery == 0 || shouldAlternate || this.animation.getByName('idle$idleSuffix') == null || shouldBop)
{
// for forced bopping, alternating dance and non-existing animation, it just works the same as before
_realDanceEvery = danceEvery;
return;
}

var daIdle = this.animation.getByName('idle$idleSuffix');
var numeratorTweak:Int = (Conductor.instance.timeSignatureNumerator % 2 == 0) ? 2 : Conductor.instance.timeSignatureNumerator; // hopefully we get only prime numbers...
if (FlxMath.getDecimals(danceEvery) == 0) // for int danceEvery
{
var idlePerBeat:Float = (daIdle.numFrames / daIdle.frameRate) / (Conductor.instance.beatLengthMs / 1000);
var danceEveryNumBeats:Int = Math.ceil(idlePerBeat);
if (danceEveryNumBeats > numeratorTweak)
{
while (danceEveryNumBeats % numeratorTweak != 0)
danceEveryNumBeats++;
}
_realDanceEvery = Math.max(danceEvery, danceEveryNumBeats);
}
else // for decymal danceEvery (X.25, X.50 and X.75)
{
// maybe to rework, for the moment it tries to have the same patern every sections
_realDanceEvery = danceEvery;
while ((4 * _realDanceEvery * Conductor.instance.stepLengthMs) < (daIdle.numFrames / daIdle.frameRate))
_realDanceEvery *= numeratorTweak;
}
}

/**
* Called every `danceEvery` beats of the song.
*/
Expand Down
5 changes: 5 additions & 0 deletions source/funkin/play/stage/Stage.hx
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,11 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
}
}

/**
* A function that gets called when the BPM of the song changes.
*/
public function onBpmChange(event:SongTimeScriptEvent):Void {}

/**
* A function that gets called once per step in the song.
* @param curStep The current step number.
Expand Down
11 changes: 11 additions & 0 deletions source/funkin/ui/MusicBeatState.hx
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,15 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler

Conductor.beatHit.add(this.beatHit);
Conductor.stepHit.add(this.stepHit);
Conductor.bpmChange.add(this.bpmChange);
}

public override function destroy():Void
{
super.destroy();
Conductor.beatHit.remove(this.beatHit);
Conductor.stepHit.remove(this.stepHit);
Conductor.bpmChange.remove(this.bpmChange);
}

function handleFunctionControls():Void
Expand Down Expand Up @@ -119,6 +121,15 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
FlxG.resetState();
}

public function bpmChange():Bool
{
var event = new SongTimeScriptEvent(SONG_BPM_CHANGE, conductorInUse.currentBeat, conductorInUse.currentStep);

dispatchEvent(event);

return true;
}

public function stepHit():Bool
{
var event = new SongTimeScriptEvent(SONG_STEP_HIT, conductorInUse.currentBeat, conductorInUse.currentStep);
Expand Down
10 changes: 10 additions & 0 deletions source/funkin/ui/MusicBeatSubState.hx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler

Conductor.beatHit.add(this.beatHit);
Conductor.stepHit.add(this.stepHit);
Conductor.bpmChange.add(this.bpmChange);

initConsoleHelpers();
}
Expand Down Expand Up @@ -108,6 +109,15 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
}

public function bpmChange():Bool
{
var event = new SongTimeScriptEvent(SONG_BPM_CHANGE, conductorInUse.currentBeat, conductorInUse.currentStep);

dispatchEvent(event);

return true;
}

/**
* Called when a step is hit in the current song.
* Continues outside of PlayState, for things like animations in menus.
Expand Down
2 changes: 2 additions & 0 deletions source/funkin/ui/charSelect/CharSelectGF.hx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
#end
}

public function onBpmChange(event:SongTimeScriptEvent):Void {}

public function onStepHit(event:SongTimeScriptEvent):Void {}

var danceEvery:Int = 2;
Expand Down
2 changes: 2 additions & 0 deletions source/funkin/ui/charSelect/CharSelectPlayer.hx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class CharSelectPlayer extends FlxAtlasSprite implements IBPMSyncedScriptedClass
});
}

public function onBpmChange(event:SongTimeScriptEvent):Void {}

public function onStepHit(event:SongTimeScriptEvent):Void {}

public function onBeatHit(event:SongTimeScriptEvent):Void
Expand Down
7 changes: 6 additions & 1 deletion source/funkin/ui/debug/charting/ChartEditorState.hx
Original file line number Diff line number Diff line change
Expand Up @@ -2167,6 +2167,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
currentPlayerCharacterPlayer.onBeatHit(cast event);
case SONG_STEP_HIT:
currentPlayerCharacterPlayer.onStepHit(cast event);
case SONG_BPM_CHANGE:
currentPlayerCharacterPlayer.onBpmChange(cast event);
case NOTE_HIT:
currentPlayerCharacterPlayer.onNoteHit(cast event);
default: // Continue
Expand All @@ -2183,6 +2185,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
currentOpponentCharacterPlayer.onBeatHit(cast event);
case SONG_STEP_HIT:
currentOpponentCharacterPlayer.onStepHit(cast event);
case SONG_BPM_CHANGE:
currentPlayerCharacterPlayer.onBpmChange(cast event);
case NOTE_HIT:
currentOpponentCharacterPlayer.onNoteHit(cast event);
default: // Continue
Expand Down Expand Up @@ -5654,7 +5658,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function handleHelpKeybinds():Void
{
// F1 = Open Help
if (FlxG.keys.justPressed.F1 && !isHaxeUIDialogOpen) {
if (FlxG.keys.justPressed.F1 && !isHaxeUIDialogOpen)
{
this.openUserGuideDialog();
}
}
Expand Down
10 changes: 10 additions & 0 deletions source/funkin/ui/haxeui/components/CharacterPlayer.hx
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,16 @@ class CharacterPlayer extends Box
if (character != null) character.onUpdate(event);
}

/**
* Called when the BPM changes in the song
* Used to play character animations.
* @param event The event.
*/
public function onBpmChange(event:SongTimeScriptEvent):Void
{
if (character != null) character.onBpmChange(event);
}

/**
* Called when an beat is hit in the song
* Used to play character animations.
Expand Down