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