Merge branch 'FNF-CNE-Devs:main' into for-prs
@@ -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!)
|
||||
|
||||
@@ -4,4 +4,5 @@ latest/windows/obj/
|
||||
latest/windows/haxe/
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
.haxelib/
|
||||
.haxelib/
|
||||
mods/Baldi's Basics in Funkin'/
|
||||
@@ -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`.
|
||||
|
||||
@@ -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
|
||||
@@ -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,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,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,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" />
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
After Width: | Height: | Size: 652 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 436 B |
|
After Width: | Height: | Size: 413 B |
|
After Width: | Height: | Size: 421 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 413 B |
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
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>
|
||||
@@ -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>) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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 _______________________________ -->
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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()],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -45,6 +45,6 @@ class SplashHandler extends FlxTypedGroup<FunkinSprite> {
|
||||
|
||||
// max 8 rendered splashes
|
||||
while(members.length > 8)
|
||||
remove(members[0]);
|
||||
remove(members[0], true);
|
||||
}
|
||||
}
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||