diff --git a/Project.xml b/Project.xml
index dca9035c..6aba35bc 100644
--- a/Project.xml
+++ b/Project.xml
@@ -125,6 +125,9 @@
+
+
+
diff --git a/assets/songs/guns/cutscene.hx b/assets/songs/guns/cutscene.hx
index 14ee6544..3485f80d 100644
--- a/assets/songs/guns/cutscene.hx
+++ b/assets/songs/guns/cutscene.hx
@@ -4,7 +4,7 @@ var tankman:FunkinSprite;
var tankTalk1, distorto:FlxSound;
function create() {
- FlxG.camera.zoom = 1;
+ FlxTween.tween(FlxG.camera, {zoom: 1}, 0.7, {ease: FlxEase.quadInOut});
game.camHUD.visible = false;
tankman = new FunkinSprite(game.dad.x + game.dad.globalOffset.x + 520, game.dad.y + game.dad.globalOffset.y + 225);
diff --git a/assets/songs/stress/cutscene.hx b/assets/songs/stress/cutscene.hx
index 5df98a5b..d7601ff0 100644
--- a/assets/songs/stress/cutscene.hx
+++ b/assets/songs/stress/cutscene.hx
@@ -8,7 +8,7 @@ var stressCutscene:FlxSound;
var step:Int = 0;
function create() {
- FlxG.camera.zoom = 1;
+ // FlxTween.tween(FlxG.camera, {zoom: 1}, 0.7, {ease: FlxEase.quadInOut});
game.camHUD.visible = false;
game.persistentUpdate = true;
diff --git a/assets/songs/ugh/cutscene.hx b/assets/songs/ugh/cutscene.hx
index a746c3a9..7fafc537 100644
--- a/assets/songs/ugh/cutscene.hx
+++ b/assets/songs/ugh/cutscene.hx
@@ -4,7 +4,7 @@ var tankman:FunkinSprite;
var tankTalk1, tankTalk2, bfBeep, distorto:FlxSound;
function create() {
- FlxG.camera.zoom = 1;
+ FlxTween.tween(FlxG.camera, {zoom: 1}, 0.7, {ease: FlxEase.quadInOut});
game.camHUD.visible = false;
tankman = new FunkinSprite(game.dad.x + game.dad.globalOffset.x + 520, game.dad.y + game.dad.globalOffset.y + 225);
diff --git a/buildnumber.txt b/buildnumber.txt
index cf548ec4..4063cbd0 100644
--- a/buildnumber.txt
+++ b/buildnumber.txt
@@ -1 +1 @@
-2219
\ No newline at end of file
+2223
\ No newline at end of file
diff --git a/source/funkin/cutscenes/Cutscene.hx b/source/funkin/cutscenes/Cutscene.hx
index 72507fb9..f13e5a1a 100644
--- a/source/funkin/cutscenes/Cutscene.hx
+++ b/source/funkin/cutscenes/Cutscene.hx
@@ -1,5 +1,8 @@
package funkin.cutscenes;
+/**
+ * Substate made for cutscenes.
+ */
class Cutscene extends MusicBeatSubstate {
var __callback:Void->Void;
var game:PlayState = PlayState.instance;
diff --git a/source/funkin/cutscenes/DialogueCutscene.hx b/source/funkin/cutscenes/DialogueCutscene.hx
index ce41c3fd..58766b30 100644
--- a/source/funkin/cutscenes/DialogueCutscene.hx
+++ b/source/funkin/cutscenes/DialogueCutscene.hx
@@ -7,6 +7,9 @@ import openfl.utils.Assets;
import funkin.cutscenes.dialogue.*;
import haxe.xml.Access;
+/**
+ * Substate made for dialogue cutscenes. To use it in a scripted cutscene, call `startDialogue`.
+ */
class DialogueCutscene extends Cutscene {
public var dialoguePath:String;
public var dialogueData:Access;
diff --git a/source/funkin/cutscenes/ScriptedCutscene.hx b/source/funkin/cutscenes/ScriptedCutscene.hx
index 3bc8d977..f3b4e5ac 100644
--- a/source/funkin/cutscenes/ScriptedCutscene.hx
+++ b/source/funkin/cutscenes/ScriptedCutscene.hx
@@ -4,6 +4,10 @@ import funkin.scripting.DummyScript;
import flixel.addons.transition.FlxTransitionableState;
import funkin.scripting.Script;
+/**
+ * Substate made for scripted cutscenes.
+ * To add cutscenes to your songs, add a `cutscene.hx` file in your song's directory (ex: `songs/song/cutscene.hx`)
+ */
class ScriptedCutscene extends Cutscene {
var script:Script;
diff --git a/source/funkin/cutscenes/VideoCutscene.hx b/source/funkin/cutscenes/VideoCutscene.hx
index ccb54eb5..75a359e4 100644
--- a/source/funkin/cutscenes/VideoCutscene.hx
+++ b/source/funkin/cutscenes/VideoCutscene.hx
@@ -5,6 +5,9 @@ import flixel.FlxG;
import flixel.FlxCamera;
import openfl.utils.Assets;
+/**
+ * Substate made for video cutscenes. To use it in a scripted cutscene, call `startVideo`.
+ */
class VideoCutscene extends Cutscene {
var path:String;
var localPath:String;
diff --git a/source/funkin/debug/AnimationDebug.hx b/source/funkin/debug/AnimationDebug.hx
deleted file mode 100644
index bb2c9c80..00000000
--- a/source/funkin/debug/AnimationDebug.hx
+++ /dev/null
@@ -1,197 +0,0 @@
-package funkin.debug;
-
-import funkin.game.Character;
-import flixel.FlxG;
-import flixel.FlxObject;
-import flixel.FlxSprite;
-import flixel.FlxState;
-import flixel.addons.display.FlxGridOverlay;
-import flixel.group.FlxGroup.FlxTypedGroup;
-import flixel.text.FlxText;
-import flixel.util.FlxColor;
-
-/**
- *DEBUG MODE
- */
-class AnimationDebug extends FlxState
-{
- var bf:Character;
- var dad:Character;
- var char:Character;
- var textAnim:FlxText;
- var dumbTexts:FlxTypedGroup;
- var animList:Array = [];
- var curAnim:Int = 0;
- var isDad:Bool = true;
- var daAnim:String = 'spooky';
- var camFollow:FlxObject;
-
- public function new(daAnim:String = 'spooky')
- {
- super();
- this.daAnim = daAnim;
- }
-
- override function create()
- {
- if (FlxG.sound.music != null)
- FlxG.sound.music.stop();
-
- var gridBG:FlxSprite = FlxGridOverlay.create(10, 10);
- gridBG.scrollFactor.set(0.5, 0.5);
- add(gridBG);
-
- if (daAnim == 'bf')
- isDad = false;
-
- if (isDad)
- {
- dad = new Character(0, 0, daAnim);
- dad.screenCenter();
- dad.debugMode = true;
- add(dad);
-
- char = dad;
- dad.flipX = false;
- }
- else
- {
- bf = new Character(0, 0, daAnim, true);
- bf.screenCenter();
- bf.debugMode = true;
- add(bf);
-
- char = bf;
- bf.flipX = false;
- }
-
- dumbTexts = new FlxTypedGroup();
- add(dumbTexts);
-
- textAnim = new FlxText(300, 16);
- textAnim.size = 26;
- textAnim.scrollFactor.set();
- add(textAnim);
-
- genBoyOffsets();
-
- camFollow = new FlxObject(0, 0, 2, 2);
- camFollow.screenCenter();
- add(camFollow);
-
- FlxG.camera.follow(camFollow);
-
- super.create();
- }
-
- function genBoyOffsets(pushList:Bool = true):Void
- {
- var daLoop:Int = 0;
-
- for (anim => offsets in char.animOffsets)
- {
- var text:FlxText = new FlxText(10, 20 + (18 * daLoop), 0, anim + ": " + offsets, 15);
- text.scrollFactor.set();
- text.color = FlxColor.BLUE;
- dumbTexts.add(text);
-
- if (pushList)
- animList.push(anim);
-
- daLoop++;
- }
- }
-
- function updateTexts():Void
- {
- dumbTexts.forEach(function(text:FlxText)
- {
- text.kill();
- dumbTexts.remove(text, true);
- });
- }
-
- override function update(elapsed:Float)
- {
- textAnim.text = char.animation.curAnim.name;
-
- if (FlxG.keys.justPressed.E)
- FlxG.camera.zoom += 0.25;
- if (FlxG.keys.justPressed.Q)
- FlxG.camera.zoom -= 0.25;
-
- if (FlxG.keys.pressed.I || FlxG.keys.pressed.J || FlxG.keys.pressed.K || FlxG.keys.pressed.L)
- {
- if (FlxG.keys.pressed.I)
- camFollow.velocity.y = -90;
- else if (FlxG.keys.pressed.K)
- camFollow.velocity.y = 90;
- else
- camFollow.velocity.y = 0;
-
- if (FlxG.keys.pressed.J)
- camFollow.velocity.x = -90;
- else if (FlxG.keys.pressed.L)
- camFollow.velocity.x = 90;
- else
- camFollow.velocity.x = 0;
- }
- else
- {
- camFollow.velocity.set();
- }
-
- if (FlxG.keys.justPressed.W)
- {
- curAnim -= 1;
- }
-
- if (FlxG.keys.justPressed.S)
- {
- curAnim += 1;
- }
-
- if (curAnim < 0)
- curAnim = animList.length - 1;
-
- if (curAnim >= animList.length)
- curAnim = 0;
-
- if (FlxG.keys.justPressed.S || FlxG.keys.justPressed.W || FlxG.keys.justPressed.SPACE)
- {
- char.playAnim(animList[curAnim]);
-
- updateTexts();
- genBoyOffsets(false);
- }
-
- var upP = FlxG.keys.anyJustPressed([UP]);
- var rightP = FlxG.keys.anyJustPressed([RIGHT]);
- var downP = FlxG.keys.anyJustPressed([DOWN]);
- var leftP = FlxG.keys.anyJustPressed([LEFT]);
-
- var holdShift = FlxG.keys.pressed.SHIFT;
- var multiplier = 1;
- if (holdShift)
- multiplier = 10;
-
- if (upP || rightP || downP || leftP)
- {
- updateTexts();
- if (upP)
- char.animOffsets.get(animList[curAnim]).y += 1 * multiplier;
- if (downP)
- char.animOffsets.get(animList[curAnim]).y -= 1 * multiplier;
- if (leftP)
- char.animOffsets.get(animList[curAnim]).x += 1 * multiplier;
- if (rightP)
- char.animOffsets.get(animList[curAnim]).x -= 1 * multiplier;
-
- updateTexts();
- genBoyOffsets(false);
- char.playAnim(animList[curAnim]);
- }
-
- super.update(elapsed);
- }
-}
diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx
index 5dc1e451..b8431a2c 100644
--- a/source/funkin/game/PlayState.hx
+++ b/source/funkin/game/PlayState.hx
@@ -193,7 +193,7 @@ class PlayState extends MusicBeatState
/**
* Previous cam follow.
*/
- private static var prevCamFollow:FlxObject;
+ private static var smoothTransitionData:PlayStateTransitionData;
/**
* All of the strum line notes.
@@ -553,8 +553,6 @@ class PlayState extends MusicBeatState
}
var dadMidpoint = dad.getGraphicMidpoint();
- var camPos:FlxPoint = new FlxPoint(dadMidpoint.x, dadMidpoint.y);
- dadMidpoint.put();
comboGroup = new FlxSpriteGroup(FlxG.width * 0.55, (FlxG.height * 0.5) - 60);
@@ -565,6 +563,9 @@ class PlayState extends MusicBeatState
if (SONG.stage == null || SONG.stage.trim() == "") SONG.stage = "stage";
add(stage = new Stage(SONG.stage));
+ var camPos:FlxPoint = new FlxPoint(dadMidpoint.x, dadMidpoint.y);
+ dadMidpoint.put();
+
switch(SONG.song) {
// case "":
@@ -619,19 +620,19 @@ class PlayState extends MusicBeatState
generateSong(SONG);
- if (prevCamFollow != null)
- {
- camFollow = prevCamFollow;
- prevCamFollow = null;
- } else {
- camFollow = new FlxObject(0, 0, 2, 2);
- camFollow.setPosition(camPos.x, camPos.y);
- }
+ camFollow = new FlxObject(0, 0, 2, 2);
+ camFollow.setPosition(camPos.x, camPos.y);
add(camFollow);
FlxG.camera.follow(camFollow, LOCKON, 0.04);
FlxG.camera.zoom = defaultCamZoom;
- FlxG.camera.focusOn(camFollow.getPosition());
+ if (smoothTransitionData != null && smoothTransitionData.stage == curStage) {
+ FlxG.camera.scroll.set(smoothTransitionData.camX, smoothTransitionData.camY);
+ FlxG.camera.zoom = smoothTransitionData.camZoom;
+ FlxTransitionableState.skipNextTransIn = true;
+ } else {
+ FlxG.camera.focusOn(camFollow.getPosition());
+ }
FlxG.worldBounds.set(0, 0, FlxG.width, FlxG.height);
@@ -1356,9 +1357,15 @@ class PlayState extends MusicBeatState
trace('LOADING NEXT SONG');
trace(PlayState.storyPlaylist[0].toLowerCase(), difficulty);
+ smoothTransitionData = {
+ stage: curStage,
+ camX: FlxG.camera.scroll.x,
+ camY: FlxG.camera.scroll.y,
+ camZoom: FlxG.camera.zoom
+ };
FlxTransitionableState.skipNextTransIn = true;
FlxTransitionableState.skipNextTransOut = true;
- prevCamFollow = camFollow;
+
PlayState.SONG = Song.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), difficulty);
FlxG.sound.music.stop();
@@ -1705,4 +1712,11 @@ class ComboRating {
this.rating = rating;
this.color = color;
}
+}
+
+typedef PlayStateTransitionData = {
+ var stage:String;
+ var camX:Float;
+ var camY:Float;
+ var camZoom:Float;
}
\ No newline at end of file
diff --git a/source/funkin/macros/BuildCounterMacro.hx b/source/funkin/macros/BuildCounterMacro.hx
index 48a07d5f..6f18d997 100644
--- a/source/funkin/macros/BuildCounterMacro.hx
+++ b/source/funkin/macros/BuildCounterMacro.hx
@@ -8,8 +8,15 @@ import haxe.macro.Context;
import sys.io.File;
#end
+/**
+ * Macro that loads the current build number from `buildnumber.txt`, then make it available as an integer.
+ *
+ * `buildnumber.txt` is automatically incremented when the engine is launched with `lime test windows`.
+ */
class BuildCounterMacro {
- // non functional
+ /**
+ * Returns the obtained build number.
+ */
public static macro function getBuildNumber():haxe.macro.Expr.ExprOf {
#if !display
var buildNum:Int = Std.parseInt(File.getContent("buildnumber.txt"));
diff --git a/source/funkin/macros/EventMacro.hx b/source/funkin/macros/EventMacro.hx
index bbcb8b37..de6a4d7d 100644
--- a/source/funkin/macros/EventMacro.hx
+++ b/source/funkin/macros/EventMacro.hx
@@ -1,7 +1,10 @@
package funkin.macros;
#if macro
-
+/**
+ * Macro that generates all additional fields, making events much easier to code in.
+ * It adds the `recycle` function, which allows you to "reset" an event's values.
+ */
class EventMacro {
public static function build():Array {
var fields = Context.getBuildFields();
diff --git a/source/funkin/macros/FlxSpriteProxyMacro.hx b/source/funkin/macros/FlxSpriteProxyMacro.hx
deleted file mode 100644
index 1e111619..00000000
--- a/source/funkin/macros/FlxSpriteProxyMacro.hx
+++ /dev/null
@@ -1,106 +0,0 @@
-package funkin.macros;
-#if macro
-
-class FlxSpriteProxyMacro {
- public static function build() {
- return Context.getBuildFields();
-
- /*
- //TODO: FIX ALL OF THIS
- var parentFields:Array = [];
- var parentFieldsNames:Array = [];
- var superClass = Context.getLocalClass().get().superClass.t.get();
- while(superClass != null) {
- for(e in superClass.fields.get()) {
- if (!parentFieldsNames.contains(e.name) && !e.kind.match(FMethod(MethInline))) {
- parentFields.push(e);
- parentFieldsNames.push(e.name);
- }
- }
- if (superClass.superClass != null)
- superClass = superClass.superClass.t.get();
- else
- superClass = null;
- }
- parentFieldsNames = [];
- trace(Context.getLocalType());
- var fields:Array = Context.getBuildFields();
-
- // loop through all parent functions
- for(f in parentFields) {
- for(field in fields)
- if (field.name == f.name)
- continue;
- switch(f.type) {
- case TLazy(lazyFunc):
- switch(lazyFunc()) {
- case TFun(args, ret):
-
- // its a function / getter / setter! overriding
- var overFunc:Function = {
- args: [for(a in args) {
- value: null,
- type: TypeTools.toComplexType(a.t),
- opt: a.opt,
- name: a.name
- }]
- };
- var funcName = f.name;
-
- var argNames:Array = [];
- for(a in args)
- argNames.push({
- pos: Context.makePosition({
- min: 1,
- max: 2,
- file: "DeezNuts.hx"
- }),
- expr: EConst(CIdent(a.name))
- });
- if (ret == null) {
- overFunc.expr = macro {
- function test(p1) {
- trace(p1);
- }
- if (this.proxy != null) {
- this.proxy.$funcName($a{argNames});
- return;
- }
- super.$funcName($a{argNames});
- };
- } else {
- overFunc.expr = macro {
- if (this.proxy != null) {
- return this.proxy.$funcName($a{argNames});
- }
- return super.$funcName($a{argNames});
- };
- }
- var field:Field = {
- pos: Context.makePosition({
- min: 0,
- max: 0,
- file: "DeezNuts.hx"
- }),
- name: f.name,
- kind: FFun(overFunc),
- access: [AOverride, APublic]
- };
- var p = new Printer();
- trace(p.printField(field));
- fields.push(field);
- default:
- // nothing
- }
- default:
- // nothing
- }
- }
-
-
-
- return fields;
- */
- }
-}
-#end
\ No newline at end of file
diff --git a/source/funkin/macros/OptionsMacro.hx b/source/funkin/macros/OptionsMacro.hx
index 29e8c867..c5f73214 100644
--- a/source/funkin/macros/OptionsMacro.hx
+++ b/source/funkin/macros/OptionsMacro.hx
@@ -3,6 +3,10 @@ package funkin.macros;
#if macro
using StringTools;
+
+/**
+ * Macro used in the options class to add getters for the SOLO keybinds.
+ */
class OptionsMacro {
public static function build():Array {
var fields = Context.getBuildFields();
diff --git a/source/funkin/macros/ScriptsMacro.hx b/source/funkin/macros/ScriptsMacro.hx
index cbadbdfe..681627b5 100644
--- a/source/funkin/macros/ScriptsMacro.hx
+++ b/source/funkin/macros/ScriptsMacro.hx
@@ -5,6 +5,9 @@ import haxe.macro.Context;
import haxe.macro.Compiler;
import haxe.macro.Expr;
+/**
+ * Macros containing additional help functions to expand HScript capabilities.
+ */
class ScriptsMacro {
public static function addAdditionalClasses() {
Compiler.include("flixel");
diff --git a/source/funkin/system/FunkinGame.hx b/source/funkin/system/FunkinGame.hx
new file mode 100644
index 00000000..0268c4e0
--- /dev/null
+++ b/source/funkin/system/FunkinGame.hx
@@ -0,0 +1,20 @@
+package funkin.system;
+
+import flixel.FlxGame;
+
+class FunkinGame extends FlxGame {
+ var skipNextTickUpdate:Bool = false;
+ public override function switchState() {
+ super.switchState();
+ // draw once to put all images in gpu then put the last update time to now to prevent lag spikes or whatever
+ draw();
+ _total = ticks = getTicks();
+ skipNextTickUpdate = true;
+ }
+
+ public override function onEnterFrame(t) {
+ if (skipNextTickUpdate != (skipNextTickUpdate = false))
+ _total = ticks = getTicks();
+ super.onEnterFrame(t);
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/system/Main.hx b/source/funkin/system/Main.hx
index a3073244..d2503354 100644
--- a/source/funkin/system/Main.hx
+++ b/source/funkin/system/Main.hx
@@ -100,7 +100,7 @@ class Main extends Sprite
}
- addChild(new FlxGame(gameWidth, gameHeight, null, zoom, framerate, framerate, skipSplash, startFullscreen));
+ addChild(new FunkinGame(gameWidth, gameHeight, null, zoom, framerate, framerate, skipSplash, startFullscreen));
loadGameSettings();
// FlxG.switchState(new TitleState());
FlxG.switchState(new funkin.menus.BetaWarningState());
diff --git a/source/funkin/system/Paths.hx b/source/funkin/system/Paths.hx
index 61ceabaf..790d5cbf 100644
--- a/source/funkin/system/Paths.hx
+++ b/source/funkin/system/Paths.hx
@@ -41,6 +41,9 @@ class Paths
static public function video(key:String) {
return getPath('videos/$key.mp4', BINARY, null);
}
+ static public function ndll(key:String) {
+ return getPath('ndlls/$key.ndll', BINARY, null);
+ }
static public function getLibraryPath(file:String, library = "default")
{
diff --git a/source/funkin/utils/NdllUtil.hx b/source/funkin/utils/NdllUtil.hx
index 52720f8d..466848a4 100644
--- a/source/funkin/utils/NdllUtil.hx
+++ b/source/funkin/utils/NdllUtil.hx
@@ -1,5 +1,69 @@
package funkin.utils;
+import openfl.utils.Assets;
+import lime.app.Application;
+
+/**
+ * Small util that allows you to load any function from ndlls via `getFunction`.
+ *
+ * NDLLs must be in your mod's "ndlls" folder, and must follow this name scheme:
+ * - `name-windows.ndll` for Windows targeted ndlls
+ * - `name-linux.ndll` for Linux targeted ndlls
+ * - `name-mac.ndll` for Mac targeted ndlls
+ *
+ * If:
+ * - The platform does not support NDLLs
+ * - The NDLL is not found
+ * - The Function cannot be found in the NDLL
+ * then an empty function will be returned instead, and a message will be shown in logs.
+ */
class NdllUtil {
+ #if NDLLS_SUPPORTED
+ #if windows public static final os:String = "windows"; #end
+ #if linux public static final os:String = "linux"; #end
+ #if macos public static final os:String = "mac"; #end
+ #if android public static final os:String = "android"; #end
+ #end
+
+ /**
+ * Returns an function from a Haxe NDLL.
+ *
+ * @param ndll Name of the NDLL.
+ * @param name Name of the function.
+ * @param args Number of arguments of that function.
+ */
+ public static function getFunction(ndll:String, name:String, args:Int) {
+ #if NDLLS_SUPPORTED
+ return getFunctionFromPath(Paths.ndll('$ndll-$os'), name, args);
+ #else
+ Logs.trace('NDLLs are not supported on this platform.', WARNING);
+ return function() {};
+ #end
+ }
+ /**
+ * Returns an function from a Haxe NDLL at specified path.
+ *
+ * @param ndll Asset path to the NDLL.
+ * @param name Name of the function.
+ * @param args Number of arguments of that function.
+ */
+ public static function getFunctionFromPath(ndll:String, name:String, args:Int) {
+ #if NDLLS_SUPPORTED
+ if (!Assets.exists(ndll)) {
+ Logs.trace('Couldn\'t find ndll at ${ndll}.', WARNING);
+ return function() {};
+ }
+ var func = lime.system.CFFI.load(Assets.getPath(ndll), name, args);
+
+ if (func == null) {
+ Logs.trace('Method ${name} in ndll ${ndll} with ${args} args was not found.', ERROR);
+ return function() {};
+ }
+ return func;
+ #else
+ Logs.trace('NDLLs are not supported on this platform.', WARNING);
+ #end
+ return function() {};
+ }
}
\ No newline at end of file