1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-12-01 16:51:27 +00:00

Tweaks to song events

This commit is contained in:
Eric Myllyoja 2022-09-24 15:02:10 -04:00
parent 7b71fc2dff
commit 679ca4aafb
9 changed files with 393 additions and 252 deletions

View file

@ -11,6 +11,7 @@ import funkin.charting.ChartingState;
import funkin.modding.module.ModuleHandler;
import funkin.play.PlayState;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.play.event.SongEvent.SongEventHandler;
import funkin.play.song.SongData.SongDataParser;
import funkin.play.stage.StageData;
import funkin.ui.PreferencesMenu;
@ -118,6 +119,9 @@ class InitState extends FlxTransitionableState
// FlxTransitionableState.skipNextTransOut = true;
FlxTransitionableState.skipNextTransIn = true;
SongEventHandler.registerBaseEventCallbacks();
// TODO: Register custom event callbacks here
SongDataParser.loadSongCache();
StageDataParser.loadStageCache();
CharacterDataParser.loadCharacterCache();

View file

@ -10,8 +10,6 @@ import flixel.util.FlxColor;
import flixel.util.FlxDirectionFlags;
import flixel.util.FlxTimer;
import funkin.audiovis.SpectogramSprite;
import funkin.noteStuff.NoteUtil;
import funkin.shaderslmfao.BuildingShaders;
import funkin.shaderslmfao.ColorSwap;
import funkin.shaderslmfao.TitleOutline;
import funkin.ui.AtlasText;

View file

@ -232,6 +232,9 @@ class PolymodHandler
// Reload everything that is cached.
// Currently this freezes the game for a second but I guess that's tolerable?
// TODO: Reload event callbacks
SongDataParser.loadSongCache();
StageDataParser.loadStageCache();
CharacterDataParser.loadCharacterCache();

View file

@ -1,206 +0,0 @@
package funkin.play;
import flixel.FlxSprite;
import flixel.addons.effects.FlxTrail;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.math.FlxMath;
import flixel.util.FlxColor;
import flixel.util.FlxDirectionFlags;
import funkin.audiovis.PolygonSpectogram;
import funkin.noteStuff.NoteBasic.NoteData;
class PicoFight extends MusicBeatState
{
var picoHealth:Float = 1;
var darnellHealth:Float = 1;
var pico:Fighter;
var darnell:Fighter;
var darnellGhost:Fighter;
var nextHitTmr:FlxSprite;
var funnyWave:PolygonSpectogram;
var noteQueue:Array<NoteData> = [];
var noteSpawner:FlxTypedGroup<FlxSprite>;
override function create()
{
Paths.setCurrentLevel("weekend1");
var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height);
bg.scrollFactor.set();
add(bg);
FlxG.sound.playMusic(Paths.inst("blazin"));
SongLoad.loadFromJson('blazin', "blazin");
Conductor.forceBPM(SongLoad.songData.bpm);
for (dumbassSection in SongLoad.songData.noteMap['hard'])
{
for (noteStuf in dumbassSection.sectionNotes)
{
noteQueue.push(noteStuf);
trace(noteStuf);
}
}
funnyWave = new PolygonSpectogram(FlxG.sound.music, FlxColor.RED, FlxG.height);
funnyWave.x = (FlxG.width / 2);
funnyWave.realtimeVisLenght = 0.6;
add(funnyWave);
noteSpawner = new FlxTypedGroup<FlxSprite>();
add(noteSpawner);
makeNotes();
nextHitTmr = new FlxSprite((FlxG.width / 2) - 5).makeGraphic(10, FlxG.height, FlxColor.BLACK);
add(nextHitTmr);
var trailShit:FlxTrail = new FlxTrail(nextHitTmr);
add(trailShit);
pico = new Fighter(0, 300, "pico-fighter");
add(pico);
darnell = new Fighter(0, 300, "darnell-fighter");
add(darnell);
darnellGhost = new Fighter(0, 300, "darnell-fighter");
darnellGhost.alpha = 0.5;
add(darnellGhost);
mid = (FlxG.width / 2) - (pico.width / 2);
resetPositions();
// fuk u, hardcoded bullshit bitch
super.create();
}
function makeNotes()
{
for (notes in noteQueue)
{
if (notes.strumTime < Conductor.songPosition + (Conductor.crochet * 4))
{
spawnNote(notes);
spawnNote(notes, FlxDirectionFlags.RIGHT);
}
}
}
function spawnNote(note:NoteData, facing:Int = FlxDirectionFlags.LEFT)
{
var spr:FlxSprite = new FlxSprite(0, (FlxG.height / 2) - 60).makeGraphic(10, 120, Note.codeColors[note.noteData]);
spr.ID = Std.int(note.strumTime); // using ID as strum, lol!
spr.facing = facing;
noteSpawner.add(spr);
}
var mid:Float = (FlxG.width * 0.5) - 200;
function resetPositions()
{
resetPicoPos();
resetDarnell();
}
function resetPicoPos()
{
pico.x = mid + pico.width;
}
function resetDarnell()
{
darnell.x = mid - darnell.width;
}
var prevNoteHit:Float = 0;
override function update(elapsed:Float)
{
darnellGhost.x = darnell.x;
Conductor.songPosition = FlxG.sound.music.time;
funnyWave.thickness = CoolUtil.coolLerp(funnyWave.thickness, 2, 0.5);
funnyWave.waveAmplitude = Std.int(CoolUtil.coolLerp(funnyWave.waveAmplitude, 100, 0.1));
funnyWave.realtimeVisLenght = CoolUtil.coolLerp(funnyWave.realtimeVisLenght, 0.6, 0.1);
noteSpawner.forEachAlive((nt:FlxSprite) ->
{
// i forget how to make rhythm games
nt.x = (nt.ID - Conductor.songPosition) * (nt.ID / (Conductor.songPosition * 0.8));
if (nt.facing == FlxDirectionFlags.RIGHT)
{
nt.x = FlxMath.remapToRange(nt.x, 0, FlxG.width, FlxG.width, 0);
nt.x -= FlxG.width / 2;
}
else
{
nt.x += FlxG.width / 2;
}
nt.scale.x = FlxMath.remapToRange(nt.ID - Conductor.songPosition, 0, Conductor.crochet * 3, 0.2, 2);
nt.scale.y = FlxMath.remapToRange((nt.ID - Conductor.songPosition), 0, Conductor.crochet * 2, 6, 0.2);
if (nt.ID < Conductor.songPosition)
nt.kill();
});
if (noteQueue.length > 0)
{
nextHitTmr.scale.y = FlxMath.remapToRange(Conductor.songPosition, prevNoteHit, noteQueue[0].strumTime, 1, 0);
darnellGhost.scale.x = darnellGhost.scale.y = FlxMath.remapToRange(Conductor.songPosition, prevNoteHit, noteQueue[0].strumTime, 2, 1);
darnellGhost.alpha = FlxMath.remapToRange(Conductor.songPosition, prevNoteHit, noteQueue[0].strumTime, 0.3, 0.1);
if (Conductor.songPosition >= noteQueue[0].strumTime)
{
prevNoteHit = noteQueue[0].strumTime;
noteQueue.shift();
darnell.doSomething(darnellGhost.curAction);
darnellGhost.doSomething();
darnellGhost.animation.curAnim.frameRate = 12;
}
}
if (controls.NOTE_LEFT_P)
{
pico.punch();
}
if (controls.NOTE_LEFT_R)
pico.playAnimation('idle');
super.update(elapsed);
}
override function stepHit():Bool
{
return super.stepHit();
}
override function beatHit():Bool
{
// super.beatHit() returns false if a module cancelled the event.
if (!super.beatHit())
return false;
funnyWave.thickness = 10;
funnyWave.waveAmplitude = 300;
funnyWave.realtimeVisLenght = 0.1;
picoHealth += 1;
makeNotes();
return true;
}
}

View file

@ -27,6 +27,7 @@ import funkin.play.Strumline.StrumlineArrow;
import funkin.play.Strumline.StrumlineStyle;
import funkin.play.character.BaseCharacter;
import funkin.play.character.CharacterData;
import funkin.play.event.SongEvent;
import funkin.play.scoring.Scoring;
import funkin.play.song.Song;
import funkin.play.song.SongData.SongNoteData;
@ -162,6 +163,8 @@ class PlayState extends MusicBeatState
*/
private var inactiveNotes:Array<Note>;
private var songEvents:Array<SongEvent>;
/**
* If true, the player is allowed to pause the game.
* Disabled during the ending of a song.
@ -279,7 +282,6 @@ class PlayState extends MusicBeatState
var perfectMode:Bool = false;
var previousFrameTime:Int = 0;
var songTime:Float = 0;
var cameraRightSide:Bool = false;
#if discord_rpc
// Discord RPC variables
@ -306,6 +308,12 @@ class PlayState extends MusicBeatState
instance = this;
if (currentSong_NEW != null)
{
// TODO: Do this in the loading state.
currentSong_NEW.cacheCharts(true);
}
// Displays the camera follow point as a sprite for debug purposes.
// TODO: Put this on a toggle?
cameraFollowPoint.makeGraphic(8, 8, 0xFF00FF00);
@ -1172,6 +1180,10 @@ class PlayState extends MusicBeatState
function regenNoteData_NEW():Void
{
// Reset song events.
songEvents = currentChart.getEvents();
SongEventHandler.resetEvents(songEvents);
// Destroy inactive notes.
inactiveNotes = [];
@ -1727,6 +1739,16 @@ class PlayState extends MusicBeatState
});
}
if (songEvents != null && songEvents.length > 0)
{
var songEventsToActivate:Array<SongEvent> = SongEventHandler.queryEvents(songEvents, Conductor.songPosition);
if (songEventsToActivate.length > 0)
trace('[EVENTS] Found ${songEventsToActivate.length} event(s) to activate.');
SongEventHandler.activateEvents(songEventsToActivate);
}
if (!isInCutscene)
keyShit(true);
}
@ -1915,38 +1937,63 @@ class PlayState extends MusicBeatState
comboPopUps.displayCombo(combo);
}
function controlCamera()
{
if (currentStage == null)
return;
var isFocusedOnDad = cameraFollowPoint.x == currentStage.getDad().cameraFocusPoint.x;
var isFocusedOnBF = cameraFollowPoint.x == currentStage.getBoyfriend().cameraFocusPoint.x;
if (cameraRightSide && !isFocusedOnBF)
/*
function controlCamera()
{
// Focus the camera on the player.
cameraFollowPoint.setPosition(currentStage.getBoyfriend().cameraFocusPoint.x, currentStage.getBoyfriend().cameraFocusPoint.y);
if (currentStage == null)
return;
// TODO: Un-hardcode this.
if (currentSong.song.toLowerCase() == 'tutorial')
FlxTween.tween(FlxG.camera, {zoom: 1 * FlxCamera.defaultZoom}, (Conductor.stepCrochet * 4 / 1000), {ease: FlxEase.elasticInOut});
}
else if (!cameraRightSide && !isFocusedOnDad)
{
// Focus the camera on the opponent.
cameraFollowPoint.setPosition(currentStage.getDad().cameraFocusPoint.x, currentStage.getDad().cameraFocusPoint.y);
// TODO: Un-hardcode this stuff.
if (currentStage.getDad().characterId == 'mom')
switch (cameraFocusCharacter)
{
vocals.volume = 1;
default: // null = No change
break;
case 0: // Boyfriend
var isFocusedOnBF = cameraFollowPoint.x == currentStage.getBoyfriend().cameraFocusPoint.x;
if (!isFocusedOnBF)
{
// Focus the camera on the player.
cameraFollowPoint.setPosition(currentStage.getBoyfriend().cameraFocusPoint.x, currentStage.getBoyfriend().cameraFocusPoint.y);
}
case 1: // Dad
var isFocusedOnDad = cameraFollowPoint.x == currentStage.getDad().cameraFocusPoint.x;
if (!isFocusedOnDad)
{
cameraFollowPoint.setPosition(currentStage.getDad().cameraFocusPoint.x, currentStage.getDad().cameraFocusPoint.y);
}
case 2: // Girlfriend
var isFocusedOnGF = cameraFollowPoint.x == currentStage.getGirlfriend().cameraFocusPoint.x;
if (!isFocusedOnGF)
{
cameraFollowPoint.setPosition(currentStage.getGirlfriend().cameraFocusPoint.x, currentStage.getGirlfriend().cameraFocusPoint.y);
}
}
if (currentSong.song.toLowerCase() == 'tutorial')
tweenCamIn();
}
}
/*
if (cameraRightSide && !isFocusedOnBF)
{
// Focus the camera on the player.
cameraFollowPoint.setPosition(currentStage.getBoyfriend().cameraFocusPoint.x, currentStage.getBoyfriend().cameraFocusPoint.y);
// TODO: Un-hardcode this.
if (currentSong.song.toLowerCase() == 'tutorial')
FlxTween.tween(FlxG.camera, {zoom: 1 * FlxCamera.defaultZoom}, (Conductor.stepCrochet * 4 / 1000), {ease: FlxEase.elasticInOut});
}
else if (!cameraRightSide && !isFocusedOnDad)
{
// Focus the camera on the opponent.
cameraFollowPoint.setPosition(currentStage.getDad().cameraFocusPoint.x, currentStage.getDad().cameraFocusPoint.y);
// TODO: Un-hardcode this stuff.
if (currentStage.getDad().characterId == 'mom')
{
vocals.volume = 1;
}
if (currentSong.song.toLowerCase() == 'tutorial')
tweenCamIn();
}
*/
// }
public function keyShit(test:Bool):Void
{
@ -2193,6 +2240,7 @@ class PlayState extends MusicBeatState
if (generatedMusic)
{
// TODO: Sort more efficiently, or less often, to improve performance.
activeNotes.sort(SortUtil.byStrumtime, FlxSort.DESCENDING);
}
@ -2201,8 +2249,7 @@ class PlayState extends MusicBeatState
{
if (generatedMusic && SongLoad.getSong()[Std.int(Conductor.currentStep / 16)] != null)
{
cameraRightSide = SongLoad.getSong()[Std.int(Conductor.currentStep / 16)].mustHitSection;
controlCamera();
// cameraRightSide = SongLoad.getSong()[Std.int(Conductor.currentStep / 16)].mustHitSection;
}
if (SongLoad.getSong()[Math.floor(Conductor.currentStep / 16)] != null)
@ -2215,6 +2262,9 @@ class PlayState extends MusicBeatState
}
}
// Manage the camera focus, if necessary.
// controlCamera();
// HARDCODING FOR MILF ZOOMS!
if (PreferencesMenu.getPref('camera-zoom'))

View file

@ -64,8 +64,8 @@ class VanillaCutscenes
FlxTween.tween(FlxG.camera, {zoom: PlayState.defaultCameraZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut});
@:privateAccess
PlayState.instance.startCountdown();
@:privateAccess
PlayState.instance.controlCamera();
// @:privateAccess
// PlayState.instance.controlCamera();
}
public static function playHorrorStartCutscene()

View file

@ -0,0 +1,282 @@
package funkin.play.event;
import flixel.FlxSprite;
import funkin.play.PlayState;
import funkin.play.song.SongData.RawSongEventData;
import haxe.DynamicAccess;
typedef RawSongEvent =
{
> RawSongEventData,
/**
* Whether the event has been activated or not.
*/
var a:Bool;
}
@:forward
abstract SongEvent(RawSongEvent)
{
public function new(time:Float, event:String, value:Dynamic = null)
{
this = {
t: time,
e: event,
v: value,
a: false
};
}
public var time(get, set):Float;
public function get_time():Float
{
return this.t;
}
public function set_time(value:Float):Float
{
return this.t = value;
}
public var event(get, set):String;
public function get_event():String
{
return this.e;
}
public function set_event(value:String):String
{
return this.e = value;
}
public var value(get, set):Dynamic;
public function get_value():Dynamic
{
return this.v;
}
public function set_value(value:Dynamic):Dynamic
{
return this.v = value;
}
public inline function getBool():Bool
{
return cast this.v;
}
public inline function getInt():Int
{
return cast this.v;
}
public inline function getFloat():Float
{
return cast this.v;
}
public inline function getString():String
{
return cast this.v;
}
public inline function getArray():Array<Dynamic>
{
return cast this.v;
}
public inline function getMap():DynamicAccess<Dynamic>
{
return cast this.v;
}
public inline function getBoolArray():Array<Bool>
{
return cast this.v;
}
}
typedef SongEventCallback = SongEvent->Void;
class SongEventHandler
{
private static final eventCallbacks:Map<String, SongEventCallback> = new Map<String, SongEventCallback>();
public static function registerCallback(event:String, callback:SongEventCallback):Void
{
eventCallbacks.set(event, callback);
}
public static function unregisterCallback(event:String):Void
{
eventCallbacks.remove(event);
}
public static function clearCallbacks():Void
{
eventCallbacks.clear();
}
/**
* Register each of the event callbacks provided by the base game.
*/
public static function registerBaseEventCallbacks():Void
{
// TODO: Add a system for mods to easily add their own event callbacks.
// Should be easy as creating character or stage scripts.
registerCallback('FocusCamera', VanillaEventCallbacks.focusCamera);
registerCallback('PlayAnimation', VanillaEventCallbacks.playAnimation);
}
/**
* Given a list of song events and the current timestamp,
* return a list of events that should be activated.
*/
public static function queryEvents(events:Array<SongEvent>, currentTime:Float):Array<SongEvent>
{
return events.filter(function(event:SongEvent):Bool
{
// If the event is already activated, don't activate it again.
if (event.a)
return false;
// If the event is in the future, don't activate it.
if (event.time > currentTime)
return false;
return true;
});
}
public static function activateEvents(events:Array<SongEvent>):Void
{
for (event in events)
{
activateEvent(event);
}
}
public static function activateEvent(event:SongEvent):Void
{
if (event.a)
{
trace('Event already activated: ' + event);
return;
}
// Prevent the event from being activated again.
event.a = true;
// Perform the action.
if (eventCallbacks.exists(event.event))
{
eventCallbacks.get(event.event)(event);
}
}
public static function resetEvents(events:Array<SongEvent>):Void
{
for (event in events)
{
resetEvent(event);
}
}
public static function resetEvent(event:SongEvent):Void
{
// TODO: Add a system for mods to easily add their reset callbacks.
event.a = false;
}
}
class VanillaEventCallbacks
{
/**
* Event Name: "FocusCamera"
* Event Value: Int
* 0: Focus on the player.
* 1: Focus on the opponent.
* 2: Focus on the girlfriend.
*/
public static function focusCamera(event:SongEvent):Void
{
// Does nothing if there is no PlayState camera or stage.
if (PlayState.instance == null || PlayState.instance.currentStage == null)
return;
switch (event.getInt())
{
case 0: // Boyfriend
// Focus the camera on the player.
trace('[EVENT] Focusing camera on player.');
PlayState.instance.cameraFollowPoint.setPosition(PlayState.instance.currentStage.getBoyfriend().cameraFocusPoint.x,
PlayState.instance.currentStage.getBoyfriend().cameraFocusPoint.y);
case 1: // Dad
// Focus the camera on the dad.
trace('[EVENT] Focusing camera on dad.');
PlayState.instance.cameraFollowPoint.setPosition(PlayState.instance.currentStage.getDad().cameraFocusPoint.x,
PlayState.instance.currentStage.getDad().cameraFocusPoint.y);
case 2: // Girlfriend
// Focus the camera on the girlfriend.
trace('[EVENT] Focusing camera on girlfriend.');
PlayState.instance.cameraFollowPoint.setPosition(PlayState.instance.currentStage.getGirlfriend().cameraFocusPoint.x,
PlayState.instance.currentStage.getGirlfriend().cameraFocusPoint.y);
default:
trace('[EVENT] Unknown camera focus: ' + event.value);
}
}
/**
* Event Name: "playAnimation"
* Event Value: Object
* {
* target: String, // "player", "dad", "girlfriend", or <stage prop id>
* animation: String,
* force: Bool // optional
* }
*/
public static function playAnimation(event:SongEvent):Void
{
// Does nothing if there is no PlayState camera or stage.
if (PlayState.instance == null || PlayState.instance.currentStage == null)
return;
var data:Dynamic = event.value;
var targetName:String = Reflect.field(data, 'target');
var anim:String = Reflect.field(data, 'anim');
var force:Null<Bool> = Reflect.field(data, 'force');
if (force == null)
force = false;
var target:FlxSprite = null;
switch (targetName)
{
case 'boyfriend':
trace('[EVENT] Playing animation $anim on boyfriend.');
target = PlayState.instance.currentStage.getBoyfriend();
case 'dad':
trace('[EVENT] Playing animation $anim on dad.');
target = PlayState.instance.currentStage.getDad();
case 'girlfriend':
trace('[EVENT] Playing animation $anim on girlfriend.');
target = PlayState.instance.currentStage.getGirlfriend();
default:
target = PlayState.instance.currentStage.getNamedProp(targetName);
if (target == null)
trace('[EVENT] Unknown animation target: $targetName');
else
trace('[EVENT] Fetched animation target $targetName from stage.');
}
if (target != null)
{
target.animation.play(anim, force);
}
}
}

View file

@ -1,6 +1,7 @@
package funkin.play.song;
import funkin.VoicesGroup;
import funkin.play.event.SongEvent;
import funkin.play.song.SongData.SongChartData;
import funkin.play.song.SongData.SongDataParser;
import funkin.play.song.SongData.SongEventData;
@ -42,9 +43,6 @@ class Song // implements IPlayStateScriptedClass
}
populateFromMetadata();
// TODO: Disable later.
cacheCharts();
}
/**
@ -89,8 +87,13 @@ class Song // implements IPlayStateScriptedClass
/**
* Parse and cache the chart for all difficulties of this song.
*/
public function cacheCharts():Void
public function cacheCharts(?force:Bool = false):Void
{
if (force)
{
clearCharts();
}
trace('Caching ${variations.length} chart files for song $songId');
for (variation in variations)
{
@ -158,6 +161,16 @@ class SongDifficulty
*/
public final variation:String;
/**
* The note chart for this difficulty.
*/
public var notes:Array<SongNoteData>;
/**
* The event chart for this difficulty.
*/
public var events:Array<SongEventData>;
public var songName:String = SongValidator.DEFAULT_SONGNAME;
public var songArtist:String = SongValidator.DEFAULT_ARTIST;
public var timeFormat:SongTimeFormat = SongValidator.DEFAULT_TIMEFORMAT;
@ -172,9 +185,6 @@ class SongDifficulty
public var scrollSpeed:Float = SongValidator.DEFAULT_SCROLLSPEED;
public var notes:Array<SongNoteData>;
public var events:Array<SongEventData>;
public function new(song:Song, diffId:String, variation:String)
{
this.song = song;
@ -202,6 +212,11 @@ class SongDifficulty
return chars.get(id);
}
public function getEvents():Array<SongEvent>
{
return cast events;
}
public inline function cacheInst()
{
FlxG.sound.cache(Paths.inst(this.song.songId));

View file

@ -471,11 +471,6 @@ abstract SongEventData(RawSongEventData)
return cast this.v;
}
public inline function getMap():DynamicAccess<Dynamic>
{
return cast this.v;
}
public inline function getBoolArray():Array<Bool>
{
return cast this.v;