diff --git a/.github/ISSUE_TEMPLATE/compiling.md b/.github/ISSUE_TEMPLATE/compiling.md
index bd6f439c..4f516469 100644
--- a/.github/ISSUE_TEMPLATE/compiling.md
+++ b/.github/ISSUE_TEMPLATE/compiling.md
@@ -16,5 +16,5 @@ labels: compiling help
[ ] Mac
### Explain your issue
-##### Please check first if your issue haven't already been reported yet.
-[weed]: <> (MAKE SURE TO ADD A SCREENSHOT OF THE CRASH HANDLER, IF AVAILABLE, AND STEPS TO REPRODUCE THE BUG!)
\ No newline at end of file
+##### Please check first if your issue haven't already been reported yet, and make sure you ran the `update.bat` file before building.
+[weed]: <> (MAKE SURE TO ADD A SCREENSHOT OF THE CRASH HANDLER, IF AVAILABLE, AND STEPS TO REPRODUCE THE BUG!)
diff --git a/.gitignore b/.gitignore
index bee4df7e..43d2c37c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@ latest/windows/obj/
latest/windows/haxe/
.vscode/*
!.vscode/settings.json
-.haxelib/
\ No newline at end of file
+.haxelib/
+mods/Baldi's Basics in Funkin'/
\ No newline at end of file
diff --git a/README.md b/README.md
index 6c8f365c..3b8538b9 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ Latest builds for the engine can be found in the [Actions](https://github.com/Yo
Windows
##### Tested on Windows 10 21H2
-1. Install the [latest version of Haxe](https://haxe.org/download/).
+1. Install [version 4.2.5 of Haxe](https://haxe.org/download/version/4.2.5/).
2. Download [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe)
3. Wait for the Visual Studio Installer to install
4. On the Visual Studio installer screen, go to the "Individual components" tab and only select those options:
@@ -63,7 +63,7 @@ Latest builds for the engine can be found in the [Actions](https://github.com/Yo
Linux
##### Requires testing
-1. Install the [latest version of Haxe](https://haxe.org/download/).
+1. Install [version 4.2.5 of Haxe](https://haxe.org/download/version/4.2.5/).
2. Install `g++`, if not present already.
3. Download and install [`git-scm`](https://git-scm.com/download/linux).
4. Open a terminal in the Codename Engine source folder, and run `update.sh`.
@@ -74,7 +74,7 @@ Latest builds for the engine can be found in the [Actions](https://github.com/Yo
MacOS
##### Requires testing
-1. Install the [latest version of Haxe](https://haxe.org/download/).
+1. Install [version 4.2.5 of Haxe](https://haxe.org/download/version/4.2.5/).
2. Install `Xcode` to allow C++ app building.
3. Download and install [`git-scm`](https://git-scm.com/download/mac).
4. Open a terminal in the Codename Engine source folder, and run `update.sh`.
diff --git a/TODO.md b/TODO.md
index b5d1e224..e5dc40d8 100644
--- a/TODO.md
+++ b/TODO.md
@@ -11,27 +11,39 @@
- [x] Fix the strumlines bar when zooming out
- [x] Algorithm to make note placements faster & sorted out
- [x] Play here working
- - [ ] Events (including Camera Movement)
+ - [x] Cap zoom in & out
+ - [x] Events (including Camera Movement)
+ - [x] Metadata Editing
- [ ] Custom Notes
- - [ ] Metadata Editing
- - [ ] Cap zoom in & out
- [ ] Step based zooming
+- [ ] Character Editor
+ - [ ] Change ghost anim
+ - [ ] Make gf characters be positioned correctly
+ - [ ] Make it so dragging the character moves the offset
+ - [ ] Disable volume controls when selecting stuff
- [ ] Use latest hxCodec
- [ ] Re-export gf-christmas, gf-car and gf-tankmen with padding
- [ ] Multiple state scripts & Multiple global scripts
-- [ ] Re-add shader camera stuff
+- [x] Re-add shader camera stuff
- [ ] Make update.bat use libs.xml instead of hmm.json
- [ ] New Switch Mod menu
-- [ ] Port shader camera stuff over to new flixel fork
+- [x] Port shader camera stuff over to new flixel fork
- [ ] Add note offset
- [ ] Dialogue
- [ ] Finish Base Game port
-- [ ] Character Editor
+- [ ] Add credits
+- [ ] Verbose mode (`--verbose`)
+- [ ] Tooltips
- [ ] Fix bugs
## `v1.1.0`
- [ ] Charter Waveforms (maybe in v1.1.0, dont wanna slow shit down)
- [ ] Custom classes
+ - [ ] Proper super overrides (FlxText_HSX doesn't allow overrides for update because of this)
+ - [ ] Custom Class extending Custom Classes
+ - [ ] Statics on Custom Classes
+ - [ ] Fix Bugs
- [ ] Full 3D support via stages files
- [ ] Multithread sprites loading (for faster load times) ?
-- [ ] Stage Editor
\ No newline at end of file
+- [ ] Stage Editor
+- [ ] Optimize flxanimate isOnScreen
\ No newline at end of file
diff --git a/assets/data/characters/bf-pixel.xml b/assets/data/characters/bf-pixel.xml
index c6e103b1..fe0231cb 100644
--- a/assets/data/characters/bf-pixel.xml
+++ b/assets/data/characters/bf-pixel.xml
@@ -7,4 +7,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/data/characters/gf-car.xml b/assets/data/characters/gf-car.xml
index c1ee735b..54062e72 100644
--- a/assets/data/characters/gf-car.xml
+++ b/assets/data/characters/gf-car.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/assets/data/characters/gf-christmas.xml b/assets/data/characters/gf-christmas.xml
index a6282cd2..89a9947a 100644
--- a/assets/data/characters/gf-christmas.xml
+++ b/assets/data/characters/gf-christmas.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/assets/data/characters/gf-tankmen.xml b/assets/data/characters/gf-tankmen.xml
index a9bdabb6..70d29d7e 100644
--- a/assets/data/characters/gf-tankmen.xml
+++ b/assets/data/characters/gf-tankmen.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/assets/data/scripts/pixel.hx b/assets/data/scripts/pixel.hx
index 36fe9a6a..b0976cf2 100644
--- a/assets/data/scripts/pixel.hx
+++ b/assets/data/scripts/pixel.hx
@@ -1,5 +1,5 @@
import funkin.game.HudCamera;
-import funkin.scripting.events.NoteHitEvent;
+import funkin.backend.scripting.events.NoteHitEvent;
public var pixelNotesForBF = true;
public var pixelNotesForDad = true;
@@ -82,16 +82,16 @@ function postCreate() {
PauseSubState.script = 'data/scripts/week6-pause';
}
if (enableCameraHacks) {
- FlxG.camera.antialiasing = false;
- FlxG.camera.pixelPerfectRender = true;
-
-
- iconP1.antialiasing = false;
- iconP2.antialiasing = false;
+ camGame.pixelPerfectRender = true;
+ camGame.antialiasing = false;
makeCameraPixely(camGame);
defaultCamZoom /= daPixelZoom;
}
+
+ iconP1.antialiasing = false;
+ iconP2.antialiasing = false;
+
if (enablePixelGameOver) {
gameOverSong = "pixel/gameOver";
lossSFX = "pixel/gameOverSFX";
@@ -100,7 +100,7 @@ function postCreate() {
}
function onStartCountdown() {
- var newNoteCamera = new HudCamera();
+ /*var newNoteCamera = new HudCamera();
newNoteCamera.bgColor = 0; // transparent
FlxG.cameras.add(newNoteCamera, false);
@@ -114,7 +114,7 @@ function onStartCountdown() {
i++;
}
}
- makeCameraPixely(newNoteCamera);
+ makeCameraPixely(newNoteCamera);*/
}
/**
@@ -122,6 +122,8 @@ function onStartCountdown() {
*/
public function makeCameraPixely(cam) {
cam.pixelPerfectRender = true;
+ if(!enableCameraHacks) return;
+
cam.zoom /= Math.min(FlxG.scaleMode.scale.x, FlxG.scaleMode.scale.y) * daPixelZoom;
var shad = new CustomShader('pixelZoomShader');
diff --git a/assets/data/stages/school.hx b/assets/data/stages/school.hx
index f73625fc..e88c2d1a 100644
--- a/assets/data/stages/school.hx
+++ b/assets/data/stages/school.hx
@@ -1,8 +1,9 @@
function create() {
importScript("data/scripts/pixel");
- if (PlayState.SONG.song == "Roses") {
+ if (PlayState.SONG.meta.name.toLowerCase() == "roses") {
flabbergastGirls();
}
+ bgGirls.animation.play("danceLeft", true); // horrible fix, please fix later
}
public function flabbergastGirls() {
diff --git a/assets/images/editors/charter/add-strumline.png b/assets/images/editors/charter/add-strumline.png
new file mode 100644
index 00000000..d205c17e
Binary files /dev/null and b/assets/images/editors/charter/add-strumline.png differ
diff --git a/assets/images/editors/charter/event-icons.png b/assets/images/editors/charter/event-icons.png
deleted file mode 100644
index fee6d3d0..00000000
Binary files a/assets/images/editors/charter/event-icons.png and /dev/null differ
diff --git a/assets/images/editors/charter/event-icons/Alt Animation Toggle.png b/assets/images/editors/charter/event-icons/Alt Animation Toggle.png
new file mode 100644
index 00000000..f2180276
Binary files /dev/null and b/assets/images/editors/charter/event-icons/Alt Animation Toggle.png differ
diff --git a/assets/images/editors/charter/event-icons/BPM Change.png b/assets/images/editors/charter/event-icons/BPM Change.png
new file mode 100644
index 00000000..a59fe83a
Binary files /dev/null and b/assets/images/editors/charter/event-icons/BPM Change.png differ
diff --git a/assets/images/editors/charter/event-icons/Camera Movement.png b/assets/images/editors/charter/event-icons/Camera Movement.png
new file mode 100644
index 00000000..c7b117f0
Binary files /dev/null and b/assets/images/editors/charter/event-icons/Camera Movement.png differ
diff --git a/assets/images/editors/charter/event-icons/HScript Call.png b/assets/images/editors/charter/event-icons/HScript Call.png
new file mode 100644
index 00000000..ab2ff5e1
Binary files /dev/null and b/assets/images/editors/charter/event-icons/HScript Call.png differ
diff --git a/assets/images/editors/charter/event-icons/Unknown.png b/assets/images/editors/charter/event-icons/Unknown.png
new file mode 100644
index 00000000..ba1116fd
Binary files /dev/null and b/assets/images/editors/charter/event-icons/Unknown.png differ
diff --git a/assets/shaders/engine/colorPicker.frag b/assets/shaders/engine/colorPicker.frag
new file mode 100644
index 00000000..c773584e
--- /dev/null
+++ b/assets/shaders/engine/colorPicker.frag
@@ -0,0 +1,20 @@
+#pragma header
+
+vec3 hsvToRgb(vec3 c) { //hsv.x = hue, hsv.y = saturation, hsv.z = value
+ vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+ vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+ return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+}
+
+uniform float hue;
+
+void main()
+{
+ vec2 uv = openfl_TextureCoordv.xy;
+ vec4 col = texture2D(bitmap, uv);
+
+ if (col.a > 0.0)
+ col = vec4(hsvToRgb(vec3(hue, uv.x, 1.0-uv.y)), 0.0);
+
+ gl_FragColor = col;
+}
\ No newline at end of file
diff --git a/assets/shaders/engine/editorBlurFast.frag b/assets/shaders/engine/editorBlurFast.frag
new file mode 100644
index 00000000..514670e4
--- /dev/null
+++ b/assets/shaders/engine/editorBlurFast.frag
@@ -0,0 +1,48 @@
+#pragma header
+
+float uBlur = 0.075;
+float uBrightness = 0.9;
+
+vec4 getColor(vec2 pos) {
+ if (pos.x < 0) pos.x = 0;
+ else if (pos.x > 1.0 - (1.0 / openfl_TextureSize.x)) pos.x = 1.0 - (1.0 / openfl_TextureSize.x);
+ if (pos.y < 0) pos.y = 0;
+ else if (pos.y > 1.0 - (1.0 / openfl_TextureSize.y)) pos.y = 1.0 - (1.0 / openfl_TextureSize.y);
+ // return textureCam(bitmap, pos);
+ return flixel_texture2D(bitmap, pos);
+}
+
+vec2 fixvec2(float x, float y) { // makes an uv the same across sizes
+ vec2 val = vec2(x, y);
+ val.xy *= vec2(1280.0, 720.0);
+ val.xy /= openfl_TextureSize.xy;
+ return val;
+}
+vec2 fixvec2(vec2 uv) { // makes an uv the same across sizes
+ vec2 val = uv;
+ val.xy *= vec2(1280.0, 720.0);
+ val.xy /= openfl_TextureSize.xy;
+ return val;
+}
+
+vec2 random(vec2 p) {
+ p = vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3)));
+ return fract(sin(p)*4375.5);
+}
+
+void main() {
+ // vec2 camPos = getCamPos(openfl_TextureCoordv);
+ vec2 camPos = openfl_TextureCoordv;
+ if (camPos.x < 0 || camPos.x > 1 || camPos.y < 0 || camPos.y > 1)
+ return;
+
+ //vec4 color = getColor(camPos);
+
+ vec2 blur = vec2(uBlur) * vec2(1.0, openfl_TextureSize.x / openfl_TextureSize.y);
+
+ vec4 a = getColor(camPos+fixvec2(random(camPos)*blur - blur / 2.0)) * uBrightness;
+ a += getColor(camPos+fixvec2(random(camPos+0.1)*blur - blur / 2.0)) * uBrightness;
+ a += getColor(camPos+fixvec2(random(camPos+0.2)*blur - blur / 2.0)) * uBrightness;
+ //a += getColor(camPos+fixvec2(random(camPos+0.3)*blur - blur / 2.0)) * uBrightness;
+ gl_FragColor = a / 4.0;
+}
\ No newline at end of file
diff --git a/assets/week6/images/weeb/gfPixel - Copy.png b/assets/week6/images/weeb/gfPixel - Copy.png
deleted file mode 100644
index b298a13a..00000000
Binary files a/assets/week6/images/weeb/gfPixel - Copy.png and /dev/null differ
diff --git a/assets/week6/images/weeb/gfPixel - Copy.xml b/assets/week6/images/weeb/gfPixel - Copy.xml
deleted file mode 100644
index 87b998c8..00000000
--- a/assets/week6/images/weeb/gfPixel - Copy.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/commandline/commands/Compiler.hx b/commandline/commands/Compiler.hx
index 24ace428..0e0eb692 100644
--- a/commandline/commands/Compiler.hx
+++ b/commandline/commands/Compiler.hx
@@ -5,13 +5,13 @@ class Compiler {
__build(args, ["test", getBuildTarget(), "-D", "TEST_BUILD"]);
}
public static function build(args:Array) {
- __build(args, ["build", "windows", "-D", "TEST_BUILD"]);
+ __build(args, ["build", getBuildTarget(), "-D", "TEST_BUILD"]);
}
public static function release(args:Array) {
- __build(args, ["build", "windows"]);
+ __build(args, ["build", getBuildTarget()]);
}
public static function testRelease(args:Array) {
- __build(args, ["test", "windows"]);
+ __build(args, ["test", getBuildTarget()]);
}
private static function __build(args:Array, arg:Array) {
diff --git a/commandline/commands/Update.hx b/commandline/commands/Update.hx
index 42c29a61..6404ab35 100644
--- a/commandline/commands/Update.hx
+++ b/commandline/commands/Update.hx
@@ -1,5 +1,6 @@
package commands;
+import haxe.xml.Access;
import haxe.Json;
import sys.io.File;
import sys.io.Process;
@@ -7,16 +8,34 @@ import sys.FileSystem;
class Update {
public static function main(args:Array) {
- // to prevent messing with currently installed libs
+ prettyPrint("Preparing installation...");
+
+ // to prevent messing with currently installed libs
if (!FileSystem.exists('.haxelib'))
FileSystem.createDirectory('.haxelib');
+
+ var libs:Array = [];
+ var libsXML:Access = new Access(Xml.parse(File.getContent('./libs.xml')).firstElement());
- var json:Array = Json.parse(File.getContent('./hmm.json')).dependencies;
- prettyPrint("Preparing installation...");
- for(lib in json) {
+ for (libNode in libsXML.elements) {
+ var lib:Library = {
+ name: libNode.att.name,
+ type: libNode.name
+ };
+ switch (lib.type) {
+ case "lib":
+ if (libNode.has.version) lib.version = libNode.att.version;
+ case "git":
+ if (libNode.has.url) lib.url = libNode.att.url;
+ if (libNode.has.ref) lib.ref = libNode.att.ref;
+ }
+ libs.push(lib);
+ }
+
+ for(lib in libs) {
// install libs
switch(lib.type) {
- case "haxelib":
+ case "lib":
prettyPrint('Installing "${lib.name}"...');
Sys.command('haxelib install ${lib.name} ${lib.version != null ? " " + lib.version : " "}');
case "git":
@@ -71,8 +90,7 @@ class Update {
typedef Library = {
var name:String;
var type:String;
- var version:String;
- var dir:String;
- var ref:String;
- var url:String;
+ var ?version:String;
+ var ?ref:String;
+ var ?url:String;
}
\ No newline at end of file
diff --git a/hmm.json b/hmm.json
deleted file mode 100644
index 50d9475f..00000000
--- a/hmm.json
+++ /dev/null
@@ -1,77 +0,0 @@
-{
- "dependencies": [
- {
- "name": "away3d",
- "type": "haxelib",
- "version": null
- },
- {
- "name": "dox",
- "type": "haxelib",
- "version": null
- },
- {
- "name": "flixel",
- "type": "git",
- "ref": "dev",
- "url": "https://www.github.com/FNF-CNE-Devs/flixel"
- },
- {
- "name": "flixel-addons",
- "type": "git",
- "ref": "dev",
- "url": "https://www.github.com/FNF-CNE-Devs/flixel-addons"
- },
- {
- "name": "flxanimate",
- "type": "git",
- "dir": "flxanimate",
- "ref": "master",
- "url": "https://github.com/FNF-CNE-Devs/flxanimate/"
- },
- {
- "name": "format",
- "type": "haxelib",
- "version": null
- },
- {
- "name": "hscript-improved",
- "type": "git",
- "dir": "hscript-improved",
- "ref": "custom-classes",
- "url": "https://www.github.com/FNF-CNE-Devs/hscript-improved"
- },
- {
- "name": "hxCodec",
- "type": "haxelib",
- "version": "2.5.1"
- },
- {
- "name": "hxcpp-debug-server",
- "type": "haxelib",
- "version": null
- },
- {
- "name": "lime",
- "type": "haxelib",
- "version": null
- },
- {
- "name": "linc_discord-rpc",
- "type": "git",
- "dir": "linc_discord-rpc",
- "ref": "master",
- "url": "https://github.com/Aidan63/linc_discord-rpc"
- },
- {
- "name": "markdown",
- "type": "haxelib",
- "version": null
- },
- {
- "name": "openfl",
- "type": "haxelib",
- "version": null
- }
- ]
-}
\ No newline at end of file
diff --git a/libs.xml b/libs.xml
index 721e0c90..3ee3beeb 100644
--- a/libs.xml
+++ b/libs.xml
@@ -8,12 +8,12 @@
+
-
diff --git a/project.xml b/project.xml
index 6fbbd258..327e5bd7 100644
--- a/project.xml
+++ b/project.xml
@@ -116,10 +116,14 @@
-
+
+
+
@@ -144,6 +148,9 @@
+
+
+
@@ -161,12 +168,14 @@
-
+
-
+
@@ -178,6 +187,7 @@
+
diff --git a/source/away3d/core/managers/Stage3DProxy.hx b/source/away3d/core/managers/Stage3DProxy.hx
index 057da05b..5e1263d5 100644
--- a/source/away3d/core/managers/Stage3DProxy.hx
+++ b/source/away3d/core/managers/Stage3DProxy.hx
@@ -1,5 +1,6 @@
package away3d.core.managers;
+#if THREE_D_SUPPORT
import away3d.debug.Debug;
import away3d.events.Stage3DEvent;
@@ -711,4 +712,5 @@ class Stage3DProxy extends EventDispatcher
ib.uploadFromVector(data, startOffset, count);
_ibUploadCount++;
}
-}
\ No newline at end of file
+}
+#end
\ No newline at end of file
diff --git a/source/flx3d/Flx3DCamera.hx b/source/flx3d/Flx3DCamera.hx
index 183b7225..e6e10079 100644
--- a/source/flx3d/Flx3DCamera.hx
+++ b/source/flx3d/Flx3DCamera.hx
@@ -1,5 +1,6 @@
package flx3d;
+#if THREE_D_SUPPORT
import away3d.entities.SegmentSet;
import away3d.cameras.Camera3D;
import away3d.entities.TextureProjector;
@@ -25,10 +26,12 @@ import openfl.Assets;
import away3d.entities.Mesh;
import away3d.loaders.Loader3D;
import funkin.backend.utils.NativeAPI.ConsoleColor;
-import flixel.FlxCamera;
import away3d.containers.View3D;
+#end
+import flixel.FlxCamera;
class Flx3DCamera extends FlxCamera {
+ #if THREE_D_SUPPORT
private static var __3DIDS:Int = 0;
public var view:View3D;
@@ -169,6 +172,7 @@ class Flx3DCamera extends FlxCamera {
super.destroy();
}
- public inline function addChild(c)
+ public function addChild(c)
view.scene.addChild(c);
+ #end
}
\ No newline at end of file
diff --git a/source/flx3d/Flx3DUtil.hx b/source/flx3d/Flx3DUtil.hx
index f23788ae..9a5e0b53 100644
--- a/source/flx3d/Flx3DUtil.hx
+++ b/source/flx3d/Flx3DUtil.hx
@@ -1,8 +1,10 @@
package flx3d;
+#if THREE_D_SUPPORT
import away3d.core.managers.Stage3DManager;
import away3d.library.assets.IAsset;
import flixel.FlxG;
+#end
/**
* @author lunarclient
@@ -13,6 +15,7 @@ import flixel.FlxG;
*/
class Flx3DUtil
{
+ #if THREE_D_SUPPORT
/**
* Returns the total amount of 3D stages (not excluding the ones in use)
* @return Int
@@ -48,4 +51,5 @@ class Flx3DUtil
return null;
}
+ #end
}
diff --git a/source/flx3d/Flx3DView.hx b/source/flx3d/Flx3DView.hx
index 98df3181..b034190d 100644
--- a/source/flx3d/Flx3DView.hx
+++ b/source/flx3d/Flx3DView.hx
@@ -1,5 +1,6 @@
package flx3d;
+#if THREE_D_SUPPORT
import away3d.entities.SegmentSet;
import away3d.cameras.Camera3D;
import away3d.entities.TextureProjector;
@@ -25,10 +26,11 @@ import openfl.Assets;
import away3d.entities.Mesh;
import away3d.loaders.Loader3D;
import funkin.backend.utils.NativeAPI.ConsoleColor;
+#end
// FlxView3D with helpers for easier updating
-
class Flx3DView extends FlxView3D {
+ #if THREE_D_SUPPORT
private static var __3DIDS:Int = 0;
var meshes:Array = [];
@@ -138,6 +140,7 @@ class Flx3DView extends FlxView3D {
super.destroy();
}
- public inline function addChild(c)
+ public function addChild(c)
view.scene.addChild(c);
+ #end
}
\ No newline at end of file
diff --git a/source/flx3d/FlxView3D.hx b/source/flx3d/FlxView3D.hx
index 416a87f8..11137eed 100644
--- a/source/flx3d/FlxView3D.hx
+++ b/source/flx3d/FlxView3D.hx
@@ -1,10 +1,12 @@
package flx3d;
+#if THREE_D_SUPPORT
import away3d.containers.View3D;
import away3d.library.assets.IAsset;
import flixel.FlxG;
-import flixel.FlxSprite;
import openfl.display.BitmapData;
+#end
+import flixel.FlxSprite;
/**
* @author Ne_Eo
@@ -15,6 +17,7 @@ import openfl.display.BitmapData;
*/
class FlxView3D extends FlxSprite
{
+ #if THREE_D_SUPPORT
@:noCompletion private var bmp:BitmapData;
/**
@@ -114,4 +117,5 @@ class FlxView3D extends FlxSprite
super.set_height(newHeight);
return view != null ? view.height = height : height;
}
+ #end
}
diff --git a/source/funkin/backend/MusicBeatState.hx b/source/funkin/backend/MusicBeatState.hx
index 567cfbf9..31bd204b 100644
--- a/source/funkin/backend/MusicBeatState.hx
+++ b/source/funkin/backend/MusicBeatState.hx
@@ -241,7 +241,10 @@ class MusicBeatState extends FlxState implements IBeatReceiver
public override function draw() {
graphicCache.draw();
- super.draw();
+ var e = event("draw", EventManager.get(DrawEvent).recycle());
+ if (!e.cancelled)
+ super.draw();
+ event("postDraw", e);
}
public override function switchTo(nextState:FlxState) {
diff --git a/source/funkin/backend/assets/Paths.hx b/source/funkin/backend/assets/Paths.hx
index daf52620..531a0ca1 100644
--- a/source/funkin/backend/assets/Paths.hx
+++ b/source/funkin/backend/assets/Paths.hx
@@ -120,6 +120,10 @@ class Paths
return getPath('songs/$song/charts/$difficulty.json', null);
}
+ static public function character(character:String):String {
+ return getPath('data/characters/$character.xml', null);
+ }
+
inline static public function font(key:String)
{
return getPath('fonts/$key');
diff --git a/source/funkin/backend/chart/BaseGameParser.hx b/source/funkin/backend/chart/BaseGameParser.hx
index 3c56e069..b8d2d6b9 100644
--- a/source/funkin/backend/chart/BaseGameParser.hx
+++ b/source/funkin/backend/chart/BaseGameParser.hx
@@ -26,14 +26,14 @@ class BaseGameParser {
position: "boyfriend",
notes: []
});
- if (!p2isGF && data.gf != "none") {
+ var gfName = data.gf != null ? data.gf : (data.gfVersion != null ? data.gfVersion : "gf");
+ if (!p2isGF && gfName != "none") {
result.strumLines.push({
- characters: [data.gf != null ? data.gf : "gf"],
+ characters: [gfName],
type: 2,
position: "girlfriend",
notes: [],
visible: false,
- strumLinePos: 0.5
});
}
@@ -55,7 +55,7 @@ class BaseGameParser {
if (camFocusedBF != (camFocusedBF = section.mustHitSection)) {
result.events.push({
time: curTime,
- type: CAM_MOVEMENT,
+ name: "Camera Movement",
params: [camFocusedBF ? 1 : 0]
});
}
@@ -90,7 +90,7 @@ class BaseGameParser {
result.events.push({
time: curTime,
- type: BPM_CHANGE,
+ name: "BPM Change",
params: [section.bpm]
});
}
@@ -112,6 +112,7 @@ typedef SwagSong =
var player1:String;
var player2:String;
var gf:String;
+ var gfVersion:String;
var validScore:Bool;
// ADDITIONAL STUFF THAT MAY NOT BE PRESENT IN CHART
@@ -129,4 +130,4 @@ typedef SwagSection =
var changeBPM:Bool;
var altAnim:Bool;
@:optional var camTarget:Null;
-}
\ No newline at end of file
+}
diff --git a/source/funkin/backend/chart/Chart.hx b/source/funkin/backend/chart/Chart.hx
index 8253a7a8..e8692241 100644
--- a/source/funkin/backend/chart/Chart.hx
+++ b/source/funkin/backend/chart/Chart.hx
@@ -108,6 +108,23 @@ class Chart {
*/
#if REGION
if (Reflect.hasField(data, "codenameChart") && Reflect.field(data, "codenameChart") == true) {
+ // backward compat on events since its caused problems
+ var eventTypesToString:Map = [
+ -1 => "HScript Call",
+ 0 => "Unknown",
+ 1 => "Camera Movement",
+ 2 => "BPM Change",
+ 3 => "Alt Animation Toggle",
+ ];
+
+ for (event in cast(data.events, Array)) {
+ if (Reflect.hasField(event, "type")) {
+ if(event.type != null)
+ event.name = eventTypesToString[event.type];
+ Reflect.deleteField(event, "type");
+ }
+ }
+
// codename chart
base = data;
} else {
@@ -159,11 +176,11 @@ class Chart {
var meta = filteredChart.meta;
#if sys
- if (!FileSystem.exists('${songFolderPath}\\charts\\'))
- FileSystem.createDirectory('${songFolderPath}\\charts\\');
+ if (!FileSystem.exists('${songFolderPath}/charts/'))
+ FileSystem.createDirectory('${songFolderPath}/charts/');
- var chartPath = '${songFolderPath}\\charts\\${difficulty.trim()}.json';
- var metaPath = '${songFolderPath}\\meta.json';
+ var chartPath = '${songFolderPath}/charts/${difficulty.trim()}.json';
+ var metaPath = '${songFolderPath}/meta.json';
File.saveContent(chartPath, Json.stringify(filteredChart, null, saveSettings.prettyPrint == true ? "\t" : null));
@@ -199,4 +216,4 @@ typedef ChartSaveSettings = {
var ?overrideExistingMeta:Bool;
var ?saveMetaInChart:Bool;
var ?prettyPrint:Bool;
-}
\ No newline at end of file
+}
diff --git a/source/funkin/backend/chart/ChartData.hx b/source/funkin/backend/chart/ChartData.hx
index 3dd309e7..b6f0a639 100644
--- a/source/funkin/backend/chart/ChartData.hx
+++ b/source/funkin/backend/chart/ChartData.hx
@@ -36,8 +36,9 @@ typedef ChartStrumLine = {
var type:ChartStrumLineType;
var notes:Array;
var position:String;
- var ?strumLinePos:Float; // 0.25 = default opponent pos, 0.75 = default boyfriend pos
var ?visible:Null;
+ var ?strumPos:Array;
+ var ?strumScale:Float;
}
typedef ChartNote = {
@@ -48,8 +49,8 @@ typedef ChartNote = {
}
typedef ChartEvent = {
+ var name:String;
var time:Float;
- var type:ChartEventType;
var params:Array;
}
@@ -67,43 +68,4 @@ abstract ChartStrumLineType(Int) from Int to Int {
* STRUMLINE IS MARKED AS ADDITIONAL - WILL BE PLAYED AS CPU EVEN IF OPPONENT MODE IS ENABLED
*/
var ADDITIONAL = 2;
-}
-@:enum
-abstract ChartEventType(Int) from Int to Int {
- /**
- * CUSTOM EVENT
- * Params:
- * - Function Name (String)
- * - Function Parameters...
- */
- var CUSTOM = -1;
- /**
- * NO EVENT, MADE FOR UNKNOWN EVENTS / EVENTS THAT CANNOT BE PARSED
- */
- var NONE = 0;
- /**
- * CAMERA MOVEMENT EVENT
- * Params:
- * - Target Strumline ID (Int)
- */
- var CAM_MOVEMENT = 1;
- /**
- * BPM CHANGE EVENT
- * Params:
- * - Target BPM (Float)
- */
- var BPM_CHANGE = 2;
- /**
- * ALT ANIM TOGGLE
- * Params:
- * - Strum Line which is going to be toggled (Int)
- * - Whenever its going to be toggled or not (Bool)
- */
- var ALT_ANIM_TOGGLE = 3;
-
- /**
- * Returns all usable event types.
- */
- public static inline function getChartEventTypes():Array
- return [CUSTOM, CAM_MOVEMENT, BPM_CHANGE, ALT_ANIM_TOGGLE];
}
\ No newline at end of file
diff --git a/source/funkin/backend/scripting/Script.hx b/source/funkin/backend/scripting/Script.hx
index 1522db09..927e1189 100644
--- a/source/funkin/backend/scripting/Script.hx
+++ b/source/funkin/backend/scripting/Script.hx
@@ -4,7 +4,9 @@ import lime.app.Application;
import flixel.util.FlxDestroyUtil.IFlxDestroyable;
import haxe.io.Path;
import hscript.IHScriptCustomConstructor;
+import flixel.util.FlxStringUtil;
+@:allow(funkin.backend.scripting.ScriptPack)
/**
* Class used for scripting.
*/
@@ -15,12 +17,12 @@ class Script extends FlxBasic implements IFlxDestroyable {
*/
public static var staticVariables:Map = [];
-
public static function getDefaultVariables(?script:Script):Map {
return [
// Haxe related stuff
"Std" => Std,
"Math" => Math,
+ "Reflect" => Reflect,
"StringTools" => StringTools,
"Json" => haxe.Json,
@@ -125,6 +127,8 @@ class Script extends FlxBasic implements IFlxDestroyable {
*/
public var path:String = null;
+ private var didLoad:Bool = false;
+
/**
* Creates a script from the specified asset path. The language is automatically determined.
* @param path Path in assets
@@ -157,6 +161,10 @@ class Script extends FlxBasic implements IFlxDestroyable {
for(k=>e in getDefaultVariables(this)) {
set(k, e);
}
+ set("disableScript", () -> {
+ active = false;
+ });
+ set("__script__", this);
}
@@ -164,10 +172,14 @@ class Script extends FlxBasic implements IFlxDestroyable {
* Loads the script
*/
public function load() {
+ if(didLoad) return;
+
var oldScript = curScript;
curScript = this;
onLoad();
curScript = oldScript;
+
+ didLoad = true;
}
/**
@@ -244,6 +256,17 @@ class Script extends FlxBasic implements IFlxDestroyable {
], ERROR);
}
+ override public function toString():String {
+ return FlxStringUtil.getDebugString(didLoad ? [
+ LabelValuePair.weak("path", path),
+ LabelValuePair.weak("active", active),
+ ] : [
+ LabelValuePair.weak("path", path),
+ LabelValuePair.weak("active", active),
+ LabelValuePair.weak("loaded", didLoad),
+ ]);
+ }
+
/**
* PRIVATE HANDLERS - DO NOT TOUCH
*/
diff --git a/source/funkin/backend/scripting/ScriptPack.hx b/source/funkin/backend/scripting/ScriptPack.hx
index 2105e2c6..33799690 100644
--- a/source/funkin/backend/scripting/ScriptPack.hx
+++ b/source/funkin/backend/scripting/ScriptPack.hx
@@ -1,5 +1,6 @@
package funkin.backend.scripting;
+import flixel.util.FlxStringUtil;
import funkin.backend.scripting.events.CancellableEvent;
@:access(CancellableEvent)
@@ -52,7 +53,8 @@ class ScriptPack extends Script {
public override function call(func:String, ?parameters:Array):Dynamic {
for(e in scripts)
- e.call(func, parameters);
+ if(e.active)
+ e.call(func, parameters);
return null;
}
@@ -64,8 +66,9 @@ class ScriptPack extends Script {
*/
public inline function event(func:String, event:T):T {
for(e in scripts) {
+ if(!e.active) continue;
+
e.call(func, [event]);
- @:privateAccess
if (event.cancelled && !event.__continueCalls) break;
}
return event;
@@ -95,7 +98,7 @@ class ScriptPack extends Script {
public override function onDestroy() {
for(e in scripts) e.destroy();
}
-
+
public override function onCreate(path:String) {}
public function add(script:Script) {
@@ -117,4 +120,11 @@ class ScriptPack extends Script {
script.setPublicMap(publicVariables);
for(k=>e in additionalDefaultVariables) script.set(k, e);
}
+
+ override public function toString():String {
+ return FlxStringUtil.getDebugString([
+ LabelValuePair.weak("parent", FlxStringUtil.getClassName(parent, true)),
+ LabelValuePair.weak("total", scripts.length),
+ ]);
+ }
}
\ No newline at end of file
diff --git a/source/funkin/backend/scripting/events/AmountEvent.hx b/source/funkin/backend/scripting/events/AmountEvent.hx
new file mode 100644
index 00000000..45b5c9ce
--- /dev/null
+++ b/source/funkin/backend/scripting/events/AmountEvent.hx
@@ -0,0 +1,8 @@
+package funkin.backend.scripting.events;
+
+final class AmountEvent extends CancellableEvent {
+ /**
+ * Amount
+ */
+ public var amount:Int;
+}
\ No newline at end of file
diff --git a/source/funkin/backend/scripting/events/CancellableEvent.hx b/source/funkin/backend/scripting/events/CancellableEvent.hx
index 94bf066e..084ecba3 100644
--- a/source/funkin/backend/scripting/events/CancellableEvent.hx
+++ b/source/funkin/backend/scripting/events/CancellableEvent.hx
@@ -2,7 +2,7 @@ package funkin.backend.scripting.events;
import flixel.util.FlxDestroyUtil.IFlxDestroyable;
-@:allow(ScriptPack)
+@:allow(funkin.backend.scripting.ScriptPack)
@:autoBuild(funkin.backend.system.macros.EventMacro.build())
@:noCustomClass
class CancellableEvent implements IFlxDestroyable {
diff --git a/source/funkin/backend/scripting/events/DrawEvent.hx b/source/funkin/backend/scripting/events/DrawEvent.hx
new file mode 100644
index 00000000..94e1aa8b
--- /dev/null
+++ b/source/funkin/backend/scripting/events/DrawEvent.hx
@@ -0,0 +1,4 @@
+package funkin.backend.scripting.events;
+
+final class DrawEvent extends CancellableEvent {
+}
\ No newline at end of file
diff --git a/source/funkin/backend/scripting/events/EventGameEvent.hx b/source/funkin/backend/scripting/events/EventGameEvent.hx
new file mode 100644
index 00000000..836e44c9
--- /dev/null
+++ b/source/funkin/backend/scripting/events/EventGameEvent.hx
@@ -0,0 +1,7 @@
+package funkin.backend.scripting.events;
+
+import funkin.backend.chart.ChartData;
+
+final class EventGameEvent extends CancellableEvent {
+ public var event:ChartEvent;
+}
\ No newline at end of file
diff --git a/source/funkin/backend/scripting/events/FreeplayAlphaUpdateEvent.hx b/source/funkin/backend/scripting/events/FreeplayAlphaUpdateEvent.hx
new file mode 100644
index 00000000..5862f280
--- /dev/null
+++ b/source/funkin/backend/scripting/events/FreeplayAlphaUpdateEvent.hx
@@ -0,0 +1,24 @@
+package funkin.backend.scripting.events;
+
+final class FreeplayAlphaUpdateEvent extends CancellableEvent {
+ /**
+ * The alpha when nothing is playing and isnt selected
+ */
+ public var idleAlpha:Float;
+ /**
+ * The alpha when something is playing and isnt selected
+ */
+ public var idlePlayingAlpha:Float;
+ /**
+ * The alpha when nothing is playing and selected
+ */
+ public var selectedAlpha:Float;
+ /**
+ * The alpha when something is playing and selected
+ */
+ public var selectedPlayingAlpha:Float;
+ /**
+ * The lerp of the alpha
+ */
+ public var lerp:Float;
+}
\ No newline at end of file
diff --git a/source/funkin/backend/scripting/events/StageNodeEvent.hx b/source/funkin/backend/scripting/events/StageNodeEvent.hx
new file mode 100644
index 00000000..93465fa6
--- /dev/null
+++ b/source/funkin/backend/scripting/events/StageNodeEvent.hx
@@ -0,0 +1,26 @@
+package funkin.backend.scripting.events;
+
+import funkin.game.Stage;
+import haxe.xml.Access;
+
+final class StageNodeEvent extends CancellableEvent {
+ /**
+ * The stage instance
+ */
+ public var stage:Stage;
+
+ /**
+ * The node which is currently being parsed
+ */
+ public var node:Access;
+
+ /**
+ * The sprite which was parsed
+ */
+ public var sprite:Dynamic;
+
+ /**
+ * The name of the node, quicker access than e.node.name
+ */
+ public var name:String;
+}
\ No newline at end of file
diff --git a/source/funkin/backend/scripting/events/StageXMLEvent.hx b/source/funkin/backend/scripting/events/StageXMLEvent.hx
new file mode 100644
index 00000000..16693d33
--- /dev/null
+++ b/source/funkin/backend/scripting/events/StageXMLEvent.hx
@@ -0,0 +1,21 @@
+package funkin.backend.scripting.events;
+
+import funkin.game.Stage;
+import haxe.xml.Access;
+
+final class StageXMLEvent extends CancellableEvent {
+ /**
+ * The stage instance
+ */
+ public var stage:Stage;
+
+ /**
+ * The xml
+ */
+ public var xml:Access;
+
+ /**
+ * The object which was parsed
+ */
+ public var elems:Array;
+}
\ No newline at end of file
diff --git a/source/funkin/backend/scripting/events/StrumCreationEvent.hx b/source/funkin/backend/scripting/events/StrumCreationEvent.hx
index 699f88bc..d591d958 100644
--- a/source/funkin/backend/scripting/events/StrumCreationEvent.hx
+++ b/source/funkin/backend/scripting/events/StrumCreationEvent.hx
@@ -21,6 +21,11 @@ final class StrumCreationEvent extends CancellableEvent {
*/
public var strumID:Int;
+ /**
+ * Animation prefix (`left` = `arrowLEFT`, `left press`, `left confirm`).
+ */
+ public var animPrefix:String;
+
/**
* Sprite path, in case you only want to change the sprite.
*/
diff --git a/source/funkin/backend/shaders/CustomShader.hx b/source/funkin/backend/shaders/CustomShader.hx
index 0a3e9a80..7ea07c7e 100644
--- a/source/funkin/backend/shaders/CustomShader.hx
+++ b/source/funkin/backend/shaders/CustomShader.hx
@@ -14,6 +14,8 @@ import hscript.IHScriptCustomBehaviour;
* To access the shader's uniform variables, use `shader.variable`
*/
class CustomShader extends FunkinShader {
+ public var path:String = "";
+
/**
* Creates a new custom shader
* @param name Name of the frag and vert files.
@@ -25,6 +27,8 @@ class CustomShader extends FunkinShader {
var fragCode = Assets.exists(fragShaderPath) ? Assets.getText(fragShaderPath) : null;
var vertCode = Assets.exists(vertShaderPath) ? Assets.getText(vertShaderPath) : null;
+ path = fragShaderPath+vertShaderPath;
+
if (fragCode == null && vertCode == null)
Logs.trace('Shader "$name" couldn\'t be found.', ERROR);
diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx
index a35f57c7..d38db2c8 100644
--- a/source/funkin/backend/system/Conductor.hx
+++ b/source/funkin/backend/system/Conductor.hx
@@ -134,7 +134,7 @@ class Conductor
var songTime:Float = 0;
var stepTime:Float = 0;
- for(e in song.events) if (e.type == BPM_CHANGE && e.params != null && e.params[0] is Float) {
+ for(e in song.events) if (e.name == "BPM Change" && e.params != null && e.params[0] is Float) {
if (e.params[0] == curBPM) continue;
var steps = (e.time - songTime) / ((60 / curBPM) * 1000 / 4);
stepTime += steps;
diff --git a/source/funkin/backend/system/Main.hx b/source/funkin/backend/system/Main.hx
index f932180e..e3c29ca8 100644
--- a/source/funkin/backend/system/Main.hx
+++ b/source/funkin/backend/system/Main.hx
@@ -63,6 +63,8 @@ class Main extends Sprite
public function new()
{
super();
+ CrashHandler.init();
+
addChild(game = new FunkinGame(gameWidth, gameHeight, MainState, Options.framerate, Options.framerate, skipSplash, startFullscreen));
#if !mobile
@@ -114,7 +116,7 @@ class Main extends Sprite
#if UPDATE_CHECKING
funkin.backend.system.updating.UpdateUtil.init();
#end
- CrashHandler.init();
+ ShaderResizeFix.init();
Logs.init();
Paths.init();
ModsFolder.init();
@@ -158,7 +160,7 @@ class Main extends Sprite
#if MOD_SUPPORT
ModsFolder.switchMod(modToLoad.getDefault(Options.lastLoadedMod));
#end
-
+
initTransition();
}
diff --git a/source/funkin/backend/system/MainState.hx b/source/funkin/backend/system/MainState.hx
index 0c03257b..00f69f49 100644
--- a/source/funkin/backend/system/MainState.hx
+++ b/source/funkin/backend/system/MainState.hx
@@ -6,6 +6,7 @@ import sys.FileSystem;
import funkin.backend.assets.ModsFolder;
import funkin.menus.TitleState;
import funkin.menus.BetaWarningState;
+import funkin.editors.charter.EventsData;
import flixel.FlxState;
/**
@@ -42,9 +43,9 @@ class MainState extends FlxState {
Main.refreshAssets();
ModsFolder.onModSwitch.dispatch(ModsFolder.currentModFolder);
DiscordUtil.reloadJsonData();
+ EventsData.reloadEvents();
TitleState.initialized = false;
-
if (betaWarningShown)
FlxG.switchState(new TitleState());
else {
diff --git a/source/funkin/backend/system/framerate/AssetTreeInfo.hx b/source/funkin/backend/system/framerate/AssetTreeInfo.hx
index 7417e124..1ebba189 100644
--- a/source/funkin/backend/system/framerate/AssetTreeInfo.hx
+++ b/source/funkin/backend/system/framerate/AssetTreeInfo.hx
@@ -28,7 +28,7 @@ class AssetTreeInfo extends FramerateCategory {
}
if (text != "")
text = text.substr(0, text.length-1);
-
+
this.text.text = text;
super.__enterFrame(t);
}
diff --git a/source/funkin/backend/system/framerate/SystemInfo.hx b/source/funkin/backend/system/framerate/SystemInfo.hx
index 3c775623..974b578b 100644
--- a/source/funkin/backend/system/framerate/SystemInfo.hx
+++ b/source/funkin/backend/system/framerate/SystemInfo.hx
@@ -13,25 +13,30 @@ class SystemInfo extends FramerateCategory {
public static var cpuName:String = "Unknown";
public static var totalMem:String = "Unknown";
public static var memType:String = "Unknown";
+ public static var gpuMaxSize:String = "Unknown";
static var __formattedSysText:String = "";
public static inline function init() {
if (lime.system.System.platformLabel != null && lime.system.System.platformLabel != "" && lime.system.System.platformVersion != null && lime.system.System.platformVersion != "")
osInfo = '${lime.system.System.platformLabel.replace(lime.system.System.platformVersion, "").trim()} ${lime.system.System.platformVersion}';
- else
+ else
Logs.trace('Unable to grab OS Label', ERROR, RED);
try {
#if windows
var process = new HiddenProcess("wmic", ["cpu", "get", "name"]);
- if (process.exitCode() == 0) cpuName = process.stdout.readAll().toString().trim().split("\n")[1].trim();
+ if (process.exitCode() != 0) throw 'Could not fetch CPU information';
+
+ cpuName = process.stdout.readAll().toString().trim().split("\n")[1].trim();
#elseif mac
- var process = new HiddenProcess("sysctl -a | grep brand_string");
- if (process.exitCode() == 0) cpuName = process.stdout.readAll().toString().trim().split(":")[1].trim();
+ var process = new HiddenProcess("sysctl -a | grep brand_string"); // Somehow this isnt able to use the args but it still works
+ if (process.exitCode() != 0) throw 'Could not fetch CPU information';
+
+ cpuName = process.stdout.readAll().toString().trim().split(":")[1].trim();
#elseif linux
var process = new HiddenProcess("cat", ["/proc/cpuinfo"]);
- if (process.exitCode() != 0) return;
+ if (process.exitCode() != 0) throw 'Could not fetch CPU information';
for (line in process.stdout.readAll().toString().split("\n")) {
if (line.indexOf("model name") == 0) {
@@ -44,23 +49,28 @@ class SystemInfo extends FramerateCategory {
Logs.trace('Unable to grab CPU Name: $e', ERROR, RED);
}
-
@:privateAccess {
if (flixel.FlxG.stage.context3D != null && flixel.FlxG.stage.context3D.gl != null) {
gpuName = Std.string(flixel.FlxG.stage.context3D.gl.getParameter(flixel.FlxG.stage.context3D.gl.RENDERER)).split("/")[0].trim();
+ #if !flash
+ var size = FlxG.bitmap.maxTextureSize;
+ gpuMaxSize = size+"x"+size;
+ #end
- var vRAMBytes:UInt = cast(flixel.FlxG.stage.context3D.gl.getParameter(openfl.display3D.Context3D.__glMemoryTotalAvailable), UInt);
- if (vRAMBytes == 1000 || vRAMBytes <= 0)
- Logs.trace('Unable to grab GPU VRAM', ERROR, RED);
- else
- vRAM = CoolUtil.getSizeString(vRAMBytes * 1000);
- } else
+ if(openfl.display3D.Context3D.__glMemoryTotalAvailable != -1) {
+ var vRAMBytes:UInt = cast(flixel.FlxG.stage.context3D.gl.getParameter(openfl.display3D.Context3D.__glMemoryTotalAvailable), UInt);
+ if (vRAMBytes == 1000 || vRAMBytes == 1 || vRAMBytes <= 0)
+ Logs.trace('Unable to grab GPU VRAM', ERROR, RED);
+ else
+ vRAM = CoolUtil.getSizeString(vRAMBytes * 1000);
+ }
+ } else
Logs.trace('Unable to grab GPU Info', ERROR, RED);
}
#if cpp
totalMem = Std.string(MemoryUtil.getTotalMem() / 1024) + " GB";
- #else
+ #else
Logs.trace('Unable to grab RAM Amount', ERROR, RED);
#end
@@ -74,9 +84,19 @@ class SystemInfo extends FramerateCategory {
static function formatSysInfo() {
if (osInfo != "Unknown") __formattedSysText = 'System: $osInfo';
- if (cpuName != "Unknown") __formattedSysText += '\nCPU: ${cpuName} ${openfl.system.Capabilities.cpuArchitecture} ${(openfl.system.Capabilities.supports64BitProcesses ? '64-Bit' : '32-Bit')}';
- if (gpuName != cpuName && (gpuName != "Unknown" && vRAM != "Unknown")) __formattedSysText += '\nGPU: ${gpuName} | VRAM: ${vRAM}'; // 1000 bytes of vram (apus)
- if (totalMem != "Unknown" && memType != "Unknown") __formattedSysText += '\nTotal MEM: ${totalMem} $memType';
+ if (cpuName != "Unknown") __formattedSysText += '\nCPU: $cpuName ${openfl.system.Capabilities.cpuArchitecture} ${(openfl.system.Capabilities.supports64BitProcesses ? '64-Bit' : '32-Bit')}';
+ if (gpuName != cpuName || vRAM != "Unknown") {
+ var gpuNameKnown = gpuName != "Unknown" && gpuName != cpuName;
+ var vramKnown = vRAM != "Unknown";
+
+ if(gpuNameKnown || vramKnown) __formattedSysText += "\n";
+
+ if(gpuNameKnown) __formattedSysText += 'GPU: $gpuName';
+ if(gpuNameKnown && vramKnown) __formattedSysText += " | ";
+ if(vramKnown) __formattedSysText += 'VRAM: $vRAM'; // 1000 bytes of vram (apus)
+ }
+ //if (gpuMaxSize != "Unknown") __formattedSysText += '\nMax Bitmap Size: $gpuMaxSize';
+ if (totalMem != "Unknown" && memType != "Unknown") __formattedSysText += '\nTotal MEM: $totalMem $memType';
}
public function new() {
diff --git a/source/funkin/backend/utils/MemoryUtil.hx b/source/funkin/backend/utils/MemoryUtil.hx
index 47f55114..4c0c40c0 100644
--- a/source/funkin/backend/utils/MemoryUtil.hx
+++ b/source/funkin/backend/utils/MemoryUtil.hx
@@ -128,7 +128,7 @@ class MemoryUtil {
reg.match(process.stdout.readAll().toString());
if (process.exitCode() == 0) return reg.matched(1);
#elseif linux
- var process = HiddenProcess("sudo", ["dmidecode", "--type", "17"]);
+ var process = new HiddenProcess("sudo", ["dmidecode", "--type", "17"]);
if (process.exitCode() != 0) return "Unknown";
var lines = process.stdout.readAll().toString().split("\n");
for (line in lines) {
diff --git a/source/funkin/backend/utils/ShaderResizeFix.hx b/source/funkin/backend/utils/ShaderResizeFix.hx
new file mode 100644
index 00000000..4bbc401e
--- /dev/null
+++ b/source/funkin/backend/utils/ShaderResizeFix.hx
@@ -0,0 +1,38 @@
+package funkin.backend.utils;
+
+import openfl.display.Sprite;
+
+class ShaderResizeFix {
+ public static var doResizeFix:Bool = true;
+
+ public static function init() {
+ FlxG.signals.gameResized.add((w:Int, h:Int) -> {fixSpritesShadersSizes();});
+ }
+
+ public inline static function fixSpritesShadersSizes() {
+ if (!doResizeFix) return;
+
+ fixSpriteShaderSize(Main.instance);
+ if (FlxG.game != null) fixSpriteShaderSize(FlxG.game);
+
+ if (FlxG.cameras == null) return;
+ for (cam in FlxG.cameras.list) {
+ @:privateAccess
+ if (cam != null && (cam._filters != null || cam._filters != []))
+ fixSpriteShaderSize(cam.flashSprite);
+ }
+ }
+
+ public inline static function fixSpriteShaderSize(sprite:Sprite) // Shout out to Ne_Eo for bringing this to my attention
+ {
+ if (sprite == null) return;
+
+ @:privateAccess {
+ sprite.__cacheBitmap = null;
+ sprite.__cacheBitmapData = null;
+ sprite.__cacheBitmapData2 = null;
+ sprite.__cacheBitmapData3 = null;
+ sprite.__cacheBitmapColorTransform = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/editors/DebugOptions.hx b/source/funkin/editors/DebugOptions.hx
index 734342c4..b704feac 100644
--- a/source/funkin/editors/DebugOptions.hx
+++ b/source/funkin/editors/DebugOptions.hx
@@ -44,5 +44,9 @@ class DebugOptionsScreen extends OptionsScreen {
"Resizable Editors",
"If checked, editors will be resizable and extensible like other programs instead of zooming in upon maximization.",
"resizableEditors"));
+ add(new Checkbox(
+ "Intensive Blur",
+ "If checked, will use more intensive blur that may be laggier but look better.",
+ "intensiveBlur"));
}
}
\ No newline at end of file
diff --git a/source/funkin/editors/EditorPicker.hx b/source/funkin/editors/EditorPicker.hx
index 7bd7235c..188756b3 100644
--- a/source/funkin/editors/EditorPicker.hx
+++ b/source/funkin/editors/EditorPicker.hx
@@ -16,7 +16,7 @@ class EditorPicker extends MusicBeatSubstate {
{
name: "Character Editor",
iconID: 1,
- state: null
+ state: funkin.editors.character.CharacterSelection
},
{
name: "Stage Editor",
@@ -86,7 +86,7 @@ class EditorPicker extends MusicBeatSubstate {
return;
}
changeSelection(-FlxG.mouse.wheel + (controls.UP_P ? -1 : 0) + (controls.DOWN_P ? 1 : 0));
-
+
FlxG.mouse.getScreenPosition(camera, curMousePos);
if (curMousePos.x != oldMousePos.x || curMousePos.y != oldMousePos.y) {
oldMousePos.set(curMousePos.x, curMousePos.y);
@@ -98,7 +98,7 @@ class EditorPicker extends MusicBeatSubstate {
if (options[curSelected].state != null) {
selected = true;
CoolUtil.playMenuSFX(CONFIRM);
-
+
MusicBeatState.skipTransIn = true;
MusicBeatState.skipTransOut = true;
@@ -187,7 +187,7 @@ class EditorPickerOption extends FlxTypedSpriteGroup {
selectionBG.alpha = (iconSpr.alpha = FlxEase.cubeOut(selectionLerp)) * 0.5;
selectionBG.x = FlxMath.lerp(-FlxG.width, 0, selectionLerp);
-
+
label.x = FlxMath.lerp(10, 25 + iconSpr.width + 25, selectionLerp);
iconSpr.x = label.x - 25 - iconSpr.width;
iconSpr.angle = Math.sin(iconRotationCycle * 0.5) * 5;
diff --git a/source/funkin/editors/EditorTreeMenu.hx b/source/funkin/editors/EditorTreeMenu.hx
index 86e1e1cf..f65cd4bf 100644
--- a/source/funkin/editors/EditorTreeMenu.hx
+++ b/source/funkin/editors/EditorTreeMenu.hx
@@ -18,7 +18,7 @@ class EditorTreeMenu extends TreeMenu {
bg.antialiasing = true;
add(bg);
}
-
+
public override function exit() {
FlxG.switchState(new MainMenuState());
}
diff --git a/source/funkin/editors/character/AddAnimScreen.hx b/source/funkin/editors/character/AddAnimScreen.hx
new file mode 100644
index 00000000..c01ad927
--- /dev/null
+++ b/source/funkin/editors/character/AddAnimScreen.hx
@@ -0,0 +1,113 @@
+package funkin.editors.character;
+
+import flixel.math.FlxPoint;
+import funkin.backend.utils.XMLUtil.AnimData;
+import funkin.game.Character;
+
+class AddAnimScreen extends UISubstateWindow
+{
+ public var char:Character;
+ public var ghostChar:Character;
+
+ public var addButton:UIButton;
+ public var cancelButton:UIButton;
+ public var editor:CharacterEditor;
+
+ public var animName:UITextBox;
+ public var animPrefix:UITextBox;
+ public var fps:UITextBox;
+ public var loop:UICheckbox;
+ public var indices:UITextBox;
+
+ public function new(char:Character, ghostChar, editor:CharacterEditor)
+ {
+ super();
+ this.char = char;
+ this.ghostChar = ghostChar;
+ }
+
+ public override function create()
+ {
+ winTitle = "Add new Animation";
+ winWidth = 960;
+
+ super.create();
+
+ // Animation Properties
+ var y_ofs = 60;
+ var y_text_ofs = 20;
+
+ animName = new UITextBox(windowSpr.x + 50, windowSpr.y + y_ofs, "");
+ add(animName);
+ add(new UIText(animName.x, animName.y - y_text_ofs, -1, "Animation Name: "));
+
+ animPrefix = new UITextBox(animName.x, animName.y + y_ofs, "");
+ add(animPrefix);
+ add(new UIText(animPrefix.x, animPrefix.y - y_text_ofs, -1, "Animation on XML/Atlas file: "));
+
+ fps = new UITextBox(animPrefix.x, animPrefix.y + y_ofs, "24");
+ add(fps);
+ add(new UIText(fps.x, fps.y - y_text_ofs, -1, "Framerate: "));
+
+ loop = new UICheckbox(fps.x, fps.y + y_ofs, "Looped", false);
+ add(loop);
+
+ indices = new UITextBox(loop.x, loop.y + y_ofs, "");
+ add(indices);
+ add(new UIText(indices.x, indices.y - y_text_ofs, -1, "Indices (0, 1, 2, 3): "));
+
+ // Add / Cancel Buttons
+ addButton = new UIButton(windowSpr.x + 1, windowSpr.y + 31, "", function()
+ {
+ newAnimation(animName.label.text, animPrefix.label.text, Std.parseInt(fps.label.text), loop.checked, indices.label.text);
+ close();
+ });
+ addButton.bWidth = addButton.bHeight = 30;
+ add(addButton);
+
+ var addButtonIcon = new FlxSprite(addButton.x, addButton.y, Paths.image('editors/charter/add-button'));
+ addButtonIcon.x += (30 - addButtonIcon.width) / 2;
+ addButtonIcon.y += (30 - addButtonIcon.height) / 2;
+ add(addButtonIcon);
+
+ cancelButton = new UIButton(addButton.x - 10, addButton.y, "Cancel", function()
+ {
+ close();
+ });
+ cancelButton.x -= cancelButton.bWidth;
+ add(cancelButton);
+ }
+
+ public function newAnimation(name:String, anim:String, ?fps:Int = 24, ?looped:Bool = false, ?indices:String)
+ {
+ var animType:XMLAnimType = NONE;
+
+ var animData:AnimData = {
+ name: name,
+ anim: anim,
+ fps: fps,
+ loop: looped,
+ animType: animType,
+ indices: [],
+ x: 0,
+ y: 0
+ };
+
+ if (indices != "" && indices != null)
+ {
+ var indicesSplit = indices.split(",");
+
+ for (indice in indicesSplit)
+ {
+ var i = Std.parseInt(indice.trim());
+ if (i != null)
+ animData.indices.push(i);
+ }
+ }
+
+ XMLUtil.addAnimToSprite(char, animData);
+ XMLUtil.addAnimToSprite(ghostChar, animData);
+ char.characterData.animations.push(animData);
+ ghostChar.characterData.animations.push(animData);
+ }
+}
diff --git a/source/funkin/editors/character/CharacterConfig.hx b/source/funkin/editors/character/CharacterConfig.hx
new file mode 100644
index 00000000..0c93959e
--- /dev/null
+++ b/source/funkin/editors/character/CharacterConfig.hx
@@ -0,0 +1,169 @@
+package funkin.editors.character;
+
+import haxe.xml.Printer;
+import sys.io.File;
+import sys.FileSystem;
+import flixel.group.FlxGroup;
+import haxe.xml.Access;
+import haxe.Exception;
+import flixel.util.FlxColor;
+import funkin.game.Character.CharacterData;
+
+class CharacterConfig extends FlxTypedGroup
+{
+ public static var DEFAULT_CHARACTER = "bf";
+
+ public static function loadCharacterData(character:String):CharacterData
+ {
+ var xml:Access;
+
+ var charData:CharacterData = {
+ offsetY: 0,
+ offsetX: 0,
+ camOffsetY: 0,
+ camOffsetX: 0,
+ quickDancer: false,
+ icon: character,
+ iconColor: FlxColor.fromRGB(94, 94, 94),
+ scale: 1,
+ isPlayer: false,
+ isGF: false,
+ holdTime: 4,
+ flipX: false,
+ antialiasing: true,
+ sprite: "bf",
+ name: character,
+ animations: []
+ }
+
+ var xmlPath = Paths.xml('characters/$character');
+
+ var plainXML = Assets.getText(xmlPath);
+ try
+ {
+ var charXML = Xml.parse(plainXML).firstElement();
+ if (charXML == null)
+ throw new Exception("Missing \"character\" node in XML.");
+ xml = new Access(charXML);
+ }
+
+ charData = xmlToData(charData, xml);
+
+ return charData;
+ }
+
+ public static function xmlToData(charData:CharacterData, xml:Access):CharacterData
+ {
+ if (xml.has.isPlayer)
+ charData.isPlayer = (xml.att.isPlayer == "true");
+ if (xml.has.isGF)
+ charData.isGF = (xml.att.isGF == "true");
+ if (xml.has.x)
+ charData.offsetX = Std.parseFloat(xml.att.x);
+ if (xml.has.y)
+ charData.offsetY = Std.parseFloat(xml.att.y);
+ if (xml.has.gameOverChar)
+ charData.gameOverChar = xml.att.gameOverChar;
+ if (xml.has.camx)
+ charData.camOffsetX = Std.parseFloat(xml.att.camx);
+ if (xml.has.camy)
+ charData.camOffsetY = Std.parseFloat(xml.att.camy);
+ if (xml.has.holdTime)
+ charData.holdTime = CoolUtil.getDefault(Std.parseFloat(xml.att.holdTime), 4);
+ if (xml.has.flipX)
+ charData.flipX = (xml.att.flipX == "true");
+ if (xml.has.icon)
+ charData.icon = xml.att.icon;
+ if (xml.has.iconColor)
+ charData.iconColor = stringToRGB(xml.att.iconColor);
+ if (xml.has.scale)
+ {
+ var scale = Std.parseFloat(xml.att.scale).getDefault(1);
+ charData.scale = scale;
+ }
+ if (xml.has.antialiasing)
+ charData.antialiasing = (xml.att.antialiasing == "true");
+ if (xml.has.sprite)
+ charData.sprite = xml.att.sprite;
+ else
+ charData.sprite = charData.name;
+
+ for (anim in xml.nodes.anim)
+ {
+ var animType:XMLAnimType = NONE;
+ var animloop = false;
+ if (anim.has.loop)
+ animloop = (anim.att.loop == "true");
+ charData.animations.push(XMLUtil.extractAnimFromXML(anim, animType, animloop));
+ }
+ return charData;
+ }
+
+ public static function dataToXml(charData:CharacterData):Access
+ {
+ var root = Xml.createElement("character");
+ root.set("x", Std.string(Math.floor(charData.offsetX)));
+ root.set("y", Std.string(Math.floor(charData.offsetY)));
+ root.set("gameOverChar", Std.string(charData.gameOverChar));
+ root.set("camx", Std.string(Math.floor(charData.camOffsetX)));
+ root.set("camy", Std.string(Math.floor(charData.camOffsetY)));
+ root.set("holdTime", Std.string(charData.holdTime));
+ root.set("flipX", Std.string(charData.flipX));
+ root.set("icon", charData.icon);
+ root.set("iconColor", Std.string(charData.iconColor));
+ root.set("scale", Std.string(charData.scale));
+ root.set("antialiasing", Std.string(charData.antialiasing));
+ root.set("sprite", Std.string(charData.sprite));
+ root.set("isPlayer", Std.string(charData.isPlayer));
+ root.set("isGF", Std.string(charData.isGF));
+
+ for (anim in charData.animations)
+ {
+ var child:Xml = Xml.createElement('anim');
+ child.set("name", Std.string(anim.name));
+ child.set("anim", Std.string(anim.anim));
+ child.set("loop", Std.string(anim.loop));
+ child.set("fps", Std.string(anim.fps));
+ child.set("x", Std.string(anim.x));
+ child.set("y", Std.string(anim.y));
+ child.set("indices", Std.string(anim.indices));
+ root.addChild(child);
+ }
+ var xml = new Access(Xml.parse('${root.toString()}'));
+
+ return xml;
+ }
+
+ public static function stringToRGB(rgbColor:String):FlxColor
+ {
+ var colorSplit = rgbColor.split(",");
+ if (colorSplit[0] != null && colorSplit[1] != null && colorSplit[2] != null)
+ return FlxColor.fromRGB(Std.parseInt(colorSplit[0]), Std.parseInt(colorSplit[1]), Std.parseInt(colorSplit[2]));
+ else
+ return FlxColor.fromRGB(90,90,90);
+ }
+
+ public static function rgbToString(color:FlxColor):String
+ return '${color.red}, ${color.green}, ${color.blue}';
+ /**
+ * @return Saves the character to the specific song folder path.
+ */
+ public static function save(character:String, xml:String):String
+ {
+ var charPath = '';
+ var data = '$xml';
+ #if sys
+ var charPath = '${character}';
+
+ File.saveContent(charPath, data);
+
+ // idk how null reacts to it so better be sure
+ #end
+ return xml;
+ }
+
+ public static function generateCharXML(xml:Xml):String
+ {
+ return '\n${Printer.print(xml, true)}';
+ }
+}
diff --git a/source/funkin/editors/character/CharacterEditor.hx b/source/funkin/editors/character/CharacterEditor.hx
new file mode 100644
index 00000000..22bbd600
--- /dev/null
+++ b/source/funkin/editors/character/CharacterEditor.hx
@@ -0,0 +1,697 @@
+package funkin.editors.character;
+
+import funkin.backend.system.framerate.Framerate;
+import funkin.backend.FlxAnimate;
+import flixel.group.FlxSpriteGroup;
+import flixel.graphics.FlxGraphic;
+import funkin.backend.utils.XMLUtil.AnimData;
+import flixel.math.FlxPoint;
+import funkin.editors.ui.UIContextMenu.UIContextMenuOption;
+import funkin.game.Character;
+import flixel.system.debug.interaction.tools.Pointer.GraphicCursorCross;
+import flixel.util.FlxColor;
+
+class CharacterEditor extends UIState
+{
+ public var blockedInputs = [];
+ public var blockedStuff = [];
+
+ public var curChar:String;
+
+ public var char:Character;
+ public var ghostChar:Character;
+ public var charLayer:FlxTypedSpriteGroup = new FlxTypedSpriteGroup(0, 0);
+ public var animAtlas:FlxAnimate;
+
+ public var cameraPointer:FlxSprite;
+
+ public var camFollow:FlxObject;
+ public var editorCamera:FlxCamera;
+ public var uiCamera:FlxCamera;
+
+ public var topMenu:Array;
+ public var topMenuSpr:UITopMenu;
+
+ public var uiGroup:FlxTypedGroup = new FlxTypedGroup();
+ public var stage:FlxTypedSpriteGroup = new FlxTypedSpriteGroup(0, 0);
+
+ public var curAnim:Int = 0;
+
+ public var playingAnim:UIText;
+ public var movingCamText:UIText;
+
+ public var camZoom(default, set):Float = 1;
+
+ public function new(charName:String)
+ {
+ super();
+ this.curChar = charName;
+ }
+
+ override function create()
+ {
+ super.create();
+
+ // Camera setup
+ camFollow = new FlxObject(0, 0, 2, 2);
+ camFollow.screenCenter();
+ add(camFollow);
+ editorCamera = FlxG.camera;
+ uiCamera = new FlxCamera();
+ uiCamera.bgColor = 0;
+ FlxG.cameras.add(uiCamera, false);
+ FlxG.camera.follow(camFollow);
+
+ // Add Stage and Centered Cross
+ add(stage);
+ add(ofs_cross);
+ loadCross();
+
+ // Indicators
+ var pointer:FlxGraphic = FlxGraphic.fromClass(GraphicCursorCross);
+ cameraPointer = new FlxSprite().loadGraphic(pointer);
+ cameraPointer.setGraphicSize(40, 40);
+ cameraPointer.updateHitbox();
+ cameraPointer.color = FlxColor.WHITE;
+
+ // Character
+ add(charLayer);
+ add(cameraPointer);
+ loadChar(!CharacterConfig.loadCharacterData(curChar).isPlayer);
+
+ // Reload Stage
+ reloadStage();
+
+ // UI
+ loadUI();
+
+ playingAnim = new UIText((FlxG.width / 2), 30, -1, 'Animation:\n${char.characterData.animations[curAnim].name}', 24);
+ playingAnim.cameras = [uiCamera];
+
+ movingCamText = new UIText((FlxG.width / 4) - 310, (FlxG.height / 2), -1,
+ "Press the left mouse button anywhere on this stage to set the character's camera position.", 24);
+ movingCamText.cameras = [uiCamera];
+ movingCamText.visible = false;
+
+ // Top Menu
+ add(playingAnim);
+ add(movingCamText);
+ add(topMenuSpr);
+ add(uiGroup);
+ }
+
+ override function update(elapsed:Float)
+ {
+ // Control
+ controlCamera(elapsed);
+ controlCharacter();
+ changeAnim();
+ moveCharPos();
+ detectCamClick();
+
+ ghostChar.setPosition(char.x, char.y);
+ super.update(elapsed);
+ }
+
+ public function searchAnim(name:String, ?array:Array):AnimData
+ {
+ if (array == null)
+ array = char.characterData.animations;
+ for (i in 0...array.length - 1)
+ if (array[i].name == name)
+ return array[i];
+ return array[0];
+ }
+
+ public function getAnimIndex(name:String, ?array:Array):Int
+ {
+ if (array == null)
+ array = char.characterData.animations;
+ for (i in 0...array.length - 1)
+ if (array[i].name == name)
+ return i;
+ return 0;
+ }
+
+ function resortAnims(animations:Array)
+ {
+ // TODO: add support for danceLeft danceRight
+ if (getAnimIndex("idle") != 0)
+ {
+ var oldanims = animations.copy();
+ animations.clear();
+ animations.push(searchAnim("idle", oldanims));
+ for (anim in oldanims)
+ if (anim != searchAnim("idle", oldanims))
+ animations.push(anim);
+ }
+ }
+
+ public function reloadChar(isOpponent:Bool, charData:CharacterData)
+ {
+ var i:Int = charLayer.members.length - 1;
+ while (i >= 0)
+ {
+ var memb:Character = charLayer.members[i];
+ if (memb != null)
+ {
+ memb.kill();
+ charLayer.remove(memb);
+ memb.destroy();
+ }
+ --i;
+ }
+ charLayer.clear();
+ ghostChar = new Character(0, 0, curChar, !isOpponent);
+ ghostChar.characterData = charData;
+ ghostChar.debugMode = true;
+ ghostChar.alpha = 0.6;
+
+ char = new Character(0, 0, curChar, !isOpponent);
+ char.characterData = charData;
+
+ var charData = char.characterData;
+ resortAnims(charData.animations);
+ var availableAnim = searchAnim("idle");
+ curAnim = getAnimIndex("idle");
+ ghostChar.characterData.animations = char.characterData.animations;
+ char.playAnim(availableAnim.name, true);
+ ghostChar.playAnim(availableAnim.name, true);
+
+ char.debugMode = true;
+ char.setPosition(char.characterData.offsetX + OFFSET_X + 100, char.characterData.offsetY);
+
+ charLayer.add(ghostChar);
+ charLayer.add(char);
+
+ animAtlas = (char.animateAtlas != null ? char.animateAtlas : null);
+
+ updateCharPos(charData.offsetX, charData.offsetY);
+ updatePointerPos();
+ mixDataToChar(char, charData);
+ mixDataToChar(ghostChar, charData);
+ }
+
+ function mixDataToChar(target:Character, data:CharacterData)
+ {
+ var sprite = (target.xml.has.sprite ? target.xml.att.sprite : target.curCharacter);
+ if (data.sprite != sprite)
+ target.loadSprite(Paths.image('characters/${data.sprite}'));
+ target.icon = data.icon;
+ target.flipX = !!data.flipX;
+ target.antialiasing = data.antialiasing;
+ target.isPlayer = data.isPlayer;
+ target.playerOffsets = data.isPlayer;
+ target.isGF = data.isGF;
+ target.scale.set(data.scale, data.scale);
+ target.holdTime = data.holdTime;
+ target.fixChar(true);
+ target.updateHitbox();
+ }
+
+ function loadChar(isOpponent:Bool)
+ {
+ var i:Int = charLayer.members.length - 1;
+ while (i >= 0)
+ {
+ var memb:Character = charLayer.members[i];
+ if (memb != null)
+ {
+ memb.kill();
+ charLayer.remove(memb);
+ memb.destroy();
+ }
+ --i;
+ }
+ charLayer.clear();
+ ghostChar = new Character(0, 0, curChar, !isOpponent);
+ ghostChar.debugMode = true;
+ ghostChar.alpha = 0.6;
+
+ char = new Character(0, 0, curChar, !isOpponent);
+ char.debugMode = true;
+ char.setPosition(char.characterData.offsetX + OFFSET_X + 100, char.characterData.offsetY);
+
+ charLayer.add(ghostChar);
+ charLayer.add(char);
+
+ var charData = char.characterData;
+ animAtlas = (char.animateAtlas != null ? char.animateAtlas : null);
+ updateCharPos(charData.offsetX, charData.offsetY);
+ resortAnims(char.characterData.animations);
+ curAnim = getAnimIndex("idle");
+ char.playAnim(charData.animations[getAnimIndex("idle")].name, true);
+ ghostChar.characterData.animations = char.characterData.animations;
+
+ char.updateHitbox();
+ ghostChar.updateHitbox();
+ updatePointerPos();
+ }
+
+ function loadUI()
+ {
+ topMenu = [
+ {
+ label: "File",
+ childs: [
+ {
+ label: "New"
+ },
+ null,
+ {
+ label: "Save",
+ keybind: [CONTROL, S],
+ onSelect: _file_save,
+ },
+ {
+ label: "Save As...",
+ keybind: [CONTROL, SHIFT, S],
+ onSelect: _file_saveas,
+ },
+ null,
+ {
+ label: "Exit",
+ keybind: [ESCAPE],
+ onSelect: _file_exit
+ }
+ ]
+ },
+ {
+ label: "Edit",
+ childs: [
+ {
+ label: "Camera Position",
+ onSelect: moveCameraPos
+ },
+ {
+ label: "Properties",
+ onSelect: edit_properties_window,
+ }
+ ]
+ },
+ {
+ label: "Animation",
+ childs: [
+ {
+ label: "New Animation",
+ keybind: [CONTROL, N],
+ onSelect: add_anim_window,
+ },
+ null,
+ {
+ label: "Update Animation",
+ keybind: [CONTROL, U],
+ onSelect: update_anim_window,
+ },
+ null,
+ {
+ label: "Remove Animation",
+ keybind: [CONTROL, D],
+ onSelect: remove_anim_window,
+ },
+ ]
+ },
+ {
+ label: "Help",
+ childs: [
+ {
+ label: "Controls",
+ onSelect: help_controls_window
+ }
+ ]
+ }
+ ];
+
+ topMenuSpr = new UITopMenu(topMenu);
+ topMenuSpr.cameras = uiGroup.cameras = [uiCamera];
+
+ Framerate.fpsCounter.alpha = 0.4;
+ Framerate.memoryCounter.alpha = 0.4;
+ Framerate.codenameBuildField.alpha = 0.4;
+ }
+
+ var ofs_cross:FlxSpriteGroup = new FlxSpriteGroup(0, 0);
+
+ public function loadCross()
+ {
+ var i:Int = ofs_cross.members.length - 1;
+ while (i >= 0)
+ {
+ var memb:FlxSprite = ofs_cross.members[i];
+ if (memb != null)
+ {
+ memb.kill();
+ ofs_cross.remove(memb);
+ memb.destroy();
+ }
+ --i;
+ }
+ ofs_cross.clear();
+
+
+ var x = -800;
+
+ var horizontal_cross = new FlxSprite(x, -70).makeSolid(2000, 5);
+ var vertical_cross = new FlxSprite(x, 0).makeSolid(5, 2000);
+ horizontal_cross.y += (vertical_cross.height / 2);
+ vertical_cross.x += (horizontal_cross.width / 2);
+ ofs_cross.add(horizontal_cross);
+ ofs_cross.add(vertical_cross);
+ horizontal_cross.scrollFactor.set(1, 1);
+ vertical_cross.scrollFactor.set(1, 1);
+ // ofs_cross.screenCenter();
+ ofs_cross.y = -200;
+ ofs_cross.scrollFactor.set(1, 1);
+ ofs_cross.alpha = 0.6;
+ }
+
+ var OFFSET_X:Float = 150;
+
+ public function reloadStage()
+ {
+ var i:Int = stage.members.length - 1;
+ while (i >= 0)
+ {
+ var memb:FunkinSprite = stage.members[i];
+ if (memb != null)
+ {
+ memb.kill();
+ stage.remove(memb);
+ memb.destroy();
+ }
+ --i;
+ }
+ stage.clear();
+ var playerXDifference = 0;
+ if (char.isPlayer)
+ playerXDifference = 670;
+
+ var bg = new FunkinSprite(-600 + OFFSET_X - playerXDifference, -200, Paths.image("stages/default/stageback"));
+ stage.add(bg);
+ bg.scrollFactor.set(0.9, 0.9);
+
+ var stageFront = new FunkinSprite(-600 + OFFSET_X - playerXDifference, 600, Paths.image("stages/default/stagefront"));
+ stageFront.setGraphicSize(Std.int(stageFront.width * 1.1));
+ stageFront.updateHitbox();
+ stage.add(stageFront);
+ stageFront.scrollFactor.set(0.9, 0.9);
+
+ stage.setPosition(0, 0);
+ }
+
+ function hasAtlas():Bool
+ return (animAtlas != null);
+
+ var movingCam:Bool = false;
+ var movingCamDefaultPos:FlxPoint = null;
+ var movingCamDefaultPosCam:FlxPoint = null;
+
+ public function moveCameraPos(_)
+ {
+ movingCam = true;
+ movingCamText.visible = true;
+ updatePointerPos();
+ }
+
+ public function detectCamClick()
+ {
+ var charData = char.characterData;
+
+ if (movingCam)
+ {
+ if (FlxG.mouse.justPressed)
+ {
+ var midx = char.getMidpoint().x;
+ var midy = char.getMidpoint().y;
+
+ var pos = FlxG.mouse.getWorldPosition(editorCamera);
+ var placedPos:FlxPoint = new FlxPoint((!char.isPlayer ? pos.x + -(midx) : pos.x + -(midx)), -(pos.y - midy));
+
+ movingCam = false;
+ movingCamText.visible = false;
+ charData.camOffsetX = placedPos.x;
+ charData.camOffsetY = placedPos.y;
+ updatePointerPos();
+ }
+ }
+ }
+
+ function updatePointerPos()
+ {
+ var x:Float = char.getMidpoint().x;
+ var y:Float = char.getMidpoint().y;
+ /*if (!char.isPlayer)
+ { */
+ x += char.characterData.camOffsetX;
+ /*}
+ else
+ {
+ x -= char.characterData.camOffsetX;
+ }*/
+ y -= char.characterData.camOffsetY;
+
+ x -= cameraPointer.width / 2;
+ y -= cameraPointer.height / 2;
+ cameraPointer.setPosition(x, y);
+ }
+
+ var movingOffset:Bool = false;
+ var dragX:Float = -1;
+ var dragY:Float = -1;
+
+ public function moveCharPos()
+ {
+ var charSprite = (hasAtlas() ? char.animateAtlas : char);
+ var charData = char.characterData;
+ if (FlxG.mouse.justPressed && FlxG.mouse.overlaps(charSprite))
+ {
+ var pos = FlxG.mouse.getWorldPosition(FlxG.camera);
+ dragX = charData.offsetX - pos.x;
+ dragY = charData.offsetY - pos.y;
+ movingOffset = true;
+ }
+ else if (!FlxG.mouse.overlaps(charSprite))
+ movingOffset = false;
+
+ if (movingOffset && FlxG.mouse.pressed)
+ {
+ var pos = FlxG.mouse.getWorldPosition(editorCamera);
+ updateCharPos((pos.x + dragX), (pos.y + dragY));
+ }
+ }
+
+ public function updateCharPos(x:Float, y:Float)
+ {
+ char.characterData.offsetX = x;
+ char.characterData.offsetY = y;
+ char.x = x;
+ char.y = y;
+ updatePointerPos();
+ }
+
+ public function reloadGhost()
+ {
+ ghostChar.loadSprite(char.characterData.sprite);
+ for (i in 0...char.characterData.animations.length - 1)
+ {
+ XMLUtil.addAnimToSprite(ghostChar, char.characterData.animations[i]);
+ }
+
+ char.alpha = 0.85;
+ ghostChar.visible = true;
+ /*if (ghostDropDown.selectedLabel == '')
+ {
+ ghostChar.visible = false;
+ char.alpha = 1;
+ }*/
+ ghostChar.color = 0xFF666688;
+ ghostChar.antialiasing = char.antialiasing;
+ ghostChar.playAnim(char.characterData.animations[curAnim].name, true);
+ }
+
+ function controlCharacter()
+ {
+ var controlArray:Array = [
+ FlxG.keys.justPressed.LEFT,
+ FlxG.keys.justPressed.RIGHT,
+ FlxG.keys.justPressed.UP,
+ FlxG.keys.justPressed.DOWN
+ ];
+ var holdShift = FlxG.keys.pressed.SHIFT;
+ var holdAlt = FlxG.keys.pressed.ALT;
+
+ for (i in 0...controlArray.length)
+ {
+ if (controlArray[i])
+ {
+ var multiplier:Float = 1;
+ if (holdShift)
+ multiplier = 10;
+ if (holdAlt)
+ multiplier = 0.1;
+
+ var negaMult:Int = 1;
+ if (i % 2 == 1)
+ negaMult = -1;
+
+ // For Player Offsets
+ if (i == 1 && char.playerOffsets)
+ negaMult = -1;
+ if (i == 0 && char.playerOffsets)
+ negaMult = 1;
+
+ if (i > 1)
+ {
+ char.characterData.animations[curAnim].y += negaMult * multiplier;
+ ghostChar.characterData.animations[curAnim].y += negaMult * multiplier;
+ }
+ else
+ {
+ char.characterData.animations[curAnim].x += negaMult * multiplier;
+ ghostChar.characterData.animations[curAnim].x += negaMult * multiplier;
+ }
+
+ char.animOffsets.set(char.characterData.animations[curAnim].name,
+ new FlxPoint(char.characterData.animations[curAnim].x, char.characterData.animations[curAnim].y));
+ ghostChar.animOffsets.set(char.characterData.animations[curAnim].name,
+ new FlxPoint(char.characterData.animations[curAnim].x, char.characterData.animations[curAnim].y));
+
+ char.addOffset(char.characterData.animations[curAnim].anim, char.characterData.animations[curAnim].x,
+ char.characterData.animations[curAnim].y);
+ ghostChar.addOffset(char.characterData.animations[curAnim].anim, char.characterData.animations[curAnim].x,
+ char.characterData.animations[curAnim].y);
+ /*ghostChar.animOffsets.set(char.animation.curAnim.name,
+ new FlxPoint(char.characterData.animations[curAnim].x, char.characterData.animations[curAnim].y)); */
+
+ char.playAnim(char.characterData.animations[curAnim].name, false);
+ if (char.getAnimName() != null && char.getAnimName() == ghostChar.getAnimName())
+ {
+ ghostChar.playAnim(char.getAnimName(), false);
+ }
+ }
+ }
+ }
+
+ function controlCamera(elapsed:Float)
+ {
+ var ofs:Float = 500 * elapsed;
+ var shift:Bool = (FlxG.keys.pressed.SHIFT);
+ var cam_up:Bool = (FlxG.keys.pressed.W);
+ var cam_down:Bool = (FlxG.keys.pressed.S);
+ var cam_left:Bool = (FlxG.keys.pressed.A);
+ var cam_right:Bool = (FlxG.keys.pressed.D);
+ var cam_zoomers:Array = [FlxG.keys.pressed.I, FlxG.keys.pressed.O];
+
+ if (shift)
+ ofs *= 4;
+
+ if (cam_up)
+ camFollow.y -= ofs;
+ if (cam_down)
+ camFollow.y += ofs;
+ if (cam_left)
+ camFollow.x -= ofs;
+ if (cam_right)
+ camFollow.x += ofs;
+
+ var zoom_ofs = FlxG.camera.zoom * elapsed;
+ if (cam_zoomers[0] && camZoom < 3)
+ {
+ camZoom += zoom_ofs;
+ if (camZoom > 3)
+ camZoom = 3;
+ }
+ if (cam_zoomers[1] && camZoom > 0.1)
+ {
+ camZoom -= zoom_ofs;
+ if (camZoom < 0.1)
+ camZoom = 0.1;
+ }
+ }
+
+ function changeAnim()
+ {
+ var space = (FlxG.keys.justPressed.SPACE);
+ var forward = (FlxG.keys.justPressed.L);
+ var backwards = (FlxG.keys.justPressed.K);
+
+ if (forward || backwards)
+ {
+ var changeLimit = char.characterData.animations.length;
+ curAnim = FlxMath.wrap(curAnim + (forward ? 1 : -1), 0, changeLimit-1);
+
+ trace(curAnim);
+ trace(char.characterData.animations[curAnim]);
+
+ char.playAnim(char.characterData.animations[curAnim].name, false);
+ playingAnim.text = 'Animation:\n${char.characterData.animations[curAnim].name}';
+ }
+
+ if (space)
+ char.playAnim(char.characterData.animations[curAnim].name, true);
+
+ char.updateHitbox();
+ ghostChar.updateHitbox();
+ }
+
+ // Animation Windows
+ function add_anim_window(_)
+ openSubState(new AddAnimScreen(char, ghostChar, this));
+
+ function update_anim_window(_)
+ openSubState(new SelectAnimUpdate(char, ghostChar, this));
+
+ function remove_anim_window(_)
+ openSubState(new DeleteAnimScreen(char, ghostChar, this));
+
+ function help_controls_window(_)
+ openSubState(new HelpControls());
+
+ function _file_exit(_)
+ {
+ FlxG.switchState(new CharacterSelection());
+ }
+
+ override function destroy() {
+ Framerate.fpsCounter.alpha = 1;
+ Framerate.memoryCounter.alpha = 1;
+ Framerate.codenameBuildField.alpha = 1;
+ super.destroy();
+ }
+
+ function edit_properties_window(_)
+ FlxG.state.openSubState(new CharacterProperties(char, ghostChar, this));
+
+ function _file_save(_)
+ {
+ #if sys
+ var assetPath = Paths.character(char.curCharacter);
+ var path = Assets.getPath(assetPath);
+ saveTo(path);
+ return;
+ #end
+ _file_saveas(_);
+ }
+
+ function _file_saveas(_)
+ {
+ var xml = CharacterConfig.dataToXml(char.characterData);
+ openSubState(new SaveSubstate(CharacterConfig.generateCharXML(xml.x), {
+ defaultSaveFile: '${char.curCharacter}.xml'
+ }));
+ }
+
+ #if sys
+ function saveTo(path:String)
+ {
+ var xml = CharacterConfig.dataToXml(char.characterData);
+ CharacterConfig.save(path, CharacterConfig.generateCharXML(xml.x));
+ }
+ #end
+
+ function set_camZoom(value:Float):Float
+ {
+ editorCamera.zoom = value;
+ return camZoom = value;
+ }
+}
diff --git a/source/funkin/editors/character/CharacterProperties.hx b/source/funkin/editors/character/CharacterProperties.hx
new file mode 100644
index 00000000..2c167c9c
--- /dev/null
+++ b/source/funkin/editors/character/CharacterProperties.hx
@@ -0,0 +1,123 @@
+package funkin.editors.character;
+
+import flixel.util.FlxColor;
+import flixel.math.FlxPoint;
+import funkin.backend.utils.XMLUtil.AnimData;
+import funkin.game.Character;
+
+class CharacterProperties extends UISubstateWindow
+{
+ public var char:Character;
+ public var ghostChar:Character;
+ public var charData:CharacterData;
+
+ public var applyButton:UIButton;
+ public var cancelButton:UIButton;
+ public var editor:CharacterEditor;
+
+ public var curAnimData:AnimData;
+
+ public var spriteFile:UITextBox;
+ public var gameOverChar:UITextBox;
+ public var healthIcon:UITextBox;
+ public var iconColor:UITextBox;
+ public var flipX:UICheckbox;
+ public var isPlayer:UICheckbox;
+ public var isGF:UICheckbox;
+ public var antialiasing:UICheckbox;
+ public var scale:UITextBox;
+ public var holdTime:UITextBox;
+
+ public function new(char:Character, ghostChar, editor:CharacterEditor)
+ {
+ super();
+ this.char = char;
+ this.ghostChar = ghostChar;
+ this.charData = char.characterData;
+ this.editor = editor;
+ }
+
+ public override function create()
+ {
+ winTitle = 'Character Properties';
+ winWidth = 960;
+
+ super.create();
+
+ // Offsets
+ var y_ofs = 60;
+ var x_ofs = 60;
+ var y_text_ofs = 20;
+
+ // Character Properties
+ spriteFile = new UITextBox(windowSpr.x + 50, windowSpr.y + 10 + y_ofs, charData.sprite);
+ add(spriteFile);
+ add(new UIText(spriteFile.x, spriteFile.y - y_text_ofs, -1, "Image/Atlas file: "));
+
+ gameOverChar = new UITextBox(spriteFile.x + spriteFile.bWidth + x_ofs, spriteFile.y, charData.gameOverChar);
+ add(gameOverChar);
+ add(new UIText(gameOverChar.x, gameOverChar.y - y_text_ofs, -1, "Game Over character: "));
+
+ healthIcon = new UITextBox(spriteFile.x, spriteFile.y + y_ofs, charData.icon);
+ add(healthIcon);
+ add(new UIText(healthIcon.x, healthIcon.y - y_text_ofs, -1, "Health Icon: "));
+
+ iconColor = new UITextBox(healthIcon.x + healthIcon.bWidth + x_ofs, healthIcon.y, CharacterConfig.rgbToString(charData.iconColor));
+ add(iconColor);
+ add(new UIText(iconColor.x, iconColor.y - y_text_ofs, -1, "Icon color (RGB): "));
+
+ flipX = new UICheckbox(healthIcon.x, healthIcon.y + y_ofs, "Flip X", charData.flipX);
+ add(flipX);
+
+ antialiasing = new UICheckbox(flipX.x + flipX.field.width + x_ofs, flipX.y, "Antialiasing", charData.antialiasing);
+ add(antialiasing);
+
+ isPlayer = new UICheckbox(flipX.x, flipX.y + y_ofs, "Playable Character", charData.isPlayer);
+ add(isPlayer);
+
+ isGF = new UICheckbox(isPlayer.x + isPlayer.field.width + x_ofs, isPlayer.y, "GF Character", charData.isGF);
+ add(isGF);
+
+ scale = new UITextBox(isPlayer.x, isPlayer.y + y_ofs, Std.string(charData.scale));
+ add(scale);
+ add(new UIText(scale.x, scale.y - y_text_ofs, -1, "Scale: "));
+
+ holdTime = new UITextBox(scale.x + scale.bWidth + y_ofs, scale.y, Std.string(charData.holdTime));
+ add(holdTime);
+ add(new UIText(holdTime.x, holdTime.y - y_text_ofs, -1, "Hold time: "));
+
+ // Apply / Cancel buttons
+ applyButton = new UIButton(windowSpr.x - x_ofs - 70, windowSpr.y, "Apply", function()
+ {
+ updateProperties(spriteFile.label.text, healthIcon.label.text, CharacterConfig.stringToRGB(healthIcon.label.text), flipX.checked,
+ antialiasing.checked, isPlayer.checked, isGF.checked, Std.parseFloat(scale.label.text), Std.parseFloat(holdTime.label.text), gameOverChar.label.text);
+ });
+ add(applyButton);
+
+ cancelButton = new UIButton(applyButton.x, applyButton.y + y_ofs, "Cancel", function()
+ {
+ close();
+ });
+ add(cancelButton);
+ }
+
+ public function updateProperties(sprite:String, healthIcon:String, iconColor:FlxColor, flipX:Bool, antialiasing:Bool, isPlayer:Bool, isGF:Bool,
+ scale:Float, holdTime:Float, gameOverChar:String)
+ {
+ // Character Data
+ charData.sprite = sprite;
+ charData.icon = healthIcon;
+ charData.iconColor = iconColor;
+ charData.flipX = flipX;
+ charData.antialiasing = antialiasing;
+ charData.isPlayer = isPlayer;
+ charData.isGF = isGF;
+ charData.scale = scale;
+ charData.holdTime = holdTime;
+ charData.gameOverChar = gameOverChar;
+
+ editor.reloadChar(!char.isPlayer, charData);
+ editor.reloadStage();
+ editor.loadCross();
+ }
+}
diff --git a/source/funkin/editors/character/CharacterSelection.hx b/source/funkin/editors/character/CharacterSelection.hx
new file mode 100644
index 00000000..1d26f6a6
--- /dev/null
+++ b/source/funkin/editors/character/CharacterSelection.hx
@@ -0,0 +1,87 @@
+package funkin.editors.character;
+
+import flixel.util.FlxColor;
+import funkin.game.Character;
+import funkin.backend.chart.Chart;
+import funkin.game.Character.CharacterData;
+import funkin.options.type.TextOption;
+import funkin.options.type.IconOption;
+import funkin.options.OptionsScreen;
+import sys.FileSystem;
+
+class CharacterSelection extends EditorTreeMenu
+{
+ public var characterList:CharactersList;
+
+ public override function create()
+ {
+ bgType = "charter";
+
+ super.create();
+
+ characterList = CharactersList.get();
+
+ main = new OptionsScreen("Character Editor", "Select a character to edit", [
+ for (s in characterList.characters)
+ new IconOption(s.name, "Press ACCEPT to edit this character.", s.icon, function()
+ {
+ FlxG.switchState(new CharacterEditor(s.name));
+ })
+ ]);
+ }
+
+ public override function update(elapsed:Float)
+ {
+ super.update(elapsed);
+ }
+
+ public override function onMenuChange() {
+ super.onMenuChange();
+ if (optionsTree.members.length > 1) { // selected a song
+ // small flashbang
+ var color:FlxColor = characterList.characters[main.curSelected].iconColor.getDefault(0xFFFFFFFF);
+
+ bg.colorTransform.redOffset = 0.25 * color.red;
+ bg.colorTransform.greenOffset = 0.25 * color.green;
+ bg.colorTransform.blueOffset = 0.25 * color.blue;
+ bg.colorTransform.redMultiplier = FlxMath.lerp(1, color.redFloat, 0.25);
+ bg.colorTransform.greenMultiplier = FlxMath.lerp(1, color.greenFloat, 0.25);
+ bg.colorTransform.blueMultiplier = FlxMath.lerp(1, color.blueFloat, 0.25);
+ }
+ }
+}
+
+class CharactersList
+{
+ public var characters:Array = [];
+
+ public function new()
+ {
+ }
+
+ public function getCharactersFromSource(source:funkin.backend.assets.AssetsLibraryList.AssetSource)
+ {
+ var charactersFound:Array = [];
+
+ charactersFound = Paths.getFolderContent('data/characters', false, source);
+
+ if (charactersFound.length > 0)
+ {
+ for (s in charactersFound)
+ if (s.endsWith(".xml"))
+ characters.push(CharacterConfig.loadCharacterData(s.replace(".xml", "")));
+ return true;
+ }
+ return false;
+ }
+
+ public static function get()
+ {
+ var characterList = new CharactersList();
+
+ if (!characterList.getCharactersFromSource(MODS))
+ characterList.getCharactersFromSource(SOURCE);
+
+ return characterList;
+ }
+}
diff --git a/source/funkin/editors/character/DeleteAnimScreen.hx b/source/funkin/editors/character/DeleteAnimScreen.hx
new file mode 100644
index 00000000..ec5d6299
--- /dev/null
+++ b/source/funkin/editors/character/DeleteAnimScreen.hx
@@ -0,0 +1,73 @@
+package funkin.editors.character;
+
+import flixel.math.FlxRandom;
+import cpp.Random;
+import flixel.math.FlxPoint;
+import funkin.backend.utils.XMLUtil.AnimData;
+import funkin.game.Character;
+
+class DeleteAnimScreen extends UISubstateWindow
+{
+ public var char:Character;
+ public var ghostChar:Character;
+
+ public var editor:CharacterEditor;
+
+ public var animButtons:FlxTypedSpriteGroup = new FlxTypedSpriteGroup(365, -10);
+
+ public var closeButton:UIButton;
+
+ public function new(char:Character, ghostChar:Character, editor:CharacterEditor)
+ {
+ super();
+ this.char = char;
+ this.ghostChar = ghostChar;
+ this.editor = editor;
+ }
+
+ public override function create()
+ {
+ winTitle = "Delete Animation";
+ winWidth = 480;
+ winHeight = 700;
+
+ super.create();
+
+ add(animButtons);
+
+ var y_adder = 60;
+ for (anim in char.characterData.animations)
+ {
+ var newbutton = new UIButton(animButtons.x - 550, animButtons.y + y_adder, anim.name, function()
+ {
+ removeAnimation(anim);
+ close();
+ });
+ animButtons.add(newbutton);
+ y_adder += 45;
+ }
+
+ closeButton = new UIButton(windowSpr.x - 120, windowSpr.y + 20, "Close", function() {
+ close();
+ });
+ add(closeButton);
+ }
+
+ public function removeAnimation(anim:AnimData)
+ {
+ var refreshAnim:Bool = false;
+ if (char.animation.curAnim != null && anim.name == char.animation.curAnim.name)
+ refreshAnim = true;
+
+ if (char.animation.getByName(anim.name) != null)
+ char.animation.remove(anim.name);
+ if (char.animOffsets.exists(anim.name))
+ char.animOffsets.remove(anim.name);
+ char.characterData.animations.remove(anim);
+
+ if (refreshAnim && char.characterData.animations.length > 0)
+ char.playAnim(char.characterData.animations[0].name, true);
+
+ editor.reloadGhost();
+ }
+}
diff --git a/source/funkin/editors/character/HelpControls.hx b/source/funkin/editors/character/HelpControls.hx
new file mode 100644
index 00000000..a2d6855e
--- /dev/null
+++ b/source/funkin/editors/character/HelpControls.hx
@@ -0,0 +1,31 @@
+package funkin.editors.character;
+
+class HelpControls extends UISubstateWindow
+{
+ public function new()
+ {
+ super();
+ }
+
+ public override function create()
+ {
+ winTitle = 'Help: Controls';
+ winWidth = 960;
+
+ super.create();
+
+ var helpText = new UIText(windowSpr.x + 25, windowSpr.y + 240, -1, "Left/Right/Up/Down Arrow: To change the offset of the current\nplaying animation.\n
+Left mouse button on the character: Change character's position.\nK to change to prev anim, L to change to new anim
+Space to play animation
+
+I to zoom in
+O to zoom out", 24);
+ add(helpText);
+
+ var closeButton = new UIButton(helpText.x, helpText.y + 160, "Close", function()
+ {
+ close();
+ });
+ add(closeButton);
+ }
+}
diff --git a/source/funkin/editors/character/SelectAnimUpdate.hx b/source/funkin/editors/character/SelectAnimUpdate.hx
new file mode 100644
index 00000000..576f0189
--- /dev/null
+++ b/source/funkin/editors/character/SelectAnimUpdate.hx
@@ -0,0 +1,62 @@
+package funkin.editors.character;
+
+import flixel.math.FlxRandom;
+import cpp.Random;
+import flixel.math.FlxPoint;
+import funkin.backend.utils.XMLUtil.AnimData;
+import funkin.game.Character;
+
+class SelectAnimUpdate extends UISubstateWindow
+{
+ public var cancelButton:UIButton;
+
+ public var char:Character;
+ public var ghostChar:Character;
+
+ public var editor:CharacterEditor;
+
+ public var animButtons:FlxTypedSpriteGroup = new FlxTypedSpriteGroup(365, -10);
+
+ public function new(char:Character, ghostChar:Character, editor:CharacterEditor)
+ {
+ super();
+ this.char = char;
+ this.ghostChar = ghostChar;
+ this.editor = editor;
+ }
+
+ public override function create()
+ {
+ winTitle = "Select an animation to update:";
+ winWidth = 480;
+ winHeight = 700;
+
+ super.create();
+
+ add(animButtons);
+
+ var y_ofs = 60;
+
+ for (anim in char.characterData.animations)
+ {
+ var newbutton = new UIButton(animButtons.x - 550, animButtons.y + y_ofs, anim.name, function()
+ {
+ openUpdateWindow(anim);
+ });
+ animButtons.add(newbutton);
+ y_ofs += 45;
+ }
+
+ cancelButton = new UIButton(windowSpr.x - 10, windowSpr.y + 20, "Cancel", function()
+ {
+ close();
+ });
+ cancelButton.x -= cancelButton.bWidth;
+ add(cancelButton);
+ }
+
+ public function openUpdateWindow(anim:AnimData)
+ {
+ openSubState(new UpdateAnimScreen(this.char, this.ghostChar, anim, this.editor));
+ }
+}
diff --git a/source/funkin/editors/character/UpdateAnimScreen.hx b/source/funkin/editors/character/UpdateAnimScreen.hx
new file mode 100644
index 00000000..e04ccd87
--- /dev/null
+++ b/source/funkin/editors/character/UpdateAnimScreen.hx
@@ -0,0 +1,125 @@
+package funkin.editors.character;
+
+import flixel.math.FlxPoint;
+import funkin.backend.utils.XMLUtil.AnimData;
+import funkin.game.Character;
+
+class UpdateAnimScreen extends UISubstateWindow
+{
+ public var char:Character;
+ public var ghostChar:Character;
+
+ public var updateButton:UIButton;
+ public var cancelButton:UIButton;
+ public var editor:CharacterEditor;
+
+ public var curAnimData:AnimData;
+
+ public var animName:UITextBox;
+ public var animPrefix:UITextBox;
+ public var fps:UITextBox;
+ public var loop:UICheckbox;
+ public var indices:UITextBox;
+
+ public function new(char:Character, ghostChar, animData:AnimData, editor:CharacterEditor)
+ {
+ super();
+ this.char = char;
+ this.ghostChar = ghostChar;
+ this.curAnimData = animData;
+ this.editor = editor;
+ }
+
+ public override function create()
+ {
+ winTitle = 'Update Animation: ${curAnimData.name}';
+ winWidth = 960;
+
+ super.create();
+
+ // Animation Properties
+ var y_ofs = 60;
+ var y_text_ofs = 20;
+
+ animName = new UITextBox(windowSpr.x + 50, windowSpr.y + y_ofs, curAnimData.name);
+ add(animName);
+ add(new UIText(animName.x, animName.y - y_text_ofs, -1, "Animation Name: "));
+
+ animPrefix = new UITextBox(animName.x, animName.y + y_ofs, curAnimData.anim);
+ add(animPrefix);
+ add(new UIText(animPrefix.x, animPrefix.y - y_text_ofs, -1, "Animation on XML/Atlas file: "));
+
+ fps = new UITextBox(animPrefix.x, animPrefix.y + y_ofs, Std.string(curAnimData.fps));
+ add(fps);
+ add(new UIText(fps.x, fps.y - y_text_ofs, -1, "Framerate: "));
+
+ loop = new UICheckbox(fps.x, fps.y + y_ofs, "Looped", curAnimData.loop);
+ add(loop);
+
+ indices = new UITextBox(loop.x, loop.y + y_ofs, Std.string(curAnimData.indices).replace("[", "").replace("]", ""));
+ add(indices);
+ add(new UIText(indices.x, indices.y - y_text_ofs, -1, "Indices (0, 1, 2, 3): "));
+
+ // Add / Cancel Buttons
+ updateButton = new UIButton(windowSpr.x - 10, windowSpr.y + 31, "Update", function()
+ {
+ updateAnimation(animName.label.text, animPrefix.label.text, Std.parseInt(fps.label.text), loop.checked, indices.label.text);
+ close();
+ });
+ updateButton.x -= updateButton.bWidth;
+ add(updateButton);
+
+ cancelButton = new UIButton(updateButton.x, updateButton.y + y_ofs, "Cancel", function()
+ {
+ close();
+ });
+ add(cancelButton);
+ }
+
+ public function updateAnimation(name:String, ?anim:String, ?fps:Int = 24, ?looped:Bool = false, ?indices:String)
+ {
+ var animType:XMLAnimType = NONE;
+
+ var selectedAnim:AnimData = null;
+ var selectedAnimIndex = 0;
+
+ for (i in 0...char.characterData.animations.length)
+ {
+ if (char.characterData.animations[i].name == name)
+ {
+ selectedAnim = char.characterData.animations[i];
+ selectedAnimIndex = i;
+ }
+ }
+
+ if (selectedAnim == null)
+ return;
+
+ var animData:AnimData = {
+ name: name,
+ anim: anim,
+ fps: fps,
+ loop: looped,
+ animType: animType,
+ indices: [],
+ x: selectedAnim.x,
+ y: selectedAnim.y
+ };
+
+ if (indices != "" && indices != null)
+ {
+ var indicesSplit = indices.split(",");
+
+ for (indice in indicesSplit)
+ {
+ var i = Std.parseInt(indice.trim());
+ if (i != null)
+ animData.indices.push(i);
+ }
+ }
+
+ XMLUtil.addAnimToSprite(char, animData);
+ char.characterData.animations[selectedAnimIndex] = animData;
+ char.playAnim(name, true);
+ }
+}
diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx
index 80759bf3..88351491 100644
--- a/source/funkin/editors/charter/Charter.hx
+++ b/source/funkin/editors/charter/Charter.hx
@@ -54,6 +54,7 @@ class Charter extends UIState {
public var topLimit:FlxSprite;
public var strumlineInfoBG:FlxSprite;
+ public var strumlineAddButton:CharterStrumlineAddButton;
public var hitsound:FlxSound;
public var metronome:FlxSound;
@@ -113,6 +114,17 @@ class Charter extends UIState {
onSelect: _file_saveas,
},
null,
+ {
+ label: "Save Mata",
+ keybind: [CONTROL, ALT, S],
+ onSelect: _file_meta_save,
+ },
+ {
+ label: "Save Meta As...",
+ keybind: [CONTROL, ALT ,SHIFT, S],
+ onSelect: _file_meta_saveas,
+ },
+ null,
{
label: "Exit",
onSelect: _file_exit
@@ -187,7 +199,8 @@ class Charter extends UIState {
},
null,
{
- label: "Edit metadata information"
+ label: "Edit metadata information",
+ onSelect: chart_edit_metadata
}
]
},
@@ -375,6 +388,9 @@ class Charter extends UIState {
strumlineInfoBG.y = 23;
strumlineInfoBG.scrollFactor.set();
+ strumlineAddButton = new CharterStrumlineAddButton();
+
+ strumlineAddButton.cameras = [charterCamera];
strumlineInfoBG.cameras = [charterCamera];
strumLines.cameras = [charterCamera];
@@ -394,6 +410,7 @@ class Charter extends UIState {
add(conductorFollowerSpr);
add(selectionBox);
add(strumlineInfoBG);
+ add(strumlineAddButton);
add(strumLines);
// add the top menu last OUT of the ui group so that it stays on top
add(topMenuSpr);
@@ -405,8 +422,10 @@ class Charter extends UIState {
var instPath:String;
public function loadSong() {
- if (__reload)
+ if (__reload) {
+ EventsData.reloadEvents();
PlayState.loadSong(__song, __diff, false, false);
+ }
Conductor.setupSong(PlayState.SONG);
@@ -415,22 +434,8 @@ class Charter extends UIState {
vocals.group = FlxG.sound.defaultMusicGroup;
trace("generating notes...");
- for(strID=>strL in PlayState.SONG.strumLines) {
- for(note in strL.notes) {
- var n = new CharterNote();
- var t = Conductor.getStepForTime(note.time);
- n.updatePos(t, (strID * 4) + note.id, Conductor.getStepForTime(note.time + note.sLen) - t, note.type);
- notesGroup.add(n);
- }
-
- strumLines.add(new CharterStrumline(strL));
- }
- trace("sorting notes...");
- notesGroup.sort(function(i, n1, n2) {
- if (n1.step == n2.step)
- return FlxSort.byValues(FlxSort.ASCENDING, n1.id, n2.id);
- return FlxSort.byValues(FlxSort.ASCENDING, n1.step, n2.step);
- });
+ for(strL in PlayState.SONG.strumLines)
+ createStrumline(strumLines.members.length, strL, false);
trace("generating events...");
var __last:CharterEvent = null;
@@ -487,6 +492,7 @@ class Charter extends UIState {
if (FlxG.mouse.justReleasedRight) {
if (!selection.contains(n))
selection = [n];
+ closeCurrentContextMenu();
openContextMenu(topMenu[1].childs);
}
}
@@ -614,8 +620,10 @@ class Charter extends UIState {
}
}
- if (FlxG.mouse.justReleasedRight)
+ if (FlxG.mouse.justReleasedRight) {
+ closeCurrentContextMenu();
openContextMenu(topMenu[1].childs);
+ }
}
if (gridActionType == NONE && mousePos.x < 0) {
@@ -667,6 +675,108 @@ class Charter extends UIState {
return [];
}
+ // STRUMLINE DELETION/CREATION
+ public function createStrumline(strumLineID:Int, strL:ChartStrumLine, addToUndo:Bool = true) {
+ var cStr = new CharterStrumline(strL);
+ strumLines.insert(strumLineID, cStr);
+
+ // Push forward notes that are infront of the strumLine
+ for (note in notesGroup.members) {
+ if (Std.int(note.id / 4) >= strumLineID)
+ note.updatePos(note.step, note.id + 4, note.susLength, note.type);
+ }
+
+ for(note in strL.notes) {
+ var n = new CharterNote();
+ var t = Conductor.getStepForTime(note.time);
+ n.updatePos(t, ((strumLines.members.indexOf(cStr)) * 4) + note.id, Conductor.getStepForTime(note.time + note.sLen) - t, note.type);
+ notesGroup.add(n);
+ }
+ sortNotes();
+
+ if (addToUndo)
+ this.addToUndo(CCreateStrumLine(strumLineID, strL));
+ }
+
+ public function deleteStrumline(strumLineID:Int, addToUndo:Bool = true) {
+ removeStrumlineFromSelection(strumLineID);
+
+ var strL = strumLines.members[strumLineID].strumLine;
+ strumLines.members[strumLineID].destroy();
+ strumLines.members.remove(strumLines.members[strumLineID]);
+
+ var deletedstrumNotes:Array = [];
+ // Delete this strums notes
+ var i = 0; // thanks yosh!!!!! (deleteNote removes a index)
+ while(i < notesGroup.members.length) {
+ var note = notesGroup.members[i];
+ if (Std.int(note.id / 4) == strumLineID) { // thanks neo lol!!!!!
+ deletedstrumNotes.push(note);
+ deleteNote(note, false);
+ } else
+ i++;
+ }
+
+ // Push back strumline notes that are infront
+ for (note in notesGroup.members) {
+ if (Std.int(note.id / 4) > strumLineID)
+ note.updatePos(note.step, note.id - 4, note.susLength, note.type);
+ }
+ sortNotes();
+
+ // Undo shit
+ if (addToUndo) {
+ var newStrL = Reflect.copy(strL);
+ newStrL.notes.clear();
+
+ for (note in deletedstrumNotes) {
+ var time = Conductor.getTimeForStep(note.step);
+ newStrL.notes.push({
+ type: note.type,
+ time: time,
+ sLen: Conductor.getTimeForStep(note.step + note.susLength) - time,
+ id: note.id % 4
+ });
+ }
+ this.addToUndo(CDeleteStrumLine(strumLineID, newStrL));
+ }
+ }
+
+ public function getStrumlineID(strL:ChartStrumLine):Int {
+ for (index=>strumLine in strumLines.members) {
+ if (strumLine.strumLine == strL)
+ return index;
+ }
+ return -1;
+ }
+
+ public function createStrumWithUI() {
+ FlxG.state.openSubState(new CharterStrumlineScreen(strumLines.members.length, null, (_) -> {
+ createStrumline(strumLines.members.length, _);
+ }));
+ }
+
+ public inline function deleteStrumlineFromData(strL:ChartStrumLine)
+ deleteStrumline(getStrumlineID(strL));
+
+ public inline function editStrumline(strL:ChartStrumLine) {
+ var strID = getStrumlineID(strL);
+ FlxG.state.openSubState(new CharterStrumlineScreen(strID, strL, (_) -> {
+ strumLines.members[strID].strumLine = _;
+ strumLines.members[strID].updateInfo();
+ }));
+ }
+
+ public inline function removeStrumlineFromSelection(strumLineID:Int) {
+ var i = 0;
+ while(i < selection.length) {
+ var note = selection[i];
+ if (Std.int(note.id / 4) == strumLineID)
+ selection.remove(note);
+ else i++;
+ }
+ }
+
// UNDO/REDO LOGIC
#if REGION
public inline function addToUndo(c:CharterChange) {
@@ -677,8 +787,13 @@ class Charter extends UIState {
}
#end
- public inline function sortNotes()
- notesGroup.sort((i, c1, c2) -> FlxSort.byValues(i, c1.step, c2.step), FlxSort.ASCENDING);
+ public inline function sortNotes() {
+ notesGroup.sort(function(i, n1, n2) {
+ if (n1.step == n2.step)
+ return FlxSort.byValues(FlxSort.ASCENDING, n1.id, n2.id);
+ return FlxSort.byValues(FlxSort.ASCENDING, n1.step, n2.step);
+ });
+ }
#end
var __crochet:Float;
@@ -690,7 +805,7 @@ class Charter extends UIState {
scrollBar.size = (FlxG.height / 40 / charterCamera.zoom);
scrollBar.start = Conductor.curStepFloat - (scrollBar.size / 2);
- if (gridBackdrop.strumlinesAmount != (gridBackdrop.strumlinesAmount = strumLines.length))
+ if (gridBackdrop.strumlinesAmount != strumLines.members.length)
updateDisplaySprites();
sectionSeparator.spacing.y = (10 * Conductor.beatsPerMesure * Conductor.stepsPerBeat) - 1;
@@ -700,7 +815,8 @@ class Charter extends UIState {
if (true) {
__crochet = ((60 / Conductor.bpm) * 1000);
- UIUtil.processShortcuts(topMenu);
+ if(FlxG.keys.justPressed.ANY)
+ UIUtil.processShortcuts(topMenu);
if (FlxG.keys.pressed.CONTROL) {
if (FlxG.mouse.wheel != 0) {
@@ -736,6 +852,8 @@ class Charter extends UIState {
public static var startHere:Bool = false;
function updateDisplaySprites() {
+ gridBackdrop.strumlinesAmount = strumLines.members.length;
+
conductorFollowerSpr.scale.set(gridBackdrop.strumlinesAmount * 4 * 40, 4);
conductorFollowerSpr.updateHitbox();
@@ -760,16 +878,26 @@ class Charter extends UIState {
str.x = id * 40 * 4;
str.y = strumlineInfoBG.y;
}
+
+ strumlineAddButton.x = strumLines.members.length * (40 * 4);
+ strumlineAddButton.y = strumlineInfoBG.y;
}
- var zoom:Float = 0;
- var __camZoom:Float = 1;
+ var zoom(default, set):Float = 0;
+ var __camZoom(default, set):Float = 1;
+ function set_zoom(val:Float) {
+ return zoom = FlxMath.bound(val, -3.5, 1.75); // makes zooming not lag behind when continuing scrolling
+ }
+ function set___camZoom(val:Float) {
+ return __camZoom = FlxMath.bound(val, 0.1, 3);
+ }
// TOP MENU OPTIONS
#if REGION
function _file_exit(_) {
FlxG.switchState(new CharterSelection());
}
+
function _file_save(_) {
#if sys
for(assetPath in [Paths.chart(__song, __diff.toLowerCase()), instPath]) {
@@ -783,19 +911,41 @@ class Charter extends UIState {
}
function _file_saveas(_) {
- openSubState(new SaveSubstate(Json.stringify(Chart.filterChartForSaving(PlayState.SONG, true)), {
+ openSubState(new SaveSubstate(Json.stringify(Chart.filterChartForSaving(PlayState.SONG, false)), {
defaultSaveFile: '${__diff.toLowerCase()}.json'
}));
}
+ function _file_meta_save(_) {
+ #if sys
+ var metaPath = Paths.file('songs/${__song.toLowerCase()}/meta.json');
+ var metaDiffPath = Paths.file('songs/${__song.toLowerCase()}/meta-${__diff.toLowerCase()}.json');
+
+ sys.io.File.saveContent(
+ Assets.getPath(Assets.exists(metaDiffPath) ? metaDiffPath : metaPath),
+ Json.stringify(PlayState.SONG.meta == null ? {} : PlayState.SONG.meta, null, "\t")
+ );
+ return;
+ #end
+ _file_meta_saveas(_);
+ }
+
+ function _file_meta_saveas(_) {
+ openSubState(new SaveSubstate(Json.stringify(PlayState.SONG.meta == null ? {} : PlayState.SONG.meta, null, "\t"), { // always pretty print meta
+ defaultSaveFile: 'meta.json'
+ }));
+ }
+
#if sys
function saveTo(path:String) {
buildChart();
- Chart.save(path, PlayState.SONG, __diff.toLowerCase());
+ Chart.save(path, PlayState.SONG, __diff.toLowerCase(), {saveMetaInChart: false});
}
#end
function _edit_copy(_) {
+ if(selection.length == 0) return;
+
var minStep:Float = selection[0].step;
for(s in selection)
if (s.step < minStep)
@@ -862,7 +1012,10 @@ class Charter extends UIState {
d.revive();
}
selection = [for(n in notes) n.note];
-
+ case CDeleteStrumLine(strumLineID, strumLine):
+ createStrumline(strumLineID, strumLine, false);
+ case CCreateStrumLine(strumLineID, strumLine):
+ deleteStrumline(strumLineID, false);
}
if (v != null)
redoList.insert(0, v);
@@ -909,6 +1062,10 @@ class Charter extends UIState {
}
deleteNotes(deletes, false);
selection = [for(n in notes) n.note];
+ case CDeleteStrumLine(strumLineID, strumLine):
+ deleteStrumline(strumLineID, false);
+ case CCreateStrumLine(strumLineID, strumLine):
+ createStrumline(strumLineID, strumLine, false);
}
if (v != null)
undoList.insert(0, v);
@@ -925,6 +1082,8 @@ class Charter extends UIState {
function _chart_enablescripts(t) {
t.icon = (Options.charterEnablePlaytestScripts = !Options.charterEnablePlaytestScripts) ? 1 : 0;
}
+ function chart_edit_metadata(_)
+ FlxG.state.openSubState(new MetaDataScreen(PlayState.SONG.meta));
function _playback_metronome(t) {
t.icon = (Options.charterMetronomeEnabled = !Options.charterMetronomeEnabled) ? 1 : 0;
@@ -1044,6 +1203,8 @@ enum CharterChange {
CCreateNotes(notes:Array);
CDeleteNotes(notes:Array);
CNoteDrag(notes:Array, deletes:Array);
+ CCreateStrumLine(strumLineID:Int, strumLine:ChartStrumLine);
+ CDeleteStrumLine(strumLineID:Int, strumLine:ChartStrumLine);
}
enum CharterCopyboardObject {
diff --git a/source/funkin/editors/charter/CharterBackdrop.hx b/source/funkin/editors/charter/CharterBackdrop.hx
index 3e6c7c67..840e7ca5 100644
--- a/source/funkin/editors/charter/CharterBackdrop.hx
+++ b/source/funkin/editors/charter/CharterBackdrop.hx
@@ -11,9 +11,13 @@ class CharterBackdrop extends FlxBackdrop {
makeGraphic(160, 160, 0xFF272727, true);
pixels.lock();
+
+ // Checkerboard
for(y in 0...4)
for(x in 0...2)
pixels.fillRect(new Rectangle(40*((x*2)+(y%2)), 40*y, 40, 40), 0xFF545454);
+
+ // Edges
pixels.fillRect(new Rectangle(0, 0, 1, 160), 0xFFDDDDDD);
pixels.fillRect(new Rectangle(159, 0, 1, 160), 0xFFDDDDDD);
pixels.unlock();
diff --git a/source/funkin/editors/charter/CharterEvent.hx b/source/funkin/editors/charter/CharterEvent.hx
index 0d62361d..a28480e3 100644
--- a/source/funkin/editors/charter/CharterEvent.hx
+++ b/source/funkin/editors/charter/CharterEvent.hx
@@ -1,76 +1,11 @@
package funkin.editors.charter;
+import funkin.game.Character;
import funkin.backend.system.Conductor;
-import funkin.backend.chart.ChartData.ChartEventType;
import funkin.game.HealthIcon;
import funkin.backend.chart.ChartData.ChartEvent;
class CharterEvent extends UISliceSprite {
- public static function getEventInfo(id:ChartEventType):EventDefineInfo {
- return switch(id) {
- case CUSTOM:
- // hscript
- {
- name: "HScript Call",
- params: [
- {
- name: "Function name",
- type: TString,
- defValue: "myFunc"
- },
- {
- name: "Function parameters (string)",
- type: TArrayOfString,
- defValue: []
- }
- ]
- }
- case CAM_MOVEMENT:
- {
- name: "Camera Movement",
- params: [
- {
- name: "Camera Target",
- type: TStrumLine,
- defValue: 0
- }
- ]
- }
- case BPM_CHANGE:
- {
- name: "BPM Change",
- params: [
- {
- name: "Target BPM",
- type: TFloat(1),
- defValue: Conductor.bpm
- }
- ]
- }
- case ALT_ANIM_TOGGLE:
- {
- name: "Alt Animation Toggle",
- params: [
- {
- name: "Strumline",
- type: TStrumLine,
- defValue: 0
- },
- {
- name: "Enable",
- type: TBool,
- defValue: true
- }
- ]
- }
- default:
- {
- name: "Unknown",
- params: []
- }
- };
- }
-
public var events:Array;
public var step:Float;
@@ -92,26 +27,31 @@ class CharterEvent extends UISliceSprite {
}
}
- private static function generateDefaultIcon(type:Int) {
- var spr = new FlxSprite().loadGraphic(Paths.image('editors/charter/event-icons'), true, 16, 16);
- spr.frame = spr.frames.frames[type+1];
- return spr;
+ private static function generateDefaultIcon(name:String) {
+ var path:String = Paths.image('editors/charter/event-icons/$name');
+ if (!Assets.exists(path)) path = Paths.image('editors/charter/event-icons/Unknown');
+
+ var spr = new FlxSprite().loadGraphic(path);
+ return spr;
}
public static function generateEventIcon(event:ChartEvent) {
- return switch(event.type) {
+ return switch(event.name) {
default:
- generateDefaultIcon(event.type);
- case CAM_MOVEMENT:
+ generateDefaultIcon(event.name);
+ case "Camera Movement":
// custom icon for camera movement
var state = cast(FlxG.state, Charter);
- if (event.params[0] != null && event.params[0] >= 0 && event.params[0] < state.strumLines.length) {
+ if (event.params != null && event.params[0] != null && event.params[0] >= 0 && event.params[0] < state.strumLines.length) {
// camera movement, use health icon
- var healthIcon = new HealthIcon('${state.strumLines.members[event.params[0]].strumLine.characters[0]}');
+ var icon = Character.getIconFromCharName(state.strumLines.members[event.params[0]].strumLine.characters[0]);
+ var healthIcon = new HealthIcon(icon);
healthIcon.setUnstretchedGraphicSize(32, 32, false);
+ healthIcon.scrollFactor.set(1, 1);
+ healthIcon.active = false;
healthIcon;
} else
- generateDefaultIcon(event.type);
+ generateDefaultIcon(event.name);
}
}
@@ -136,28 +76,4 @@ class CharterEvent extends UISliceSprite {
x = -(bWidth = 37 + (icons.length * 22));
}
-}
-
-typedef EventDefineInfo = {
- var name:String;
- var params:Array;
-}
-typedef EventInfo = {
- > EventDefineInfo,
- var paramValues:Array;
-}
-
-typedef EventParamInfo = {
- var name:String;
- var type:EventParamType;
- var defValue:Dynamic;
-}
-
-enum EventParamType {
- TBool;
- TInt(?min:Int, ?max:Int, ?step:Float);
- TFloat(?min:Int, ?max:Int, ?step:Float, ?precision:Int);
- TString;
- TArrayOfString;
- TStrumLine;
}
\ No newline at end of file
diff --git a/source/funkin/editors/charter/CharterEventAdd.hx b/source/funkin/editors/charter/CharterEventAdd.hx
index 545e749a..a46306a4 100644
--- a/source/funkin/editors/charter/CharterEventAdd.hx
+++ b/source/funkin/editors/charter/CharterEventAdd.hx
@@ -14,7 +14,6 @@ class CharterEventAdd extends UISliceSprite {
members.push(text);
cursor = BUTTON;
-
}
public override function onHovered() {
diff --git a/source/funkin/editors/charter/CharterEventGroup.hx b/source/funkin/editors/charter/CharterEventGroup.hx
index c631a05a..90719fec 100644
--- a/source/funkin/editors/charter/CharterEventGroup.hx
+++ b/source/funkin/editors/charter/CharterEventGroup.hx
@@ -1,5 +1,3 @@
package funkin.editors.charter;
-class CharterEventGroup extends FlxTypedGroup {
-
-}
\ No newline at end of file
+class CharterEventGroup extends FlxTypedGroup {}
\ No newline at end of file
diff --git a/source/funkin/editors/charter/CharterEventScreen.hx b/source/funkin/editors/charter/CharterEventScreen.hx
index b81ee135..8859738e 100644
--- a/source/funkin/editors/charter/CharterEventScreen.hx
+++ b/source/funkin/editors/charter/CharterEventScreen.hx
@@ -2,6 +2,10 @@ package funkin.editors.charter;
import funkin.backend.system.Conductor;
import flixel.group.FlxGroup;
+import funkin.editors.charter.EventsData;
+import flixel.util.FlxColor;
+
+using StringTools;
class CharterEventScreen extends UISubstateWindow {
public var cam:FlxCamera;
@@ -30,6 +34,7 @@ class CharterEventScreen extends UISubstateWindow {
super.create();
FlxG.sound.music.pause(); // prevent the song from continuing
+ Charter.instance.vocals.pause();
var bg:FlxSprite = new FlxSprite(windowSpr.x + 1, windowSpr.y + 31, Paths.image('editors/ui/scrollbar-bg'));
bg.setGraphicSize(30, windowSpr.bHeight - 32);
@@ -40,11 +45,11 @@ class CharterEventScreen extends UISubstateWindow {
add(iconsPanel);
addButton = new UIButton(windowSpr.x + 1, windowSpr.y + 31, "", function() {
- openSubState(new CharterEventTypeSelection(function(eventType) {
+ openSubState(new CharterEventTypeSelection(function(eventName) {
chartEvent.events.push({
time: Conductor.getTimeForStep(chartEvent.step),
params: [],
- type: eventType
+ name: eventName
});
changeTab(chartEvent.events.length-1);
}));
@@ -83,7 +88,7 @@ class CharterEventScreen extends UISubstateWindow {
deleteButton.x -= deleteButton.bWidth;
add(deleteButton);
- eventName = new UIText(windowSpr.x + 40, windowSpr.y + 41, 0, "", 24);
+ eventName = new UIText(windowSpr.x + addButton.bWidth + 15, windowSpr.y + 41, 0, "", 24);
add(eventName);
changeTab(0);
@@ -105,11 +110,10 @@ class CharterEventScreen extends UISubstateWindow {
if (id >= 0 && id < chartEvent.events.length) {
curEvent = id;
var curEvent = chartEvent.events[curEvent];
- var data = CharterEvent.getEventInfo(curEvent.type);
- eventName.text = data.name;
+ eventName.text = curEvent.name;
// add new elements
var y:Float = eventName.y + eventName.height + 10;
- for(k=>param in data.params) {
+ for(k=>param in EventsData.getEventParams(curEvent.name)) {
function addLabel() {
var label:UIText = new UIText(eventName.x, y, 0, param.name);
y += label.height + 4;
@@ -117,40 +121,49 @@ class CharterEventScreen extends UISubstateWindow {
};
var value:Dynamic = CoolUtil.getDefault(curEvent.params[k], param.defValue);
- switch(param.type) {
+ var lastAdded = switch(param.type) {
case TString:
addLabel();
var textBox:UITextBox = new UITextBox(eventName.x, y, cast value);
- y += textBox.height + 10;
- paramsPanel.add(textBox);
- paramsFields.push(textBox);
+ paramsPanel.add(textBox); paramsFields.push(textBox);
+ textBox;
case TBool:
var checkbox = new UICheckbox(eventName.x, y, param.name, cast value);
- y += checkbox.height + 10;
- paramsPanel.add(checkbox);
- paramsFields.push(checkbox);
+ paramsPanel.add(checkbox); paramsFields.push(checkbox);
+ checkbox;
case TInt(min, max, step):
addLabel();
var numericStepper = new UINumericStepper(eventName.x, y, cast value, step.getDefault(1), 0, min, max);
- y += numericStepper.height + 10;
- paramsPanel.add(numericStepper);
- paramsFields.push(numericStepper);
+ paramsPanel.add(numericStepper); paramsFields.push(numericStepper);
+ numericStepper;
case TFloat(min, max, step, precision):
addLabel();
var numericStepper = new UINumericStepper(eventName.x, y, cast value, step.getDefault(1), precision, min, max);
- y += numericStepper.height + 10;
- paramsPanel.add(numericStepper);
- paramsFields.push(numericStepper);
+ paramsPanel.add(numericStepper); paramsFields.push(numericStepper);
+ numericStepper;
case TStrumLine:
addLabel();
var dropdown = new UIDropDown(eventName.x, y, 320, 32, [for(k=>s in cast(FlxG.state, Charter).strumLines.members) 'Strumline #${k+1} (${s.strumLine.characters[0]})'], cast value);
- y += dropdown.height + 10;
- paramsPanel.add(dropdown);
- paramsFields.push(dropdown);
+ paramsPanel.add(dropdown); paramsFields.push(dropdown);
+ dropdown;
+ case TColorWheel:
+ addLabel();
+ var colorWheel = new UIColorwheel(eventName.x, y, value is String ? FlxColor.fromString(value) : Std.int(value));
+ paramsPanel.add(colorWheel); paramsFields.push(colorWheel);
+ colorWheel;
+ case TDropDown(options):
+ addLabel();
+ var dropdown = new UIDropDown(eventName.x, y, 320, 32, options, Std.int(Math.abs(options.indexOf(cast value))));
+ paramsPanel.add(dropdown); paramsFields.push(dropdown);
+ dropdown;
default:
- // none
paramsFields.push(null);
+ null;
}
+ if (lastAdded is UISliceSprite)
+ y += cast(lastAdded, UISliceSprite).bHeight + 4;
+ else if (lastAdded is FlxSprite)
+ y += cast(lastAdded, FlxSprite).height + 6;
}
} else {
eventName.text = "No event";
@@ -187,12 +200,15 @@ class CharterEventScreen extends UISubstateWindow {
chartEvent.events[curEvent].params = [
for(p in paramsFields) {
- if (p is UIDropDown)
- cast(p, UIDropDown).index;
+ if (p is UIDropDown) {
+ var dataParams = EventsData.getEventParams(chartEvent.events[curEvent].name);
+ if (dataParams[paramsFields.indexOf(p)].type == TStrumLine) cast(p, UIDropDown).index;
+ else cast(p, UIDropDown).label.text;
+ }
else if (p is UINumericStepper) {
var stepper = cast(p, UINumericStepper);
- // int
- if (stepper.precision == 0)
+ @:privateAccess stepper.__onChange(stepper.label.text);
+ if (stepper.precision == 0) // int
Std.int(stepper.value);
else
stepper.value;
@@ -201,6 +217,8 @@ class CharterEventScreen extends UISubstateWindow {
cast(p, UITextBox).label.text;
else if (p is UICheckbox)
cast(p, UICheckbox).checked;
+ else if (p is UIColorwheel)
+ cast(p, UIColorwheel).curColor;
else
null;
}
diff --git a/source/funkin/editors/charter/CharterEventTypeSelection.hx b/source/funkin/editors/charter/CharterEventTypeSelection.hx
index d04a0970..b07c8927 100644
--- a/source/funkin/editors/charter/CharterEventTypeSelection.hx
+++ b/source/funkin/editors/charter/CharterEventTypeSelection.hx
@@ -1,11 +1,11 @@
package funkin.editors.charter;
-import funkin.backend.chart.ChartData.ChartEventType;
+import funkin.editors.charter.EventsData;
class CharterEventTypeSelection extends UISubstateWindow {
- var callback:ChartEventType->Void;
+ var callback:String->Void;
- public function new(callback:ChartEventType->Void) {
+ public function new(callback:String->Void) {
super();
this.callback = callback;
}
@@ -16,12 +16,23 @@ class CharterEventTypeSelection extends UISubstateWindow {
var w:Int = winWidth - 20;
var lastIndex:Int = 0;
- for(k=>eventType in ChartEventType.getChartEventTypes()) {
- var type = CharterEvent.getEventInfo(eventType);
- add(new UIButton(10, 41 + (32 * k), type.name, function() {
+ for(k=>eventName in EventsData.eventsList) {
+ var button = new UIButton(10, 41 + (32 * k), eventName, function() {
close();
- callback(eventType);
- }, w));
+ callback(eventName);
+ }, w);
+ add(button);
+
+ var icon = CharterEvent.generateEventIcon({
+ name: eventName,
+ time: 0,
+ params: []
+ });
+ icon.setGraphicSize(20, 20); // Std.int(button.bHeight - 12)
+ icon.updateHitbox();
+ icon.x = button.x + 8;
+ icon.y = button.y + Math.abs(button.bHeight - icon.height) / 2;
+ add(icon);
lastIndex = k;
}
diff --git a/source/funkin/editors/charter/CharterNote.hx b/source/funkin/editors/charter/CharterNote.hx
index 89b56a43..aec1ed7e 100644
--- a/source/funkin/editors/charter/CharterNote.hx
+++ b/source/funkin/editors/charter/CharterNote.hx
@@ -82,6 +82,9 @@ class CharterNote extends UISprite {
if (angle == destAngle) return;
+ if(angleTween != null)
+ angleTween.cancel();
+
angleTween = FlxTween.tween(this, {angle: destAngle}, 2/3, {ease: function(t) {
return ((Math.sin(t * Math.PI) * 0.35) * 3 * t * Math.sqrt(1 - t)) + t;
}});
@@ -90,6 +93,7 @@ class CharterNote extends UISprite {
public override function kill() {
if (angleTween != null) {
angleTween.cancel();
+ angleTween = null;
angle = switch(animation.curAnim.curFrame = (id % 4)) {
case 0: -90;
case 1: 180;
diff --git a/source/funkin/editors/charter/CharterNoteGroup.hx b/source/funkin/editors/charter/CharterNoteGroup.hx
index 747b08f6..d9db9604 100644
--- a/source/funkin/editors/charter/CharterNoteGroup.hx
+++ b/source/funkin/editors/charter/CharterNoteGroup.hx
@@ -28,7 +28,7 @@ class CharterNoteGroup extends FlxTypedGroup {
if (!cast(FlxG.state, Charter).selection.contains(__loopSprite))
noteFunc(__loopSprite);
}
- for(c in cast(FlxG.state, Charter).selection)
+ for(c in cast(FlxG.state, Charter).selection.copy())
noteFunc(c);
__currentlyLooping = oldCur;
diff --git a/source/funkin/editors/charter/CharterSelection.hx b/source/funkin/editors/charter/CharterSelection.hx
index e5cb026b..842f7510 100644
--- a/source/funkin/editors/charter/CharterSelection.hx
+++ b/source/funkin/editors/charter/CharterSelection.hx
@@ -45,7 +45,7 @@ class CharterSelection extends EditorTreeMenu {
if (optionsTree.members.length > 1) { // selected a song
// small flashbang
var color:FlxColor = freeplayList.songs[main.curSelected].parsedColor.getDefault(0xFFFFFFFF);
-
+
bg.colorTransform.redOffset = 0.25 * color.red;
bg.colorTransform.greenOffset = 0.25 * color.green;
bg.colorTransform.blueOffset = 0.25 * color.blue;
diff --git a/source/funkin/editors/charter/CharterStrumline.hx b/source/funkin/editors/charter/CharterStrumline.hx
index 043b6890..e2b746a9 100644
--- a/source/funkin/editors/charter/CharterStrumline.hx
+++ b/source/funkin/editors/charter/CharterStrumline.hx
@@ -1,5 +1,6 @@
package funkin.editors.charter;
+import funkin.game.Character;
import funkin.editors.ui.UIContextMenu.UIContextMenuOption;
import funkin.editors.ui.UITopMenu.UITopMenuButton;
import funkin.game.HealthIcon;
@@ -7,10 +8,11 @@ import funkin.backend.chart.ChartData.ChartStrumLine;
class CharterStrumline extends UISprite {
public var strumLine:ChartStrumLine;
+
public var hitsounds:Bool = true;
- var healthIcon:HealthIcon;
- var button:CharterStrumlineButton;
+ public var healthIcon:HealthIcon;
+ public var button:CharterStrumlineButton;
public var curMenu:UIContextMenu = null;
@@ -21,25 +23,39 @@ class CharterStrumline extends UISprite {
scrollFactor.set(1, 0);
alpha = 0;
- healthIcon = new HealthIcon(strumLine.characters != null ? strumLine.characters[0] : null);
+ var icon = Character.getIconFromCharName(strumLine.characters != null ? strumLine.characters[0] : null);
+
+ healthIcon = new HealthIcon(icon);
healthIcon.scale.set(80 / 150, 80 / 150);
healthIcon.updateHitbox();
+ if(strumLine.visible == null)
+ strumLine.visible = true;
+ healthIcon.alpha = strumLine.visible ? 1 : 0.4;
members.push(healthIcon);
button = new CharterStrumlineButton(this);
-
members.push(button);
}
public override function update(elapsed:Float) {
- healthIcon.follow(this, ((40 * 4) - healthIcon.width) / 2, 0);
+ if (healthIcon != null)
+ healthIcon.follow(this, ((40 * 4) - healthIcon.width) / 2, 0);
button.follow(this, 0, 95);
super.update(elapsed);
}
public function updateInfo() {
- // todo
+ members.remove(healthIcon);
+ healthIcon.destroy();
+
+ var icon = Character.getIconFromCharName(strumLine.characters != null ? strumLine.characters[0] : null);
+
+ healthIcon = new HealthIcon(icon);
+ healthIcon.scale.set(80 / 150, 80 / 150);
+ healthIcon.updateHitbox();
+ healthIcon.alpha = strumLine.visible ? 1 : 0.4;
+ members.push(healthIcon);
}
}
@@ -60,25 +76,12 @@ class CharterStrumlineButton extends UITopMenuButton {
public override function openContextMenu() {
contextMenu = [
{
- label: "Player",
+ label: "Visible",
onSelect: function(_) {
- strLine.strumLine.type = PLAYER;
+ strLine.strumLine.visible = !strLine.strumLine.visible;
+ strLine.healthIcon.alpha = strLine.strumLine.visible ? 1 : 0.4;
},
- icon: strLine.strumLine.type == PLAYER ? 1 : 0
- },
- {
- label: "Opponent",
- onSelect: function(_) {
- strLine.strumLine.type = OPPONENT;
- },
- icon: strLine.strumLine.type == OPPONENT ? 1 : 0
- },
- {
- label: "Additional",
- onSelect: function(_) {
- strLine.strumLine.type = ADDITIONAL;
- },
- icon: strLine.strumLine.type == ADDITIONAL ? 1 : 0
+ icon: strLine.strumLine.visible ? 1 : 0
},
null,
{
@@ -87,6 +90,19 @@ class CharterStrumlineButton extends UITopMenuButton {
strLine.hitsounds = !strLine.hitsounds;
},
icon: strLine.hitsounds ? 1 : 0
+ },
+ null,
+ {
+ label: "Edit",
+ onSelect: function (_) {
+ Charter.instance.editStrumline(strLine.strumLine);
+ }
+ },
+ {
+ label: "Delete",
+ onSelect: function (_) {
+ Charter.instance.deleteStrumlineFromData(strLine.strumLine);
+ }
}
];
super.openContextMenu();
diff --git a/source/funkin/editors/charter/CharterStrumlineAddButton.hx b/source/funkin/editors/charter/CharterStrumlineAddButton.hx
new file mode 100644
index 00000000..21271b63
--- /dev/null
+++ b/source/funkin/editors/charter/CharterStrumlineAddButton.hx
@@ -0,0 +1,50 @@
+package funkin.editors.charter;
+
+import flixel.math.FlxPoint;
+
+class CharterStrumlineAddButton extends UISprite {
+ public var addButton:UISprite;
+ public var addText:UIText;
+
+ public override function new(?x:Float, ?y:Float) {
+ super();
+
+ scrollFactor.set(1, 0);
+ alpha = 0;
+
+ addButton = new UISprite();
+ addButton.loadGraphic(Paths.image("editors/charter/add-strumline"));
+ addButton.scale.set(0.75,0.75);
+ addButton.updateHitbox();
+ addButton.antialiasing = false;
+ members.push(addButton);
+
+ addText = new UIText(0,0, 160, "Add New");
+ addText.alignment = CENTER;
+ members.push(addText);
+ }
+
+ var addButtonScale:FlxPoint = FlxPoint.get(0,0);
+
+ public override function update(elapsed:Float) {
+ addButton.follow(this, ((40 * 4) - addButton.width) / 2, 18);
+ addText.follow(this, 0, 84);
+
+ super.update(elapsed);
+
+ addButton.__rect.x = addButton.x;
+ addButton.__rect.y = addButton.y;
+ addButton.__rect.width = addButton.width;
+ addButton.__rect.height = addButton.height;
+ if(UIState.state.isOverlapping(addButton, addButton.__rect)) {
+ addButtonScale.set(0.85, 0.85);
+ if (FlxG.mouse.justReleased) Charter.instance.createStrumWithUI();
+ }
+ else addButtonScale.set(0.75, 0.75);
+
+ addButton.scale.set(
+ FlxMath.lerp(addButton.scale.x, addButtonScale.x, 1/6),
+ FlxMath.lerp(addButton.scale.y, addButtonScale.y, 1/6)
+ );
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/editors/charter/CharterStrumlineScreen.hx b/source/funkin/editors/charter/CharterStrumlineScreen.hx
new file mode 100644
index 00000000..3751500f
--- /dev/null
+++ b/source/funkin/editors/charter/CharterStrumlineScreen.hx
@@ -0,0 +1,149 @@
+package funkin.editors.charter;
+
+import flixel.math.FlxPoint;
+import funkin.game.Note;
+import funkin.game.Character;
+import funkin.game.HealthIcon;
+import funkin.backend.chart.ChartData.ChartStrumLine;
+
+class CharterStrumlineScreen extends UISubstateWindow {
+ public var strumLineID:Int = -1;
+ public var strumLine:ChartStrumLine;
+
+ public var charactersTextBox:UITextBox;
+ public var typeDropdown:UIDropDown;
+ public var stagePositionDropdown:UIDropDown;
+ public var hudScaleStepper:UINumericStepper;
+ public var hudXStepper:UINumericStepper;
+ public var hudYStepper:UINumericStepper;
+
+ public var characterIcons:Array = [];
+
+ public var saveButton:UIButton;
+ public var closeButton:UIButton;
+
+ private var onSave:ChartStrumLine -> Void = null;
+
+ public function new(strumLineID:Int, strumLine:ChartStrumLine, ?onSave:ChartStrumLine->Void) {
+ super();
+ this.strumLineID = strumLineID;
+ this.strumLine = strumLine;
+ if (onSave != null) this.onSave = onSave;
+ }
+
+ public override function create() {
+ var creatingStrumLine:Bool = strumLine == null;
+
+ if (creatingStrumLine)
+ strumLine = {
+ characters: ["dad"],
+ type: OPPONENT,
+ notes: [],
+ position: "dad",
+ visible: true
+ };
+
+ winTitle = creatingStrumLine ? 'Creating Strumline #$strumLineID' : 'Strumline #$strumLineID properties';
+ winWidth = 690; winHeight = 334;
+
+ FlxG.sound.music.pause();
+ Charter.instance.vocals.pause();
+
+ super.create();
+
+ function addLabelOn(ui:UISprite, text:String)
+ add(new UIText(ui.x, ui.y - 24, 0, text));
+
+ var title:UIText;
+ add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, creatingStrumLine ? "Create New Strumline" : "Edit Strumline Properties", 28));
+
+ charactersTextBox = new UITextBox(title.x, title.y + title.height + 38, strumLine.characters.join(", "));
+ charactersTextBox.onChange = (newChars:String) -> {
+ updateCharacterIcons([for (char in newChars.split(",")) char.trim()]);
+ };
+ add(charactersTextBox);
+ addLabelOn(charactersTextBox, "Characters");
+
+ updateCharacterIcons(strumLine.characters);
+
+ typeDropdown = new UIDropDown(charactersTextBox.x, charactersTextBox.y + 32 + 58, 200, 32, ["OPPONENT", "PLAYER", "ADDITIONAL"], strumLine.type);
+ add(typeDropdown);
+ addLabelOn(typeDropdown, "Type");
+
+ var stagePositionI = strumLine.position == null ? strumLine.type : ["DAD", "BOYFRIEND", "GIRLFRIEND"].indexOf(strumLine.position.toUpperCase());
+
+ stagePositionDropdown = new UIDropDown(typeDropdown.x + 200 - 32 + 26, typeDropdown.y, 200, 32, ["DAD", "BOYFRIEND", "GIRLFRIEND"], stagePositionI);
+ add(stagePositionDropdown);
+ addLabelOn(stagePositionDropdown, "Stage Position");
+
+ hudScaleStepper = new UINumericStepper(stagePositionDropdown.x + 200 - 32 + 26, stagePositionDropdown.y, strumLine.strumScale == null ? 1 : strumLine.strumScale, 0.001, 2, null, null, 74);
+ add(hudScaleStepper);
+ addLabelOn(hudScaleStepper, "Scale");
+
+ var startingPos:FlxPoint = strumLine.strumPos == null ? switch (strumLine.type) {
+ case 1: FlxPoint.get((FlxG.width * 0.75) - ((Note.swagWidth * (strumLine.strumScale == null ? 1 : strumLine.strumScale)) * 2), 50);
+ default: FlxPoint.get((FlxG.width * 0.25) - ((Note.swagWidth * (strumLine.strumScale == null ? 1 : strumLine.strumScale)) * 2), 50);
+ } : FlxPoint.get(strumLine.strumPos[0], strumLine.strumPos[1]);
+
+ hudXStepper = new UINumericStepper(hudScaleStepper.x + 80 - 32 + 26, hudScaleStepper.y, startingPos.x, 0.001, 2, null, null, 84);
+ add(hudXStepper);
+ addLabelOn(hudXStepper, "Hud Position (X,Y)");
+
+ add(new UIText(hudXStepper.x + 84 - 32 + 0, hudXStepper.y + 9, 0, ",", 22));
+
+ hudYStepper = new UINumericStepper(hudXStepper.x + 84 - 32 + 26, hudXStepper.y, startingPos.y, 0.001, 2, null, null, 84);
+ add(hudYStepper);
+
+ saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() {
+ saveStrumline();
+ close();
+ }, 125);
+ add(saveButton);
+
+ if (!creatingStrumLine) {
+ closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Close", function() {
+ close();
+ }, 125);
+ add(closeButton);
+ closeButton.x -= closeButton.bWidth;
+ }
+ }
+
+ function updateCharacterIcons(characters:Array) {
+ for (icon in characterIcons) {
+ remove(icon);
+ icon.destroy();
+ }
+ characterIcons = [];
+
+ for (i => char in characters) {
+ var iconSprite = new HealthIcon(Character.getIconFromCharName(char));
+ iconSprite.scrollFactor.set(1,1);
+ iconSprite.scale.set(0.5, 0.5);
+ iconSprite.updateHitbox();
+
+ iconSprite.setPosition(
+ charactersTextBox.x + 320 + 8 + ((iconSprite.width + 4) * i),
+ (charactersTextBox.y + 16) - (iconSprite.height/2)
+ );
+ add(iconSprite);
+ characterIcons.push(iconSprite);
+ }
+ }
+
+ function saveStrumline() {
+ for (stepper in [hudXStepper, hudYStepper, hudScaleStepper])
+ @:privateAccess stepper.__onChange(stepper.label.text);
+
+ strumLine = {
+ characters: [for (char in charactersTextBox.label.text.split(",")) char.trim()],
+ type: typeDropdown.index,
+ notes: strumLine.notes,
+ position: ["DAD", "BOYFRIEND", "GIRLFRIEND"][stagePositionDropdown.index].toLowerCase(),
+ visible: strumLine.visible,
+ strumPos: [hudXStepper.value, hudYStepper.value],
+ strumScale: hudScaleStepper.value
+ };
+ if (onSave != null) onSave(strumLine);
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/editors/charter/EventsData.hx b/source/funkin/editors/charter/EventsData.hx
new file mode 100644
index 00000000..04345100
--- /dev/null
+++ b/source/funkin/editors/charter/EventsData.hx
@@ -0,0 +1,91 @@
+package funkin.editors.charter;
+
+import hscript.Parser;
+import hscript.Interp;
+import haxe.Json;
+import openfl.Assets;
+import haxe.io.Path;
+import funkin.backend.assets.Paths;
+
+using StringTools;
+
+class EventsData {
+ public static var defaultEventsList:Array = ["HScript Call", "Camera Movement", "BPM Change", "Alt Animation Toggle"];
+ 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}],
+ "BPM Change" => [{name: "Target BPM", type: TFloat(1), defValue: 100}],
+ "Alt Animation Toggle" => [{name: "Strumline", type: TStrumLine, defValue: 0}, {name: "Enable", type: TBool, defValue: true}],
+ ];
+
+ public static var eventsList:Array = defaultEventsList.copy();
+ public static var eventsParams:Map> = defaultEventsParams.copy();
+
+ public static function getEventParams(name:String):Array {
+ return eventsParams.exists(name) ? eventsParams.get(name) : [];
+ }
+
+ public static function reloadEvents() {
+ eventsList = defaultEventsList.copy();
+ eventsParams = defaultEventsParams.copy();
+
+ var hscriptInterp:Interp = new Interp();
+ hscriptInterp.variables.set("Bool", TBool);
+ hscriptInterp.variables.set("Int", function (?min:Int, ?max:Int, ?step:Float):EventParamType {return TInt(min, max, step);});
+ hscriptInterp.variables.set("Float", function (?min:Int, ?max:Int, ?step:Float, ?precision:Int):EventParamType {return TFloat(min, max, step, precision);});
+ hscriptInterp.variables.set("String", TString);
+ hscriptInterp.variables.set("StrumLine", TStrumLine);
+ hscriptInterp.variables.set("ColorWheel", TColorWheel);
+ hscriptInterp.variables.set("DropDown", Reflect.makeVarArgs((args) -> {return args.length > 0 ? TDropDown([for (arg in args) Std.string(arg)]) : TDropDown(["null"]);}));
+
+ var hscriptParser:Parser = new Parser();
+ hscriptParser.allowJSON = hscriptParser.allowMetadata = false;
+
+ for (file in Paths.getFolderContent('data/events/', true, BOTH)) {
+ if (Path.extension(file) != "json") continue;
+ var eventName:String = Path.withoutExtension(Path.withoutDirectory(file));
+
+ eventsList.push(eventName);
+ eventsParams.set(eventName, []);
+
+ var fileTxt:String = Assets.getText(file);
+ if (fileTxt.trim() == "") continue;
+
+ try {
+ var data:Dynamic = Json.parse(fileTxt);
+ if (data == null || data.params == null) continue;
+
+ var finalParams:Array = [];
+ for (paramData in cast(data.params, Array)) {
+ try {
+ finalParams.push({name: paramData.name, type: hscriptInterp.expr(hscriptParser.parseString(paramData.type)), defValue: paramData.defaultValue});
+ } catch (e) {trace('Error parsing event param ${paramData.name} - ${eventName}: $e'); finalParams.push(null);}
+ }
+ eventsParams.set(eventName, finalParams);
+ } catch (e) {trace('Error parsing file $file: $e');}
+ }
+
+ hscriptInterp = null; hscriptParser = null;
+ }
+}
+
+typedef EventInfo = {
+ var params:Array;
+ var paramValues:Array;
+}
+
+typedef EventParamInfo = {
+ var name:String;
+ var type:EventParamType;
+ var defValue:Dynamic;
+}
+
+enum EventParamType {
+ TBool;
+ TInt(?min:Int, ?max:Int, ?step:Float);
+ TFloat(?min:Int, ?max:Int, ?step:Float, ?precision:Int);
+ TString;
+ TStrumLine;
+ TColorWheel;
+ TDropDown(?options:Array);
+}
\ No newline at end of file
diff --git a/source/funkin/editors/charter/MetaDataScreen.hx b/source/funkin/editors/charter/MetaDataScreen.hx
new file mode 100644
index 00000000..c13d928e
--- /dev/null
+++ b/source/funkin/editors/charter/MetaDataScreen.hx
@@ -0,0 +1,155 @@
+package funkin.editors.charter;
+
+import flixel.math.FlxPoint;
+import funkin.backend.chart.ChartData.ChartMetaData;
+
+using StringTools;
+
+class MetaDataScreen extends UISubstateWindow {
+ public var metadata:ChartMetaData;
+ public var saveButton:UIButton;
+ public var closeButton:UIButton;
+
+ public var songNameTextBox:UITextBox;
+ public var bpmStepper:UINumericStepper;
+ public var stepsPerBeatStepper:UINumericStepper;
+ public var beatsPerMesureStepper:UINumericStepper;
+ public var needsVoicesCheckbox:UICheckbox;
+
+ public var displayNameTextBox:UITextBox;
+ public var iconTextBox:UITextBox;
+ public var iconSprite:FlxSprite;
+ public var opponentModeCheckbox:UICheckbox;
+ public var coopAllowedCheckbox:UICheckbox;
+ public var colorWheel:UIColorwheel;
+ public var difficulitesTextBox:UITextBox;
+
+ public function new(metadata:ChartMetaData) {
+ super();
+ this.metadata = metadata;
+ trace(metadata);
+ }
+
+ public override function create() {
+ winTitle = "Edit Metadata";
+ winWidth = 748 - 32 + 40;
+
+ super.create();
+
+ FlxG.sound.music.pause();
+ Charter.instance.vocals.pause();
+
+ function addLabelOn(ui:UISprite, text:String)
+ add(new UIText(ui.x, ui.y - 24, 0, text));
+
+ var title:UIText;
+ add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Song Data", 28));
+
+ songNameTextBox = new UITextBox(title.x, title.y + title.height + 36, metadata.name);
+ add(songNameTextBox);
+ addLabelOn(songNameTextBox, "Song Name");
+
+ bpmStepper = new UINumericStepper(songNameTextBox.x + 320 + 26, songNameTextBox.y, metadata.bpm, 1, 2, 1, null, 90);
+ add(bpmStepper);
+ addLabelOn(bpmStepper, "BPM");
+
+ stepsPerBeatStepper = new UINumericStepper(bpmStepper.x + 60 + 26, bpmStepper.y, metadata.stepsPerBeat, 1, 0, 1, null, 54);
+ add(stepsPerBeatStepper);
+ addLabelOn(stepsPerBeatStepper, "Time Signature");
+
+ add(new UIText(stepsPerBeatStepper.x + 30, stepsPerBeatStepper.y + 3, 0, "/", 22));
+
+ beatsPerMesureStepper = new UINumericStepper(stepsPerBeatStepper.x + 30 + 24, stepsPerBeatStepper.y, metadata.beatsPerMesure, 1, 0, 1, null, 54);
+ add(beatsPerMesureStepper);
+
+ needsVoicesCheckbox = new UICheckbox(beatsPerMesureStepper.x + 80 + 26, beatsPerMesureStepper.y, "Voices", metadata.needsVoices);
+ add(needsVoicesCheckbox);
+ addLabelOn(needsVoicesCheckbox, "Needs Voices");
+ needsVoicesCheckbox.y += 6; needsVoicesCheckbox.x += 4;
+
+ add(title = new UIText(songNameTextBox.x, songNameTextBox.y + 10 + 46, 0, "Menus Data (Freeplay/Story)", 28));
+
+ displayNameTextBox = new UITextBox(title.x, title.y + title.height + 36, metadata.displayName);
+ add(displayNameTextBox);
+ addLabelOn(displayNameTextBox, "Display Name");
+
+ iconTextBox = new UITextBox(displayNameTextBox.x + 320 + 26, displayNameTextBox.y, metadata.icon, 150);
+ iconTextBox.onChange = (newIcon:String) -> {updateIcon(newIcon);}
+ add(iconTextBox);
+ addLabelOn(iconTextBox, "Icon");
+
+ updateIcon(metadata.icon);
+
+ opponentModeCheckbox = new UICheckbox(displayNameTextBox.x, iconTextBox.y + 10 + 32 + 26, "Opponent Mode", metadata.opponentModeAllowed);
+ add(opponentModeCheckbox);
+ addLabelOn(opponentModeCheckbox, "Modes Allowed");
+
+ coopAllowedCheckbox = new UICheckbox(opponentModeCheckbox.x + 150 + 26, opponentModeCheckbox.y, "Co-op Mode", metadata.coopAllowed);
+ add(coopAllowedCheckbox);
+
+ colorWheel = new UIColorwheel(iconTextBox.x, coopAllowedCheckbox.y, metadata.parsedColor);
+ add(colorWheel);
+ addLabelOn(colorWheel, "Color");
+
+ difficulitesTextBox = new UITextBox(opponentModeCheckbox.x, opponentModeCheckbox.y + 6 + 32 + 26, metadata.difficulties.join(", "));
+ add(difficulitesTextBox);
+ addLabelOn(difficulitesTextBox, "Difficulties");
+
+ for (checkbox in [opponentModeCheckbox, coopAllowedCheckbox])
+ {checkbox.y += 6; checkbox.x += 4;}
+
+ saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, colorWheel.y + 32 + 197 + 26, "Save & Close", function() {
+ saveMeta();
+ close();
+ }, 125);
+ saveButton.x -= saveButton.bWidth;
+ saveButton.y -= saveButton.bHeight;
+
+ closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Close", function() {
+ close();
+ }, 125);
+ closeButton.x -= closeButton.bWidth;
+ //closeButton.y -= closeButton.bHeight;
+ add(closeButton);
+ add(saveButton);
+ }
+
+ function updateIcon(icon:String) {
+ if (iconSprite == null) add(iconSprite = new FlxSprite());
+
+ if (iconSprite.animation.exists(icon)) return;
+ @:privateAccess iconSprite.animation.clearAnimations();
+
+ var path:String = Paths.image('icons/$icon');
+ if (!Assets.exists(path)) path = Paths.image('icons/face');
+
+ iconSprite.loadGraphic(path, true, 150, 150);
+ iconSprite.animation.add(icon, [0], 0, false);
+ iconSprite.antialiasing = true;
+ iconSprite.animation.play(icon);
+
+ iconSprite.scale.set(0.5, 0.5);
+ iconSprite.updateHitbox();
+ iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2));
+ }
+
+ public function saveMeta() {
+ for (stepper in [bpmStepper, stepsPerBeatStepper, beatsPerMesureStepper])
+ @:privateAccess stepper.__onChange(stepper.label.text);
+
+ PlayState.SONG.meta = {
+ name: songNameTextBox.label.text,
+ bpm: bpmStepper.value,
+ stepsPerBeat: Std.int(stepsPerBeatStepper.value),
+ beatsPerMesure: Std.int(beatsPerMesureStepper.value),
+ needsVoices: needsVoicesCheckbox.checked,
+ displayName: displayNameTextBox.label.text,
+ icon: iconTextBox.label.text,
+ color: colorWheel.curColorString,
+ parsedColor: colorWheel.curColor,
+ opponentModeAllowed: opponentModeCheckbox.checked,
+ coopAllowed: coopAllowedCheckbox.checked,
+ difficulties: [for (diff in difficulitesTextBox.label.text.split(",")) diff.trim()],
+ };
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/editors/ui/UIColorwheel.hx b/source/funkin/editors/ui/UIColorwheel.hx
new file mode 100644
index 00000000..cc47b5df
--- /dev/null
+++ b/source/funkin/editors/ui/UIColorwheel.hx
@@ -0,0 +1,173 @@
+package funkin.editors.ui;
+
+import flixel.math.FlxPoint;
+import funkin.backend.shaders.CustomShader;
+import flixel.util.FlxColor;
+
+using flixel.util.FlxSpriteUtil;
+
+class UIColorwheel extends UISliceSprite {
+ var colorPicker:FlxSprite;
+ var colorPickerShader:CustomShader;
+ var colorPickerSelector:UIColorWheelSelector;
+
+ var colorSlider:FlxSprite;
+ var colorSliderSelector:UIColorWheelSelector;
+
+ var colorHexTextBox:UITextBox;
+ var rgbNumSteppers:Array = []; // r,g,b
+
+ public var curColor:Null = FlxColor.WHITE;
+ public var curColorString:String = "#FFFFFF";
+
+ public var saturation:Float;
+ public var brightness:Float;
+ public var hue:Float;
+
+ public function new (x:Float, y:Float, ?color:Int) {
+ super(x, y, 299, 125, 'editors/ui/inputbox');
+
+ curColor = cast color;
+ hue = curColor.hue; saturation = curColor.saturation; brightness = curColor.brightness;
+
+ colorPickerShader = new CustomShader("engine/colorPicker");
+ colorPickerShader.hset("hue", hue);
+
+ colorPicker = new FlxSprite(x + 12.5, (y + 125/2) - (100/2)).makeGraphic(100, 100, FlxColor.TRANSPARENT);
+ colorPicker.drawRoundRect(0, 0, 100, 100, 10, 10, FlxColor.WHITE);
+ colorPicker.shader = colorPickerShader;
+
+ colorPickerSelector = new UIColorWheelSelector(colorPicker.x + 100 - 16, colorPicker.y);
+
+ colorSlider = new FlxSprite(colorPicker.x + 100 + 12.5, colorPicker.y).makeGraphic(16, 100, FlxColor.TRANSPARENT);
+ colorSlider.drawRoundRect(0, 0, 16, 100, 10, 10, FlxColor.WHITE);
+
+ // Im too lazy to use a shader for this if its not even gonna change
+ colorSlider.pixels.lock();
+ for (pixely in 0...Std.int(colorSlider.height)) {
+ var color:Int = FlxColor.fromHSB(pixely / (colorSlider.height-1) * 360, 1, 1);
+ for (pixelx in 0...Std.int(colorSlider.width))
+ if (colorSlider.pixels.getPixel32(pixelx, pixely) != FlxColor.TRANSPARENT) colorSlider.pixels.setPixel32(pixelx, pixely, color);
+ }
+ colorSlider.pixels.unlock();
+
+ colorSliderSelector = new UIColorWheelSelector(colorSlider.x, colorSlider.y);
+
+ colorHexTextBox = new UITextBox(colorSlider.x + 16 + 12.5, colorSlider.y + 16, "", 100, 28);
+ colorHexTextBox.onChange = (colorString:String) -> {
+ curColor = FlxColor.fromString(colorString);
+ if (curColor == null) curColor = FlxColor.WHITE;
+
+ hue = curColor.hue; saturation = curColor.saturation; brightness = curColor.brightness;
+ updateWheel();
+ }
+
+ var hexLabel:UIText = new UIText(colorHexTextBox.x - 2, colorHexTextBox.y - 18, 0, "Hex (#FFFFFF)", 13);
+
+ for (i in 0...3) {
+ var numStepper:UINumericStepper = new UINumericStepper(colorSlider.x + 18 + 12.5 + (i * 44), colorHexTextBox.y + 28 + 6 + 13 + 6 + .5, 0, 1, 0, 0, 255, 40 + 28, 28);
+ numStepper.antialiasing = true; numStepper.ID = i;
+ numStepper.onChange = (text:String) -> {
+ @:privateAccess numStepper.__onChange(text);
+ switch (numStepper.ID) {
+ default: curColor.red = Std.int(numStepper.value);
+ case 1: curColor.green = Std.int(numStepper.value);
+ case 2: curColor.blue = Std.int(numStepper.value);
+ }
+ hue = curColor.hue; saturation = curColor.saturation; brightness = curColor.brightness;
+ updateWheel();
+ };
+ members.push(numStepper); rgbNumSteppers.push(numStepper);
+ }
+
+ var rgbLabel:UIText = new UIText(rgbNumSteppers[0].x - 2, rgbNumSteppers[0].y - 18, 0, "RGB (255,255,255)", 13);
+
+ colorPicker.antialiasing = colorSlider.antialiasing = colorHexTextBox.antialiasing = true;
+
+ for (item in [colorPicker, colorPickerSelector, colorSlider, colorSliderSelector, colorHexTextBox, hexLabel, rgbLabel])
+ members.push(item);
+
+ updateWheel();
+ }
+
+ inline function updateColorPickerSelector()
+ colorPickerSelector.selector.setPosition(colorPicker.x + (colorPicker.width *saturation) - 8, colorPicker.y + (colorPicker.height + (colorPicker.height * -brightness))- 8);
+
+ inline function updateColorPickerMouse(mousePos:FlxPoint) {
+ saturation = mousePos.x/colorPicker.width; brightness = 1 + -(mousePos.y/colorPicker.height);
+ }
+
+ inline function updateColorSliderPickerSelector()
+ colorSliderSelector.selector.setPosition(colorSlider.x, colorSlider.y + (hue / 360 * (colorSlider.height-1)) - 8);
+
+ inline function updateColorSliderMouse(mousePos:FlxPoint)
+ hue = (mousePos.y / colorSlider.height) * 360;
+
+ public function updateWheel() {
+ colorPickerShader.hset("hue", hue / 360);
+ colorPickerSelector.curColor = color = curColor = FlxColor.fromHSB(hue, saturation, brightness); colorSliderSelector.curColor = FlxColor.fromHSB(hue, 1, 1);
+
+ updateColorPickerSelector(); updateColorSliderPickerSelector();
+ colorHexTextBox.label.text = curColorString = curColor.toHexString(false).replace("0x", "#");
+ for (numStepper in rgbNumSteppers) {
+ numStepper.label.text = Std.string(switch (numStepper.ID) {
+ default: curColor.red;
+ case 1: curColor.green;
+ case 2: curColor.blue;
+ });
+ }
+ }
+
+ // Make the colorwhell feel better
+ static inline var hitBoxExtenstion:Float = 8;
+
+ public override function update(elapsed:Float) {
+ if (hovered && FlxG.mouse.pressed) {
+ var mousePos = FlxG.mouse.getScreenPosition(__lastDrawCameras[0], FlxPoint.get());
+
+ for (sprite in [colorPicker, colorSlider]) {
+ var spritePos:FlxPoint = sprite.getScreenPosition(FlxPoint.get(), __lastDrawCameras[0]);
+
+ if (((mousePos.x > (spritePos.x - (hitBoxExtenstion/2))) && (mousePos.x < spritePos.x - (hitBoxExtenstion/2) + (sprite.width + hitBoxExtenstion))) && ((mousePos.y > (spritePos.y - (hitBoxExtenstion/2))) && (mousePos.y < spritePos.y - (hitBoxExtenstion/2) + (sprite.height + hitBoxExtenstion)))) {
+ mousePos -= FlxPoint.weak(spritePos.x, spritePos.y);
+ mousePos.set(FlxMath.bound(mousePos.x, 0, sprite.width), FlxMath.bound(mousePos.y, 0, sprite.height));
+
+ if (sprite == colorSlider) updateColorSliderMouse(mousePos);
+ if (sprite == colorPicker) updateColorPickerMouse(mousePos);
+ updateWheel();
+
+ spritePos.put();
+ break;
+ }
+ }
+ mousePos.put();
+ }
+
+ super.update(elapsed);
+ }
+}
+
+class UIColorWheelSelector extends FlxTypedGroup {
+ public var curColor:FlxColor = FlxColor.WHITE;
+
+ public var selector:FlxSprite;
+ public var colorCircle:FlxSprite;
+
+ public function new(x:Float, y:Float, ?color:Null) {
+ super();
+ if (color != null) curColor = cast color;
+
+ add(selector = new FlxSprite(x, y).loadGraphic(Paths.image("editors/ui/slider")));
+ add(colorCircle = new FlxSprite(x + 2, y + 2).makeGraphic(12,12, FlxColor.TRANSPARENT).drawCircle());
+
+ selector.antialiasing = colorCircle.antialiasing = true;
+ colorCircle.colorTransform.color = curColor;
+ }
+
+ public override function update(elapsed:Float) {
+ super.update(elapsed);
+
+ colorCircle.setPosition(selector.x + 2, selector.y + 2);
+ colorCircle.colorTransform.color = curColor;
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/editors/ui/UIContextMenu.hx b/source/funkin/editors/ui/UIContextMenu.hx
index 0ac09dc1..5e11c3a1 100644
--- a/source/funkin/editors/ui/UIContextMenu.hx
+++ b/source/funkin/editors/ui/UIContextMenu.hx
@@ -62,7 +62,6 @@ class UIContextMenu extends MusicBeatSubstate {
if (o.bWidth > maxW)
maxW = o.bWidth;
-
for(o in contextMenuOptions)
o.bWidth = maxW;
for(o in separators) {
@@ -91,7 +90,7 @@ class UIContextMenu extends MusicBeatSubstate {
}
public override function update(elapsed:Float) {
- if (__oobDeletion && FlxG.mouse.pressed && !bg.hoveredByChild)
+ if (__oobDeletion && FlxG.mouse.justPressed && !bg.hoveredByChild)
close();
__oobDeletion = true;
diff --git a/source/funkin/editors/ui/UIDropDown.hx b/source/funkin/editors/ui/UIDropDown.hx
index 5acad965..22cb7274 100644
--- a/source/funkin/editors/ui/UIDropDown.hx
+++ b/source/funkin/editors/ui/UIDropDown.hx
@@ -44,7 +44,7 @@ class UIDropDown extends UISliceSprite {
}
super.update(elapsed);
-
+
label.follow(this, 4, Std.int((bHeight - label.height) / 2));
dropButton.follow(this, bWidth - bHeight, 0);
}
@@ -60,5 +60,4 @@ class UIDropDown extends UISliceSprite {
setOption(i);
}, screenPos.x, screenPos.y + bHeight);
}
-
}
\ No newline at end of file
diff --git a/source/funkin/editors/ui/UISprite.hx b/source/funkin/editors/ui/UISprite.hx
index 291aae31..d9fb178e 100644
--- a/source/funkin/editors/ui/UISprite.hx
+++ b/source/funkin/editors/ui/UISprite.hx
@@ -54,7 +54,7 @@ class UISprite extends FlxSprite {
FlxCamera._defaultCameras = __oldDefCams;
}
-
+
}
public override function draw() {
@@ -74,11 +74,11 @@ class UISprite extends FlxSprite {
for(m in members)
m.draw();
-
+
FlxCamera._defaultCameras = __oldDefCams;
}
}
-
+
public override function destroy() {
super.destroy();
members = FlxDestroyUtil.destroyArray(members);
diff --git a/source/funkin/editors/ui/UIState.hx b/source/funkin/editors/ui/UIState.hx
index 3cd2caf2..d287e630 100644
--- a/source/funkin/editors/ui/UIState.hx
+++ b/source/funkin/editors/ui/UIState.hx
@@ -60,7 +60,7 @@ class UIState extends MusicBeatState {
updateRectButtonHandler(spr, spr.__rect, buttonHandler);
}
- public function updateRectButtonHandler(spr:UISprite, rect:FlxRect, buttonHandler:Void->Void) {
+ public function isOverlapping(spr:UISprite, rect:FlxRect) {
for(camera in spr.__lastDrawCameras) {
var pos = FlxG.mouse.getScreenPosition(camera, FlxPoint.get());
__rect.x = rect.x;
@@ -72,14 +72,20 @@ class UIState extends MusicBeatState {
__rect.y -= camera.scroll.y * spr.scrollFactor.y;
if (((pos.x > __rect.x) && (pos.x < __rect.x + __rect.width)) && ((pos.y > __rect.y) && (pos.y < __rect.y + __rect.height))) {
- spr.hoveredByChild = true;
- this.hoveredSprite = spr;
- this.buttonHandler = buttonHandler;
pos.put();
- return;
+ return true;
}
pos.put();
}
+ return false;
+ }
+
+ public function updateRectButtonHandler(spr:UISprite, rect:FlxRect, buttonHandler:Void->Void) {
+ if(isOverlapping(spr, rect)) {
+ spr.hoveredByChild = true;
+ this.hoveredSprite = spr;
+ this.buttonHandler = buttonHandler;
+ }
}
public override function tryUpdate(elapsed:Float) {
@@ -111,9 +117,14 @@ class UIState extends MusicBeatState {
FlxG.stage.window.onTextInput.remove(onTextInput);
}
+ public function closeCurrentContextMenu() {
+ if(curContextMenu != null)
+ curContextMenu.close();
+ }
+
public function openContextMenu(options:Array, ?callback:UIContextMenuCallback, ?x:Float, ?y:Float) {
var state = FlxG.state;
- while(state.subState != null)
+ while(state.subState != null && !(state._requestSubStateReset && state._requestedSubState == null))
state = state.subState;
state.persistentDraw = true;
diff --git a/source/funkin/editors/ui/UISubstateWindow.hx b/source/funkin/editors/ui/UISubstateWindow.hx
index 6e468068..95d02abf 100644
--- a/source/funkin/editors/ui/UISubstateWindow.hx
+++ b/source/funkin/editors/ui/UISubstateWindow.hx
@@ -1,12 +1,13 @@
package funkin.editors.ui;
+import openfl.filters.ShaderFilter;
import flixel.tweens.FlxTween;
import funkin.backend.shaders.CustomShader;
// TODO: make UIWarningSubstate extend this
class UISubstateWindow extends MusicBeatSubstate {
var camShaders:Array = [];
- var blurShader:CustomShader = new CustomShader("engine/editorBlur");
+ var blurShader:CustomShader = Options.intensiveBlur ? new CustomShader("engine/editorBlur") : new CustomShader("engine/editorBlurFast");
var titleSpr:UIText;
var messageSpr:UIText;
@@ -29,6 +30,24 @@ class UISubstateWindow extends MusicBeatSubstate {
super.create();
for(c in FlxG.cameras.list) {
+ @:privateAccess if(c._filters != null) {
+ var shouldSkip = false;
+ for(filter in c._filters) {
+ if(filter is ShaderFilter) {
+ var filter:ShaderFilter = cast filter;
+ if(filter.shader is CustomShader) {
+ var shader:CustomShader = cast filter.shader;
+
+ if(shader.path == blurShader.path) {
+ shouldSkip = true;
+ break;
+ }
+ }
+ }
+ }
+ if(shouldSkip)
+ continue;
+ }
camShaders.push(c);
c.addShader(blurShader);
}
@@ -54,6 +73,7 @@ class UISubstateWindow extends MusicBeatSubstate {
for(e in camShaders)
e.removeShader(blurShader);
+ blurShader = null;
FlxTween.cancelTweensOf(subCam);
FlxG.cameras.remove(subCam);
}
diff --git a/source/funkin/editors/ui/UIText.hx b/source/funkin/editors/ui/UIText.hx
index 2b376a42..413630b2 100644
--- a/source/funkin/editors/ui/UIText.hx
+++ b/source/funkin/editors/ui/UIText.hx
@@ -8,5 +8,7 @@ class UIText extends FunkinText {
public function new(x, y, w, text, size:Int = 15, color:FlxColor = 0xFFFFFFFF) {
super(x, y, w, text, size, false);
this.color = color;
+
+ this.active = false;
}
}
\ No newline at end of file
diff --git a/source/funkin/editors/ui/UITopMenu.hx b/source/funkin/editors/ui/UITopMenu.hx
index fba0bf0d..deaa960b 100644
--- a/source/funkin/editors/ui/UITopMenu.hx
+++ b/source/funkin/editors/ui/UITopMenu.hx
@@ -51,27 +51,50 @@ class UITopMenuButton extends UISliceSprite {
members.push(this.label);
}
+ var justClosed:Int = 0;
+
public override function update(elapsed:Float) {
label.follow(this, 0, Std.int((bHeight - label.height) / 2));
super.update(elapsed);
- var opened = curMenu.contextMenuOpened();
+ if(FlxG.mouse.released) {
+ if(justClosed > 0)
+ justClosed -= 1;
+ }
+
+ var opened = curMenu != null ? curMenu.contextMenuOpened() : false;
+
+ if(opened && FlxG.mouse.justPressed) {
+ __rect.x = x;
+ __rect.y = y;
+ __rect.width = bWidth;
+ __rect.height = bHeight;
+ if(UIState.state.isOverlapping(this, __rect)) {
+ curMenu.close();
+ justClosed = 2;
+ opened = false;
+ }
+ }
+
alpha = (hovered || opened) ? 1 : 0;
framesOffset = opened ? 9 : 0;
}
public override function onHovered() {
super.onHovered();
- if (curMenu.contextMenuOpened()) {
+ if (curMenu != null && curMenu.contextMenuOpened()) {
UIState.state.curContextMenu.preventOutOfBoxClickDeletion();
} else {
- if ((parent != null && parent.anyMenuOpened) || FlxG.mouse.justReleased) {
+ if (/*(parent != null && !parent.anyMenuOpened) || */FlxG.mouse.justReleased && justClosed == 0) {
openContextMenu();
}
}
}
public function openContextMenu() {
+ //if(UIState.state.curContextMenu != null) {
+ // UIState.state.curContextMenu.close();
+ //}
var screenPos = getScreenPosition(null, __lastDrawCameras[0] == null ? FlxG.camera : __lastDrawCameras[0]);
curMenu = UIState.state.openContextMenu(contextMenu, null, screenPos.x, screenPos.y + bHeight);
}
diff --git a/source/funkin/editors/ui/UIWarningSubstate.hx b/source/funkin/editors/ui/UIWarningSubstate.hx
index 88948721..2cf765f4 100644
--- a/source/funkin/editors/ui/UIWarningSubstate.hx
+++ b/source/funkin/editors/ui/UIWarningSubstate.hx
@@ -6,7 +6,7 @@ import funkin.backend.shaders.CustomShader;
class UIWarningSubstate extends MusicBeatSubstate {
var camShaders:Array = [];
- var blurShader:CustomShader = new CustomShader("engine/editorBlur");
+ var blurShader:CustomShader = Options.intensiveBlur ? new CustomShader("engine/editorBlur") : new CustomShader("engine/editorBlurFast");
var title:String;
var message:String;
diff --git a/source/funkin/game/Character.hx b/source/funkin/game/Character.hx
index 67673047..fb5c939a 100644
--- a/source/funkin/game/Character.hx
+++ b/source/funkin/game/Character.hx
@@ -1,39 +1,63 @@
package funkin.game;
+import funkin.editors.character.CharacterConfig;
+import flixel.util.FlxColor;
import funkin.backend.FunkinSprite;
import flixel.graphics.frames.FlxFrame;
import flixel.math.FlxPoint;
import funkin.backend.system.interfaces.IBeatReceiver;
import funkin.backend.system.interfaces.IOffsetCompatible;
import funkin.backend.utils.XMLUtil;
-
import flixel.animation.FlxBaseAnimation;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.math.FlxRect;
-
import haxe.xml.Access;
import haxe.Exception;
import haxe.io.Path;
import funkin.backend.system.Conductor;
-
import openfl.geom.ColorTransform;
-
import funkin.backend.scripting.DummyScript;
import funkin.backend.scripting.Script;
import funkin.backend.scripting.events.*;
import funkin.backend.scripting.events.PlayAnimEvent.PlayAnimContext;
+
using StringTools;
+typedef CharacterData =
+{
+ var offsetX:Float;
+ var offsetY:Float;
+ var camOffsetX:Float;
+ var camOffsetY:Float;
+ var quickDancer:Bool;
+ var holdTime:Float;
+ var isPlayer:Bool;
+ var flipX:Bool;
+ var antialiasing:Bool;
+ var sprite:String;
+ var name:String;
+ var animations:Array;
+ @:optional var isGF:Bool;
+ @:optional var icon:String;
+ @:optional var iconColor:FlxColor;
+ @:optional var scale:Float;
+ @:optional var gameOverChar:String;
+}
+
@:allow(funkin.desktop.editors.CharacterEditor)
@:allow(funkin.game.StrumLine)
@:allow(funkin.game.PlayState)
class Character extends FunkinSprite implements IBeatReceiver implements IOffsetCompatible
{
+ public var extra:Map = [];
+
private var __stunnedTime:Float = 0;
private var __lockAnimThisFrame:Bool = false;
+
public var stunned(default, set):Bool = false;
- private function set_stunned(b:Bool) {
+ private function set_stunned(b:Bool)
+ {
__stunnedTime = 0;
return stunned = b;
}
@@ -58,26 +82,35 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
public var script:Script;
public var xml:Access;
+ public var characterData:CharacterData;
+
public var shadowFrame:CharacterShadowFrame;
-
- public inline function getCameraPosition() {
+ public inline function getCameraPosition()
+ {
var midpoint = getMidpoint();
- var event = EventManager.get(PointEvent).recycle(
- midpoint.x + (isPlayer ? -100 : 150) + globalOffset.x + cameraOffset.x,
- midpoint.y - 100 + globalOffset.y + cameraOffset.y);
+ var event = EventManager.get(PointEvent)
+ .recycle(midpoint.x
+ + (isPlayer ? -100 : 150)
+ + globalOffset.x
+ + cameraOffset.x, midpoint.y
+ - 100
+ + globalOffset.y
+ + cameraOffset.y);
script.call("onGetCamPos", [event]);
midpoint.put();
// this event cannot be cancelled
return new FlxPoint(event.x, event.y);
}
- public function playSingAnim(direction:Int, suffix:String = "", Context:PlayAnimContext = SING, Force:Bool = true, Reversed:Bool = false, Frame:Int = 0) {
+ public function playSingAnim(direction:Int, suffix:String = "", Context:PlayAnimContext = SING, Force:Bool = true, Reversed:Bool = false, Frame:Int = 0)
+ {
var anims = ["singLEFT", "singDOWN", "singUP", "singRIGHT"];
var event = EventManager.get(DirectionAnimEvent).recycle('${anims[direction]}$suffix', direction, suffix, Context, Reversed, Frame, Force);
script.call("onPlaySingAnim", [event]);
- if (!event.cancelled) playAnim(event.animName, event.force, Context, event.reversed, event.frame);
+ if (!event.cancelled)
+ playAnim(event.animName, event.force, Context, event.reversed, event.frame);
}
public function new(x:Float, y:Float, ?character:String = "bf", isPlayer:Bool = false, switchAnims:Bool = true)
@@ -90,53 +123,76 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
antialiasing = true;
- while(true) {
+ while (true)
+ {
switch (curCharacter)
{
// case 'your-char': // To hardcode characters
default:
// load xml
var xmlPath = Paths.xml('characters/$curCharacter');
- if (!Assets.exists(xmlPath)) {
+ if (!Assets.exists(xmlPath))
+ {
curCharacter = "bf";
continue;
}
var plainXML = Assets.getText(xmlPath);
- try {
+ try
+ {
var charXML = Xml.parse(plainXML).firstElement();
- if (charXML == null) throw new Exception("Missing \"character\" node in XML.");
+ if (charXML == null)
+ throw new Exception("Missing \"character\" node in XML.");
xml = new Access(charXML);
- } catch(e) {
+ }
+ catch (e)
+ {
Logs.trace('Error while loading character ${curCharacter}: ${e}', ERROR);
curCharacter = "bf";
continue;
}
var sprite:String = curCharacter;
- if (xml.has.isPlayer) playerOffsets = (xml.att.isPlayer == "true");
- if (xml.has.isGF) isGF = (xml.att.isGF == "true");
- if (xml.has.x) globalOffset.x = Std.parseFloat(xml.att.x);
- if (xml.has.y) globalOffset.y = Std.parseFloat(xml.att.y);
- if (xml.has.gameOverChar) gameOverCharacter = xml.att.gameOverChar;
- if (xml.has.camx) cameraOffset.x = Std.parseFloat(xml.att.camx);
- if (xml.has.camy) cameraOffset.y = Std.parseFloat(xml.att.camy);
- if (xml.has.holdTime) holdTime = CoolUtil.getDefault(Std.parseFloat(xml.att.holdTime), 4);
- if (xml.has.flipX) flipX = (xml.att.flipX == "true");
- if (xml.has.icon) icon = xml.att.icon;
- if (xml.has.scale) {
+ if (xml.has.isPlayer)
+ playerOffsets = (xml.att.isPlayer == "true");
+ if (xml.has.isGF)
+ isGF = (xml.att.isGF == "true");
+ if (xml.has.x)
+ globalOffset.x = Std.parseFloat(xml.att.x);
+ if (xml.has.y)
+ globalOffset.y = Std.parseFloat(xml.att.y);
+ if (xml.has.gameOverChar)
+ gameOverCharacter = xml.att.gameOverChar;
+ if (xml.has.camx)
+ cameraOffset.x = Std.parseFloat(xml.att.camx);
+ if (xml.has.camy)
+ cameraOffset.y = Std.parseFloat(xml.att.camy);
+ if (xml.has.holdTime)
+ holdTime = CoolUtil.getDefault(Std.parseFloat(xml.att.holdTime), 4);
+ if (xml.has.flipX)
+ flipX = (xml.att.flipX == "true");
+ if (xml.has.icon)
+ icon = xml.att.icon;
+ if (xml.has.scale)
+ {
var scale = Std.parseFloat(xml.att.scale).getDefault(1);
this.scale.set(scale, scale);
updateHitbox();
}
- if (xml.has.antialiasing) antialiasing = (xml.att.antialiasing == "true");
- if (xml.has.sprite) sprite = xml.att.sprite;
+ if (xml.has.antialiasing)
+ antialiasing = (xml.att.antialiasing == "true");
+ if (xml.has.sprite)
+ sprite = xml.att.sprite;
loadSprite(Paths.image('characters/$sprite'));
- for(anim in xml.nodes.anim) {
+
+ for (anim in xml.nodes.anim)
+ {
XMLUtil.addXMLAnimation(this, anim);
}
+ characterData = CharacterConfig.loadCharacterData(curCharacter);
+
// Loads the script and calls it's "create" function
script = Script.create(Paths.script(Path.withoutExtension(xmlPath), null, true));
script.setParent(this);
@@ -145,7 +201,8 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
}
break;
}
- if (script == null) script = new DummyScript(curCharacter);
+ if (script == null)
+ script = new DummyScript(curCharacter);
/**
NON CONVERTED CHARACTERS - DO NOT REMOVE
@@ -168,9 +225,16 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
// antialiasing = false;
// flipX = true;
+ fixChar(true);
+ dance();
+ script.call("postCreate");
+ }
+
+ public function fixChar(switchAnims:Bool = false)
+ {
isDanceLeftDanceRight = (hasAnimation("danceLeft") && hasAnimation("danceRight"));
-
+
// alternative to xor operator
// for people who dont believe it, heres the truth table
// [ a ][ b ][ a!= b ]
@@ -183,14 +247,13 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
// character is flipped
CoolUtil.switchAnimFrames(animation.getByName('singRIGHT'), animation.getByName('singLEFT'));
CoolUtil.switchAnimFrames(animation.getByName('singRIGHTmiss'), animation.getByName('singLEFTmiss'));
-
+
switchOffset('singLEFT', 'singRIGHT');
switchOffset('singLEFTmiss', 'singRIGHTmiss');
}
- if (isPlayer) flipX = !flipX;
+ if (isPlayer)
+ flipX = !flipX;
__baseFlipped = flipX;
- dance();
- script.call("postCreate");
}
var __baseFlipped:Bool = false;
@@ -200,7 +263,8 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
{
super.update(elapsed);
script.call("update", [elapsed]);
- if (stunned) {
+ if (stunned)
+ {
__stunnedTime += elapsed;
if (__stunnedTime > 5 / 60)
stunned = false;
@@ -209,7 +273,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
}
private var danced:Bool = false;
-
+
/**
* FOR GF DANCING SHIT
*/
@@ -219,22 +283,27 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
{
var event = EventManager.get(DanceEvent).recycle(danced);
script.call("onDance", [event]);
- if (event.cancelled) return;
+ if (event.cancelled)
+ return;
switch (curCharacter)
{
// hardcode custom dance animations here
default:
- if (isDanceLeftDanceRight) {
+ if (isDanceLeftDanceRight)
+ {
playAnim((danced = !danced) ? 'danceLeft' : 'danceRight', DANCE);
- } else
+ }
+ else
playAnim('idle', DANCE);
}
}
}
- public function tryDance() {
- switch(lastAnimContext) {
+ public function tryDance()
+ {
+ switch (lastAnimContext)
+ {
case SING | MISS:
if (lastHit + (Conductor.stepCrochet * holdTime) < Conductor.songPosition)
dance();
@@ -259,24 +328,30 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
*/
public var danceInterval:Int = 1;
- public override function beatHit(curBeat:Int) {
+ public override function beatHit(curBeat:Int)
+ {
script.call("beatHit", [curBeat]);
if (danceInterval < 1)
danceInterval = 1;
- if (danceOnBeat && curBeat % danceInterval == 0 && !__lockAnimThisFrame) {
+ if (danceOnBeat && curBeat % danceInterval == 0 && !__lockAnimThisFrame)
+ {
tryDance();
}
}
-
- public override function stepHit(curStep:Int) {
+
+ public override function stepHit(curStep:Int)
+ {
script.call("stepHit", [curStep]);
// nothing
}
var __reverseDrawProcedure:Bool = false;
- public override function getScreenBounds(?newRect:FlxRect, ?camera:FlxCamera):FlxRect {
- if (__reverseDrawProcedure) {
+
+ public override function getScreenBounds(?newRect:FlxRect, ?camera:FlxCamera):FlxRect
+ {
+ if (__reverseDrawProcedure)
+ {
scale.x *= -1;
var bounds = super.getScreenBounds(newRect, camera);
scale.x *= -1;
@@ -284,18 +359,21 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
}
return super.getScreenBounds(newRect, camera);
}
-
+
var __drawingShadowFrame = false;
var __oldColorTransform = new ColorTransform();
- public override function draw() {
- if (!__drawingShadowFrame && shadowFrame != null) {
+
+ public override function draw()
+ {
+ if (!__drawingShadowFrame && shadowFrame != null)
+ {
__drawingShadowFrame = true;
var oldFrame = _frame;
var oldPos = FlxPoint.get(frameOffset.x, frameOffset.y);
-
+
__oldColorTransform.copyColorTransform(colorTransform);
-
+
colorTransform.alphaMultiplier = 1;
colorTransform.alphaOffset = 0;
colorTransform.blueMultiplier = 0;
@@ -312,7 +390,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
_frame = oldFrame;
frameOffset.set(oldPos.x, oldPos.y);
-
+
colorTransform.copyColorTransform(__oldColorTransform);
oldPos.put();
@@ -320,7 +398,8 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
__drawingShadowFrame = false;
}
- if ((isPlayer != playerOffsets) != (flipX != __baseFlipped)) {
+ if ((isPlayer != playerOffsets) != (flipX != __baseFlipped))
+ {
__reverseDrawProcedure = true;
flipX = !flipX;
@@ -330,7 +409,8 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
scale.x *= -1;
__reverseDrawProcedure = false;
- } else
+ }
+ else
super.draw();
}
@@ -338,30 +418,66 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
{
var event = EventManager.get(PlayAnimEvent).recycle(AnimName, Force, Reversed, Frame, Context);
script.call("onPlayAnim", [event]);
- if (event.cancelled) return;
+ if (event.cancelled)
+ return;
super.playAnim(event.animName, event.force, event.context, event.reverse, event.startingFrame);
-
+
offset.set(globalOffset.x * (isPlayer != playerOffsets ? 1 : -1), -globalOffset.y);
if (event.context == SING || event.context == MISS)
lastHit = Conductor.songPosition;
}
-
-
- public override function destroy() {
+ public override function destroy()
+ {
super.destroy();
-
+
cameraOffset.put();
globalOffset.put();
}
- public inline function getIcon() {
+ public inline function getIcon()
+ {
return (icon != null) ? icon : curCharacter;
}
+
+ public static function getIconFromCharName(?curCharacter:String) {
+ if(curCharacter == null) return "face";
+ var icon = curCharacter;
+ while(true) {
+ switch (curCharacter)
+ {
+ // case 'your-char': // To hardcode characters icons
+ default:
+ // load xml
+ var xmlPath = Paths.xml('characters/$curCharacter');
+ if (!Assets.exists(xmlPath)) {
+ curCharacter = "bf";
+ continue;
+ }
+
+ var xml = null;
+ var plainXML = Assets.getText(xmlPath);
+ try {
+ var charXML = Xml.parse(plainXML).firstElement();
+ if (charXML == null) throw new Exception("Missing \"character\" node in XML.");
+ xml = new Access(charXML);
+ } catch(e) {
+ Logs.trace('Error while loading character ${curCharacter}: ${e}', ERROR);
+ curCharacter = "bf";
+ continue;
+ }
+
+ if (xml.has.icon) icon = xml.att.icon;
+ }
+ break;
+ }
+ return icon;
+ }
}
-typedef CharacterShadowFrame = {
+typedef CharacterShadowFrame =
+{
var anim:String;
var frame:FlxFrame;
-}
\ No newline at end of file
+}
diff --git a/source/funkin/game/HudCamera.hx b/source/funkin/game/HudCamera.hx
index 452be12d..a88f9ff5 100644
--- a/source/funkin/game/HudCamera.hx
+++ b/source/funkin/game/HudCamera.hx
@@ -50,7 +50,7 @@ class HudCamera extends FlxCamera {
if (downscroll) {
var oldPoint = FlxPoint.get(pos.x, pos.y);
pos.set(pos.x, height - pos.y - spr.height);
-
+
oldPoint.put();
}
return pos;
diff --git a/source/funkin/game/Note.hx b/source/funkin/game/Note.hx
index 38672c56..3c7e8c4b 100644
--- a/source/funkin/game/Note.hx
+++ b/source/funkin/game/Note.hx
@@ -14,6 +14,8 @@ using StringTools;
@:allow(funkin.game.PlayState)
class Note extends FlxSprite
{
+ public var extra:Map = [];
+
public var strumTime:Float = 0;
public var mustPress(get, never):Bool;
@@ -61,7 +63,7 @@ class Note extends FlxSprite
public var strumID(get, never):Int;
private function get_strumID() {
- var id = noteData % 4;
+ var id = noteData % strumLine.members.length;
if (id < 0) id = 0;
return id;
}
@@ -198,7 +200,7 @@ class Note extends FlxSprite
frameOffset.y += __notePosFrameOffset.y * 2;
super.drawComplex(camera);
frameOffset.y -= __notePosFrameOffset.y * 2;
- } else
+ } else
super.drawComplex(camera);
}
diff --git a/source/funkin/game/NoteGroup.hx b/source/funkin/game/NoteGroup.hx
index a6e6e628..c822cd81 100644
--- a/source/funkin/game/NoteGroup.hx
+++ b/source/funkin/game/NoteGroup.hx
@@ -58,7 +58,7 @@ class NoteGroup extends FlxTypedGroup {
public override function forEach(noteFunc:Note->Void, recursive:Bool = false) {
i = length-1;
__loopSprite = null;
-
+
var oldCur = __currentlyLooping;
__currentlyLooping = true;
@@ -76,7 +76,7 @@ class NoteGroup extends FlxTypedGroup {
if (note.alive) noteFunc(note);
}, recursive);
}
-
+
public override function remove(Object:Note, Splice:Bool = false):Note
{
if (members == null)
diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx
index 365eb22b..e0e518f3 100644
--- a/source/funkin/game/PlayState.hx
+++ b/source/funkin/game/PlayState.hx
@@ -1,5 +1,6 @@
package funkin.game;
+import funkin.editors.charter.EventsData;
import funkin.backend.system.RotatingSpriteGroup;
import funkin.editors.charter.Charter;
import funkin.savedata.FunkinSave;
@@ -347,6 +348,11 @@ class PlayState extends MusicBeatState
*/
public var defaultCamZoom:Float = 1.05;
+ /**
+ * Camera zoom at which the hud lerps to.
+ */
+ public var defaultHudZoom:Float = 1.0;
+
/**
* Zoom for the pixel assets.
*/
@@ -550,21 +556,23 @@ class PlayState extends MusicBeatState
// dadMidpoint.put();
var camPos:FlxPoint = new FlxPoint(0, 0);
-
if (!chartingMode || Options.charterEnablePlaytestScripts) {
switch(SONG.meta.name) {
// case "":
// ADD YOUR HARDCODED SCRIPTS HERE!
default:
- for(content in [
- Paths.getFolderContent('songs/${SONG.meta.name.toLowerCase()}/scripts', true, fromMods ? MODS : BOTH),
- Paths.getFolderContent('data/charts/', true, fromMods ? MODS : BOTH)]) {
- for(file in content) {
- var ext = Path.extension(file).toLowerCase();
- if (Script.scriptExtensions.contains(ext))
- scripts.add(Script.create(file));
- }
+ for(content in [Paths.getFolderContent('songs/${SONG.meta.name.toLowerCase()}/scripts', true, fromMods ? MODS : BOTH), Paths.getFolderContent('data/charts/', true, fromMods ? MODS : BOTH)])
+ for(file in content) addScript(file);
+
+ var songEvents:Array = [];
+ for (event in SONG.events)
+ if (!songEvents.contains(event.name)) songEvents.push(event.name);
+
+ for (file in Paths.getFolderContent('data/events/', true, fromMods ? MODS : BOTH)) {
+ var fileName:String = Path.withoutExtension(Path.withoutDirectory(file));
+ if (EventsData.eventsList.contains(fileName) && songEvents.contains(fileName)) addScript(file);
}
+
}
}
@@ -599,7 +607,7 @@ class PlayState extends MusicBeatState
}
}
- for(strumLine in SONG.strumLines) {
+ for(i=>strumLine in SONG.strumLines) {
if (strumLine == null) continue;
var chars = [];
@@ -614,11 +622,21 @@ class PlayState extends MusicBeatState
chars.push(char);
}
- var strOffset:Float = strumLine.strumLinePos == null ? (strumLine.type == 1 ? 0.75 : 0.25) : strumLine.strumLinePos;
- var strLine = new StrumLine(chars, strOffset, strumLine.type == 2 || (!coopMode && !((strumLine.type == 1 && !opponentMode) || (strumLine.type == 0 && opponentMode))), strumLine.type != 1, coopMode ? (strumLine.type == 1 ? controlsP1 : controlsP2) : controls);
+ var startingPos:FlxPoint = strumLine.strumPos == null ? switch (strumLine.type) {
+ case 1: FlxPoint.get((FlxG.width * 0.75) - ((Note.swagWidth * (strumLine.strumScale == null ? 1 : strumLine.strumScale)) * 2), this.strumLine.y);
+ default: FlxPoint.get((FlxG.width * 0.25) - ((Note.swagWidth * (strumLine.strumScale == null ? 1 : strumLine.strumScale)) * 2), this.strumLine.y);
+ } : FlxPoint.get(strumLine.strumPos[0], strumLine.strumPos[1]);
+
+ var strLine = new StrumLine(chars,
+ startingPos,
+ strumLine.strumScale == null ? 1 : strumLine.strumScale,
+ strumLine.type == 2 || (!coopMode && !((strumLine.type == 1 && !opponentMode) || (strumLine.type == 0 && opponentMode))),
+ strumLine.type != 1, coopMode ? (strumLine.type == 1 ? controlsP1 : controlsP2) : controls
+ );
strLine.cameras = [camHUD];
strLine.data = strumLine;
strLine.visible = (strumLine.visible != false);
+ strLine.ID = i;
strumLines.add(strLine);
}
@@ -633,6 +651,12 @@ class PlayState extends MusicBeatState
// CAMERA & HUD INITIALISATION
#if REGION
+ var event = EventManager.get(AmountEvent).recycle(4);
+ if (!scripts.event("onPreGenerateStrums", event).cancelled) {
+ generateStrums(event.amount);
+ scripts.event("onPostGenerateStrums", event);
+ }
+
for(str in strumLines)
str.generate(str.data, (chartingMode && Charter.startHere) ? Charter.startTime : null);
@@ -642,6 +666,7 @@ class PlayState extends MusicBeatState
FlxG.camera.follow(camFollow, LOCKON, 0.04);
FlxG.camera.zoom = defaultCamZoom;
+ // camHUD.zoom = defaultHudZoom;
if (smoothTransitionData != null && smoothTransitionData.stage == curStage) {
FlxG.camera.scroll.set(smoothTransitionData.camX, smoothTransitionData.camY);
FlxG.camera.zoom = smoothTransitionData.camZoom;
@@ -659,6 +684,8 @@ class PlayState extends MusicBeatState
healthBarBG.scrollFactor.set();
add(healthBarBG);
+
+
healthBar = new FlxBar(healthBarBG.x + 4, healthBarBG.y + 4, RIGHT_TO_LEFT, Std.int(healthBarBG.width - 8), Std.int(healthBarBG.height - 8), this,
'health', 0, maxHealth);
healthBar.scrollFactor.set();
@@ -782,18 +809,12 @@ class PlayState extends MusicBeatState
if (scripts.event("onStartCountdown", new CancellableEvent()).cancelled) return;
}
- if (!scripts.event("onPreGenerateStrums", new CancellableEvent()).cancelled) {
- generateStrums();
- scripts.call("onPostGenerateStrums");
- }
-
startedCountdown = true;
Conductor.songPosition = 0;
Conductor.songPosition -= Conductor.crochet * introLength;
var swagCounter:Int = 0;
-
startTimer = new FlxTimer().start(Conductor.crochet / 1000, function(tmr:FlxTimer)
{
countdown(swagCounter++);
@@ -897,7 +918,7 @@ class PlayState extends MusicBeatState
// get first camera focus
for(e in events) {
if (e.time > 10) break;
- if (e.type == CAM_MOVEMENT) {
+ if (e.name == "Camera Movement") {
executeEvent(e);
break;
}
@@ -930,9 +951,9 @@ class PlayState extends MusicBeatState
}
@:dox(hide)
- private inline function generateStrums():Void
+ private inline function generateStrums(amount:Int = 4):Void
for(p in strumLines)
- p.generateStrums();
+ p.generateStrums(amount);
@:dox(hide)
override function openSubState(SubState:FlxSubState)
@@ -1100,7 +1121,7 @@ class PlayState extends MusicBeatState
{
if (startedCountdown)
{
- Conductor.songPosition += FlxG.elapsed * 1000;
+ Conductor.songPosition += elapsed * 1000;
if (Conductor.songPosition >= 0)
startSong();
}
@@ -1141,7 +1162,7 @@ class PlayState extends MusicBeatState
if (camZooming)
{
FlxG.camera.zoom = lerp(FlxG.camera.zoom, defaultCamZoom, 0.05);
- camHUD.zoom = lerp(camHUD.zoom, 1, 0.05);
+ camHUD.zoom = lerp(camHUD.zoom, defaultHudZoom, 0.05);
}
// RESET = Quick Game Over Screen
@@ -1166,28 +1187,35 @@ class PlayState extends MusicBeatState
scripts.call("postUpdate", [elapsed]);
}
+ override function draw() {
+ var e = scripts.event("draw", EventManager.get(DrawEvent).recycle());
+ if (!e.cancelled)
+ super.draw();
+ scripts.event("postDraw", e);
+ }
+
public function executeEvent(event:ChartEvent) {
if (event == null) return;
if (event.params == null) return;
- switch(event.type) {
- case CUSTOM:
- if (event.params[0] is String && event.params[1] is Array) {
- scripts.call(event.params[0], event.params[1]);
+ if (scripts.event("onEvent", EventManager.get(EventGameEvent).recycle(event)).cancelled) return;
+
+ switch(event.name) {
+ case "HScript Call":
+ if (event.params[0] is String && event.params[1] is String) {
+ scripts.call(event.params[0], event.params[1].split(','));
}
- case CAM_MOVEMENT:
+ case "Camera Movement":
if (event.params[0] is Int)
curCameraTarget = event.params[0];
- case BPM_CHANGE:
- // automatically handled by conductor
- case ALT_ANIM_TOGGLE:
+ case "BPM Change": // automatically handled by conductor
+ case "Alt Animation Toggle":
if (event.params[0] is Int && event.params[1] is Bool) {
var strLine = strumLines.members[event.params[0]];
if (strLine != null)
strLine.altAnim = cast event.params[1];
}
- // todo!!!
- default:
+ case "Unknown":
}
}
@@ -1331,6 +1359,7 @@ class PlayState extends MusicBeatState
/**
* Misses a note
+ * @param strumLine The strumline the miss happened on.
* @param note Note to miss.
* @param direction Specify a custom direction in case note is null.
* @param player Specify a custom player in case note is null.
@@ -1339,14 +1368,13 @@ class PlayState extends MusicBeatState
{
var playerID:Null = note == null ? player : strumLines.members.indexOf(strumLine);
var directionID:Null = note == null ? direction : note.strumID;
- if (playerID == null || directionID == null) return;
+ if (playerID == null || directionID == null || playerID == -1) return;
- var event:NoteHitEvent = scripts.event("onPlayerMiss", EventManager.get(NoteHitEvent).recycle(true, false, false, note, strumLines.members[playerID].characters, true, note != null ? note.noteType : null, "", "", "", directionID, -10, 0, -0.04, "shit"));
+ var event:NoteHitEvent = scripts.event("onPlayerMiss", EventManager.get(NoteHitEvent).recycle(true, false, false, note, strumLines.members[playerID].characters, true, note != null ? note.noteType : null, "", "", "", directionID, -10, 0, note != null ? -0.0475 : -0.04, "shit"));
strumLine.onMiss.dispatch(event);
if (event.cancelled) return;
-
- if (event.note != null && strumLine != null) strumLine.addHealth(event.healthGain);
+ if (strumLine != null) strumLine.addHealth(event.healthGain);
if (gf != null && combo > 5 && gf.hasAnimation('sad'))
gf.playAnim('sad', event.forceAnim, MISS);
combo = 0;
@@ -1569,6 +1597,12 @@ class PlayState extends MusicBeatState
scripts.call("beatHit", [curBeat]);
}
+ public function addScript(file:String) {
+ var ext = Path.extension(file).toLowerCase();
+ if (Script.scriptExtensions.contains(ext))
+ scripts.add(Script.create(file));
+ }
+
// GETTERS & SETTERS
#if REGION
private inline function get_player():StrumLine
diff --git a/source/funkin/game/SplashGroup.hx b/source/funkin/game/SplashGroup.hx
index 81c04b38..67e136c5 100644
--- a/source/funkin/game/SplashGroup.hx
+++ b/source/funkin/game/SplashGroup.hx
@@ -66,7 +66,7 @@ class SplashGroup extends FlxTypedGroup {
splashSprite.animation.finishCallback = function(name) {
splashSprite.active = splashSprite.visible = false;
};
-
+
if (xml.has.scale) splashSprite.scale.scale(Std.parseFloat(xml.att.scale).getDefault(1));
if (xml.has.alpha) splashSprite.alpha = Std.parseFloat(xml.att.alpha).getDefault(1);
if (xml.has.antialiasing) splashSprite.antialiasing = xml.att.antialiasing == "true";
diff --git a/source/funkin/game/SplashHandler.hx b/source/funkin/game/SplashHandler.hx
index 071c49cb..d7bc4e46 100644
--- a/source/funkin/game/SplashHandler.hx
+++ b/source/funkin/game/SplashHandler.hx
@@ -45,6 +45,6 @@ class SplashHandler extends FlxTypedGroup {
// max 8 rendered splashes
while(members.length > 8)
- remove(members[0]);
+ remove(members[0], true);
}
}
\ No newline at end of file
diff --git a/source/funkin/game/Stage.hx b/source/funkin/game/Stage.hx
index 56f705be..695961b5 100644
--- a/source/funkin/game/Stage.hx
+++ b/source/funkin/game/Stage.hx
@@ -1,5 +1,7 @@
package funkin.game;
+import funkin.backend.scripting.events.StageXMLEvent;
+import funkin.backend.scripting.events.StageNodeEvent;
import flixel.math.FlxPoint;
import funkin.backend.MusicBeatGroup;
import flixel.FlxState;
@@ -35,8 +37,14 @@ class Stage extends FlxBasic implements IBeatReceiver {
} catch(e) {
Logs.trace('Couldn\'t load stage "$stage": ${e.message}', ERROR);
}
- if (stageXML != null) {
+ if (PlayState.instance != null) {
+ stageScript = Script.create(Paths.script('data/stages/$stage'));
+ PlayState.instance.scripts.add(stageScript);
+ stageScript.load();
+ }
+
+ if (stageXML != null) {
var spritesParentFolder = "";
if (PlayState.instance != null) {
if (stageXML.has.zoom) {
@@ -59,6 +67,11 @@ class Stage extends FlxBasic implements IBeatReceiver {
elems.push(node);
}
+ if (PlayState.instance != null) {
+ var event = PlayState.instance.scripts.event("onStageXMLParsed", EventManager.get(StageXMLEvent).recycle(this, stageXML, elems));
+ elems = event.elems;
+ }
+
for(node in elems) {
var sprite:Dynamic = switch(node.name) {
case "sprite" | "spr" | "sparrow":
@@ -72,22 +85,24 @@ class Stage extends FlxBasic implements IBeatReceiver {
stageSprites.set(spr.name, spr);
state.add(spr);
spr;
- case "box":
+ case "box" | "solid":
if ( !node.has.name || !node.has.width || !node.has.height) continue;
var spr = new FlxSprite(
(node.has.x) ? Std.parseFloat(node.att.x) : 0,
(node.has.y) ? Std.parseFloat(node.att.y) : 0
- ).makeGraphic(
+ );
+
+ (node.name == "solid" ? spr.makeSolid : spr.makeGraphic)(
Std.parseInt(node.att.width),
Std.parseInt(node.att.height),
- (node.has.color) ? CoolUtil.getColorFromDynamic(node.att.color) : 0xFFFFFFFF
+ (node.has.color) ? CoolUtil.getColorFromDynamic(node.att.color) : -1
);
stageSprites.set(node.getAtt("name"), spr);
state.add(spr);
spr;
- case "boyfriend" | "bf":
+ case "boyfriend" | "bf" | "player":
addCharPos("boyfriend", node, {
x: 770,
y: 100,
@@ -121,6 +136,11 @@ class Stage extends FlxBasic implements IBeatReceiver {
PlayState.instance.comboGroup;
default: null;
}
+
+ if(PlayState.instance != null) {
+ sprite = PlayState.instance.scripts.event("onStageNodeParsed", EventManager.get(StageNodeEvent).recycle(this, node, sprite, node.name)).sprite;
+ }
+
if (sprite != null) {
for(e in node.nodes.property)
XMLUtil.applyXMLProperty(sprite, e);
@@ -153,11 +173,9 @@ class Stage extends FlxBasic implements IBeatReceiver {
});
if (PlayState.instance == null) return;
- stageScript = Script.create(Paths.script('data/stages/$stage'));
for(k=>e in stageSprites) {
stageScript.set(k, e);
}
- PlayState.instance.scripts.add(stageScript);
}
public function addCharPos(name:String, node:Access, ?nonXMLInfo:StageCharPosInfo):StageCharPos {
@@ -245,8 +263,9 @@ class StageCharPos extends FlxObject {
public function prepareCharacter(char:Character, id:Float = 0) {
char.setPosition(x + (id * charSpacingX), y + (id * charSpacingY));
char.scrollFactor.set(scrollFactor.x, scrollFactor.y);
- char.scale.x *= scale.x;
- char.scale.y *= scale.y;
+ char.scale.x *= scale.x; char.scale.y *= scale.y;
+ char.cameraOffset += FlxPoint.weak(camxoffset, camyoffset);
+ char.skew.x += skewX; char.skew.y += skewY;
}
}
typedef StageCharPosInfo = {
diff --git a/source/funkin/game/Strum.hx b/source/funkin/game/Strum.hx
index 1e3b3d99..cd5a8caf 100644
--- a/source/funkin/game/Strum.hx
+++ b/source/funkin/game/Strum.hx
@@ -4,21 +4,55 @@ import flixel.math.FlxPoint;
import funkin.backend.system.Conductor;
class Strum extends FlxSprite {
+ public var extra:Map = [];
+
public var cpu = false;
public var lastHit:Float = -5000;
public var scrollSpeed:Null = null; // custom scroll speed per strum
public var noteAngle:Null = null;
-
+
public var lastDrawCameras(default, null):Array = [];
+ public var getPressed:StrumLine->Bool = null;
+ public var getJustPressed:StrumLine->Bool = null;
+ public var getJustReleased:StrumLine->Bool = null;
+
+ public inline function __getPressed(strumLine:StrumLine):Bool {
+ return getPressed != null ? getPressed(strumLine) : switch(ID) {
+ case 0: strumLine.controls.NOTE_LEFT;
+ case 1: strumLine.controls.NOTE_DOWN;
+ case 2: strumLine.controls.NOTE_UP;
+ case 3: strumLine.controls.NOTE_RIGHT;
+ default: false;
+ }
+ }
+ public inline function __getJustPressed(strumLine:StrumLine) {
+ return getJustPressed != null ? getJustPressed(strumLine) : switch(ID) {
+ case 0: strumLine.controls.NOTE_LEFT_P;
+ case 1: strumLine.controls.NOTE_DOWN_P;
+ case 2: strumLine.controls.NOTE_UP_P;
+ case 3: strumLine.controls.NOTE_RIGHT_P;
+ default: false;
+ }
+ }
+ public inline function __getJustReleased(strumLine:StrumLine) {
+ return getJustReleased != null ? getJustReleased(strumLine) : switch(ID) {
+ case 0: strumLine.controls.NOTE_LEFT_R;
+ case 1: strumLine.controls.NOTE_DOWN_R;
+ case 2: strumLine.controls.NOTE_UP_R;
+ case 3: strumLine.controls.NOTE_RIGHT_R;
+ default: false;
+ }
+ }
+
public inline function getScrollSpeed(?note:Note):Float {
if (note != null && note.scrollSpeed != null) return note.scrollSpeed;
if (scrollSpeed != null) return scrollSpeed;
if (PlayState.instance != null) return PlayState.instance.scrollSpeed;
return 1;
}
-
+
public inline function getNotesAngle(?note:Note):Float {
if (note != null && note.noteAngle != null) return note.noteAngle;
if (noteAngle != null) return noteAngle;
@@ -44,7 +78,7 @@ class Strum extends FlxSprite {
public function updateNotePosition(daNote:Note) {
if (!daNote.exists) return;
-
+
daNote.__strumCameras = lastDrawCameras;
daNote.__strum = this;
daNote.scrollFactor.set(scrollFactor.x, scrollFactor.y);
@@ -63,7 +97,7 @@ class Strum extends FlxSprite {
var realOffset = FlxPoint.get(0, 0);
if (daNote.isSustainNote) offset.y -= N_WIDTHDIV2;
-
+
if (Std.int(daNote.__noteAngle % 360) != 0) {
var noteAngleCos = FlxMath.fastCos(daNote.__noteAngle / PIX180);
var noteAngleSin = FlxMath.fastSin(daNote.__noteAngle / PIX180);
@@ -81,9 +115,9 @@ class Strum extends FlxSprite {
realOffset.y = offset.y;
}
realOffset.y *= -1;
-
+
daNote.setPosition(x + realOffset.x, y + realOffset.y);
-
+
offset.put();
realOffset.put();
}
diff --git a/source/funkin/game/StrumLine.hx b/source/funkin/game/StrumLine.hx
index 3d959738..2f0c6ebd 100644
--- a/source/funkin/game/StrumLine.hx
+++ b/source/funkin/game/StrumLine.hx
@@ -1,5 +1,6 @@
package funkin.game;
+import flixel.math.FlxPoint;
import flixel.util.FlxSignal.FlxTypedSignal;
import funkin.backend.scripting.events.*;
@@ -65,6 +66,12 @@ class StrumLine extends FlxTypedGroup {
* Whenever alt animation is enabled on this strumline.
*/
public var altAnim:Bool = false;
+ /**
+ * TODO: Write documention about this being a variable that can help when making multi key
+ */
+ public var strumAnimPrefix = ["left", "down", "up", "right"];
+
+ public var extra:Map = [];
private function get_ghostTapping() {
if (this.ghostTapping != null) return this.ghostTapping;
@@ -75,12 +82,14 @@ class StrumLine extends FlxTypedGroup {
private inline function set_ghostTapping(b:Bool):Bool
return this.ghostTapping = b;
- private var strumOffset:Float = 0.25;
+ private var startingPos:FlxPoint = FlxPoint.get(0,0);
+ private var strumScale:Float = 1;
- public function new(characters:Array, strumOffset:Float = 0.25, cpu:Bool = false, opponentSide:Bool = true, ?controls:Controls) {
+ public function new(characters:Array, startingPos:FlxPoint, strumScale:Float, cpu:Bool = false, opponentSide:Bool = true, ?controls:Controls) {
super();
this.characters = characters;
- this.strumOffset = strumOffset;
+ this.startingPos = startingPos;
+ this.strumScale = strumScale;
this.cpu = cpu;
this.opponentSide = opponentSide;
this.controls = controls;
@@ -187,18 +196,20 @@ class StrumLine extends FlxTypedGroup {
__justPressed.clear();
__justReleased.clear();
- __pressed.pushGroup(controls.NOTE_LEFT, controls.NOTE_DOWN, controls.NOTE_UP, controls.NOTE_RIGHT);
- __justPressed.pushGroup(controls.NOTE_LEFT_P, controls.NOTE_DOWN_P, controls.NOTE_UP_P, controls.NOTE_RIGHT_P);
- __justReleased.pushGroup(controls.NOTE_LEFT_R, controls.NOTE_DOWN_R, controls.NOTE_UP_R, controls.NOTE_RIGHT_R);
+ for(s in members) {
+ __pressed.push(s.__getPressed(this));
+ __justPressed.push(s.__getJustPressed(this));
+ __justReleased.push(s.__getJustReleased(this));
+ }
- var event = PlayState.instance.scripts.event("onKeyShit", EventManager.get(InputSystemEvent).recycle(__pressed, __justPressed, __justReleased, this, id));
+ var event = PlayState.instance.scripts.event("onInputUpdate", EventManager.get(InputSystemEvent).recycle(__pressed, __justPressed, __justReleased, this, id));
if (event.cancelled) return;
__pressed = CoolUtil.getDefault(event.pressed, []);
__justPressed = CoolUtil.getDefault(event.justPressed, []);
__justReleased = CoolUtil.getDefault(event.justReleased, []);
- __notePerStrum = [for(_ in 0...4) null];
+ __notePerStrum = [for(_ in 0...members.length) null];
if (__pressed.contains(true)) {
@@ -224,68 +235,64 @@ class StrumLine extends FlxTypedGroup {
for(e in __notePerStrum) if (e != null) PlayState.instance.goodNoteHit(this, e);
forEach(function(str:Strum) {
- str.updatePlayerInput(__pressed[str.ID], __justPressed[str.ID], __justReleased[str.ID]);
+ str.updatePlayerInput(str.__getPressed(this), str.__getJustPressed(this), str.__getJustReleased(this));
});
- PlayState.instance.scripts.call("onPostKeyShit");
+ PlayState.instance.scripts.call("onPostInputUpdate");
}
public inline function addHealth(health:Float)
PlayState.instance.health += health * (opponentSide ? -1 : 1);
- public function generateStrums(amount:Int = 4) {
- for (i in 0...4)
- {
- var babyArrow:Strum = new Strum((FlxG.width * strumOffset) + (Note.swagWidth * (i - 2)), PlayState.instance.strumLine.y);
- babyArrow.ID = i;
+ public inline function generateStrums(amount:Int = 4) {
+ for (i in 0...amount)
+ add(createStrum(i));
+ }
- var event = PlayState.instance.scripts.event("onStrumCreation", EventManager.get(StrumCreationEvent).recycle(babyArrow, PlayState.instance.strumLines.members.indexOf(this), i));
+ /**
+ * Creates a strum and returns the created strum (needs to be added manually).
+ * @param i Index of the strum
+ * @param animPrefix (Optional) Animation prefix (`left` = `arrowLEFT`, `left press`, `left confirm`).
+ */
+ public function createStrum(i:Int, ?animPrefix:String) {
+ if (animPrefix == null)
+ animPrefix = strumAnimPrefix[i % strumAnimPrefix.length];
+ var babyArrow:Strum = new Strum(startingPos.x + ((Note.swagWidth * strumScale) * i), startingPos.y);
+ babyArrow.ID = i;
- if (!event.cancelled) {
- babyArrow.frames = Paths.getFrames(event.sprite);
- babyArrow.animation.addByPrefix('green', 'arrowUP');
- babyArrow.animation.addByPrefix('blue', 'arrowDOWN');
- babyArrow.animation.addByPrefix('purple', 'arrowLEFT');
- babyArrow.animation.addByPrefix('red', 'arrowRIGHT');
+ var event = PlayState.instance.scripts.event("onStrumCreation", EventManager.get(StrumCreationEvent).recycle(babyArrow, PlayState.instance.strumLines.members.indexOf(this), i, animPrefix));
- babyArrow.antialiasing = true;
- babyArrow.setGraphicSize(Std.int(babyArrow.width * 0.7));
+ if (!event.cancelled) {
+ babyArrow.frames = Paths.getFrames(event.sprite);
+ babyArrow.animation.addByPrefix('green', 'arrowUP');
+ babyArrow.animation.addByPrefix('blue', 'arrowDOWN');
+ babyArrow.animation.addByPrefix('purple', 'arrowLEFT');
+ babyArrow.animation.addByPrefix('red', 'arrowRIGHT');
- switch (babyArrow.ID % 4)
- {
- case 0:
- babyArrow.animation.addByPrefix('static', 'arrowLEFT');
- babyArrow.animation.addByPrefix('pressed', 'left press', 24, false);
- babyArrow.animation.addByPrefix('confirm', 'left confirm', 24, false);
- case 1:
- babyArrow.animation.addByPrefix('static', 'arrowDOWN');
- babyArrow.animation.addByPrefix('pressed', 'down press', 24, false);
- babyArrow.animation.addByPrefix('confirm', 'down confirm', 24, false);
- case 2:
- babyArrow.animation.addByPrefix('static', 'arrowUP');
- babyArrow.animation.addByPrefix('pressed', 'up press', 24, false);
- babyArrow.animation.addByPrefix('confirm', 'up confirm', 24, false);
- case 3:
- babyArrow.animation.addByPrefix('static', 'arrowRIGHT');
- babyArrow.animation.addByPrefix('pressed', 'right press', 24, false);
- babyArrow.animation.addByPrefix('confirm', 'right confirm', 24, false);
- }
- }
+ babyArrow.antialiasing = true;
+ babyArrow.setGraphicSize(Std.int((babyArrow.width * 0.7) * strumScale));
- babyArrow.cpu = cpu;
- babyArrow.updateHitbox();
- babyArrow.scrollFactor.set();
-
- if (event.__doAnimation)
- {
- babyArrow.y -= 10;
- babyArrow.alpha = 0;
- FlxTween.tween(babyArrow, {y: babyArrow.y + 10, alpha: 1}, 1, {ease: FlxEase.circOut, startDelay: 0.5 + (0.2 * i)});
- }
-
- add(babyArrow);
-
- babyArrow.playAnim('static');
+ babyArrow.animation.addByPrefix('static', 'arrow${event.animPrefix.toUpperCase()}');
+ babyArrow.animation.addByPrefix('pressed', '${event.animPrefix} press', 24, false);
+ babyArrow.animation.addByPrefix('confirm', '${event.animPrefix} confirm', 24, false);
}
+
+ babyArrow.cpu = cpu;
+ babyArrow.updateHitbox();
+ babyArrow.scrollFactor.set();
+
+ if (event.__doAnimation)
+ {
+ babyArrow.y -= 10;
+ babyArrow.alpha = 0;
+ FlxTween.tween(babyArrow, {y: babyArrow.y + 10, alpha: 1}, 1, {ease: FlxEase.circOut, startDelay: 0.5 + (0.2 * i)});
+ }
+ babyArrow.playAnim('static');
+
+ insert(i, babyArrow);
+
+ PlayState.instance.scripts.event("onPostStrumCreation", event);
+
+ return babyArrow;
}
/**
diff --git a/source/funkin/game/cutscenes/MP4Handler.hx b/source/funkin/game/cutscenes/MP4Handler.hx
deleted file mode 100644
index 6d10b9f6..00000000
--- a/source/funkin/game/cutscenes/MP4Handler.hx
+++ /dev/null
@@ -1,175 +0,0 @@
-package funkin.game.cutscenes;
-
-#if VIDEO_CUTSCENES
-import vlc.bitmap.VlcBitmap;
-import flixel.util.FlxTimer;
-import funkin.options.PlayerSettings;
-import openfl.events.Event;
-import flixel.FlxState;
-
-class MP4Handler
-{
- public var finishCallback:Void->Void;
- public var stateCallback:FlxState;
-
- public var bitmap:VlcBitmap;
-
- public var sprite:FlxSprite;
- public var canvasWidth:Null = null;
- public var canvasHeight:Null = null;
- public var fillScreen:Bool;
- public var skippable:Bool = true;
-
- public function new()
- {
- //FlxG.autoPause = false;
- }
-
- public function playMP4(path:String, ?repeat:Bool = false, ?outputTo:FlxSprite = null, ?isWindow:Bool = false, ?isFullscreen:Bool = false,
- ?midSong:Bool = false):Void
- {
- if (!midSong)
- {
- if (FlxG.sound.music != null)
- {
- FlxG.sound.music.stop();
- }
- }
-
- bitmap = new VlcBitmap();
-
- if (FlxG.stage.stageHeight / 9 < FlxG.stage.stageWidth / 16)
- {
- bitmap.set_width(FlxG.stage.stageHeight * (16 / 9));
- bitmap.set_height(FlxG.stage.stageHeight);
- }
- else
- {
- bitmap.set_width(FlxG.stage.stageWidth);
- bitmap.set_height(FlxG.stage.stageWidth / (16 / 9));
- }
-
-
-
- bitmap.onVideoReady = onVLCVideoReady;
- bitmap.onComplete = onVLCComplete;
- bitmap.onError = onVLCError;
-
- FlxG.stage.addEventListener(Event.ENTER_FRAME, update);
-
- if (repeat)
- bitmap.repeat = -1;
- else
- bitmap.repeat = 0;
-
- // bitmap.inWindow = isWindow;
- // bitmap.fullscreen = isFullscreen;
-
- FlxG.addChildBelowMouse(bitmap);
- bitmap.play(checkFile(path));
-
- if (outputTo != null)
- {
- // lol this is bad kek
- bitmap.alpha = 0;
-
- sprite = outputTo;
- }
- }
-
- function checkFile(fileName:String):String
- {
- var pDir = "";
- var appDir = "file:///" + Sys.getCwd() + "/";
-
- if (fileName.indexOf(":") == -1) // Not a path
- pDir = appDir;
- else if (fileName.indexOf("file://") == -1 || fileName.indexOf("http") == -1) // C:, D: etc? ..missing "file:///" ?
- pDir = "file:///";
-
- return pDir + fileName;
- }
-
- /////////////////////////////////////////////////////////////////////////////////////
-
- function onVLCVideoReady()
- {
- trace("video loaded!");
-
- if (sprite != null) {
- sprite.loadGraphic(bitmap.bitmapData);
- if (canvasWidth != null && canvasHeight != null) {
- sprite.setGraphicSize(canvasWidth, canvasHeight);
- sprite.updateHitbox();
-
- var r = (fillScreen ? Math.max : Math.min)(sprite.scale.x, sprite.scale.y);
- sprite.scale.set(r, r); // lol
- }
- }
- }
-
- public function onVLCComplete()
- {
- bitmap.stop();
-
- new FlxTimer().start(0.3, function(tmr:FlxTimer)
- {
- if (finishCallback != null)
- {
- finishCallback();
- }
- else if (stateCallback != null)
- {
- FlxG.switchState(stateCallback);
- }
-
- bitmap.dispose();
-
- if (FlxG.game.contains(bitmap))
- {
- FlxG.game.removeChild(bitmap);
- }
- });
- }
-
- public function kill()
- {
- bitmap.stop();
-
- if (finishCallback != null)
- {
- finishCallback();
- }
-
- bitmap.visible = false;
- }
-
- function onVLCError()
- {
- if (finishCallback != null)
- {
- finishCallback();
- }
- else if (stateCallback != null)
- {
- FlxG.switchState(stateCallback);
- }
- }
-
- function update(e:Event)
- {
- if (PlayerSettings.player1.controls.ACCEPT && skippable)
- {
- if (bitmap.isPlaying)
- {
- onVLCComplete();
- }
- }
-
- bitmap.volume = FlxG.sound.volume + 0.3; // shitty volume fix. then make it louder.
-
- if (FlxG.sound.volume <= 0.1 || FlxG.sound.muted)
- bitmap.volume = 0;
- }
-}
-#end
\ No newline at end of file
diff --git a/source/funkin/game/cutscenes/VideoCutscene.hx b/source/funkin/game/cutscenes/VideoCutscene.hx
index 0a8031e6..2bf8ff90 100644
--- a/source/funkin/game/cutscenes/VideoCutscene.hx
+++ b/source/funkin/game/cutscenes/VideoCutscene.hx
@@ -8,7 +8,9 @@ import sys.io.File;
#end
import funkin.backend.FunkinText;
import haxe.xml.Access;
-
+#if VIDEO_CUTSCENES
+import hxcodec.flixel.FlxVideo;
+#end
/**
* Substate made for video cutscenes. To use it in a scripted cutscene, call `startVideo`.
@@ -19,8 +21,8 @@ class VideoCutscene extends Cutscene {
var localPath:String;
#if VIDEO_CUTSCENES
- var video:MP4Handler;
- var videoSprite:FlxSprite;
+ var video:FlxVideo;
+ public var skippable:Bool = true;
#end
var cutsceneCamera:FlxCamera;
@@ -50,14 +52,9 @@ class VideoCutscene extends Cutscene {
#if VIDEO_CUTSCENES
parseSubtitles();
- video = new MP4Handler();
- video.finishCallback = close;
- video.canvasWidth = cutsceneCamera.width;
- video.canvasHeight = cutsceneCamera.height;
-
- videoSprite = new FlxSprite();
- videoSprite.cameras = [cutsceneCamera];
- videoSprite.antialiasing = true;
+ video = new FlxVideo();
+ video.onEndReached.add(video.dispose);
+ video.onEndReached.add(close);
bg = new FlxSprite(0, FlxG.height * 0.85).makeGraphic(1, 1, 0xFF000000);
bg.alpha = 0.5;
@@ -88,9 +85,8 @@ class VideoCutscene extends Cutscene {
videoReady = true;
});
} else {
- video.playMP4(localPath, false, videoSprite);
+ video.play(localPath);
}
- add(videoSprite);
add(bg);
add(subtitle);
#end
@@ -164,18 +160,21 @@ class VideoCutscene extends Cutscene {
#if VIDEO_CUTSCENES
if (videoReady) {
videoReady = false;
- video.playMP4(localPath, false, videoSprite);
+ video.play(localPath);
if (loadingBackdrop != null)
loadingBackdrop.visible = false;
}
@:privateAccess
- var time = video.bitmap.getTime();
+ var time = video.time;
while (subtitles.length > 0 && subtitles[0].time < time)
setSubtitle(subtitles.shift());
if (loadingBackdrop != null) {
loadingBackdrop.x -= elapsed * FlxG.width * 0.5;
}
+ if (skippable && video.isPlaying && controls.ACCEPT) {
+ video.onEndReached.dispatch();
+ }
#else
close();
#end
@@ -202,4 +201,4 @@ typedef CutsceneSubtitle = {
var time:Float; // time in ms
var subtitle:String; // subtitle text
var color:FlxColor;
-}
\ No newline at end of file
+}
diff --git a/source/funkin/menus/BetaWarningState.hx b/source/funkin/menus/BetaWarningState.hx
index 8b40bb1b..e374dda7 100644
--- a/source/funkin/menus/BetaWarningState.hx
+++ b/source/funkin/menus/BetaWarningState.hx
@@ -20,7 +20,7 @@ class BetaWarningState extends MusicBeatState {
disclaimer = new FunkinText(16, titleAlphabet.y + titleAlphabet.height + 10, FlxG.width - 32, "", 32);
disclaimer.alignment = CENTER;
- disclaimer.applyMarkup("This engine is still in a alpha state. That means *majority of the features* are either *buggy* or *non finished*. If you find any bug, please report them to the Codename Engine GitHub.\n\nPress ENTER to continue",
+ disclaimer.applyMarkup("This engine is still in a alpha state. That means *majority of the features* are either *buggy* or *non finished*. If you find any bugs, please report them to the Codename Engine GitHub.\n\nPress ENTER to continue",
[
new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFFF4444), "*")
]
diff --git a/source/funkin/menus/FreeplayState.hx b/source/funkin/menus/FreeplayState.hx
index 50692a7e..8a77b0e9 100644
--- a/source/funkin/menus/FreeplayState.hx
+++ b/source/funkin/menus/FreeplayState.hx
@@ -22,7 +22,11 @@ class FreeplayState extends MusicBeatState
/**
* How much time a song stays selected until it autoplays.
*/
- public inline static var timeUntilAutoplay:Float = 1;
+ public var timeUntilAutoplay:Float = 1;
+ /**
+ * Whenever the song autoplays when hovered over.
+ */
+ public var disableAutoPlay:Bool = false;
/**
* Array containing all of the songs metadatas
@@ -202,7 +206,7 @@ class FreeplayState extends MusicBeatState
if (FlxG.sound.music != null && FlxG.sound.music.volume < 0.7)
{
- FlxG.sound.music.volume += 0.5 * FlxG.elapsed;
+ FlxG.sound.music.volume += 0.5 * elapsed;
}
lerpScore = Math.floor(lerp(lerpScore, intendedScore, 0.4));
@@ -233,7 +237,7 @@ class FreeplayState extends MusicBeatState
var dontPlaySongThisFrame = false;
#if PRELOAD_ALL
autoplayElapsed += elapsed;
- if (!songInstPlaying && (autoplayElapsed > timeUntilAutoplay || FlxG.keys.justPressed.SPACE)) {
+ if (!disableAutoPlay && !songInstPlaying && (autoplayElapsed > timeUntilAutoplay || FlxG.keys.justPressed.SPACE)) {
if (curPlayingInst != (curPlayingInst = Paths.inst(songs[curSelected].name, songs[curSelected].difficulties[curDifficulty])))
FlxG.sound.playMusic(curPlayingInst, 0);
songInstPlaying = true;
@@ -398,22 +402,25 @@ class FreeplayState extends MusicBeatState
}
function updateOptionsAlpha() {
+ var event = event("onUpdateOptionsAlpha", EventManager.get(FreeplayAlphaUpdateEvent).recycle(0.6, 0.45, 1, 1, 0.25));
+ if (event.cancelled) return;
+
var bullShit:Int = 0;
for (i in 0...iconArray.length)
- iconArray[i].alpha = lerp(iconArray[i].alpha, #if PRELOAD_ALL songInstPlaying ? 0.45 : #end 0.6, 0.25);
+ iconArray[i].alpha = lerp(iconArray[i].alpha, #if PRELOAD_ALL songInstPlaying ? event.idlePlayingAlpha : #end event.idleAlpha, event.lerp);
- iconArray[curSelected].alpha = 1;
+ iconArray[curSelected].alpha = #if PRELOAD_ALL songInstPlaying ? event.selectedPlayingAlpha : #end event.selectedAlpha;
for (item in grpSongs.members)
{
item.targetY = bullShit - curSelected;
bullShit++;
- item.alpha = lerp(item.alpha, #if PRELOAD_ALL songInstPlaying ? 0.45 : #end 0.6, 0.25);
+ item.alpha = lerp(item.alpha, #if PRELOAD_ALL songInstPlaying ? event.idlePlayingAlpha : #end event.idleAlpha, event.lerp);
if (item.targetY == 0)
- item.alpha = 1;
+ item.alpha = #if PRELOAD_ALL songInstPlaying ? event.selectedPlayingAlpha : #end event.selectedAlpha;
}
}
}
diff --git a/source/funkin/options/Options.hx b/source/funkin/options/Options.hx
index a4b38c30..8ac2f947 100644
--- a/source/funkin/options/Options.hx
+++ b/source/funkin/options/Options.hx
@@ -39,6 +39,7 @@ class Options
/**
* EDITORS SETTINGS
*/
+ public static var intensiveBlur:Bool = true;
public static var editorSFX:Bool = true;
public static var resizableEditors:Bool = true;
public static var maxUndos:Int = 120;
diff --git a/source/funkin/options/TreeMenu.hx b/source/funkin/options/TreeMenu.hx
index 669bdb4b..1b2fa993 100644
--- a/source/funkin/options/TreeMenu.hx
+++ b/source/funkin/options/TreeMenu.hx
@@ -10,7 +10,6 @@ import funkin.options.type.OptionType;
import funkin.options.categories.*;
class TreeMenu extends MusicBeatState {
-
public var main:OptionsScreen;
public var optionsTree:OptionsTree;
public var pathLabel:FunkinText;
@@ -40,7 +39,7 @@ class TreeMenu extends MusicBeatState {
pathBG.updateHitbox();
pathBG.alpha = 0.25;
pathBG.scrollFactor.set();
-
+
optionsTree = new OptionsTree();
optionsTree.onMenuChange = onMenuChange;
optionsTree.onMenuClose = onMenuClose;
diff --git a/update.sh b/update.sh
index 6f843ad6..b664c4fd 100644
--- a/update.sh
+++ b/update.sh
@@ -1,2 +1,2 @@
#!/bin/sh
-haxe -cp update -D analyzer-optimize -main Update --interp
\ No newline at end of file
+haxe -cp commandline -D analyzer-optimize --run Main setup
\ No newline at end of file