From 4b145bf62e0ad6b7de0b745bc701dd125f0d5350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=8D=9A=7ENex?= <87421482+NexIsDumb@users.noreply.github.com> Date: Wed, 2 Apr 2025 01:50:00 +0200 Subject: [PATCH] A LOT OF NEW STUFF + more vslice events WIP JUST GOTTA MAKE THE TWEEN ON THE CAMERA MOVEMENT FRUNCTION PROPERLY AND WE'RE GOOD (and also make the charter not save not changed paramteres) --- source/funkin/backend/chart/EventsData.hx | 40 ++++- source/funkin/backend/chart/VSliceParser.hx | 35 +++- source/funkin/backend/system/Flags.hx | 2 +- source/funkin/backend/utils/CoolUtil.hx | 4 +- source/funkin/game/PlayState.hx | 167 ++++++++++++++------ 5 files changed, 190 insertions(+), 58 deletions(-) diff --git a/source/funkin/backend/chart/EventsData.hx b/source/funkin/backend/chart/EventsData.hx index b1aede64..85595621 100644 --- a/source/funkin/backend/chart/EventsData.hx +++ b/source/funkin/backend/chart/EventsData.hx @@ -10,17 +10,48 @@ import openfl.Assets; using StringTools; class EventsData { - public static var defaultEventsList:Array = ["HScript Call", "Camera Movement", "Add Camera Zoom", "Camera Modulo Change", "Camera Flash", "BPM Change", "Scroll Speed Change", "Alt Animation Toggle", "Play Animation"]; + public static var defaultEventsList:Array = ["HScript Call", "Camera Movement", "Add Camera Zoom", "Camera Zoom", "Camera Modulo Change", "Camera Flash", "BPM Change", "Scroll Speed Change", "Alt Animation Toggle", "Play Animation"]; public static var defaultEventsParams:Map> = [ "HScript Call" => [ {name: "Function Name", type: TString, defValue: "myFunc"}, {name: "Function Parameters (String split with commas)", type: TString, defValue: ""} ], - "Camera Movement" => [{name: "Camera Target", type: TStrumLine, defValue: 0}], + "Camera Movement" => [ + {name: "Camera Target", type: TStrumLine, defValue: 0}, + {name: "Tween Movement?", type: TBool, defValue: true}, + {name: "Tween Time (Steps, IF NOT CLASSIC)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4}, + { + name: "Tween Ease (ex: circ, quad, cube)", + type: TDropDown(['CLASSIC', 'linear', 'back', 'bounce', 'circ', 'cube', 'elastic', 'expo', 'quad', 'quart', 'quint', 'sine', 'smoothStep', 'smootherStep']), + defValue: "CLASSIC" + }, + { + name: "Tween Type (excluded if CLASSIC or linear, ex: InOut)", + type: TDropDown(['In', 'Out', 'InOut']), + defValue: "In" + } + ], "Add Camera Zoom" => [ {name: "Amount", type: TFloat(-10, 10, 0.01, 2), defValue: 0.05}, {name: "Camera", type: TDropDown(['camGame', 'camHUD']), defValue: "camGame"} ], + "Camera Zoom" => [ + {name: "Tween Zoom?", type: TBool, defValue: true}, + {name: "New Zoom", type: TFloat(-10, 10, 0.01, 2), defValue: 1}, + {name: "Camera", type: TDropDown(['camGame', 'camHUD']), defValue: "camGame"}, + {name: "Tween Time (Steps)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4}, + { + name: "Tween Ease (ex: circ, quad, cube)", + type: TDropDown(['linear', 'back', 'bounce', 'circ', 'cube', 'elastic', 'expo', 'quad', 'quart', 'quint', 'sine', 'smoothStep', 'smootherStep']), + defValue: "linear" + }, + { + name: "Tween Type (excluded if linear, ex: InOut)", + type: TDropDown(['In', 'Out', 'InOut']), + defValue: "In" + }, + {name: "Multiplicative?", type: TBool, defValue: false} + ], "Camera Modulo Change" => [ {name: "Modulo Interval (Beats)", type: TInt(1, 9999999, 1), defValue: 4}, {name: "Bump Strength", type: TFloat(0.1, 10, 0.01, 2), defValue: 1} @@ -42,10 +73,11 @@ class EventsData { defValue: "linear" }, { - name: "Tween Type (ex: InOut)", + name: "Tween Type (excluded if linear, ex: InOut)", type: TDropDown(['In', 'Out', 'InOut']), defValue: "In" - } + }, + {name: "Multiplicative?", type: TBool, defValue: false} ], "Alt Animation Toggle" => [{name: "Enable On Sing Poses", type: TBool, defValue: true}, {name: "Enable On Idle", type: TBool, defValue: true}, {name: "Strumline", type: TStrumLine, defValue: 0}], "Play Animation" => [{name: "Character", type: TStrumLine, defValue: 0}, {name: "Animation", type: TString, defValue: "animation"}, {name: "Is forced?", type: TBool, defValue: true}], diff --git a/source/funkin/backend/chart/VSliceParser.hx b/source/funkin/backend/chart/VSliceParser.hx index f5159b33..b6533777 100644 --- a/source/funkin/backend/chart/VSliceParser.hx +++ b/source/funkin/backend/chart/VSliceParser.hx @@ -45,8 +45,8 @@ class VSliceParser { for (note in data) { var daNoteType:Null = null; - if (note.k != null) // TODO: check if kind is alt maybe - Nex - daNoteType = Chart.addNoteType(result, note.k); + if (note.k != null) + daNoteType = Chart.addNoteType(result, note.k == "alt" ? "Alt Anim Note" : note.k); // they hardcoded "alt" for converting old charts BUT THEN WHY THE HELL WOULD YOU CALL "MOM" THE NOTE KIND IN WEEK5 GRAHHH - Nex var daNoteData:Int = Std.int(note.d % 8); var isMustHit:Bool = Math.floor(daNoteData / 4) == 0; @@ -76,13 +76,42 @@ class VSliceParser { for (event in events) { + var values = event.v; switch (event.e) { case "FocusCamera": result.events.push({ time: event.t, name: "Camera Movement", - params: [event.v.char] + params: [values.char == -1 ? 2 : values.char, values.ease != "INSTANT", values.duration == null ? 4 : values.duration, values.ease == null || values.ease == "INSTANT" ? "CLASSIC" : values.ease, ""] + }); + case "PlayAnimation": + result.events.push({ + time: event.t, + name: "Play Animation", + params: [switch(values.target) { + case 'boyfriend' | 'bf' | 'player': 1; + case 'dad' | 'opponent': 0; + default /*case 'girlfriend' | 'gf'*/: 2; // usually the default should be the stage prop but we dont have that sooo - Nex + }, values.anim, values.force == null ? false : values.force] + }); + case "ScrollSpeed": + result.events.push({ + time: event.t, + name: "Scroll Speed Change", // we dont support the strumline value and also i will put the whole ease name into a single parameter since it works anyways - Nex + params: [values.ease != "INSTANT", values.scroll == null ? 1 : values.scroll, values.duration == null ? 4 : values.duration, values.ease == null || values.ease == "INSTANT" ? "linear" : values.ease, "", values.absolute != true] + }); + case "SetCameraBop": + result.events.push({ + time: event.t, + name: "Camera Modulo Change", + params: [values.rate == null ? 4 : values.rate, values.intensity == null ? 1 : values.intensity] + }); + case "ZoomCamera": + result.events.push({ + time: event.t, + name: "Camera Zoom", // we dont support the direct mode since welp, its kind of useless here - Nex + params: [values.ease != "INSTANT", values.zoom == null ? 1 : values.zoom, "camGame", values.duration == null ? 4 : values.duration, values.ease == null || values.ease == "INSTANT" ? "linear" : values.ease, "", false] }); } } diff --git a/source/funkin/backend/system/Flags.hx b/source/funkin/backend/system/Flags.hx index 56ae3e92..c9400404 100644 --- a/source/funkin/backend/system/Flags.hx +++ b/source/funkin/backend/system/Flags.hx @@ -97,7 +97,7 @@ class Flags { public static var DEFAULT_CAM_ZOOM_STRENGTH:Int = 1; public static var DEFAULT_CAM_ZOOM:Float = 1.05; // what zoom level it defaults to public static var DEFAULT_HUD_ZOOM:Float = 1.0; - public static var MAX_CAMERA_ZOOM:Float = 1.35; + public static var MAX_CAMERA_ZOOM_MULT:Float = 1.35; public static var DEFAULT_PAUSE_ITEMS:Array = ['Resume', 'Restart Song', 'Change Controls', 'Change Options', 'Exit to menu', "Exit to charter"]; public static var DEFAULT_CUTSCENE_PAUSE_ITEMS:Array = ['Resume Cutscene', 'Skip Cutscene', 'Restart Cutscene', 'Exit to menu']; diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx index ef700daa..84178eec 100644 --- a/source/funkin/backend/utils/CoolUtil.hx +++ b/source/funkin/backend/utils/CoolUtil.hx @@ -819,8 +819,8 @@ class CoolUtil * @param mainEase Main ease * @param suffix Suffix (Ignored if `mainEase` is `linear`) */ - @:noUsing public static inline function flxeaseFromString(mainEase:String, suffix:String) - return Reflect.field(FlxEase, mainEase + (mainEase == "linear" ? "" : suffix)); + @:noUsing public static inline function flxeaseFromString(mainEase:String, ?suffix:String) + return Reflect.field(FlxEase, mainEase + (mainEase == "linear" || suffix == null ? "" : suffix)); /** * Sorts an array alphabetically. diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 7fb98698..0a1713af 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -7,6 +7,7 @@ import flixel.math.FlxPoint; import flixel.sound.FlxSound; import flixel.text.FlxText; import flixel.tweens.FlxTween; +import flixel.tweens.misc.VarTween; import flixel.ui.FlxBar; import flixel.util.FlxColor; import flixel.util.FlxSort; @@ -18,9 +19,9 @@ import funkin.backend.chart.EventsData; import funkin.backend.scripting.DummyScript; import funkin.backend.scripting.Script; import funkin.backend.scripting.ScriptPack; +import funkin.backend.scripting.events.*; import funkin.backend.scripting.events.gameplay.*; import funkin.backend.scripting.events.note.*; -import funkin.backend.scripting.events.*; import funkin.backend.system.Conductor; import funkin.backend.system.RotatingSpriteGroup; import funkin.editors.SaveWarning; @@ -138,8 +139,8 @@ class PlayState extends MusicBeatState */ public var downscroll(get, set):Bool; - @:dox(hide) private function set_downscroll(v:Bool) {return camHUD.downscroll = v;} - @:dox(hide) private function get_downscroll():Bool {return camHUD.downscroll;} + private inline function set_downscroll(v:Bool) return camHUD.downscroll = v; + private inline function get_downscroll():Bool return camHUD.downscroll; /** * Instrumental sound (Inst.ogg). @@ -225,23 +226,6 @@ class PlayState extends MusicBeatState */ public var doIconBop:Bool = true; - /** - * Whenever cam zooming is enabled, enables on a note hit if not cancelled. - */ - public var camZooming:Bool = false; - /** - * Interval of cam zooming (beats). - * For example: if set to 4, the camera will zoom every 4 beats. - */ - public var camZoomingInterval:Int = Flags.DEFAULT_CAM_ZOOM_INTERVAL; - /** - * How strong the cam zooms should be (defaults to 1) - */ - public var camZoomingStrength:Float = Flags.DEFAULT_CAM_ZOOM_STRENGTH; - /** - * Maximum amount of zoom for the camera. - */ - public var maxCamZoom:Float = Flags.MAX_CAMERA_ZOOM; /** * Current song name (lowercase) */ @@ -383,6 +367,30 @@ class PlayState extends MusicBeatState */ public var camHUDZoomLerp:Float = Flags.DEFAULT_HUD_ZOOM_LERP; + /** + * Whenever cam zooming is enabled, enables on a note hit if not cancelled. + */ + public var camZooming:Bool = false; + /** + * Interval of cam zooming (beats). + * For example: if set to 4, the camera will zoom every 4 beats. + */ + public var camZoomingInterval:Int = Flags.DEFAULT_CAM_ZOOM_INTERVAL; + /** + * How strong the cam zooms should be (defaults to 1) + */ + public var camZoomingStrength:Float = Flags.DEFAULT_CAM_ZOOM_STRENGTH; + /** + * Default multiplier for `maxCamZoom`. + */ + public var maxCamZoomMult:Float = Flags.MAX_CAMERA_ZOOM_MULT; + /** + * Maximum amount of zoom for the camera (based on `maxCamZoomMult` and the camera's zoom IF not set). + */ + public var maxCamZoom(get, default):Float = Math.NaN; + + private inline function get_maxCamZoom() return Math.isNaN(maxCamZoom) ? maxCamZoomMult * defaultCamZoom : maxCamZoom; + /** * Zoom for the pixel assets. */ @@ -849,6 +857,7 @@ class PlayState extends MusicBeatState /** * Function used to update Discord Presence. + * * This function is dynamic, which means you can do `updateDiscordPresence = function() {}` in scripts. */ public dynamic function updateDiscordPresence() @@ -1029,10 +1038,16 @@ class PlayState extends MusicBeatState Note.__customNoteTypeExists = []; } - // keping this for backwards compat - Nex - @:deprecated("resetSongInfos is deprecated, it's now handled in the loading song/weeks functions") + // Backwards compat + @:dox(hide) @:deprecated("resetSongInfos is deprecated, it's now handled in the loading song/weeks functions") public static function resetSongInfos() {} + @:dox(hide) @:deprecated("scrollSpeedTween is deprecated, use eventsTween['scrollSpeed'] instead") + public var scrollSpeedTween(get, set):FlxTween; + private inline function get_scrollSpeedTween() return eventsTween.get("scrollSpeed"); + private inline function set_scrollSpeedTween(val:FlxTween) {eventsTween.set("scrollSpeed", val); return val;} + // End of backwards compat + @:dox(hide) private function generateSong(?songData:ChartData):Void { if (songData == null) songData = SONG; @@ -1342,28 +1357,8 @@ class PlayState extends MusicBeatState while(events.length > 0 && events.last().time <= Conductor.songPosition) executeEvent(events.pop()); - if (generatedMusic && strumLines.members[curCameraTarget] != null) - { - var pos = FlxPoint.get(); - var r = 0; - for(c in strumLines.members[curCameraTarget].characters) { - if (c == null || !c.visible) continue; - var cpos = c.getCameraPosition(); - pos.x += cpos.x; - pos.y += cpos.y; - r++; - //cpos.put(); - } - if (r > 0) { - pos.x /= r; - pos.y /= r; - - var event = gameAndCharsEvent("onCameraMove", EventManager.get(CamMoveEvent).recycle(pos, strumLines.members[curCameraTarget], r)); - if (!event.cancelled) - camFollow.setPosition(pos.x, pos.y); - } - pos.put(); - } + if (generatedMusic) + moveCamera(); if (camZooming) { @@ -1400,7 +1395,48 @@ class PlayState extends MusicBeatState scripts.event("postDraw", e); } - public var scrollSpeedTween:FlxTween = null; + public function moveCamera() if (strumLines.members[curCameraTarget] != null) { + var pos = FlxPoint.weak(); + var amount = calculateCamPos(strumLines.members[curCameraTarget].characters, pos); + + var tween = eventsTween.get("cameraMovement"); + if (tween != null && !tween.finished && tween is VarTween) { + var tween:VarTween = cast tween; + @:privateAccess pos.set(tween._object.x, tween._object.y); + } + + if (amount > 0) { + var event = gameAndCharsEvent("onCameraMove", EventManager.get(CamMoveEvent).recycle(pos, strumLines.members[curCameraTarget], amount)); + if (!event.cancelled) + camFollow.setPosition(pos.x, pos.y); + } + pos.put(); + } + + /** + * Function used to calculate the camera position based on the characters. + * + * This function is dynamic, which means you can do `calculateCamPos = function(chars:Array, pos:FlxPoint) {}` in scripts. + */ + public dynamic function calculateCamPos(chars:Array, pos:FlxPoint):Int { + var r:Int = 0; + for (c in chars) { + if (c == null || !c.visible) continue; + var cpos = c.getCameraPosition(); + pos.x += cpos.x; + pos.y += cpos.y; + r++; + } + + if (r > 0) { + pos.x /= r; + pos.y /= r; + } + + return r; + } + + public var eventsTween:Map = []; public function executeEvent(event:ChartEvent) @:privateAccess { if (event == null || event.params == null) return; @@ -1414,10 +1450,41 @@ class PlayState extends MusicBeatState case "HScript Call": scripts.call(event.params[0], event.params[1].split(',')); case "Camera Movement": + var tween = eventsTween.get("cameraMovement"); + if (tween != null) tween.cancel(); + curCameraTarget = event.params[0]; + if (strumLines.members[curCameraTarget] != null) { + if (event.params[1] == false) { + moveCamera(); + FlxG.camera.snapToTarget(); + } else if (event.params[3] != "CLASSIC" && strumLines.members[curCameraTarget] != null) { + var pos = FlxPoint.weak(), finalPos = FlxPoint.weak(); // pos will be handled in update() through the tween map - Nex + if (calculateCamPos(strumLines.members[curCameraTarget].characters, finalPos) > 0) + eventsTween.set("cameraMovement", FlxTween.tween(pos, {x: finalPos.x, y: finalPos.y}, (Conductor.stepCrochet / 1000) * event.params[2], {ease: CoolUtil.flxeaseFromString(event.params[3], event.params[4])})); + } + } case "Add Camera Zoom": var camera:FlxCamera = event.params[1] == "camHUD" ? camHUD : camGame; camera.zoom += event.params[0]; + case "Camera Zoom": + var cam = event.params[2] == "camHUD" ? camHUD : camGame; + var name = (event.params[2] == "camHUD" ? "camHUD" : "camGame") + ".zoom"; // avoiding having different values from these 2 - Nex + var tween = eventsTween.get(name); + if (tween != null) tween.cancel(); + + var finalZoom:Float = event.params[1]; + if (event.params[6] == true) finalZoom *= cam.zoom; + + if (event.params[0] == false) { + cam.zoom = finalZoom; + if (cam == camHUD) defaultHudZoom = finalZoom; + else defaultCamZoom = finalZoom; + } else + eventsTween.set(name, FlxTween.tween(cam, {zoom: finalZoom}, (Conductor.stepCrochet / 1000) * event.params[3], {ease: CoolUtil.flxeaseFromString(event.params[4], event.params[5]), onUpdate: function(_) { + if (cam == camHUD) defaultHudZoom = cam.zoom; + else defaultCamZoom = cam.zoom; + }})); case "Camera Modulo Change": camZoomingInterval = event.params[0]; camZoomingStrength = event.params[1]; @@ -1430,12 +1497,16 @@ class PlayState extends MusicBeatState camera.flash(event.params[1], (Conductor.stepCrochet / 1000) * event.params[2], null, true); case "BPM Change": // automatically handled by conductor case "Scroll Speed Change": - if (scrollSpeedTween != null) scrollSpeedTween.cancel(); + var tween = eventsTween.get("scrollSpeedTween"); + if (tween != null) tween.cancel(); + + var finalScroll:Float = event.params[1]; + if (event.params[5] == true) finalScroll *= scrollSpeed; if (event.params[0] == false) - scrollSpeed = event.params[1]; + scrollSpeed = finalScroll; else - scrollSpeedTween = FlxTween.tween(this, {scrollSpeed: event.params[1]}, (Conductor.stepCrochet / 1000) * event.params[2], {ease: CoolUtil.flxeaseFromString(event.params[3], event.params[4])}); + eventsTween.set("scrollSpeedTween", FlxTween.tween(this, {scrollSpeed: finalScroll}, (Conductor.stepCrochet / 1000) * event.params[2], {ease: CoolUtil.flxeaseFromString(event.params[3], event.params[4])})); case "Alt Animation Toggle": var strLine = strumLines.members[event.params[2]]; if (strLine != null) {