Merge branch 'FNF-CNE-Devs:main' into for-prs

This commit is contained in:
Frakits
2023-07-01 12:19:15 +03:00
committed by GitHub
107 changed files with 3369 additions and 841 deletions
+2 -2
View File
@@ -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!)
##### 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!)
+2 -1
View File
@@ -4,4 +4,5 @@ latest/windows/obj/
latest/windows/haxe/
.vscode/*
!.vscode/settings.json
.haxelib/
.haxelib/
mods/Baldi's Basics in Funkin'/
+3 -3
View File
@@ -38,7 +38,7 @@ Latest builds for the engine can be found in the [Actions](https://github.com/Yo
<summary>Windows</summary>
##### 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
<summary>Linux</summary>
##### 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
<summary>MacOS</summary>
##### 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`.
+19 -7
View File
@@ -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
- [ ] Stage Editor
- [ ] Optimize flxanimate isOnScreen
+5
View File
@@ -7,4 +7,9 @@
<anim name="singDOWN" anim="BF DOWN NOTE" fps="24" loop="false" x="0" y="0" />
<anim name="singUP" anim="BF UP NOTE" fps="24" loop="false" x="0" y="0" />
<anim name="singRIGHT" anim="BF RIGHT NOTE" fps="24" loop="false" x="0" y="0" />
<anim name="singUPmiss" anim="BF UP MISS" fps="24" loop="false" x="0" y="0"/>
<anim name="singLEFTmiss" anim="BF LEFT MISS" fps="24" loop="false" x="0" y="0"/>
<anim name="singRIGHTmiss" anim="BF RIGHT MISS" fps="24" loop="false" x="0" y="0"/>
<anim name="singDOWNmiss" anim="BF DOWN MISS" fps="24" loop="false" x="0" y="0"/>
</character>
+1 -1
View File
@@ -1,5 +1,5 @@
<!DOCTYPE codename-engine-character>
<character isPlayer="false" flipX="false" isGF="true" x="75" y="335">
<character isPlayer="false" flipX="false" isGF="true" x="75" y="335" icon="gf">
<anim name="danceLeft" anim="GF Dancing Beat Hair blowing CAR" fps="24" loop="false" x="0" y="0" indices="30,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14" />
<anim name="danceRight" anim="GF Dancing Beat Hair blowing CAR" fps="24" loop="false" x="0" y="0" indices="15,16,17,18,19,20,21,22,23,24,25,26,27,28,29" />
</character>
+1 -1
View File
@@ -1,5 +1,5 @@
<!DOCTYPE codename-engine-character>
<character isPlayer="false" flipX="false" isGF="true" x="115" y="325">
<character isPlayer="false" flipX="false" isGF="true" x="115" y="325" icon="gf">
<anim name="cheer" anim="GF Cheer" fps="24" loop="false" />
<anim name="singUP" anim="GF Up Note" fps="24" loop="false" x="0" y="4" />
<anim name="singLEFT" anim="GF left note" fps="24" loop="false" x="0" y="-19" />
+1 -1
View File
@@ -1,5 +1,5 @@
<!DOCTYPE codename-engine-character>
<character isPlayer="false" flipX="false" isGF="true" x="100" y="275">
<character isPlayer="false" flipX="false" isGF="true" x="100" y="275" icon="gf">
<anim name="danceLeft" anim="GF Dancing at Gunpoint" fps="24" loop="false" indices="30,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14" />
<anim name="danceRight" anim="GF Dancing at Gunpoint" fps="24" loop="false" indices="15,16,17,18,19,20,21,22,23,24,25,26,27,28,29" />
<anim name="dance-cutscene" anim="GF Dancing at Gunpoint" fps="24" loop="true" />
+11 -9
View File
@@ -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');
+2 -1
View File
@@ -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() {
Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

+20
View File
@@ -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;
}
+48
View File
@@ -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;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextureAtlas imagePath="gfPixel.png">
<!-- Created with Adobe Animate version 20.0.0.17400 -->
<!-- http://www.adobe.com/products/animate.html -->
<SubTexture name="GF IDLE0000" x="0" y="0" width="120" height="106"/>
<SubTexture name="GF IDLE0001" x="0" y="0" width="120" height="106"/>
<SubTexture name="GF IDLE0002" x="130" y="0" width="120" height="106"/>
<SubTexture name="GF IDLE0003" x="130" y="0" width="120" height="106"/>
<SubTexture name="GF IDLE0004" x="130" y="0" width="120" height="106"/>
<SubTexture name="GF IDLE0005" x="260" y="0" width="120" height="106"/>
<SubTexture name="GF IDLE0006" x="260" y="0" width="120" height="106"/>
<SubTexture name="GF IDLE0007" x="260" y="0" width="120" height="106"/>
<SubTexture name="GF IDLE0008" x="260" y="0" width="120" height="106"/>
<SubTexture name="GF IDLE0009" x="390" y="0" width="120" height="106"/>
<SubTexture name="GF IDLE0010" x="390" y="0" width="120" height="106"/>
<SubTexture name="GF IDLE0011" x="390" y="0" width="120" height="106"/>
<SubTexture name="GF IDLE0012" x="390" y="0" width="120" height="106"/>
<SubTexture name="GF IDLE0013" x="0" y="116" width="120" height="106"/>
<SubTexture name="GF IDLE0014" x="0" y="116" width="120" height="106"/>
<SubTexture name="GF IDLE0015" x="130" y="116" width="120" height="106"/>
<SubTexture name="GF IDLE0016" x="130" y="116" width="120" height="106"/>
<SubTexture name="GF IDLE0017" x="260" y="116" width="120" height="106"/>
<SubTexture name="GF IDLE0018" x="260" y="116" width="120" height="106"/>
<SubTexture name="GF IDLE0019" x="260" y="116" width="120" height="106"/>
<SubTexture name="GF IDLE0020" x="390" y="116" width="120" height="106"/>
<SubTexture name="GF IDLE0021" x="390" y="116" width="120" height="106"/>
<SubTexture name="GF IDLE0022" x="390" y="116" width="120" height="106"/>
<SubTexture name="GF IDLE0023" x="390" y="116" width="120" height="106"/>
<SubTexture name="GF IDLE0024" x="390" y="116" width="120" height="106"/>
<SubTexture name="GF IDLE0025" x="0" y="232" width="120" height="106"/>
<SubTexture name="GF IDLE0026" x="0" y="232" width="120" height="106"/>
<SubTexture name="GF IDLE0027" x="0" y="232" width="120" height="106"/>
<SubTexture name="GF IDLE0028" x="0" y="232" width="120" height="106"/>
<SubTexture name="GF IDLE0029" x="0" y="232" width="120" height="106"/>
</TextureAtlas>
+3 -3
View File
@@ -5,13 +5,13 @@ class Compiler {
__build(args, ["test", getBuildTarget(), "-D", "TEST_BUILD"]);
}
public static function build(args:Array<String>) {
__build(args, ["build", "windows", "-D", "TEST_BUILD"]);
__build(args, ["build", getBuildTarget(), "-D", "TEST_BUILD"]);
}
public static function release(args:Array<String>) {
__build(args, ["build", "windows"]);
__build(args, ["build", getBuildTarget()]);
}
public static function testRelease(args:Array<String>) {
__build(args, ["test", "windows"]);
__build(args, ["test", getBuildTarget()]);
}
private static function __build(args:Array<String>, arg:Array<String>) {
+27 -9
View File
@@ -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<String>) {
// 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<Library> = [];
var libsXML:Access = new Access(Xml.parse(File.getContent('./libs.xml')).firstElement());
var json:Array<Library> = 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;
}
-77
View File
@@ -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
}
]
}
+1 -1
View File
@@ -8,12 +8,12 @@
<git name="hscript-improved" url="https://www.github.com/FNF-CNE-Devs/hscript-improved" ref="custom-classes" />
<git name="discord-rpc" url="https://github.com/Aidan63/linc_discord-rpc"/>
<git name="flxanimate" url="https://github.com/FNF-CNE-Devs/flxanimate/" />
<git name="hxCodec" url="https://github.com/polybiusproxy/hxCodec"/>
<!-- Documentation and other features -->
<lib name="dox"/>
<lib name="away3d" />
<lib name="format" />
<lib name="hxCodec" version="2.5.1" />
<lib name="markdown" />
<lib name="hxcpp-debug-server" />
+13 -3
View File
@@ -116,10 +116,14 @@
<!-- Comment this out to disable GitHub API integration !-->
<define name="GITHUB_API" unless="web || hl"/>
<!-- Comment this off to prevent unused classes from being compiled.
<!-- Comment this out to prevent unused classes from being compiled.
Reduces compilation time at the cost of limited HScript flexibility. !-->
<define name="COMPILE_ALL_CLASSES" />
<!-- Comment this out to prevent extending classes in hscript.
Reduces compilation time at the cost of limited HScript flexibility. !-->
<define name="CUSTOM_CLASSES" />
<!-- Comment this out to disable multithreading !-->
<haxedef name="ALLOW_MULTITHREADING" unless="web || flash" />
@@ -144,6 +148,9 @@
<!-- Comment this out to disable support for custom extended classes (data/classes). !-->
<define name="SOFTCODED_CLASSES" />
<!-- Comment this out to disable support for Away3D Flixel Intergration (reduces compile times)-->
<define name="THREE_D_SUPPORT" />
<!-- Comment this out to disable video cutscene support, in case of compilation errors. !-->
<define name="VIDEO_CUTSCENES" unless="web || linux || hl || mac" />
@@ -161,12 +168,14 @@
<haxedef name="openfl_dpi_aware" if="openfl_dpi_aware" />
<!-- _______________________________ Libraries ______________________________ -->
<haxelib name="flixel" />
<haxelib name="flixel-addons" />
<haxelib name="hxCodec" if="VIDEO_CUTSCENES" />
<haxelib name="away3d" />
<section if="THREE_D_SUPPORT">
<haxelib name="away3d" />
</section>
<haxelib name="format" />
<haxelib name="flxanimate" />
@@ -178,6 +187,7 @@
<haxelib name="markdown" />
<haxelib name="hxcpp-debug-server" if="desktop debug"/>
<haxedef name="no-deprecation-warnings"/>
<!-- _________________________________ Additional stuff _______________________________ -->
+3 -1
View File
@@ -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++;
}
}
}
#end
+6 -2
View File
@@ -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
}
+4
View File
@@ -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
}
+5 -2
View File
@@ -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<Mesh> = [];
@@ -138,6 +140,7 @@ class Flx3DView extends FlxView3D {
super.destroy();
}
public inline function addChild(c)
public function addChild(c)
view.scene.addChild(c);
#end
}
+5 -1
View File
@@ -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
}
+4 -1
View File
@@ -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) {
+4
View File
@@ -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');
@@ -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<Float>;
}
}
+22 -5
View File
@@ -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<Int, String> = [
-1 => "HScript Call",
0 => "Unknown",
1 => "Camera Movement",
2 => "BPM Change",
3 => "Alt Animation Toggle",
];
for (event in cast(data.events, Array<Dynamic>)) {
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;
}
}
+3 -41
View File
@@ -36,8 +36,9 @@ typedef ChartStrumLine = {
var type:ChartStrumLineType;
var notes:Array<ChartNote>;
var position:String;
var ?strumLinePos:Float; // 0.25 = default opponent pos, 0.75 = default boyfriend pos
var ?visible:Null<Bool>;
var ?strumPos:Array<Float>;
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<Dynamic>;
}
@@ -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<ChartEventType>
return [CUSTOM, CAM_MOVEMENT, BPM_CHANGE, ALT_ANIM_TOGGLE];
}
+24 -1
View File
@@ -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<String, Dynamic> = [];
public static function getDefaultVariables(?script:Script):Map<String, Dynamic> {
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
*/
+13 -3
View File
@@ -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>):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<T:CancellableEvent>(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),
]);
}
}
@@ -0,0 +1,8 @@
package funkin.backend.scripting.events;
final class AmountEvent extends CancellableEvent {
/**
* Amount
*/
public var amount:Int;
}
@@ -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 {
@@ -0,0 +1,4 @@
package funkin.backend.scripting.events;
final class DrawEvent extends CancellableEvent {
}
@@ -0,0 +1,7 @@
package funkin.backend.scripting.events;
import funkin.backend.chart.ChartData;
final class EventGameEvent extends CancellableEvent {
public var event:ChartEvent;
}
@@ -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;
}
@@ -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;
}
@@ -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<Access>;
}
@@ -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.
*/
@@ -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);
+1 -1
View File
@@ -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;
+4 -2
View File
@@ -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();
}
+2 -1
View File
@@ -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 {
@@ -28,7 +28,7 @@ class AssetTreeInfo extends FramerateCategory {
}
if (text != "")
text = text.substr(0, text.length-1);
this.text.text = text;
super.__enterFrame(t);
}
@@ -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() {
+1 -1
View File
@@ -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) {
@@ -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;
}
}
}
+4
View File
@@ -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"));
}
}
+4 -4
View File
@@ -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<FlxSprite> {
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;
+1 -1
View File
@@ -18,7 +18,7 @@ class EditorTreeMenu extends TreeMenu {
bg.antialiasing = true;
add(bg);
}
public override function exit() {
FlxG.switchState(new MainMenuState());
}
@@ -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);
}
}
@@ -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<FlxBasic>
{
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 '<!DOCTYPE codename-engine-character>\n${Printer.print(xml, true)}';
}
}
@@ -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<Character> = new FlxTypedSpriteGroup<Character>(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<UIContextMenuOption>;
public var topMenuSpr:UITopMenu;
public var uiGroup:FlxTypedGroup<FlxSprite> = new FlxTypedGroup<FlxSprite>();
public var stage:FlxTypedSpriteGroup<FunkinSprite> = new FlxTypedSpriteGroup<FunkinSprite>(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>):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<AnimData>):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<AnimData>)
{
// 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<Bool> = [
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<Bool> = [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;
}
}
@@ -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();
}
}
@@ -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<CharacterData> = [];
public function new()
{
}
public function getCharactersFromSource(source:funkin.backend.assets.AssetsLibraryList.AssetSource)
{
var charactersFound:Array<String> = [];
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;
}
}
@@ -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<UIButton> = new FlxTypedSpriteGroup<UIButton>(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();
}
}
@@ -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);
}
}
@@ -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<UIButton> = new FlxTypedSpriteGroup<UIButton>(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));
}
}
@@ -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);
}
}
+189 -28
View File
@@ -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<CharterNote> = [];
// 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<CharterNote>);
CDeleteNotes(notes:Array<CharterNote>);
CNoteDrag(notes:Array<NoteDragChange>, deletes:Array<CharterNote>);
CCreateStrumLine(strumLineID:Int, strumLine:ChartStrumLine);
CDeleteStrumLine(strumLineID:Int, strumLine:ChartStrumLine);
}
enum CharterCopyboardObject {
@@ -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();
+16 -100
View File
@@ -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<ChartEvent>;
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<EventParamInfo>;
}
typedef EventInfo = {
> EventDefineInfo,
var paramValues:Array<Dynamic>;
}
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;
}
@@ -14,7 +14,6 @@ class CharterEventAdd extends UISliceSprite {
members.push(text);
cursor = BUTTON;
}
public override function onHovered() {
@@ -1,5 +1,3 @@
package funkin.editors.charter;
class CharterEventGroup extends FlxTypedGroup<CharterEvent> {
}
class CharterEventGroup extends FlxTypedGroup<CharterEvent> {}
@@ -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;
}
@@ -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;
}
@@ -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;
@@ -28,7 +28,7 @@ class CharterNoteGroup extends FlxTypedGroup<CharterNote> {
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;
@@ -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;
@@ -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();
@@ -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)
);
}
}
@@ -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<HealthIcon> = [];
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<String>) {
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);
}
}
@@ -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<String> = ["HScript Call", "Camera Movement", "BPM Change", "Alt Animation Toggle"];
public static var defaultEventsParams:Map<String, Array<EventParamInfo>> = [
"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<String> = defaultEventsList.copy();
public static var eventsParams:Map<String, Array<EventParamInfo>> = defaultEventsParams.copy();
public static function getEventParams(name:String):Array<EventParamInfo> {
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<EventParamInfo> = [];
for (paramData in cast(data.params, Array<Dynamic>)) {
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<EventParamInfo>;
var paramValues:Array<Dynamic>;
}
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<String>);
}
@@ -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()],
};
}
}
+173
View File
@@ -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<UINumericStepper> = []; // r,g,b
public var curColor:Null<FlxColor> = 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<FlxSprite> {
public var curColor:FlxColor = FlxColor.WHITE;
public var selector:FlxSprite;
public var colorCircle:FlxSprite;
public function new(x:Float, y:Float, ?color:Null<Int>) {
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;
}
}
+1 -2
View File
@@ -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;
+1 -2
View File
@@ -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);
}
}
+3 -3
View File
@@ -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);
+17 -6
View File
@@ -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<UIContextMenuOption>, ?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;
+21 -1
View File
@@ -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<FlxCamera> = [];
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);
}
+2
View File
@@ -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;
}
}
+26 -3
View File
@@ -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);
}
@@ -6,7 +6,7 @@ import funkin.backend.shaders.CustomShader;
class UIWarningSubstate extends MusicBeatSubstate {
var camShaders:Array<FlxCamera> = [];
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;
+183 -67
View File
@@ -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<AnimData>;
@: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<String, Dynamic> = [];
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;
}
}
+1 -1
View File
@@ -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;
+4 -2
View File
@@ -14,6 +14,8 @@ using StringTools;
@:allow(funkin.game.PlayState)
class Note extends FlxSprite
{
public var extra:Map<String, Dynamic> = [];
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);
}
+2 -2
View File
@@ -58,7 +58,7 @@ class NoteGroup extends FlxTypedGroup<Note> {
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<Note> {
if (note.alive) noteFunc(note);
}, recursive);
}
public override function remove(Object:Note, Splice:Bool = false):Note
{
if (members == null)
+71 -37
View File
@@ -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<String> = [];
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<Int> = note == null ? player : strumLines.members.indexOf(strumLine);
var directionID:Null<Int> = 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
+1 -1
View File
@@ -66,7 +66,7 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> {
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";
+1 -1
View File
@@ -45,6 +45,6 @@ class SplashHandler extends FlxTypedGroup<FunkinSprite> {
// max 8 rendered splashes
while(members.length > 8)
remove(members[0]);
remove(members[0], true);
}
}
+28 -9
View File
@@ -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 = {
+40 -6
View File
@@ -4,21 +4,55 @@ import flixel.math.FlxPoint;
import funkin.backend.system.Conductor;
class Strum extends FlxSprite {
public var extra:Map<String, Dynamic> = [];
public var cpu = false;
public var lastHit:Float = -5000;
public var scrollSpeed:Null<Float> = null; // custom scroll speed per strum
public var noteAngle:Null<Float> = null;
public var lastDrawCameras(default, null):Array<FlxCamera> = [];
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();
}
+65 -58
View File
@@ -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<Strum> {
* 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<String, Dynamic> = [];
private function get_ghostTapping() {
if (this.ghostTapping != null) return this.ghostTapping;
@@ -75,12 +82,14 @@ class StrumLine extends FlxTypedGroup<Strum> {
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<Character>, strumOffset:Float = 0.25, cpu:Bool = false, opponentSide:Bool = true, ?controls:Controls) {
public function new(characters:Array<Character>, 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<Strum> {
__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<Strum> {
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;
}
/**

Some files were not shown because too many files have changed in this diff Show More