diff --git a/.github/actions/setup-haxeshit/action.yml b/.github/actions/setup-haxeshit/action.yml
index 3931e62a7..5844499b7 100644
--- a/.github/actions/setup-haxeshit/action.yml
+++ b/.github/actions/setup-haxeshit/action.yml
@@ -1,10 +1,20 @@
name: setup-haxeshit
-description: "sets up haxe shit, using lix!"
+description: "sets up haxe shit, using HMM!"
runs:
using: "composite"
steps:
- - uses: lix-pm/setup-lix@1.0.0
+ - uses: krdlab/setup-haxe@v1.4.0
with:
- lix-version: 15.8.9 # optional
- env:
- ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
+ haxe-version: 4.2.5
+ - name: Config haxelib
+ run: |
+ haxelib config
+ shell: bash
+ - name: Installing Haxe lol
+ run: |
+ haxe -version
+ haxelib git haxelib https://github.com/HaxeFoundation/haxelib.git
+ haxelib version
+ haxelib --global install hmm
+ haxelib --global run hmm install --quiet
+ shell: bash
diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml
index 182ee2af6..574f1c3ef 100644
--- a/.github/workflows/build-shit.yml
+++ b/.github/workflows/build-shit.yml
@@ -55,15 +55,10 @@ jobs:
export/debug/windows/haxe/
export/debug/windows/obj/
- - name: lix stuff
- run: |
- npm i -g lix
- lix download
- lix +lib lime
- lix run lime setup
+ - uses: ./.github/actions/setup-haxeshit
- name: Build game
run: |
- lix run lime build windows
+ haxelib run lime build windows
dir
- uses: ./.github/actions/upload-itch
with:
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index c46be2c9c..483db9ea9 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -5,6 +5,7 @@
"vshaxe.haxe-checkstyle", // Haxe code style and conventions
"vshaxe.hxcpp-debugger", // CPP debugging
"openfl.lime-vscode-extension", // Lime integration
- "esbenp.prettier-vscode" // JSON formatting
+ "esbenp.prettier-vscode", // JSON formatting
+ "redhat.vscode-xml" // XML formatting
]
}
diff --git a/CODESTYLE.md b/CODESTYLE.md
index 9fb08eef3..2641febfa 100644
--- a/CODESTYLE.md
+++ b/CODESTYLE.md
@@ -13,7 +13,7 @@ Code Quality is handled by the `vshaxe.haxe-checkstyle` extension, which include
* Checks can be disabled by setting the severity to `IGNORE`.
* `IndentationCharacter` checks what is used to indent, `Indentation` checks how deep the intentation is.
* `CommentedOutCode` check is in place because old code should be retrieved via Git history.
-* TODO items:
+* TODO items: Enable these one-by-one and fix them to improve the overall code quality.
- Reconfigure `MethodLength`
- Reconfigure `CyclomaticComplexity`
- Re-enable `MagicNumber`
diff --git a/Project.xml b/Project.xml
index a251ccdbf..c2f03f83b 100644
--- a/Project.xml
+++ b/Project.xml
@@ -115,21 +115,21 @@
-
-
+
+
+
-
-
-
-
+
+
-
-
-
-
-
+
+
+
+
+
+
diff --git a/checkstyle.json b/checkstyle.json
index 69a86dbfe..d9200ea12 100644
--- a/checkstyle.json
+++ b/checkstyle.json
@@ -41,7 +41,9 @@
"type": "AvoidStarImport"
},
{
- "props": {},
+ "props": {
+ "severity": "IGNORE"
+ },
"type": "AvoidTernaryOperator"
},
{
@@ -215,7 +217,8 @@
},
{
"props": {
- "character": " "
+ "character": " ",
+ "severity": "IGNORE"
},
"type": "Indentation"
},
@@ -229,7 +232,8 @@
},
{
"props": {
- "ignoreReturnAssignments": false
+ "ignoreReturnAssignments": false,
+ "severity": "WARNING"
},
"type": "InnerAssignment"
},
@@ -275,11 +279,25 @@
},
"type": "MagicNumber"
},
+ {
+ "props": {
+ "format": "^[A-Z][a-zA-Z0-9]*$",
+ "tokens": ["ABSTRACT"]
+ },
+ "type": "MemberName"
+ },
+ {
+ "props": {
+ "format": "^[_a-zA-Z][a-z][a-zA-Z0-9]*$",
+ "tokens": ["ABSTRACT"]
+ },
+ "type": "MemberName"
+ },
{
"props": {
"ignoreExtern": true,
- "format": "^[a-z][a-zA-Z0-9]*$",
- "tokens": []
+ "format": "^[_a-z][a-zA-Z0-9]*$",
+ "tokens": ["PUBLIC", "PRIVATE", "CLASS", "TYPEDEF"]
},
"type": "MemberName"
},
@@ -310,10 +328,10 @@
{
"props": {
"modifiers": [
- "MACRO",
- "OVERRIDE",
"PUBLIC_PRIVATE",
"STATIC",
+ "MACRO",
+ "OVERRIDE",
"INLINE",
"DYNAMIC",
"FINAL"
@@ -342,13 +360,15 @@
},
{
"props": {
- "max": 3
+ "max": 3,
+ "severity": "IGNORE"
},
"type": "NestedControlFlow"
},
{
"props": {
- "max": 1
+ "max": 1,
+ "severity": "IGNORE"
},
"type": "NestedForDepth"
},
@@ -366,7 +386,7 @@
},
{
"props": {
- "option": "questionMark"
+ "option": "nullDefault"
},
"type": "NullableParameter"
},
@@ -390,7 +410,6 @@
{
"props": {
"tokens": [
- "=",
"+",
"-",
"*",
@@ -426,6 +445,13 @@
"++",
"--"
],
+ "option": "nl"
+ },
+ "type": "OperatorWrap"
+ },
+ {
+ "props": {
+ "tokens": ["="],
"option": "eol"
},
"type": "OperatorWrap"
@@ -446,7 +472,9 @@
"type": "ParameterNumber"
},
{
- "props": {},
+ "props": {
+ "severity": "WARNING"
+ },
"type": "PublicAccessor"
},
{
@@ -480,7 +508,8 @@
{
"props": {
"ignoreFormat": "^$",
- "max": 2
+ "max": 2,
+ "severity": "IGNORE"
},
"type": "ReturnCount"
},
diff --git a/hmm.json b/hmm.json
index 087fd5fef..9edc42cdd 100644
--- a/hmm.json
+++ b/hmm.json
@@ -1,108 +1,116 @@
{
- "dependencies": [{
- "name": "discord_rpc",
- "type": "git",
- "dir": null,
- "ref": "2d83fa8",
- "url": "https://github.com/Aidan63/linc_discord-rpc"
- },
- {
- "name": "flixel",
- "type": "git",
- "dir": null,
- "ref": "d6100cc8",
- "url": "https://github.com/EliteMasterEric/flixel"
- },
- {
- "name": "flixel-addons",
- "type": "git",
- "dir": null,
- "ref": "f107166",
- "url": "https://github.com/EliteMasterEric/flixel-addons"
- },
- {
- "name": "flixel-ui",
- "type": "haxelib",
- "version": "2.4.0"
- },
- {
- "name": "flxanimate",
- "type": "git",
- "dir": null,
- "ref": "18b2060",
- "url": "https://github.com/Dot-Stuff/flxanimate"
- },
- {
- "name": "format",
- "type": "haxelib",
- "version": "3.5.0"
- },
- {
- "name": "haxeui-core",
- "type": "git",
- "dir": null,
- "ref": "e5cf78d",
- "url": "https://github.com/haxeui/haxeui-core/"
- },
- {
- "name": "haxeui-flixel",
- "type": "git",
- "dir": null,
- "ref": "f03bb6d",
- "url": "https://github.com/haxeui/haxeui-flixel"
- },
- {
- "name": "hmm",
- "type": "git",
- "dir": null,
- "ref": "3ef9522",
- "url": "https://github.com/steviegt6/hmm"
- },
- {
- "name": "hscript",
- "type": "haxelib",
- "version": "2.5.0"
- },
- {
- "name": "hxcpp",
- "type": "haxelib",
- "version": "4.2.1"
- },
- {
- "name": "hxcpp-debug-server",
- "type": "haxelib",
- "version": "1.2.4"
- },
- {
- "name": "hxp",
- "type": "haxelib",
- "version": null
- },
- {
- "name": "lime",
- "type": "git",
- "dir": null,
- "ref": "afadf5f",
- "url": "https://github.com/openfl/lime"
- },
- {
- "name": "openfl",
- "type": "git",
- "dir": null,
- "ref": "b2c18513",
- "url": "https://github.com/EliteMasterEric/openfl"
- },
- {
- "name": "polymod",
- "type": "git",
- "dir": null,
- "ref": "4e5b4b3",
- "url": "https://github.com/larsiusprime/polymod"
- },
- {
- "name": "thx.semver",
- "type": "haxelib",
- "version": "0.2.2"
- }
- ]
-}
\ No newline at end of file
+ "dependencies": [
+ {
+ "name": "discord_rpc",
+ "type": "git",
+ "dir": null,
+ "ref": "2d83fa8",
+ "url": "https://github.com/Aidan63/linc_discord-rpc"
+ },
+ {
+ "name": "flixel",
+ "type": "git",
+ "dir": null,
+ "ref": "d6100cc8",
+ "url": "https://github.com/EliteMasterEric/flixel"
+ },
+ {
+ "name": "flixel-addons",
+ "type": "git",
+ "dir": null,
+ "ref": "f107166",
+ "url": "https://github.com/EliteMasterEric/flixel-addons"
+ },
+ {
+ "name": "flixel-ui",
+ "type": "haxelib",
+ "version": "2.4.0"
+ },
+ {
+ "name": "flxanimate",
+ "type": "git",
+ "dir": null,
+ "ref": "18b2060",
+ "url": "https://github.com/Dot-Stuff/flxanimate"
+ },
+ {
+ "name": "format",
+ "type": "haxelib",
+ "version": "3.5.0"
+ },
+ {
+ "name": "haxeui-core",
+ "type": "git",
+ "dir": null,
+ "ref": "e5cf78d",
+ "url": "https://github.com/haxeui/haxeui-core/"
+ },
+ {
+ "name": "haxeui-flixel",
+ "type": "git",
+ "dir": null,
+ "ref": "f03bb6d",
+ "url": "https://github.com/haxeui/haxeui-flixel"
+ },
+ {
+ "name": "hmm",
+ "type": "git",
+ "dir": null,
+ "ref": "3ef9522",
+ "url": "https://github.com/steviegt6/hmm"
+ },
+ {
+ "name": "hscript",
+ "type": "haxelib",
+ "version": "2.5.0"
+ },
+ {
+ "name": "hxcodec",
+ "type": "git",
+ "dir": null,
+ "ref": "master",
+ "url": "https://github.com/EliteMasterEric/hxCodec"
+ },
+ {
+ "name": "hxcpp",
+ "type": "haxelib",
+ "version": "4.2.1"
+ },
+ {
+ "name": "hxcpp-debug-server",
+ "type": "haxelib",
+ "version": "1.2.4"
+ },
+ {
+ "name": "hxp",
+ "type": "haxelib",
+ "version": null
+ },
+ {
+ "name": "lime",
+ "type": "git",
+ "dir": null,
+ "ref": "afadf5f",
+ "url": "https://github.com/openfl/lime"
+ },
+ {
+ "name": "openfl",
+ "type": "git",
+ "dir": null,
+ "ref": "b2c18513",
+ "url": "https://github.com/EliteMasterEric/openfl"
+ },
+ {
+ "name": "polymod",
+ "type": "git",
+ "dir": null,
+ "ref": "4e5b4b3",
+ "url": "https://github.com/larsiusprime/polymod"
+ },
+ {
+ "name": "thx.semver",
+ "type": "haxelib",
+ "version": "0.2.2"
+ }
+ ]
+}
diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx
index f7f662b7b..fea8899d2 100644
--- a/source/funkin/InitState.hx
+++ b/source/funkin/InitState.hx
@@ -211,7 +211,7 @@ class InitState extends FlxTransitionableState
#elseif FREEPLAY
FlxG.switchState(new FreeplayState());
#elseif ANIMATE
- FlxG.switchState(new funkin.animate.dotstuff.DotStuffTestStage());
+ FlxG.switchState(new funkin.ui.animDebugShit.FlxAnimateTest());
#elseif CHARTING
FlxG.switchState(new ChartingState());
#elseif STAGEBUILD
diff --git a/source/funkin/Paths.hx b/source/funkin/Paths.hx
index f961a049f..3a1c65285 100644
--- a/source/funkin/Paths.hx
+++ b/source/funkin/Paths.hx
@@ -51,6 +51,11 @@ class Paths
return getPath(file, type, library);
}
+ public static inline function animateAtlas(path:String, library:String)
+ {
+ return getLibraryPathForce('images/$path', library);
+ }
+
inline static public function txt(key:String, ?library:String)
{
return getPath('data/$key.txt', TEXT, library);
diff --git a/source/funkin/animate/AnimTestStage.hx b/source/funkin/animate/AnimTestStage.hx
deleted file mode 100644
index 0b2291ee4..000000000
--- a/source/funkin/animate/AnimTestStage.hx
+++ /dev/null
@@ -1,36 +0,0 @@
-package funkin.animate;
-
-import flixel.FlxSprite;
-import flixel.FlxState;
-import flixel.addons.display.FlxGridOverlay;
-
-class AnimTestStage extends FlxState
-{
- var tl:AnimateTimeline;
- var swag:FlxAnimate;
-
- override function create()
- {
- var bg:FlxSprite = FlxGridOverlay.create(32, 32);
- add(bg);
- bg.scrollFactor.set();
-
- swag = new FlxAnimate(200, 200);
- add(swag);
-
- tl = new AnimateTimeline(Paths.file('images/tightBarsLol/Animation.json'));
- add(tl);
-
- super.create();
- }
-
- override function update(elapsed:Float)
- {
- tl.curFrame = swag.daFrame;
-
- CoolUtil.mouseWheelZoom();
- CoolUtil.mouseCamDrag();
-
- super.update(elapsed);
- }
-}
diff --git a/source/funkin/animate/AnimateTimeline.hx b/source/funkin/animate/AnimateTimeline.hx
deleted file mode 100644
index f5831d199..000000000
--- a/source/funkin/animate/AnimateTimeline.hx
+++ /dev/null
@@ -1,74 +0,0 @@
-package funkin.animate;
-
-import flixel.FlxCamera;
-import flixel.FlxSprite;
-import flixel.group.FlxGroup.FlxTypedGroup;
-import flixel.group.FlxGroup;
-import flixel.text.FlxText;
-import flixel.util.FlxColor;
-import haxe.Json;
-import lime.utils.Assets;
-
-class AnimateTimeline extends FlxTypedGroup
-{
- // var coolParsed:Parsed;
- var playhead:FlxSprite;
-
- public var curFrame(default, set):Int;
-
- function set_curFrame(frm:Int):Int
- {
- if (playhead != null) playhead.x = 5 + (frm * 12) + (12 * 5);
- return frm;
- }
-
- var hudCamShit:FlxCamera;
-
- public function new(parsed:String)
- {
- super();
-
- /* hudCamShit = new FlxCamera();
- hudCamShit.bgColor = FlxColor.TRANSPARENT;
- FlxG.cameras.add(hudCamShit, false);
-
- playhead = new FlxSprite(0, -12).makeGraphic(2, 10, FlxColor.MAGENTA);
- add(playhead);
-
- hudCamShit.follow(playhead);
- hudCamShit.setScrollBounds(0, null, -14, null);
-
- curFrame = 0;
-
- coolParsed = cast Json.parse(Assets.getText(parsed));
-
- var layerNum:Int = 0;
- for (layer in coolParsed.AN.TL.L)
- {
- var frameNum:Int = 0;
-
- for (frame in layer.FR)
- {
- var coolFrame:TimelineFrame = new TimelineFrame((frame.I * 12) + 12 * 5, layerNum * 12, frame.DU, frame);
- add(coolFrame);
- frameNum++;
- }
-
- var layerName:FlxText = new FlxText(0, layerNum * 12, 0, layer.LN, 10);
- layerName.color = FlxColor.PURPLE;
- layerName.scrollFactor.x = 0;
-
- var layerBG:FlxSprite = new FlxSprite(0, layerNum * 12).makeGraphic(12 * 4, 12);
- layerBG.scrollFactor.x = 0;
-
- add(layerBG);
- add(layerName);
-
- layerNum++;
- }
-
-
- this.cameras = [hudCamShit];
- */
- }
-}
diff --git a/source/funkin/animate/FlxAnimate.hx b/source/funkin/animate/FlxAnimate.hx
deleted file mode 100644
index 1a6d3dbad..000000000
--- a/source/funkin/animate/FlxAnimate.hx
+++ /dev/null
@@ -1,285 +0,0 @@
-package funkin.animate;
-
-import funkin.animate.ParseAnimate.AnimJson;
-import funkin.animate.ParseAnimate.Sprite;
-import funkin.animate.ParseAnimate.Spritemap;
-import flixel.FlxCamera;
-import flixel.FlxSprite;
-import flixel.graphics.FlxGraphic;
-import flixel.graphics.frames.FlxAtlasFrames;
-import flixel.graphics.frames.FlxFrame.FlxFrameAngle;
-import flixel.group.FlxGroup;
-import flixel.math.FlxMatrix;
-import flixel.math.FlxPoint;
-import flixel.math.FlxRect;
-import flixel.system.FlxAssets.FlxGraphicAsset;
-import haxe.format.JsonParser;
-import openfl.Assets;
-import openfl.display.BitmapData;
-import openfl.geom.Matrix;
-import openfl.geom.Rectangle;
-
-class FlxAnimate extends FlxSymbol
-{
- // var myAnim:Animation;
- // var animBitmap:BitmapData;
- var jsonAnim:AnimJson;
-
- var sprGrp:FlxTypedGroup;
-
- public function new(x:Float, y:Float)
- {
- super(x, y);
-
- sprGrp = new FlxTypedGroup();
-
- var tests:Array = ['tightBarsLol', 'tightestBars'];
-
- var folder:String = tests[1];
-
- frames = FlxAnimate.fromAnimate(Paths.file('images/' + folder + "/spritemap1.png"), Paths.file('images/$folder/spritemap1.json'));
-
- jsonAnim = cast CoolUtil.coolJSON(Assets.getText(Paths.file('images/$folder/Animation.json')));
- ParseAnimate.generateSymbolmap(jsonAnim.SD.S);
- ParseAnimate.resetFrameList();
-
- ParseAnimate.parseTimeline(jsonAnim.AN.TL, 0, 0);
-
- generateSpriteShit();
-
- /* var folder:String = 'tightestBars';
- coolParse = cast Json.parse(Assets.getText(Paths.file('images/' + folder + '/Animation.json')));
-
- // reverses the layers, for proper rendering!
- coolParse.AN.TL.L.reverse();
- super(x, y, coolParse);
-
- frames = FlxAnimate.fromAnimate(Paths.file('images/' + folder + '/spritemap1.png'), Paths.file('images/' + folder + '/spritemap1.json'));
- */
-
- // frames
- }
-
- override function draw()
- {
- // having this commented out fixes some wacky scaling bullshit?
- // or fixes drawing it twice?
- // super.draw();
-
- // renderFrame(coolParse.AN.TL, coolParse, true);
-
- actualFrameRender();
- }
-
- /**
- * Puts all the needed sprites into a FlxTypedGroup, and properly recycles them?
- **/
- function generateSpriteShit()
- {
- sprGrp.kill(); // kills group, maybe dont need to do this one so broadly? ehh whatev
-
- for (frameSorted in ParseAnimate.frameList)
- {
- for (i in frameSorted)
- {
- // instead of making them every frame, regenerate when needed?
- var spr:FlxSymbol = sprGrp.recycle(FlxSymbol); // redo this to recycle from a list later
- spr.frames = frames;
- spr.frame = spr.frames.getByName(i.frameName); // this one is fine
- spr.updateHitbox();
-
- // move this? wont work here!
- if (FlxG.keys.justPressed.I)
- {
- trace(i.frameName);
- trace(i.depthString);
- // trace("random lol: " + i.randomLol);
- }
-
- // cuz its in group, gets a lil fuckie when animated, need to go thru and properly reset each thing for shit like matrix!
- // merely resets the matrix to normal ass one!
- spr.transformMatrix.identity();
- spr.setPosition();
-
- /* for (swagMatrix in i.matrixArray)
- {
- var alsoSwag:FlxMatrix = new FlxMatrix(swagMatrix[0], swagMatrix[1], swagMatrix[4], swagMatrix[5], swagMatrix[12], swagMatrix[13]);
- spr.matrixExposed = true;
- spr.transformMatrix.concat(alsoSwag);
- }*/
-
- // i.fullMatrix.concat
-
- spr.matrixExposed = true;
-
- // trace(i.fullMatrix);
-
- if (i.fullMatrix.a < 0)
- {
- trace('negative?');
- trace(i.fullMatrix);
- }
-
- spr.transformMatrix.concat(i.fullMatrix);
-
- if (i.fullMatrix.a < 0)
- {
- trace('negative?');
- trace(i.fullMatrix);
- trace(spr.transformMatrix);
- }
-
- // trace(spr.transformMatrix);
-
- spr.origin.set();
-
- /* for (trpShit in i.trpArray)
- {
- spr.origin.x -= trpShit[0];
- spr.origin.y -= trpShit[1];
- }
- */
- // spr.alpha = 0.3;
-
- spr.antialiasing = true;
- sprGrp.add(spr);
- spr.alpha = 0.5;
-
- /* if (i == "0225")
- {
- trace('FUNNY MATRIX!');
- trace(spr._matrix);
- trace("\n\n MATRIX MAP");
- for (m in ParseAnimate.matrixMap.get("0225"))
- {
- trace(m);
- }
-
- trace('\n\n');
- }*/
- }
- }
-
- // trace(sprGrp.length);
- }
-
- // fix render order of ALL layers!
- // seperate frameList into layers
- // go thru animate file to see how it should all be ordered
- // per frame symbol stuff to fix lip sync (in ParseAnimate?)
- // definitely need to dig through Animate.json stuff
- // something with TRP stuff, look through tighterBars (GF scene)
- // redo map stuff incase there's multiple assets
- // ONE CENTRAL THING FOR THIS DUMBASS BULLSHIT
- // sorted framelist put it all in there, then make i actually mean something
-
- function actualFrameRender()
- {
- sprGrp.draw();
- }
-
- // notes to self
- // account for different layers
- var playingAnim:Bool = false;
- var frameTickTypeShit:Float = 0;
- var animFrameRate:Int = 24;
-
- // redo all the matrix animation stuff
-
- override function update(elapsed:Float)
- {
- super.update(elapsed);
-
- if (FlxG.keys.justPressed.SPACE) playingAnim = !playingAnim;
-
- if (playingAnim)
- {
- frameTickTypeShit += elapsed;
-
- // prob fix this framerate thing for higher framerates?
- if (frameTickTypeShit >= 1 / 24)
- {
- changeFrame(1);
- frameTickTypeShit = 0;
- ParseAnimate.resetFrameList();
- ParseAnimate.parseTimeline(jsonAnim.AN.TL, 0, daFrame);
-
- generateSpriteShit();
- }
- }
-
- if (FlxG.keys.justPressed.RIGHT)
- {
- changeFrame(1);
-
- ParseAnimate.resetFrameList();
- ParseAnimate.parseTimeline(jsonAnim.AN.TL, 0, daFrame);
-
- generateSpriteShit();
- }
- if (FlxG.keys.justPressed.LEFT) changeFrame(-1);
- }
-
- /**
- * PARSES THE 'spritemap1.png' or whatever into a FlxAtlasFrames!!!
- */
- public static function fromAnimate(Source:FlxGraphicAsset, Description:String):FlxAtlasFrames
- {
- var graphic:FlxGraphic = FlxG.bitmap.add(Source);
- if (graphic == null) return null;
-
- var frames:FlxAtlasFrames = FlxAtlasFrames.findFrame(graphic);
- if (frames != null) return frames;
-
- if (graphic == null || Description == null) return null;
-
- frames = new FlxAtlasFrames(graphic);
-
- var data:Spritemap;
-
- var json:String = Description;
-
- // trace(json);
-
- var funnyJson:Dynamic = {};
- if (Assets.exists(json)) funnyJson = JaySon.parseFile(json);
-
- // trace(json);
-
- // data = c
-
- data = cast funnyJson;
-
- for (sprite in data.ATLAS.SPRITES)
- {
- // probably nicer way to do this? Oh well
- var swagSprite:Sprite = sprite.SPRITE;
-
- var rect = FlxRect.get(swagSprite.x, swagSprite.y, swagSprite.w, swagSprite.h);
-
- var size = new Rectangle(0, 0, rect.width, rect.height);
-
- var offset = FlxPoint.get(-size.left, -size.top);
- var sourceSize = FlxPoint.get(size.width, size.height);
-
- frames.addAtlasFrame(rect, sourceSize, offset, swagSprite.name);
- }
-
- return frames;
- }
-}
-
-// handy json function that has some hashlink fix, see the thing in CoolUtils file to see the link / where i stole it from
-class JaySon
-{
- public static function parseFile(name:String)
- {
- var cont = Assets.getText(name);
- function is(n:Int, what:Int)
- return cont.charCodeAt(n) == what;
- return JsonParser.parse(cont.substr(if (is(0, 65279)) /// looks like a HL target, skipping only first character here:
- 1 else if (is(0, 239) && is(1, 187) && is(2, 191)) /// it seems to be Neko or PHP, start from position 3:
- 3 else /// all other targets, that prepare the UTF string correctly
- 0));
- }
-}
diff --git a/source/funkin/animate/FlxSymbol.hx b/source/funkin/animate/FlxSymbol.hx
deleted file mode 100644
index cd01b4937..000000000
--- a/source/funkin/animate/FlxSymbol.hx
+++ /dev/null
@@ -1,81 +0,0 @@
-package funkin.animate;
-
-import funkin.animate.ParseAnimate.AnimJson;
-import funkin.animate.ParseAnimate.Animation;
-import funkin.animate.ParseAnimate.Frame;
-import funkin.animate.ParseAnimate.Sprite;
-import funkin.animate.ParseAnimate.Spritemap;
-import funkin.animate.ParseAnimate.SymbolDictionary;
-import funkin.animate.ParseAnimate.Timeline;
-import flixel.FlxCamera;
-import flixel.FlxSprite;
-import flixel.graphics.frames.FlxFrame.FlxFrameAngle;
-import flixel.math.FlxAngle;
-import flixel.math.FlxMath;
-import flixel.math.FlxMatrix;
-import flixel.math.FlxPoint;
-import lime.system.System;
-import openfl.Assets;
-import openfl.geom.Matrix;
-
-class FlxSymbol extends FlxSprite
-{
- // Loop types shit
- public static inline var LOOP:String = 'LP';
- public static inline var PLAY_ONCE:String = 'PO';
- public static inline var SINGLE_FRAME:String = 'SF';
-
- public var transformMatrix:Matrix = new Matrix();
- public var daLoopType:String = 'LP'; // LP by default, is set below!!!
-
- /**
- * Bool flag showing whether transformMatrix is used for rendering or not.
- * False by default, which means that transformMatrix isn't used for rendering
- */
- public var matrixExposed:Bool = true;
-
- public function new(x:Float, y:Float)
- {
- super(x, y);
- }
-
- public var daFrame:Int = 0;
-
- function changeFrame(frameChange:Int = 0):Void
- {
- daFrame += frameChange;
- }
-
- /**
- * custom "homemade" (nabbed from FlxSkewSprite) draw function, to make having a matrix transform slightly
- * less painful
- */
- override function drawComplex(camera:FlxCamera):Void
- {
- _frame.prepareMatrix(_matrix, FlxFrameAngle.ANGLE_0, checkFlipX(), checkFlipY());
- _matrix.translate(-origin.x, -origin.y);
- _matrix.scale(scale.x, scale.y);
-
- if (matrixExposed)
- {
- _matrix.concat(transformMatrix);
- }
-
- if (bakedRotationAngle <= 0)
- {
- updateTrig();
-
- if (angle != 0) _matrix.rotateWithTrig(_cosAngle, _sinAngle);
- }
-
- _point.addPoint(origin);
- _matrix.translate(_point.x, _point.y);
-
- if (isPixelPerfectRender(camera))
- {
- _matrix.tx = Math.floor(_matrix.tx);
- _matrix.ty = Math.floor(_matrix.ty);
- }
- camera.drawPixels(_frame, framePixels, _matrix, colorTransform, blend, antialiasing, shader);
- }
-}
diff --git a/source/funkin/animate/ParseAnimate.hx b/source/funkin/animate/ParseAnimate.hx
deleted file mode 100644
index 9375ed47b..000000000
--- a/source/funkin/animate/ParseAnimate.hx
+++ /dev/null
@@ -1,517 +0,0 @@
-package funkin.animate;
-
-import haxe.format.JsonParser;
-import openfl.Assets;
-import openfl.geom.Matrix3D;
-import openfl.geom.Matrix;
-#if sys
-import sys.io.File;
-#end
-
-/**
- * Generally designed / written in a way that can be easily taken out of FNF and used elsewhere
- * I don't think it even has ties to OpenFL? Could probably just use it for ANY haxe
- * project if needed, DOES NEED A LOT OF CLEANUP THOUGH!
- */
-class ParseAnimate
-{
- // make list of frames needed to render (with ASI)
- // make GIANT list of all the frames ever and have them in order?
- public static var symbolMap:Map = new Map();
- public static var actualSprites:Map = new Map();
-
- var _atlas:Map;
- var _symbolData:Map;
- var _defaultSymbolName:String;
-
- public function new(data:AnimJson, atlas:Spritemap)
- {
- // bitmap data could prob be instead
- // this code is mostly nabbed from https://github.com/miltoncandelero/OpenFLAnimateAtlas/blob/master/Source/animateatlas/displayobject/SpriteAnimationLibrary.hx
- parseAnimationData(data);
- parseAtlasData(atlas);
- }
-
- function parseAnimationData(data:AnimJson):Void
- {
- _symbolData = new Map();
-
- var symbols = data.SD.S;
- for (symbol in symbols)
- _symbolData[symbol.SN] = preprocessSymbolData(symbol);
-
- var defaultSymbol:Symbol = preprocessSymbolData(data.AN);
- _defaultSymbolName = defaultSymbol.SN;
- _symbolData.set(_defaultSymbolName, defaultSymbol);
- }
-
- // at little redundant, does exactly the same thing as genSpritemap()
- function parseAtlasData(atlas:Spritemap):Void
- {
- _atlas = new Map();
- if (atlas.ATLAS != null && atlas.ATLAS.SPRITES != null)
- {
- for (s in atlas.ATLAS.SPRITES)
- _atlas.set(s.SPRITE.name, s.SPRITE);
- }
- }
-
- /**
- * Not used, was used for testing stuff though!
- */
- public static function init()
- {
- // Main.gids
- var folder:String = 'tightestBars';
-
- // var spritemap:Spritemap =
- // var spritemap:Spritemap = genSpritemap('test/$folder/spritemap1.json');
-
- actualSprites = genSpritemap('test/$folder/spritemap1.json');
-
- var animation:AnimJson = cast CoolUtil.coolJSON(Assets.getText('src/$folder/Animation.json'));
-
- generateSymbolmap(animation.SD.S);
-
- trace("\n\nANIMATION SHIT\n");
-
- var timelineLength:Int = 0;
- for (lyr in animation.AN.TL.L)
- timelineLength = Std.int(Math.max(lyr.FR.length, timelineLength));
-
- var content:String = animation.AN.TL.L[0].LN;
- content += "TOTAL FRAMES NEEDED: " + timelineLength + "\n";
-
- for (frm in 0...timelineLength)
- {
- trace('FRAME NUMBER ' + frm);
- try
- {
- parseTimeline(animation.AN.TL, 1, frm);
- content += 'Good write on frame: ' + frm + "\n";
- }
- catch (e)
- {
- content += "BAD WRITE : " + frm + "\n";
- content += "\t" + e + "\n";
- trace(e);
- }
-
- // File.saveContent("output.txt", content);
- }
-
- parseTimeline(animation.AN.TL, 1, 0);
- trace(actualSprites);
- }
-
- /**
- * a MAP of SPRITES, not to be confused with Spritemap... lol
- */
- public static function genSpritemap(json:String):Map
- {
- var sprShitty:Spritemap = cast CoolUtil.coolJSON(json);
- var sprMap:Map = new Map();
-
- for (spr in sprShitty.ATLAS.SPRITES)
- sprMap.set(spr.SPRITE.name, spr.SPRITE);
- return sprMap;
- }
-
- // should change dis to all private?
- public static function generateSymbolmap(symbols:Array)
- {
- for (symbol in symbols)
- {
- // trace(symbol.SN + "has: " + symbol.TL.L.length + " LAYERS");
-
- symbolMap.set(symbol.SN, symbol);
- // parseTimeline(symbol.TL);
- }
- }
-
- public static function preprocessSymbolData(anim:Symbol):Symbol
- {
- var timelineData:Timeline = anim.TL;
- var layerData:Array = timelineData.L;
-
- if (!timelineData.sortedForRender)
- {
- timelineData.sortedForRender = true;
- layerData.reverse();
- }
-
- for (layerStuff in layerData)
- {
- var frames:Array = layerStuff.FR;
-
- for (frame in frames)
- {
- var elements:Array = frame.E;
- for (e in 0...elements.length)
- {
- var element:Element = elements[e];
- if (element.ASI != null)
- {
- element = elements[e] =
- {
- SI:
- {
- SN: "ATLAS_SYMBOL_SPRITE",
- LP: "LP",
- TRP: {x: 0, y: 0},
- M3D: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
- FF: 0,
- ST: "G",
- ASI: element.ASI
- }
- }
- }
- }
- }
- }
-
- return anim;
- }
-
- public static var curLoopType:String;
-
- /**
- * Stuff for debug parsing
- */
- public static var depthTypeBeat:String = "";
-
- /**
- * Array of bullshit that will eventually be RENDERED by whoever wanna use it!
- */
- public static var frameList:Array> = [];
-
- // for loop stuf
-
- /**
- * Similar to frameList, keeps track of shit according to framess?
- * That amount of arrays within arrays is fuckin dumb
- * but innermost array is basically just x and y value, cuz im dum
- */
- public static var matrixHelp:Array>> = [];
-
- public static var trpHelpIDK:Array>> = [];
-
- public static var loopedFrameShit:Int = 0;
-
- public static var funnyMatrix:Matrix = new Matrix();
- public static var matrixFlipper:Array = [];
-
- // clean up all the crazy ass arrays
-
- public static function resetFrameList()
- {
- // funnyMatrix.identity();
-
- frameList = [];
- frameList.push([]);
- matrixHelp = [];
- matrixHelp.push([]);
-
- trpHelpIDK = [];
- trpHelpIDK.push([]);
- }
-
- public static var isFlipped:Bool = false;
-
- public static function parseTimeline(TL:Timeline, tabbed:Int = 0, ?frameInput:Int)
- {
- var strTab:String = "";
- for (i in 0...tabbed)
- strTab += '\t';
-
- for (layer in TL.L)
- {
- var frameArray:Array = [];
- var frameMap:Map = new Map();
-
- for (frms in layer.FR)
- {
- for (i in 0...frms.DU)
- frameArray.push(frms.I);
-
- frameMap.set(frms.I, frms);
- }
-
- if (frameInput == null) frameInput = 0;
-
- var oldFrm:Int = frameInput;
- /*
- if (curLoopType == "SF")
- {
- trace(layer.LN);
-
- trace(frameArray);
- trace(frameInput);
- trace(curLoopType);
- }*/
-
- if (curLoopType == "LP") frameInput = frameArray[frameInput % frameArray.length];
- else if (curLoopType == "SF")
- {
- frameInput = frameArray[loopedFrameShit];
-
- // see what happens when something has more than 2 layer?
- // single frame stuff isn't fully implemented
- }
- else
- frameInput = frameArray[frameInput];
-
- // trace(frameMap.get(frameInput));
-
- var frame:Frame = frameMap.get(frameInput);
-
- // get somethin sorted per element list, which would essentially be per symbol things properly sorted
- // seperate data types if symbol or atlassymbolinstance? would probably be maybe slightly less memory intensive? i dunno
-
- // goes thru each layer, and then each element
- // after it gets thru each element it adds to the layer frame stuff.
- // make somethin that works recursively, maybe thats the symbol dictionary type shit?
-
- for (element in frame.E)
- {
- if (Reflect.hasField(element, "ASI"))
- {
- matrixHelp[matrixHelp.length - 1].push(element.ASI.M3D);
-
- var m3D = element.ASI.M3D;
- var lilMatrix:Matrix = new Matrix(m3D[0], m3D[1], m3D[4], m3D[5], m3D[12], m3D[13]);
- matrixFlipper.push(lilMatrix);
-
- // matrixFlipper.reverse();
-
- // funnyMatrix.identity();
-
- // for (m in matrixFlipper)
- // funnyMatrix.concat(m);
-
- if (isFlipped)
- {
- trace("MORE FLIPPED SHIT");
- trace("MORE FLIPPED SHIT");
- trace("MORE FLIPPED SHIT");
- trace(funnyMatrix);
- trace(matrixFlipper);
- }
-
- // trace(funnyMatrix);
-
- funnyMatrix.concat(lilMatrix);
- // trace(funnyMatrix);
-
- frameList[frameList.length - 1].push(
- {
- frameName: element.ASI.N,
- depthString: depthTypeBeat,
- matrixArray: matrixHelp[matrixHelp.length - 1],
- trpArray: trpHelpIDK[trpHelpIDK.length - 1],
- fullMatrix: funnyMatrix.clone()
- });
-
- // flips the matrix once?? I cant remember exactly why it needs to be flipped
- // matrixHelp[matrixHelp.length - 1].reverse();
-
- // trpHelpIDK = [];
-
- // push the matrix array after each symbol?
-
- funnyMatrix.identity();
- matrixFlipper = [];
-
- depthTypeBeat = "";
- curLoopType = "";
- loopedFrameShit = 0;
-
- isFlipped = false;
- }
- else
- {
- var m3D = element.SI.M3D;
- var lilMatrix:Matrix = new Matrix(m3D[0], m3D[1], m3D[4], m3D[5], m3D[12], m3D[13]);
-
- if (lilMatrix.a == -1)
- {
- isFlipped = true;
-
- trace('IS THE NEGATIVE ONE');
- }
-
- if (isFlipped) trace(lilMatrix);
-
- funnyMatrix.concat(lilMatrix);
- matrixFlipper.push(lilMatrix);
- // trace(funnyMatrix);
-
- matrixHelp[matrixHelp.length - 1].push(element.SI.M3D);
- trpHelpIDK[trpHelpIDK.length - 1].push([element.SI.TRP.x, element.SI.TRP.y]); // trpHelpIDK.push();
- depthTypeBeat += "->" + element.SI.SN;
- curLoopType = element.SI.LP;
-
- var inputFrame:Int = element.SI.FF;
-
- // JANKY FIX, MAY NOT ACCOUNT FOR ALL SCENARIOS OF SINGLE FRAME ANIMATIONS!!
- if (curLoopType == "SF")
- {
- // trace("LOOP SHIT: " + inputFrame);
- loopedFrameShit = inputFrame;
- }
-
- // condense the animation code, so it automatically already fills up animation shit per symbol
-
- parseTimeline(symbolMap.get(element.SI.SN).TL, tabbed + 1, inputFrame);
- }
-
- // idk if this should go per layer or per element / object?
-
- matrixHelp.push([]);
- trpHelpIDK.push([]);
- }
-
- if (tabbed == 0)
- {
- frameList[frameList.length - 1].reverse();
- frameList.push([]); // new layer essentially
- }
- }
-
- frameList.reverse();
- }
-}
-
-typedef VALIDFRAME =
-{
- frameName:String,
- depthString:String,
- matrixArray:Array>,
- trpArray:Array>,
- fullMatrix:Matrix
-}
-
-typedef AnimJson =
-{
- AN:Animation,
- SD:SymbolDictionary,
- MD:MetaData
-}
-
-typedef Animation =
-{
- N:String,
- SN:String,
- TL:Timeline
-}
-
-typedef SymbolDictionary =
-{
- S:Array
-}
-
-typedef Symbol =
-{
- /**Symbol name*/
- SN:String,
-
- TL:Timeline
-}
-
-typedef Timeline =
-{
- ?sortedForRender:Bool,
- L:Array
-}
-
-typedef Layer =
-{
- LN:String,
- FR:Array
-}
-
-typedef Frame =
-{
- E:Array,
- I:Int,
- DU:Int
- // maybe need to implement names if it has frame labels?
-}
-
-typedef Element =
-{
- SI:SymbolInstance,
- ?ASI:AlsoSymbolInstance
- // lmfao idk what ASI stands for lmfaoo, i dont think its "also"
-}
-
-typedef SymbolInstance =
-{
- SN:String,
- ASI:AlsoSymbolInstance,
-
- /**Symbol type, prob either G (graphic), or movie clip?*/ ST:String,
-
- /**First frame*/ FF:Int,
-
- /**Loop type, loop ping pong, etc.*/ LP:String,
-
- /**3D matrix*/ M3D:Array,
-
- TRP:
- {
- x:Float, y:Float
- }
-}
-
-typedef AlsoSymbolInstance =
-{
- N:String,
- M3D:Array
-}
-
-typedef MetaData =
-{
- /**
- * Framerate
- */
- FRT:Int
-}
-
-// SPRITEMAP BULLSHIT
-typedef Spritemap =
-{
- ATLAS:
- {
- SPRITES:Array
- },
- meta:Meta
-}
-
-typedef SpriteBullshit =
-{
- SPRITE:Sprite
-}
-
-typedef Sprite =
-{
- name:String,
- x:Int,
- y:Int,
- w:Int,
- h:Int,
- rotated:Bool
-}
-
-typedef Meta =
-{
- app:String,
- verstion:String,
- image:String,
- format:String,
- size:
- {
- w:Int, h:Float
- },
- resolution:Float
-}
diff --git a/source/funkin/animate/TimelineFrame.hx b/source/funkin/animate/TimelineFrame.hx
deleted file mode 100644
index 530859443..000000000
--- a/source/funkin/animate/TimelineFrame.hx
+++ /dev/null
@@ -1,63 +0,0 @@
-package funkin.animate;
-
-import flixel.FlxSprite;
-import flixel.input.mouse.FlxMouseEvent;
-import flixel.util.FlxColor;
-import funkin.animate.ParseAnimate.Frame;
-
-class TimelineFrame extends FlxSprite
-{
- public var data:Frame;
-
- public function new(x:Float, y:Float, length:Int = 0, data:Frame)
- {
- super(x, y);
-
- this.data = data;
-
- makeGraphic((10 * length) + (2 * (length - 1)), 10, FlxColor.RED);
-
- FlxMouseEvent.add(this, null, null, function(spr:TimelineFrame)
- {
- alpha = 0.5;
- }, function(spr:TimelineFrame)
- {
- alpha = 1;
- }, false, true, true);
- }
-
- override function update(elapsed:Float)
- {
- // if (FlxG.mouse.overlaps(this, cameras[1]))
- // alpha = 0.6;
- // else
- // alpha = 1;
-
- if (FlxG.mouse.overlaps(this, cameras[0]) && FlxG.mouse.justPressed)
- {
- trace("\nFRAME DATA - \n\tFRAME NUM: " + data.I + "\n\tFRAME DURATION: " + data.DU);
-
- for (e in data.E)
- {
- var elementOutput:String = "\n";
-
- if (Reflect.hasField(e, 'ASI'))
- {
- elementOutput += "ELEMENT IS ASI!";
-
- elementOutput += "\n\t";
- elementOutput += "FRAME NAME: " + e.ASI.N;
- }
- else
- {
- elementOutput += "ELEMENT IS SYMBOL INSTANCE!";
- elementOutput += "\n\tSYMBOL NAME: " + e.SI.SN;
- }
-
- trace(elementOutput);
- }
- }
-
- super.update(elapsed);
- }
-}
diff --git a/source/funkin/api/newgrounds/NGUtil.hx b/source/funkin/api/newgrounds/NGUtil.hx
index 9d6f02680..88db899d1 100644
--- a/source/funkin/api/newgrounds/NGUtil.hx
+++ b/source/funkin/api/newgrounds/NGUtil.hx
@@ -49,8 +49,7 @@ class NGUtil
trace('checking NG.io version');
GAME_VER = "v" + Application.current.meta.get('version');
- NG.core.calls.app.getCurrentVersion(GAME_VER).addDataHandler(function(response)
- {
+ NG.core.calls.app.getCurrentVersion(GAME_VER).addDataHandler(function(response) {
GAME_VER = response.result.data.currentVersion;
trace('CURRENT NG VERSION: ' + GAME_VER);
callback(GAME_VER);
@@ -141,8 +140,7 @@ class NGUtil
var onCancel:Void->Void = null;
if (onComplete != null)
{
- onSuccess = function()
- {
+ onSuccess = function() {
onNGLogin();
onComplete(Success);
}
diff --git a/source/funkin/audio/FlxAudioGroup.hx b/source/funkin/audio/FlxAudioGroup.hx
index 66044b3cc..b506eaed9 100644
--- a/source/funkin/audio/FlxAudioGroup.hx
+++ b/source/funkin/audio/FlxAudioGroup.hx
@@ -29,8 +29,7 @@ class FlxAudioGroup extends FlxTypedGroup
function set_time(time:Float):Float
{
- forEachAlive(function(sound:FlxSound)
- {
+ forEachAlive(function(sound:FlxSound) {
// account for different offsets per sound?
sound.time = time;
});
@@ -52,8 +51,7 @@ class FlxAudioGroup extends FlxTypedGroup
function set_volume(volume:Float):Float
{
- forEachAlive(function(sound:FlxSound)
- {
+ forEachAlive(function(sound:FlxSound) {
sound.volume = volume;
});
@@ -80,8 +78,7 @@ class FlxAudioGroup extends FlxTypedGroup
{
#if FLX_PITCH
trace('Setting audio pitch to ' + val);
- forEachAlive(function(sound:FlxSound)
- {
+ forEachAlive(function(sound:FlxSound) {
sound.pitch = val;
});
#end
@@ -96,8 +93,7 @@ class FlxAudioGroup extends FlxTypedGroup
function set_autoDestroyMembers(value:Bool):Bool
{
autoDestroyMembers = value;
- forEachAlive(function(sound:FlxSound)
- {
+ forEachAlive(function(sound:FlxSound) {
sound.autoDestroy = value;
});
return value;
@@ -131,8 +127,7 @@ class FlxAudioGroup extends FlxTypedGroup
*/
public function pause()
{
- forEachAlive(function(sound:FlxSound)
- {
+ forEachAlive(function(sound:FlxSound) {
sound.pause();
});
}
@@ -142,8 +137,7 @@ class FlxAudioGroup extends FlxTypedGroup
*/
public function play(forceRestart:Bool = false, startTime:Float = 0.0, ?endTime:Float)
{
- forEachAlive(function(sound:FlxSound)
- {
+ forEachAlive(function(sound:FlxSound) {
sound.play(forceRestart, startTime, endTime);
});
}
@@ -153,8 +147,7 @@ class FlxAudioGroup extends FlxTypedGroup
*/
public function resume()
{
- forEachAlive(function(sound:FlxSound)
- {
+ forEachAlive(function(sound:FlxSound) {
sound.resume();
});
}
@@ -164,8 +157,7 @@ class FlxAudioGroup extends FlxTypedGroup
*/
public function stop()
{
- forEachAlive(function(sound:FlxSound)
- {
+ forEachAlive(function(sound:FlxSound) {
sound.stop();
});
}
@@ -188,8 +180,7 @@ class FlxAudioGroup extends FlxTypedGroup
{
var deviation:Float = 0;
- forEachAlive(function(sound:FlxSound)
- {
+ forEachAlive(function(sound:FlxSound) {
if (targetTime == null) targetTime = sound.time;
else
{
diff --git a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx
new file mode 100644
index 000000000..1f1dc239e
--- /dev/null
+++ b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx
@@ -0,0 +1,174 @@
+package funkin.graphics.adobeanimate;
+
+import flixel.util.FlxSignal.FlxTypedSignal;
+import flxanimate.FlxAnimate;
+import flxanimate.FlxAnimate.Settings;
+import flixel.math.FlxPoint;
+
+/**
+ * A sprite which provides convenience functions for rendering a texture atlas with animations.
+ */
+class FlxAtlasSprite extends FlxAnimate
+{
+ static final SETTINGS:Settings =
+ {
+ // ?ButtonSettings:Map,
+ FrameRate: 24.0,
+ Reversed: false,
+ // ?OnComplete:Void -> Void,
+ ShowPivot: true,
+ Antialiasing: true,
+ ScrollFactor: new FlxPoint(1, 1),
+ // Offset: new FlxPoint(0, 0), // This is just FlxSprite.offset
+ };
+
+ /**
+ * Signal dispatched when an animation finishes playing.
+ */
+ public var onAnimationFinish:FlxTypedSignalVoid> = new FlxTypedSignalVoid>();
+
+ var currentAnimation:String;
+
+ var canPlayOtherAnims:Bool = true;
+
+ public function new(x:Float, y:Float, path:String)
+ {
+ super(x, y, path);
+
+ if (this.anim.curInstance == null)
+ {
+ throw 'FlxAtlasSprite not initialized properly. Are you sure the path (${path}) exists?';
+ }
+
+ this.antialiasing = true;
+
+ onAnimationFinish.add(cleanupAnimation);
+
+ // This defaults the sprite to play the first animation in the atlas,
+ // then pauses it. This ensures symbols are intialized properly.
+ this.anim.play('');
+ this.anim.pause();
+ }
+
+ /**
+ * @return A list of all the animations this sprite has available.
+ */
+ public function listAnimations():Array
+ {
+ return this.anim.getFrameLabels();
+ }
+
+ /**
+ * @param id A string ID of the animation.
+ * @return Whether the animation was found on this sprite.
+ */
+ public function hasAnimation(id:String):Bool
+ {
+ return getLabelIndex(id) != -1;
+ }
+
+ /**
+ * @return The current animation being played.
+ */
+ public function getCurrentAnimation():String
+ {
+ return this.currentAnimation;
+ }
+
+ /**
+ * Plays an animation.
+ * @param id A string ID of the animation to play.
+ * @param restart Whether to restart the animation if it is already playing.
+ * @param ignoreOther Whether to ignore all other animation inputs, until this one is done playing
+ */
+ public function playAnimation(id:String, ?restart:Bool = false, ?ignoreOther:Bool = false):Void
+ {
+ // Skip if not allowed to play animations.
+ if ((!canPlayOtherAnims && !ignoreOther)) return;
+
+ if (id == null || id == '') id = this.currentAnimation;
+
+ if (this.currentAnimation == id && !restart)
+ {
+ if (anim.isPlaying)
+ {
+ // Skip if animation is already playing.
+ return;
+ }
+ else
+ {
+ // Resume animation if it's paused.
+ anim.play('', false, false);
+ }
+ }
+
+ // Skip if the animation doesn't exist
+ if (!hasAnimation(id))
+ {
+ trace('Animation ' + id + ' not found');
+ return;
+ }
+
+ // Stop the current animation if it is playing.
+ // This includes removing existing frame callbacks.
+ if (this.currentAnimation != null) this.stopAnimation();
+
+ // Add a callback to ensure `onAnimationFinish` is dispatched.
+ addFrameCallback(getNextFrameLabel(id), function() {
+ trace('Animation finished: ' + id);
+ onAnimationFinish.dispatch(id);
+ });
+
+ // Prevent other animations from playing if `ignoreOther` is true.
+ if (ignoreOther) canPlayOtherAnims = false;
+
+ // Move to the first frame of the animation.
+ goToFrameLabel(id);
+ this.currentAnimation = id;
+ }
+
+ /**
+ * Stops the current animation.
+ */
+ public function stopAnimation():Void
+ {
+ if (this.currentAnimation == null) return;
+
+ this.anim.removeAllCallbacksFrom(getNextFrameLabel(this.currentAnimation));
+
+ goToFrameIndex(0);
+ }
+
+ function addFrameCallback(label:String, callback:Void->Void):Void
+ {
+ var frameLabel = this.anim.getFrameLabel(label);
+ frameLabel.add(callback);
+ }
+
+ inline function goToFrameLabel(label:String):Void
+ {
+ this.anim.goToFrameLabel(label);
+ }
+
+ inline function getNextFrameLabel(label:String):String
+ {
+ return listAnimations()[(getLabelIndex(label) + 1) % listAnimations().length];
+ }
+
+ inline function getLabelIndex(label:String):Int
+ {
+ return listAnimations().indexOf(label);
+ }
+
+ inline function goToFrameIndex(index:Int):Void
+ {
+ this.anim.curFrame = index;
+ }
+
+ public function cleanupAnimation(_:String):Void
+ {
+ canPlayOtherAnims = true;
+ this.currentAnimation = null;
+ this.anim.stop();
+ }
+}
diff --git a/source/funkin/FlxVideo.hx b/source/funkin/graphics/video/FlxVideo.hx
similarity index 93%
rename from source/funkin/FlxVideo.hx
rename to source/funkin/graphics/video/FlxVideo.hx
index 9349164b2..db95c3d95 100644
--- a/source/funkin/FlxVideo.hx
+++ b/source/funkin/graphics/video/FlxVideo.hx
@@ -1,4 +1,4 @@
-package funkin;
+package funkin.graphics.video;
import flixel.FlxBasic;
import flixel.FlxSprite;
@@ -7,6 +7,9 @@ import openfl.media.Video;
import openfl.net.NetConnection;
import openfl.net.NetStream;
+/**
+ * Plays a video via a NetStream. Only works on HTML5.
+ */
class FlxVideo extends FlxBasic
{
var video:Video;
diff --git a/source/funkin/play/Fighter.hx b/source/funkin/play/Fighter.hx
index 42e044874..691d21b83 100644
--- a/source/funkin/play/Fighter.hx
+++ b/source/funkin/play/Fighter.hx
@@ -7,12 +7,11 @@ class Fighter extends BaseCharacter
{
public function new(?x:Float = 0, ?y:Float = 0, ?char:String = "pico-fighter")
{
- super(char);
+ super(char, Custom);
this.x = x;
this.y = y;
- animation.finishCallback = function(anim:String)
- {
+ animation.finishCallback = function(anim:String) {
switch anim
{
case "punch low" | "punch high" | "block" | 'dodge':
diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx
index b2e0bba12..61d2dc509 100644
--- a/source/funkin/play/PlayState.hx
+++ b/source/funkin/play/PlayState.hx
@@ -6,7 +6,8 @@ import flixel.FlxSprite;
import flixel.FlxState;
import flixel.FlxSubState;
import flixel.addons.transition.FlxTransitionableState;
-import flixel.group.FlxGroup;
+import flixel.group.FlxGroup.FlxTypedGroup;
+import flixel.input.keyboard.FlxKey;
import flixel.math.FlxMath;
import flixel.math.FlxRect;
import flixel.text.FlxText;
@@ -23,12 +24,12 @@ import funkin.SongLoad.SwagSong;
import funkin.charting.ChartingState;
import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventDispatcher;
-import funkin.play.GameOverSubstate;
-import funkin.play.HealthIcon;
import funkin.play.Strumline.StrumlineArrow;
import funkin.play.Strumline.StrumlineStyle;
import funkin.play.character.BaseCharacter;
+import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.play.character.CharacterData;
+import funkin.play.cutscene.VanillaCutscenes;
import funkin.play.event.SongEvent.SongEventParser;
import funkin.play.scoring.Scoring;
import funkin.play.song.Song;
@@ -37,7 +38,7 @@ import funkin.play.song.SongData.SongNoteData;
import funkin.play.song.SongData.SongPlayableChar;
import funkin.play.song.SongValidator;
import funkin.play.stage.Stage;
-import funkin.play.stage.StageData;
+import funkin.play.stage.StageData.StageDataParser;
import funkin.ui.PopUpStuff;
import funkin.ui.PreferencesMenu;
import funkin.ui.stageBuildShit.StageOffsetSubstate;
@@ -48,6 +49,9 @@ import lime.ui.Haptic;
import Discord.DiscordClient;
#end
+/**
+ * The gameplay state, where all the rhythm gaming happens.
+ */
class PlayState extends MusicBeatState
{
/**
@@ -81,7 +85,7 @@ class PlayState extends MusicBeatState
public static var isPracticeMode:Bool = false;
/**
- * Whether the game is currently in a cutscene, and gameplay should be stopped.
+ * Whether the game is currently in an animated cutscene, and gameplay should be stopped.
*/
public static var isInCutscene:Bool = false;
@@ -90,6 +94,11 @@ class PlayState extends MusicBeatState
*/
public static var disableKeys:Bool = false;
+ /*
+ * Whether the game is currently in dialog, and gameplay should be stopped.
+ */
+ public static var isInDialog:Bool = false;
+
/**
* Whether the game is currently in the countdown before the song resumes.
*/
@@ -188,6 +197,12 @@ class PlayState extends MusicBeatState
*/
var criticalFailure:Bool = false;
+ /**
+ * How many beats between camera zooms.
+ * @default One camera zoom per four beats.
+ */
+ var camZoomRate:Int = 4;
+
/**
* RENDER OBJECTS
*/
@@ -243,6 +258,11 @@ class PlayState extends MusicBeatState
*/
public var camGame:FlxCamera;
+ /**
+ * The camera which contains, and controls visibility of, a video cutscene.
+ */
+ public var camCutscene:FlxCamera;
+
/**
* PROPERTIES
*/
@@ -273,9 +293,7 @@ class PlayState extends MusicBeatState
var vocals:VoicesGroup;
var vocalsFinished:Bool = false;
- var camZooming:Bool = false;
var gfSpeed:Int = 1;
- // private var combo:Int = 0;
var generatedMusic:Bool = false;
var startingSong:Bool = false;
@@ -290,14 +308,14 @@ class PlayState extends MusicBeatState
#if discord_rpc
// Discord RPC variables
- var storyDifficultyText:String = "";
- var iconRPC:String = "";
+ var storyDifficultyText:String = '';
+ var iconRPC:String = '';
var songLength:Float = 0;
- var detailsText:String = "";
- var detailsPausedText:String = "";
+ var detailsText:String = '';
+ var detailsPausedText:String = '';
#end
- override public function create()
+ override public function create():Void
{
super.create();
@@ -458,18 +476,18 @@ class PlayState extends MusicBeatState
leftWatermarkText.cameras = [camHUD];
rightWatermarkText.cameras = [camHUD];
- // if (SONG.song == 'South')
- // FlxG.camera.alpha = 0.7;
- // UI_camera.zoom = 1;
-
- // cameras = [FlxG.cameras.list[1]];
+ // Starting song!
startingSong = true;
+ // TODO: Softcode cutscenes.
+ // TODO: Alternatively: make a song script that allows startCountdown to be called,
+ // then cancels the countdown, hides the UI, plays the cutscene,
+ // then calls PlayState.startCountdown later?
if (isStoryMode && !seenCutscene)
{
seenCutscene = true;
- switch (currentSong.song.toLowerCase())
+ switch (currentSong_NEW.songId.toLowerCase())
{
case "winter-horrorland":
VanillaCutscenes.playHorrorStartCutscene();
@@ -483,9 +501,6 @@ class PlayState extends MusicBeatState
VanillaCutscenes.playGunsCutscene();
default:
// VanillaCutscenes will call startCountdown later.
- // TODO: Alternatively: make a song script that allows startCountdown to be called,
- // then cancels the countdown, hides the strumline, plays the cutscene,
- // then calls Countdown.performCountdown()
startCountdown();
}
}
@@ -497,6 +512,10 @@ class PlayState extends MusicBeatState
#if debug
this.rightWatermarkText.text = Constants.VERSION;
#end
+
+ #if debug
+ FlxG.console.registerObject('playState', this);
+ #end
}
function get_currentChart():SongDifficulty
@@ -508,7 +527,7 @@ class PlayState extends MusicBeatState
/**
* Initializes the game and HUD cameras.
*/
- function initCameras()
+ function initCameras():Void
{
// Configure the default camera zoom level.
defaultCameraZoom = FlxCamera.defaultZoom * 1.05;
@@ -516,12 +535,15 @@ class PlayState extends MusicBeatState
camGame = new SwagCamera();
camHUD = new FlxCamera();
camHUD.bgColor.alpha = 0;
+ camCutscene = new FlxCamera();
+ camCutscene.bgColor.alpha = 0;
FlxG.cameras.reset(camGame);
FlxG.cameras.add(camHUD, false);
+ FlxG.cameras.add(camCutscene, false);
}
- function initStage()
+ function initStage():Void
{
if (currentSong_NEW != null)
{
@@ -533,23 +555,21 @@ class PlayState extends MusicBeatState
switch (currentSong.song.toLowerCase())
{
case 'spookeez' | 'monster' | 'south':
- currentStageId = "spookyMansion";
+ currentStageId = 'spookyMansion';
case 'pico' | 'blammed' | 'philly':
currentStageId = 'phillyTrain';
- case "milf" | 'satin-panties' | 'high':
+ case 'milf' | 'satin-panties' | 'high':
currentStageId = 'limoRide';
- case "cocoa" | 'eggnog':
+ case 'cocoa' | 'eggnog':
currentStageId = 'mallXmas';
case 'winter-horrorland':
currentStageId = 'mallEvil';
case 'senpai' | 'roses':
currentStageId = 'school';
- case "darnell" | "lit-up" | "2hot":
+ case 'darnell' | 'lit-up' | '2hot':
currentStageId = 'phillyStreets';
- // currentStageId = 'pyro';
- case "blazin":
+ case 'blazin':
currentStageId = 'phillyBlazin';
- // currentStageId = 'pyro';
case 'pyro':
currentStageId = 'pyro';
case 'thorns':
@@ -557,13 +577,13 @@ class PlayState extends MusicBeatState
case 'guns' | 'stress' | 'ugh':
currentStageId = 'tankmanBattlefield';
default:
- currentStageId = "mainStage";
+ currentStageId = 'mainStage';
}
// Loads the relevant stage based on its ID.
loadStage(currentStageId);
}
- function initStage_NEW()
+ function initStage_NEW():Void
{
if (currentChart == null)
{
@@ -800,11 +820,19 @@ class PlayState extends MusicBeatState
if (girlfriend != null)
{
currentStage.addCharacter(girlfriend, GF);
+
+ #if debug
+ FlxG.console.registerObject('gf', girlfriend);
+ #end
}
if (boyfriend != null)
{
currentStage.addCharacter(boyfriend, BF);
+
+ #if debug
+ FlxG.console.registerObject('bf', boyfriend);
+ #end
}
if (dad != null)
@@ -812,6 +840,10 @@ class PlayState extends MusicBeatState
currentStage.addCharacter(dad, DAD);
// Camera starts at dad.
cameraFollowPoint.setPosition(dad.cameraFocusPoint.x, dad.cameraFocusPoint.y);
+
+ #if debug
+ FlxG.console.registerObject('dad', dad);
+ #end
}
// Rearrange by z-indexes.
@@ -872,6 +904,10 @@ class PlayState extends MusicBeatState
// Add the stage to the scene.
this.add(currentStage);
+
+ #if debug
+ FlxG.console.registerObject('stage', currentStage);
+ #end
}
}
@@ -941,7 +977,7 @@ class PlayState extends MusicBeatState
{
if (dialogueBox != null)
{
- isInCutscene = true;
+ isInDialog = true;
if (currentSong.song.toLowerCase() == 'thorns')
{
@@ -1016,7 +1052,7 @@ class PlayState extends MusicBeatState
function generateSong():Void
{
- // FlxG.log.add(ChartParser.parse());
+ trace('===WARNING=== Song uses old chart format!!!!!');
Conductor.forceBPM(currentSong.bpm);
@@ -1030,8 +1066,6 @@ class PlayState extends MusicBeatState
vocalsFinished = true;
};
- trace(vocals);
-
activeNotes = new FlxTypedGroup();
activeNotes.zIndex = 1000;
add(activeNotes);
@@ -1383,10 +1417,15 @@ class PlayState extends MusicBeatState
inputSpitter = [];
+ // Reset music properly.
+
FlxG.sound.music.pause();
vocals.pause();
-
FlxG.sound.music.time = 0;
+ vocals.time = 0;
+
+ FlxG.sound.music.volume = 1;
+ vocals.volume = 1;
currentStage.resetStage();
@@ -1523,7 +1562,7 @@ class PlayState extends MusicBeatState
if (health > 2.0) health = 2.0;
if (health < 0.0) health = 0.0;
- if (camZooming && subState == null)
+ if (subState == null)
{
FlxG.camera.zoom = FlxMath.lerp(defaultCameraZoom, FlxG.camera.zoom, 0.95);
camHUD.zoom = FlxMath.lerp(1 * FlxCamera.defaultZoom, camHUD.zoom, 0.95);
@@ -1542,7 +1581,6 @@ class PlayState extends MusicBeatState
switch (Conductor.currentBeat)
{
case 16:
- camZooming = true;
gfSpeed = 2;
case 48:
gfSpeed = 1;
@@ -1553,7 +1591,7 @@ class PlayState extends MusicBeatState
}
}
- if ((!isInCutscene && !disableKeys) && !_exiting)
+ if (!isInCutscene && !isInDialog && !disableKeys && !_exiting)
{
// RESET = Quick Game Over Screen
if (controls.RESET)
@@ -1666,8 +1704,6 @@ class PlayState extends MusicBeatState
if (!daNote.mustPress && daNote.wasGoodHit && !daNote.tooLate)
{
- if (currentSong != null && currentSong.song != 'Tutorial') camZooming = true;
-
var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, daNote, Highscore.tallies.combo, true);
dispatchEvent(event);
@@ -1739,14 +1775,25 @@ class PlayState extends MusicBeatState
}
}
- if (!isInCutscene && !disableKeys) keyShit(true);
+ if (!isInCutscene && !isInDialog && !disableKeys) keyShit(true);
+ if (isInCutscene && !disableKeys) handleCutsceneKeys();
+ }
+
+ static final CUTSCENE_KEYS:Array = [SPACE, ESCAPE, ENTER];
+
+ function handleCutsceneKeys():Void
+ {
+ if (FlxG.keys.anyJustPressed(CUTSCENE_KEYS))
+ {
+ VanillaCutscenes.finishCutscene();
+ }
}
function applyClipRect(daNote:Note):Void
{
// clipRect is applied to graphic itself so use frame Heights
var swagRect:FlxRect = new FlxRect(0, 0, daNote.frameWidth, daNote.frameHeight);
- var strumLineMid = playerStrumline.y + Note.swagWidth / 2;
+ var strumLineMid:Float = playerStrumline.y + Note.swagWidth / 2;
if (PreferencesMenu.getPref('downscroll'))
{
@@ -1903,7 +1950,7 @@ class PlayState extends MusicBeatState
trace('WENT TO RESULTS SCREEN!');
// unloadAssets();
- camZooming = false;
+ camZoomRate = 0;
FlxG.camera.follow(PlayState.instance.currentStage.getGirlfriend(), null, 0.05);
FlxG.camera.targetOffset.y -= 350;
@@ -2310,25 +2357,22 @@ class PlayState extends MusicBeatState
}
}
- // Manage the camera focus, if necessary.
- // controlCamera();
-
- // HARDCODING FOR MILF ZOOMS!
-
if (PreferencesMenu.getPref('camera-zoom'))
{
+ // TODO: Move this into a song script.
if (currentSong != null
&& currentSong.song.toLowerCase() == 'milf'
&& Conductor.currentBeat >= 168
- && Conductor.currentBeat < 200
- && camZooming
- && FlxG.camera.zoom < 1.35)
+ && Conductor.currentBeat < 200)
{
- FlxG.camera.zoom += 0.015 * FlxCamera.defaultZoom;
- camHUD.zoom += 0.03;
+ camZoomRate = 1;
+ }
+ if (currentSong != null && currentSong.song.toLowerCase() == 'milf' && Conductor.currentBeat >= 200)
+ {
+ camZoomRate = 4;
}
- if (camZooming && FlxG.camera.zoom < (1.35 * FlxCamera.defaultZoom) && Conductor.currentBeat % 4 == 0)
+ if (FlxG.camera.zoom < (1.35 * FlxCamera.defaultZoom) && camZoomRate > 0 && Conductor.currentBeat % camZoomRate == 0)
{
FlxG.camera.zoom += 0.015 * FlxCamera.defaultZoom;
camHUD.zoom += 0.03;
@@ -2446,7 +2490,7 @@ class PlayState extends MusicBeatState
* Function called before opening a new substate.
* @param subState The substate to open.
*/
- override function openSubState(subState:FlxSubState)
+ public override function openSubState(subState:FlxSubState)
{
// If there is a substate which requires the game to continue,
// then make this a condition.
@@ -2472,7 +2516,7 @@ class PlayState extends MusicBeatState
* Function called before closing the current substate.
* @param subState
*/
- override function closeSubState()
+ public override function closeSubState()
{
if (isGamePaused)
{
@@ -2482,7 +2526,7 @@ class PlayState extends MusicBeatState
if (event.eventCanceled) return;
- if (FlxG.sound.music != null && !startingSong && !isInCutscene) resyncVocals();
+ if (FlxG.sound.music != null && !startingSong && !isInCutscene && !isInDialog) resyncVocals();
// Resume the countdown.
Countdown.resumeCountdown();
@@ -2502,12 +2546,14 @@ class PlayState extends MusicBeatState
* Prepares to start the countdown.
* Ends any running cutscenes, creates the strumlines, and starts the countdown.
*/
- function startCountdown():Void
+ public function startCountdown():Void
{
- var result = Countdown.performCountdown(currentStageId.startsWith('school'));
+ // If Countdown.performCountdown returns false, then the countdown was canceled by a script.
+ var result:Bool = Countdown.performCountdown(currentStageId.startsWith('school'));
if (!result) return;
isInCutscene = false;
+ isInDialog = false;
camHUD.visible = true;
talking = false;
@@ -2529,6 +2575,8 @@ class PlayState extends MusicBeatState
if (currentStage != null) currentStage.dispatchToCharacters(event);
// TODO: Dispatch event to song script
+
+ // TODO: Dispatch event to note script
}
/**
diff --git a/source/funkin/play/VanillaCutscenes.hx b/source/funkin/play/VanillaCutscenes.hx
deleted file mode 100644
index 020612470..000000000
--- a/source/funkin/play/VanillaCutscenes.hx
+++ /dev/null
@@ -1,105 +0,0 @@
-package funkin.play;
-
-import flixel.FlxSprite;
-import flixel.tweens.FlxEase;
-import flixel.tweens.FlxEase;
-import flixel.tweens.FlxTween;
-import flixel.tweens.FlxTween;
-import flixel.util.FlxColor;
-import flixel.util.FlxTimer;
-
-/**
- * Static methods for playing cutscenes in the PlayState.
- * TODO: Un-hardcode this shit!!!!!1!
- */
-class VanillaCutscenes
-{
- public static function playUghCutscene():Void
- {
- playVideoCutscene('music/ughCutscene.mp4');
- }
-
- public static function playGunsCutscene():Void
- {
- playVideoCutscene('music/gunsCutscene.mp4');
- }
-
- public static function playStressCutscene():Void
- {
- playVideoCutscene('music/stressCutscene.mp4');
- }
-
- static var blackScreen:FlxSprite;
-
- /**
- * Plays a cutscene from a video file, then starts the countdown once the video is done.
- * TODO: Cutscene is currently skipped on native platforms.
- */
- static function playVideoCutscene(path:String):Void
- {
- PlayState.isInCutscene = true;
- PlayState.instance.camHUD.visible = false;
-
- blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
- blackScreen.scrollFactor.set(0, 0);
- PlayState.instance.add(blackScreen);
-
- #if html5
- var vid:FlxVideo = new FlxVideo(path);
- vid.finishCallback = finishVideoCutscene;
- #else
- finishVideoCutscene();
- #end
- }
-
- /**
- * Does the cleanup to start the countdown after the video is done.
- * Gets called immediately if the video can't be played.
- */
- static function finishVideoCutscene():Void
- {
- PlayState.instance.remove(blackScreen);
- blackScreen = null;
-
- FlxTween.tween(FlxG.camera, {zoom: PlayState.defaultCameraZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut});
- @:privateAccess
- PlayState.instance.startCountdown();
- // @:privateAccess
- // PlayState.instance.controlCamera();
- }
-
- public static function playHorrorStartCutscene()
- {
- PlayState.isInCutscene = true;
- PlayState.instance.camHUD.visible = false;
-
- blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
- blackScreen.scrollFactor.set(0, 0);
- PlayState.instance.add(blackScreen);
-
- new FlxTimer().start(0.1, function(tmr:FlxTimer)
- {
- PlayState.instance.remove(blackScreen);
- FlxG.sound.play(Paths.sound('Lights_Turn_On'));
- PlayState.instance.cameraFollowPoint.y = -2050;
- PlayState.instance.cameraFollowPoint.x += 200;
- FlxG.camera.focusOn(PlayState.instance.cameraFollowPoint.getPosition());
- FlxG.camera.zoom = 1.5;
-
- new FlxTimer().start(0.8, function(tmr:FlxTimer)
- {
- PlayState.instance.camHUD.visible = true;
- PlayState.instance.remove(blackScreen);
- blackScreen = null;
- FlxTween.tween(FlxG.camera, {zoom: PlayState.defaultCameraZoom}, 2.5,
- {
- ease: FlxEase.quadInOut,
- onComplete: function(twn:FlxTween)
- {
- Countdown.performCountdown(false);
- }
- });
- });
- });
- }
-}
diff --git a/source/funkin/play/character/AnimateAtlasCharacter.hx b/source/funkin/play/character/AnimateAtlasCharacter.hx
new file mode 100644
index 000000000..339662704
--- /dev/null
+++ b/source/funkin/play/character/AnimateAtlasCharacter.hx
@@ -0,0 +1,722 @@
+package funkin.play.character;
+
+import flixel.animation.FlxAnimationController;
+import flixel.FlxCamera;
+import flixel.FlxSprite;
+import flixel.graphics.frames.FlxFrame;
+import flixel.graphics.frames.FlxFramesCollection;
+import flixel.math.FlxMath;
+import flixel.math.FlxPoint.FlxCallbackPoint;
+import flixel.math.FlxPoint;
+import flixel.math.FlxRect;
+import flixel.system.FlxAssets.FlxGraphicAsset;
+import flixel.util.FlxColor;
+import flixel.util.FlxDestroyUtil;
+import funkin.graphics.adobeanimate.FlxAtlasSprite;
+import funkin.modding.events.ScriptEvent;
+import funkin.play.character.CharacterData.CharacterRenderType;
+import openfl.display.BitmapData;
+import openfl.display.BlendMode;
+
+/**
+ * Individual animation data for an AnimateAtlasCharacter.
+ */
+typedef AnimateAtlasAnimation =
+{
+ name:String,
+ prefix:String,
+ offset:Null>,
+ loop:Bool,
+}
+
+/**
+ * An AnimateAtlasCharacter is a Character which is rendered by
+ * displaying an animation derived from an Adobe Animate texture atlas spritesheet file.
+ *
+ * BaseCharacter has game logic, AnimateAtlasCharacter has only rendering logic.
+ * KEEP THEM SEPARATE!
+ */
+class AnimateAtlasCharacter extends BaseCharacter
+{
+ // BaseCharacter extends FlxSprite but we can't make it also extend FlxAtlasSprite UGH
+ // I basically copied the code from FlxSpriteGroup to make the FlxAtlasSprite a "child" of this class
+ var mainSprite:FlxAtlasSprite;
+
+ var _skipTransformChildren:Bool = false;
+
+ var animations:Map = new Map();
+ var currentAnimation:String;
+
+ public function new(id:String)
+ {
+ super(id, CharacterRenderType.AnimateAtlas);
+ }
+
+ override function initVars():Void
+ {
+ // this.flixelType = SPRITEGROUP;
+
+ // TODO: Make `animation` a stub that redirects calls to `mainSprite`?
+ animation = new FlxAnimationController(this);
+
+ offset = new FlxCallbackPoint(offsetCallback);
+ origin = new FlxCallbackPoint(originCallback);
+ scale = new FlxCallbackPoint(scaleCallback);
+ scrollFactor = new FlxCallbackPoint(scrollFactorCallback);
+
+ scale.set(1, 1);
+ scrollFactor.set(1, 1);
+
+ initMotionVars();
+ }
+
+ override function onCreate(event:ScriptEvent):Void
+ {
+ trace('Creating Animate Atlas character: ' + this.characterId);
+
+ var atlasSprite:FlxAtlasSprite = loadAtlasSprite();
+ setSprite(atlasSprite);
+ loadAnimations();
+
+ super.onCreate(event);
+ }
+
+ public override function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false):Void
+ {
+ if ((!canPlayOtherAnims && !ignoreOther)) return;
+
+ currentAnimation = name;
+ var prefix:String = getAnimationData(name).prefix;
+ if (prefix == null) prefix = name;
+ this.mainSprite.playAnimation(prefix, restart, ignoreOther);
+ }
+
+ function loadAtlasSprite():FlxAtlasSprite
+ {
+ trace('[ATLASCHAR] Loading sprite atlas for ${characterId}.');
+
+ var sprite:FlxAtlasSprite = new FlxAtlasSprite(0, 0, Paths.animateAtlas(_data.assetPath, 'shared'));
+
+ sprite.onAnimationFinish.removeAll();
+ sprite.onAnimationFinish.add(this.onAnimationFinished);
+
+ return sprite;
+ }
+
+ override function onAnimationFinished(prefix:String):Void
+ {
+ super.onAnimationFinished(prefix);
+
+ if (getAnimationData() != null && getAnimationData().loop)
+ {
+ playAnimation(prefix, true, false);
+ }
+ else
+ {
+ this.mainSprite.cleanupAnimation(prefix);
+ }
+ }
+
+ function setSprite(sprite:FlxAtlasSprite):Void
+ {
+ trace('[ATLASCHAR] Applying sprite properties to ${characterId}');
+
+ this.mainSprite = sprite;
+
+ var feetPos:FlxPoint = feetPosition;
+ this.updateHitbox();
+
+ sprite.x = this.x;
+ sprite.y = this.y;
+ sprite.alpha *= alpha;
+ sprite.flipX = flipX;
+ sprite.flipY = flipY;
+ sprite.scrollFactor.copyFrom(scrollFactor);
+ sprite.cameras = _cameras; // _cameras instead of cameras because get_cameras() will not return null
+
+ if (clipRect != null) clipRectTransform(sprite, clipRect);
+ }
+
+ function loadAnimations():Void
+ {
+ trace('[ATLASCHAR] Loading ${_data.animations.length} animations for ${characterId}');
+
+ var animData:Array = cast _data.animations;
+
+ for (anim in animData)
+ {
+ animations.set(anim.name, anim);
+ }
+ }
+
+ public override function getCurrentAnimation():String
+ {
+ return this.mainSprite.getCurrentAnimation();
+ }
+
+ function getAnimationData(name:String = null):AnimateAtlasAnimation
+ {
+ if (name == null) name = getCurrentAnimation();
+ return animations.get(name);
+ }
+
+ //
+ //
+ // Code copied from FlxSpriteGroup
+ //
+ //
+
+ /**
+ * Handy function that allows you to quickly transform one property of sprites in this group at a time.
+ *
+ * @param callback Function to transform the sprites. Example:
+ * `function(sprite, v:Dynamic) { s.acceleration.x = v; s.makeGraphic(10,10,0xFF000000); }`
+ * @param value Value which will passed to lambda function.
+ */
+ @:generic
+ public function transformChildren(callback:FlxAtlasSprite->V->Void, value:V):Void
+ {
+ if (_skipTransformChildren || this.mainSprite == null) return;
+
+ callback(this.mainSprite, value);
+ }
+
+ /**
+ * Calls `kill()` on the group's members and then on the group itself.
+ * You can revive this group later via `revive()` after this.
+ */
+ public override function kill():Void
+ {
+ _skipTransformChildren = true;
+ super.kill();
+ _skipTransformChildren = false;
+ this.mainSprite.kill();
+ }
+
+ /**
+ * Revives the group.
+ */
+ public override function revive():Void
+ {
+ _skipTransformChildren = true;
+ super.revive(); // calls set_exists and set_alive
+ _skipTransformChildren = false;
+ this.mainSprite.revive();
+ }
+
+ /**
+ * **WARNING:** A destroyed `FlxBasic` can't be used anymore.
+ * It may even cause crashes if it is still part of a group or state.
+ * You may want to use `kill()` instead if you want to disable the object temporarily only and `revive()` it later.
+ *
+ * This function is usually not called manually (Flixel calls it automatically during state switches for all `add()`ed objects).
+ *
+ * Override this function to `null` out variables manually or call `destroy()` on class members if necessary.
+ * Don't forget to call `super.destroy()`!
+ */
+ public override function destroy():Void
+ {
+ // normally don't have to destroy FlxPoints, but these are FlxCallbackPoints!
+ offset = FlxDestroyUtil.destroy(offset);
+ origin = FlxDestroyUtil.destroy(origin);
+ scale = FlxDestroyUtil.destroy(scale);
+ scrollFactor = FlxDestroyUtil.destroy(scrollFactor);
+
+ this.mainSprite = FlxDestroyUtil.destroy(this.mainSprite);
+
+ super.destroy();
+ }
+
+ /**
+ * Check and see if any sprite in this group is currently on screen.
+ *
+ * @param Camera Specify which game camera you want. If `null`, it will just grab the first global camera.
+ * @return Whether the object is on screen or not.
+ */
+ public override function isOnScreen(?camera:FlxCamera):Bool
+ {
+ if (this.mainSprite != null && this.mainSprite.exists && this.mainSprite.visible && this.mainSprite.isOnScreen(camera)) return true;
+
+ return false;
+ }
+
+ /**
+ * Checks to see if a point in 2D world space overlaps any `FlxSprite` object from this group.
+ *
+ * @param Point The point in world space you want to check.
+ * @param InScreenSpace Whether to take scroll factors into account when checking for overlap.
+ * @param Camera Specify which game camera you want. If `null`, it will just grab the first global camera.
+ * @return Whether or not the point overlaps this group.
+ */
+ public override function overlapsPoint(point:FlxPoint, inScreenSpace:Bool = false, camera:FlxCamera = null):Bool
+ {
+ var result:Bool = false;
+ result = this.mainSprite.overlapsPoint(point, inScreenSpace, camera);
+ return result;
+ }
+
+ /**
+ * Checks to see if a point in 2D world space overlaps any of FlxSprite object's current displayed pixels.
+ * This check is ALWAYS made in screen space, and always takes scroll factors into account.
+ *
+ * @param Point The point in world space you want to check.
+ * @param Mask Used in the pixel hit test to determine what counts as solid.
+ * @param Camera Specify which game camera you want. If `null`, it will just grab the first global camera.
+ * @return Whether or not the point overlaps this object.
+ */
+ public override function pixelsOverlapPoint(point:FlxPoint, Mask:Int = 0xFF, Camera:FlxCamera = null):Bool
+ {
+ var result:Bool = false;
+ if (this.mainSprite != null && this.mainSprite.exists && this.mainSprite.visible)
+ {
+ result = this.mainSprite.pixelsOverlapPoint(point, Mask, Camera);
+ }
+ return result;
+ }
+
+ public override function update(elapsed:Float):Void
+ {
+ this.mainSprite.update(elapsed);
+
+ if (moves) updateMotion(elapsed);
+ }
+
+ public override function draw():Void
+ {
+ this.mainSprite.draw();
+
+ #if FLX_DEBUG
+ if (FlxG.debugger.drawDebug) drawDebug();
+ #end
+ }
+
+ inline function xTransform(sprite:FlxSprite, x:Float):Void
+ sprite.x += x; // addition
+
+ inline function yTransform(sprite:FlxSprite, y:Float):Void
+ sprite.y += y; // addition
+
+ inline function angleTransform(sprite:FlxSprite, angle:Float):Void
+ sprite.angle += angle; // addition
+
+ inline function alphaTransform(sprite:FlxSprite, alpha:Float):Void
+ {
+ if (sprite.alpha != 0 || alpha == 0)
+ {
+ sprite.alpha *= alpha; // multiplication
+ }
+ else
+ {
+ sprite.alpha = 1 / alpha; // direct set to avoid stuck sprites
+ }
+ }
+
+ inline function directAlphaTransform(sprite:FlxSprite, alpha:Float):Void
+ sprite.alpha = alpha; // direct set
+
+ inline function facingTransform(sprite:FlxSprite, facing:Int):Void
+ sprite.facing = facing;
+
+ inline function flipXTransform(sprite:FlxSprite, flipX:Bool):Void
+ sprite.flipX = flipX;
+
+ inline function flipYTransform(sprite:FlxSprite, flipY:Bool):Void
+ sprite.flipY = flipY;
+
+ inline function movesTransform(sprite:FlxSprite, moves:Bool):Void
+ sprite.moves = moves;
+
+ inline function pixelPerfectTransform(sprite:FlxSprite, pixelPerfect:Bool):Void
+ sprite.pixelPerfectRender = pixelPerfect;
+
+ inline function gColorTransform(sprite:FlxSprite, color:Int):Void
+ sprite.color = color;
+
+ inline function blendTransform(sprite:FlxSprite, blend:BlendMode):Void
+ sprite.blend = blend;
+
+ inline function immovableTransform(sprite:FlxSprite, immovable:Bool):Void
+ sprite.immovable = immovable;
+
+ inline function visibleTransform(sprite:FlxSprite, visible:Bool):Void
+ sprite.visible = visible;
+
+ inline function activeTransform(sprite:FlxSprite, active:Bool):Void
+ sprite.active = active;
+
+ inline function solidTransform(sprite:FlxSprite, solid:Bool):Void
+ sprite.solid = solid;
+
+ inline function aliveTransform(sprite:FlxSprite, alive:Bool):Void
+ sprite.alive = alive;
+
+ inline function existsTransform(sprite:FlxSprite, exists:Bool):Void
+ sprite.exists = exists;
+
+ inline function cameraTransform(sprite:FlxSprite, camera:FlxCamera):Void
+ sprite.camera = camera;
+
+ inline function camerasTransform(sprite:FlxSprite, cameras:Array):Void
+ sprite.cameras = cameras;
+
+ inline function offsetTransform(sprite:FlxSprite, offset:FlxPoint):Void
+ sprite.offset.copyFrom(offset);
+
+ inline function originTransform(sprite:FlxSprite, origin:FlxPoint):Void
+ sprite.origin.copyFrom(origin);
+
+ inline function scaleTransform(sprite:FlxSprite, scale:FlxPoint):Void
+ sprite.scale.copyFrom(scale);
+
+ inline function scrollFactorTransform(sprite:FlxSprite, scrollFactor:FlxPoint):Void
+ sprite.scrollFactor.copyFrom(scrollFactor);
+
+ inline function clipRectTransform(sprite:FlxSprite, clipRect:FlxRect):Void
+ {
+ if (clipRect == null)
+ {
+ sprite.clipRect = null;
+ }
+ else
+ {
+ sprite.clipRect = FlxRect.get(clipRect.x - sprite.x + x, clipRect.y - sprite.y + y, clipRect.width, clipRect.height);
+ }
+ }
+
+ inline function offsetCallback(offset:FlxPoint):Void
+ transformChildren(offsetTransform, offset);
+
+ inline function originCallback(origin:FlxPoint):Void
+ transformChildren(originTransform, origin);
+
+ inline function scaleCallback(scale:FlxPoint):Void
+ transformChildren(scaleTransform, scale);
+
+ inline function scrollFactorCallback(scrollFactor:FlxPoint):Void
+ transformChildren(scrollFactorTransform, scrollFactor);
+
+ override function set_camera(value:FlxCamera):FlxCamera
+ {
+ if (camera != value) transformChildren(cameraTransform, value);
+ return super.set_camera(value);
+ }
+
+ override function set_cameras(value:Array):Array
+ {
+ if (cameras != value) transformChildren(camerasTransform, value);
+ return super.set_cameras(value);
+ }
+
+ override function set_exists(value:Bool):Bool
+ {
+ if (exists != value) transformChildren(existsTransform, value);
+ return super.set_exists(value);
+ }
+
+ override function set_visible(value:Bool):Bool
+ {
+ if (exists && visible != value) transformChildren(visibleTransform, value);
+ return super.set_visible(value);
+ }
+
+ override function set_active(value:Bool):Bool
+ {
+ if (exists && active != value) transformChildren(activeTransform, value);
+ return super.set_active(value);
+ }
+
+ override function set_alive(value:Bool):Bool
+ {
+ if (alive != value) transformChildren(aliveTransform, value);
+ return super.set_alive(value);
+ }
+
+ override function set_x(value:Float):Float
+ {
+ if (!exists || x == value) return x; // early return (no need to transform)
+
+ transformChildren(xTransform, value - x); // offset
+ x = value;
+ return x;
+ }
+
+ override function set_y(value:Float):Float
+ {
+ if (exists && y != value) transformChildren(yTransform, value - y); // offset
+ y = value;
+ return y;
+ }
+
+ override function set_angle(value:Float):Float
+ {
+ if (exists && angle != value) transformChildren(angleTransform, value - angle); // offset
+ angle = value;
+ return angle;
+ }
+
+ override function set_alpha(value:Float):Float
+ {
+ value = FlxMath.bound(value, 0, 1);
+
+ if (exists && alpha != value)
+ {
+ transformChildren(directAlphaTransform, value);
+ }
+ alpha = value;
+ return alpha;
+ }
+
+ override function set_facing(value:Int):Int
+ {
+ if (exists && facing != value) transformChildren(facingTransform, value);
+ facing = value;
+ return facing;
+ }
+
+ override function set_flipX(value:Bool):Bool
+ {
+ if (exists && flipX != value) transformChildren(flipXTransform, value);
+ flipX = value;
+ return flipX;
+ }
+
+ override function set_flipY(value:Bool):Bool
+ {
+ if (exists && flipY != value) transformChildren(flipYTransform, value);
+ flipY = value;
+ return flipY;
+ }
+
+ override function set_moves(value:Bool):Bool
+ {
+ if (exists && moves != value) transformChildren(movesTransform, value);
+ moves = value;
+ return moves;
+ }
+
+ override function set_immovable(value:Bool):Bool
+ {
+ if (exists && immovable != value) transformChildren(immovableTransform, value);
+ immovable = value;
+ return immovable;
+ }
+
+ override function set_solid(value:Bool):Bool
+ {
+ if (exists && solid != value) transformChildren(solidTransform, value);
+ return super.set_solid(value);
+ }
+
+ override function set_color(value:Int):Int
+ {
+ if (exists && color != value) transformChildren(gColorTransform, value);
+ color = value;
+ return color;
+ }
+
+ override function set_blend(value:BlendMode):BlendMode
+ {
+ if (exists && blend != value) transformChildren(blendTransform, value);
+ blend = value;
+ return blend;
+ }
+
+ override function set_clipRect(rect:FlxRect):FlxRect
+ {
+ if (exists) transformChildren(clipRectTransform, rect);
+ return super.set_clipRect(rect);
+ }
+
+ override function set_pixelPerfectRender(value:Bool):Bool
+ {
+ if (exists && pixelPerfectRender != value) transformChildren(pixelPerfectTransform, value);
+ return super.set_pixelPerfectRender(value);
+ }
+
+ override function set_width(value:Float):Float
+ {
+ return value;
+ }
+
+ override function get_width():Float
+ {
+ if (this.mainSprite == null) return 0;
+
+ return this.mainSprite.width;
+ }
+
+ /**
+ * Returns the left-most position of the left-most member.
+ * If there are no members, x is returned.
+ *
+ * @since 5.0.0
+ * @return the left-most position of the left-most member
+ */
+ public function findMinX():Float
+ {
+ return this.mainSprite == null ? x : findMinXHelper();
+ }
+
+ function findMinXHelper():Float
+ {
+ return this.mainSprite.x;
+ }
+
+ /**
+ * Returns the right-most position of the right-most member.
+ * If there are no members, x is returned.
+ *
+ * @since 5.0.0
+ * @return the right-most position of the right-most member
+ */
+ public function findMaxX():Float
+ {
+ return this.mainSprite == null ? x : findMaxXHelper();
+ }
+
+ function findMaxXHelper():Float
+ {
+ return this.mainSprite.x + this.mainSprite.width;
+ }
+
+ /**
+ * This functionality isn't supported in SpriteGroup
+ */
+ override function set_height(value:Float):Float
+ {
+ return value;
+ }
+
+ override function get_height():Float
+ {
+ if (this.mainSprite == null) return 0;
+
+ return this.mainSprite.height;
+ }
+
+ /**
+ * Returns the top-most position of the top-most member.
+ * If there are no members, y is returned.
+ *
+ * @since 5.0.0
+ * @return the top-most position of the top-most member
+ */
+ public function findMinY():Float
+ {
+ return this.mainSprite == null ? y : findMinYHelper();
+ }
+
+ function findMinYHelper():Float
+ {
+ return this.mainSprite.y;
+ }
+
+ /**
+ * Returns the top-most position of the top-most member.
+ * If there are no members, y is returned.
+ *
+ * @since 5.0.0
+ * @return the bottom-most position of the bottom-most member
+ */
+ public function findMaxY():Float
+ {
+ return this.mainSprite == null ? y : findMaxYHelper();
+ }
+
+ function findMaxYHelper():Float
+ {
+ return this.mainSprite.y + this.mainSprite.height;
+ }
+
+ /**
+ * This functionality isn't supported in SpriteGroup
+ * @return this sprite group
+ */
+ public override function loadGraphicFromSprite(Sprite:FlxSprite):FlxSprite
+ {
+ #if FLX_DEBUG
+ throw "This function is not supported in FlxSpriteGroup";
+ #end
+ return this;
+ }
+
+ /**
+ * This functionality isn't supported in SpriteGroup
+ * @return this sprite group
+ */
+ public override function loadGraphic(Graphic:FlxGraphicAsset, Animated:Bool = false, Width:Int = 0, Height:Int = 0, Unique:Bool = false,
+ ?Key:String):FlxSprite
+ {
+ return this;
+ }
+
+ /**
+ * This functionality isn't supported in SpriteGroup
+ * @return this sprite group
+ */
+ public override function loadRotatedGraphic(Graphic:FlxGraphicAsset, Rotations:Int = 16, Frame:Int = -1, AntiAliasing:Bool = false, AutoBuffer:Bool = false,
+ ?Key:String):FlxSprite
+ {
+ #if FLX_DEBUG
+ throw "This function is not supported in FlxSpriteGroup";
+ #end
+ return this;
+ }
+
+ /**
+ * This functionality isn't supported in SpriteGroup
+ * @return this sprite group
+ */
+ public override function makeGraphic(Width:Int, Height:Int, Color:Int = FlxColor.WHITE, Unique:Bool = false, ?Key:String):FlxSprite
+ {
+ #if FLX_DEBUG
+ throw "This function is not supported in FlxSpriteGroup";
+ #end
+ return this;
+ }
+
+ override function set_pixels(value:BitmapData):BitmapData
+ {
+ return value;
+ }
+
+ override function set_frame(value:FlxFrame):FlxFrame
+ {
+ return value;
+ }
+
+ override function get_pixels():BitmapData
+ {
+ return null;
+ }
+
+ /**
+ * Internal function to update the current animation frame.
+ *
+ * @param RunOnCpp Whether the frame should also be recalculated if we're on a non-flash target
+ */
+ override inline function calcFrame(RunOnCpp:Bool = false):Void
+ {
+ // Nothing to do here
+ }
+
+ /**
+ * This functionality isn't supported in SpriteGroup
+ */
+ override inline function resetHelpers():Void {}
+
+ /**
+ * This functionality isn't supported in SpriteGroup
+ */
+ public override inline function stamp(Brush:FlxSprite, X:Int = 0, Y:Int = 0):Void {}
+
+ override function set_frames(Frames:FlxFramesCollection):FlxFramesCollection
+ {
+ return Frames;
+ }
+
+ /**
+ * This functionality isn't supported in SpriteGroup
+ */
+ override inline function updateColorTransform():Void {}
+}
diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx
index bd12b5f2e..01156dfab 100644
--- a/source/funkin/play/character/BaseCharacter.hx
+++ b/source/funkin/play/character/BaseCharacter.hx
@@ -4,6 +4,7 @@ import flixel.math.FlxPoint;
import funkin.modding.events.ScriptEvent;
import funkin.noteStuff.NoteBasic.NoteDir;
import funkin.play.character.CharacterData.CharacterDataParser;
+import funkin.play.character.CharacterData.CharacterRenderType;
import funkin.play.stage.Bopper;
/**
@@ -62,13 +63,27 @@ class BaseCharacter extends Bopper
* The absolute position of the top-left of the character.
* @return
*/
- public var cornerPosition(get, null):FlxPoint;
+ public var cornerPosition(get, set):FlxPoint;
function get_cornerPosition():FlxPoint
{
return new FlxPoint(x, y);
}
+ function set_cornerPosition(value:FlxPoint):FlxPoint
+ {
+ var xDiff:Float = value.x - this.x;
+ var yDiff:Float = value.y - this.y;
+
+ this.cameraFocusPoint.x += xDiff;
+ this.cameraFocusPoint.y += yDiff;
+
+ super.set_x(value.x);
+ super.set_y(value.y);
+
+ return value;
+ }
+
/**
* The absolute position of the character's feet, at the bottom-center of the sprite.
*/
@@ -131,7 +146,7 @@ class BaseCharacter extends Bopper
return super.set_y(value);
}
- public function new(id:String)
+ public function new(id:String, renderType:CharacterRenderType)
{
super();
this.characterId = id;
@@ -141,6 +156,10 @@ class BaseCharacter extends Bopper
{
throw 'Could not find character data for characterId: $characterId';
}
+ else if (_data.renderType != renderType)
+ {
+ throw 'Render type mismatch for character ($characterId): expected ${renderType}, got ${_data.renderType}';
+ }
else
{
this.characterName = _data.name;
@@ -235,6 +254,8 @@ class BaseCharacter extends Bopper
override function onCreate(event:ScriptEvent):Void
{
+ super.onCreate(event);
+
// Make sure we are playing the idle animation...
this.dance();
// ...then update the hitbox so that this.width and this.height are correct.
@@ -307,14 +328,16 @@ class BaseCharacter extends Bopper
return;
}
- if (hasAnimation('idle-hold') && getCurrentAnimation() == "idle" && isAnimationFinished()) playAnimation('idle-hold');
- if (hasAnimation('singLEFT-hold') && getCurrentAnimation() == "singLEFT" && isAnimationFinished()) playAnimation('singLEFT-hold');
- if (hasAnimation('singDOWN-hold') && getCurrentAnimation() == "singDOWN" && isAnimationFinished()) playAnimation('singDOWN-hold');
- if (hasAnimation('singUP-hold') && getCurrentAnimation() == "singUP" && isAnimationFinished()) playAnimation('singUP-hold');
- if (hasAnimation('singRIGHT-hold') && getCurrentAnimation() == "singRIGHT" && isAnimationFinished()) playAnimation('singRIGHT-hold');
+ // This logic turns the idle animation into a "lead-in" animation.
+ if (hasAnimation('idle-hold') && getCurrentAnimation() == 'idle' && isAnimationFinished()) playAnimation('idle-hold');
+
+ if (hasAnimation('singLEFT-hold') && getCurrentAnimation() == 'singLEFT' && isAnimationFinished()) playAnimation('singLEFT-hold');
+ if (hasAnimation('singDOWN-hold') && getCurrentAnimation() == 'singDOWN' && isAnimationFinished()) playAnimation('singDOWN-hold');
+ if (hasAnimation('singUP-hold') && getCurrentAnimation() == 'singUP' && isAnimationFinished()) playAnimation('singUP-hold');
+ if (hasAnimation('singRIGHT-hold') && getCurrentAnimation() == 'singRIGHT' && isAnimationFinished()) playAnimation('singRIGHT-hold');
// Handle character note hold time.
- if (getCurrentAnimation().startsWith("sing"))
+ if (getCurrentAnimation().startsWith('sing'))
{
// TODO: Rework this code (and all character animations ugh)
// such that the hold time is handled by padding frames,
@@ -324,7 +347,7 @@ class BaseCharacter extends Bopper
holdTimer += event.elapsed;
var singTimeMs:Float = singTimeCrochet * (Conductor.crochet * 0.001); // x beats, to ms.
- if (getCurrentAnimation().endsWith("miss")) singTimeMs *= 2; // makes it feel more awkward when you miss
+ if (getCurrentAnimation().endsWith('miss')) singTimeMs *= 2; // makes it feel more awkward when you miss
// Without this check here, the player character would only play the `sing` animation
// for one beat, as opposed to holding it as long as the player is holding the button.
@@ -349,39 +372,31 @@ class BaseCharacter extends Bopper
/**
* Since no `onBeatHit` or `dance` calls happen in GameOverSubstate,
* this regularly gets called instead.
+ *
+ * @param force Force the deathLoop animation to play, even if `firstDeath` is still playing.
*/
public function playDeathAnimation(force:Bool = false):Void
{
- if (force || (getCurrentAnimation().startsWith("firstDeath") && isAnimationFinished()))
+ if (force || (getCurrentAnimation().startsWith('firstDeath') && isAnimationFinished()))
{
- playAnimation("deathLoop" + GameOverSubstate.animationSuffix);
+ playAnimation('deathLoop' + GameOverSubstate.animationSuffix);
}
}
- override function dance(force:Bool = false)
+ override function dance(force:Bool = false):Void
{
// Prevent default dancing behavior.
- if (debugMode) return;
-
- if (isDead) return;
+ if (debugMode || isDead) return;
if (!force)
{
- if (getCurrentAnimation().startsWith("sing"))
- {
- return;
- }
- if (["hey", "cheer"].contains(getCurrentAnimation()) && !isAnimationFinished())
- {
- return;
- }
+ if (getCurrentAnimation().startsWith('sing')) return;
+
+ if (['hey', 'cheer'].contains(getCurrentAnimation()) && !isAnimationFinished()) return;
}
// Prevent dancing while another animation is playing.
- if (!force && getCurrentAnimation().startsWith("sing"))
- {
- return;
- }
+ if (!force && getCurrentAnimation().startsWith('sing')) return;
// Otherwise, fallback to the super dance() method, which handles playing the idle animation.
super.dance();
@@ -538,15 +553,18 @@ class BaseCharacter extends Bopper
* @param miss If true, play the miss animation instead of the sing animation.
* @param suffix A suffix to append to the animation name, like `alt`.
*/
- public function playSingAnimation(dir:NoteDir, miss:Bool = false, suffix:String = ""):Void
+ public function playSingAnimation(dir:NoteDir, ?miss:Bool = false, ?suffix:String = ''):Void
{
- var anim:String = 'sing${dir.nameUpper}${miss ? 'miss' : ''}${suffix != "" ? '-${suffix}' : ''}';
+ var anim:String = 'sing${dir.nameUpper}${miss ? 'miss' : ''}${suffix != '' ? '-${suffix}' : ''}';
// restart even if already playing, because the character might sing the same note twice.
playAnimation(anim, true);
}
}
+/**
+ * The type of a given character sprite. Defines its default behaviors.
+ */
enum CharacterType
{
/**
@@ -563,7 +581,8 @@ enum CharacterType
* - At idle, dances with `danceLeft` and `danceRight` if available, or `idle` if not.
* - When the CPU hits a note, plays the appropriate `singDIR` animation until DAD is done singing.
* - If there is a `singDIR-end` animation, the `singDIR` animation will play once before looping the `singDIR-end` animation until DAD is done singing.
- * - When the CPU misses a note (NOTE: This only happens via script, not by default), plays the appropriate `singDIR-miss` animation until DAD is done singing.
+ * - When the CPU misses a note (NOTE: This only happens via script, not by default),
+ * plays the appropriate `singDIR-miss` animation until DAD is done singing.
*/
DAD;
diff --git a/source/funkin/play/character/CharacterData.hx b/source/funkin/play/character/CharacterData.hx
index 87cbd078b..7b0ac8981 100644
--- a/source/funkin/play/character/CharacterData.hx
+++ b/source/funkin/play/character/CharacterData.hx
@@ -1,18 +1,14 @@
package funkin.play.character;
-import flixel.util.typeLimit.OneOfTwo;
import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventDispatcher;
-import funkin.play.character.BaseCharacter;
-import funkin.play.character.MultiSparrowCharacter;
-import funkin.play.character.PackerCharacter;
+import funkin.play.character.ScriptedCharacter.ScriptedAnimateAtlasCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedBaseCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedMultiSparrowCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedPackerCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedSparrowCharacter;
-import funkin.play.character.SparrowCharacter;
-import funkin.util.VersionUtil;
import funkin.util.assets.DataAssets;
+import funkin.util.VersionUtil;
import haxe.Json;
import openfl.utils.Assets;
@@ -23,12 +19,12 @@ class CharacterDataParser
* Handle breaking changes by incrementing this value
* and adding migration to the `migrateStageData()` function.
*/
- public static final CHARACTER_DATA_VERSION:String = "1.0.0";
+ public static final CHARACTER_DATA_VERSION:String = '1.0.0';
/**
* The current version rule check for the stage data format.
*/
- public static final CHARACTER_DATA_VERSION_RULE:String = "1.0.x";
+ public static final CHARACTER_DATA_VERSION_RULE:String = '1.0.x';
static final characterCache:Map = new Map();
static final characterScriptedClass:Map = new Map();
@@ -44,14 +40,13 @@ class CharacterDataParser
{
// Clear any stages that are cached if there were any.
clearCharacterCache();
- trace("Loading character cache...");
+ trace('Loading character cache...');
//
// UNSCRIPTED CHARACTERS
//
var charIdList:Array = DataAssets.listDataFilesInPath('characters/');
- var unscriptedCharIds:Array = charIdList.filter(function(charId:String):Bool
- {
+ var unscriptedCharIds:Array = charIdList.filter(function(charId:String):Bool {
return !characterCache.exists(charId);
});
trace(' Fetching data for ${unscriptedCharIds.length} characters...');
@@ -85,8 +80,16 @@ class CharacterDataParser
trace(' Instantiating ${scriptedCharClassNames1.length} (Sparrow) scripted characters...');
for (charCls in scriptedCharClassNames1)
{
- var character = ScriptedSparrowCharacter.init(charCls, DEFAULT_CHAR_ID);
- characterScriptedClass.set(character.characterId, charCls);
+ try
+ {
+ var character:SparrowCharacter = ScriptedSparrowCharacter.init(charCls, DEFAULT_CHAR_ID);
+ characterScriptedClass.set(character.characterId, charCls);
+ }
+ catch (e)
+ {
+ trace(' FAILED to instantiate scripted Sparrow character: ${charCls}');
+ trace(e);
+ }
}
}
@@ -96,8 +99,16 @@ class CharacterDataParser
trace(' Instantiating ${scriptedCharClassNames2.length} (Packer) scripted characters...');
for (charCls in scriptedCharClassNames2)
{
- var character = ScriptedPackerCharacter.init(charCls, DEFAULT_CHAR_ID);
- characterScriptedClass.set(character.characterId, charCls);
+ try
+ {
+ var character:PackerCharacter = ScriptedPackerCharacter.init(charCls, DEFAULT_CHAR_ID);
+ characterScriptedClass.set(character.characterId, charCls);
+ }
+ catch (e)
+ {
+ trace(' FAILED to instantiate scripted Packer character: ${charCls}');
+ trace(e);
+ }
}
}
@@ -107,24 +118,46 @@ class CharacterDataParser
trace(' Instantiating ${scriptedCharClassNames3.length} (Multi-Sparrow) scripted characters...');
for (charCls in scriptedCharClassNames3)
{
- var character = ScriptedMultiSparrowCharacter.init(charCls, DEFAULT_CHAR_ID);
- if (character == null)
+ try
{
- trace(' Failed to instantiate scripted character: ${charCls}');
- continue;
+ var character:MultiSparrowCharacter = ScriptedMultiSparrowCharacter.init(charCls, DEFAULT_CHAR_ID);
+ characterScriptedClass.set(character.characterId, charCls);
+ }
+ catch (e)
+ {
+ trace(' FAILED to instantiate scripted Multi-Sparrow character: ${charCls}');
+ trace(e);
+ }
+ }
+ }
+
+ var scriptedCharClassNames4:Array = ScriptedAnimateAtlasCharacter.listScriptClasses();
+ if (scriptedCharClassNames4.length > 0)
+ {
+ trace(' Instantiating ${scriptedCharClassNames4.length} (Animate Atlas) scripted characters...');
+ for (charCls in scriptedCharClassNames4)
+ {
+ try
+ {
+ var character:AnimateAtlasCharacter = ScriptedAnimateAtlasCharacter.init(charCls, DEFAULT_CHAR_ID);
+ characterScriptedClass.set(character.characterId, charCls);
+ }
+ catch (e)
+ {
+ trace(' FAILED to instantiate scripted Animate Atlas character: ${charCls}');
+ trace(e);
}
- characterScriptedClass.set(character.characterId, charCls);
}
}
// NOTE: Only instantiate the ones not populated above.
// ScriptedBaseCharacter.listScriptClasses() will pick up scripts extending the other classes.
var scriptedCharClassNames:Array = ScriptedBaseCharacter.listScriptClasses();
- scriptedCharClassNames = scriptedCharClassNames.filter(function(charCls:String):Bool
- {
+ scriptedCharClassNames = scriptedCharClassNames.filter(function(charCls:String):Bool {
return !(scriptedCharClassNames1.contains(charCls)
|| scriptedCharClassNames2.contains(charCls)
- || scriptedCharClassNames3.contains(charCls));
+ || scriptedCharClassNames3.contains(charCls)
+ || scriptedCharClassNames4.contains(charCls));
});
if (scriptedCharClassNames.length > 0)
@@ -132,7 +165,7 @@ class CharacterDataParser
trace(' Instantiating ${scriptedCharClassNames.length} (Base) scripted characters...');
for (charCls in scriptedCharClassNames)
{
- var character = ScriptedBaseCharacter.init(charCls, DEFAULT_CHAR_ID);
+ var character:BaseCharacter = ScriptedBaseCharacter.init(charCls, DEFAULT_CHAR_ID, Custom);
if (character == null)
{
trace(' Failed to instantiate scripted character: ${charCls}');
@@ -149,83 +182,101 @@ class CharacterDataParser
trace(' Successfully loaded ${Lambda.count(characterCache)} stages.');
}
+ /**
+ * Fetches data for a character and returns a BaseCharacter instance,
+ * ready to be added to the scene.
+ * @param charId The character ID to fetch.
+ * @return The character instance, or null if the character was not found.
+ */
public static function fetchCharacter(charId:String):Null
{
- if (charId == null || charId == '')
+ if (charId == null || charId == '' || !characterCache.exists(charId))
{
- // Gracefully handle songs that don't use this character.
+ // Gracefully handle songs that don't use this character,
+ // or throw an error if the character is missing.
+
+ if (charId != null && charId != '') trace('Failed to build character, not found in cache: ${charId}');
return null;
}
- if (characterCache.exists(charId))
+ var charData:CharacterData = characterCache.get(charId);
+ var charScriptClass:String = characterScriptedClass.get(charId);
+
+ var char:BaseCharacter;
+
+ if (charScriptClass != null)
{
- var charData:CharacterData = characterCache.get(charId);
- var charScriptClass:String = characterScriptedClass.get(charId);
-
- var char:BaseCharacter;
-
- if (charScriptClass != null)
+ switch (charData.renderType)
{
- switch (charData.renderType)
- {
- case CharacterRenderType.MULTISPARROW:
- char = ScriptedMultiSparrowCharacter.init(charScriptClass, charId);
- case CharacterRenderType.SPARROW:
- char = ScriptedSparrowCharacter.init(charScriptClass, charId);
- case CharacterRenderType.PACKER:
- char = ScriptedPackerCharacter.init(charScriptClass, charId);
- default:
- // We're going to assume that the script class does the rendering.
- char = ScriptedBaseCharacter.init(charScriptClass, charId);
- }
+ case CharacterRenderType.AnimateAtlas:
+ char = ScriptedAnimateAtlasCharacter.init(charScriptClass, charId);
+ case CharacterRenderType.MultiSparrow:
+ char = ScriptedMultiSparrowCharacter.init(charScriptClass, charId);
+ case CharacterRenderType.Sparrow:
+ char = ScriptedSparrowCharacter.init(charScriptClass, charId);
+ case CharacterRenderType.Packer:
+ char = ScriptedPackerCharacter.init(charScriptClass, charId);
+ default:
+ // We're going to assume that the script class does the rendering.
+ char = ScriptedBaseCharacter.init(charScriptClass, charId, CharacterRenderType.Custom);
}
- else
- {
- switch (charData.renderType)
- {
- case CharacterRenderType.MULTISPARROW:
- char = new MultiSparrowCharacter(charId);
- case CharacterRenderType.SPARROW:
- char = new SparrowCharacter(charId);
- case CharacterRenderType.PACKER:
- char = new PackerCharacter(charId);
- default:
- trace('[WARN] Creating character with undefined renderType ${charData.renderType}');
- char = new BaseCharacter(charId);
- }
- }
-
- trace('Successfully instantiated character: ${charId}');
-
- // Call onCreate only in the fetchCharacter() function, not at application initialization.
- ScriptEventDispatcher.callEvent(char, new ScriptEvent(ScriptEvent.CREATE));
-
- return char;
}
else
{
- trace('Failed to build character, not found in cache: ${charId}');
+ switch (charData.renderType)
+ {
+ case CharacterRenderType.AnimateAtlas:
+ char = new AnimateAtlasCharacter(charId);
+ case CharacterRenderType.MultiSparrow:
+ char = new MultiSparrowCharacter(charId);
+ case CharacterRenderType.Sparrow:
+ char = new SparrowCharacter(charId);
+ case CharacterRenderType.Packer:
+ char = new PackerCharacter(charId);
+ default:
+ trace('[WARN] Creating character with undefined renderType ${charData.renderType}');
+ char = new BaseCharacter(charId, CharacterRenderType.Custom);
+ }
+ }
+
+ if (char == null)
+ {
+ trace('Failed to instantiate character: ${charId}');
return null;
}
+
+ trace('Successfully instantiated character: ${charId}');
+
+ // Call onCreate only in the fetchCharacter() function, not at application initialization.
+ ScriptEventDispatcher.callEvent(char, new ScriptEvent(ScriptEvent.CREATE));
+
+ return char;
}
+ /**
+ * Fetches just the character data for a character.
+ * @param charId The character ID to fetch.
+ * @return The character data, or null if the character was not found.
+ */
public static function fetchCharacterData(charId:String):Null
{
- if (characterCache.exists(charId))
- {
- return characterCache.get(charId);
- }
- else
- {
- return null;
- }
+ if (characterCache.exists(charId)) return characterCache.get(charId);
+
+ return null;
}
+ /**
+ * Lists all the valid character IDs.
+ * @return An array of character IDs.
+ */
public static function listCharacterIds():Array
{
return characterCache.keys().array();
}
+ /**
+ * Clears the character data cache.
+ */
static function clearCharacterCache():Void
{
if (characterCache != null)
@@ -239,7 +290,7 @@ class CharacterDataParser
}
/**
- * Load a character's JSON file, parse its data, and return it.
+ * Load a character's JSON file and parse its data.
*
* @param charId The character to load.
* @return The character data, or null if validation failed.
@@ -258,7 +309,7 @@ class CharacterDataParser
var charFilePath:String = Paths.json('characters/${charPath}');
var rawJson = Assets.getText(charFilePath).trim();
- while (!StringTools.endsWith(rawJson, "}"))
+ while (!StringTools.endsWith(rawJson, '}'))
{
rawJson = rawJson.substr(0, rawJson.length - 1);
}
@@ -266,7 +317,7 @@ class CharacterDataParser
return rawJson;
}
- static function migrateCharacterData(rawJson:String, charId:String)
+ static function migrateCharacterData(rawJson:String, charId:String):Null
{
// If you update the character data format in a breaking way,
// handle migration here by checking the `version` value.
@@ -298,13 +349,13 @@ class CharacterDataParser
static final DEFAULT_FRAMERATE:Int = 24;
static final DEFAULT_ISPIXEL:Bool = false;
static final DEFAULT_LOOP:Bool = false;
- static final DEFAULT_NAME:String = "Untitled Character";
+ static final DEFAULT_NAME:String = 'Untitled Character';
static final DEFAULT_OFFSETS:Array = [0, 0];
static final DEFAULT_HEALTHICON_OFFSETS:Array = [0, 25];
- static final DEFAULT_RENDERTYPE:CharacterRenderType = CharacterRenderType.SPARROW;
+ static final DEFAULT_RENDERTYPE:CharacterRenderType = CharacterRenderType.Sparrow;
static final DEFAULT_SCALE:Float = 1;
static final DEFAULT_SCROLL:Array = [0, 0];
- static final DEFAULT_STARTINGANIM:String = "idle";
+ static final DEFAULT_STARTINGANIM:String = 'idle';
/**
* Set unspecified parameters to their defaults.
@@ -317,7 +368,7 @@ class CharacterDataParser
{
if (input == null)
{
- // trace('ERROR: Could not parse character data for "${id}".');
+ trace('ERROR: Could not parse character data for "${id}".');
return null;
}
@@ -471,20 +522,40 @@ class CharacterDataParser
}
}
+/**
+ * Describes the available rendering types for a character.
+ */
enum abstract CharacterRenderType(String) from String to String
{
- var SPARROW = 'sparrow';
- var PACKER = 'packer';
- var MULTISPARROW = 'multisparrow';
- // TODO: FlxSpine?
- // https://api.haxeflixel.com/flixel/addons/editors/spine/FlxSpine.html
- // TODO: Aseprite?
- // https://lib.haxe.org/p/openfl-aseprite/
- // TODO: Animate?
- // https://lib.haxe.org/p/flxanimate
- // TODO: REDACTED
+ /**
+ * Renders the character using a single spritesheet and XML data.
+ */
+ public var Sparrow = 'sparrow';
+
+ /**
+ * Renders the character using a single spritesheet and TXT data.
+ */
+ public var Packer = 'packer';
+
+ /**
+ * Renders the character using multiple spritesheets and XML data.
+ */
+ public var MultiSparrow = 'multisparrow';
+
+ /**
+ * Renders the character using a spritesheet of symbols and JSON data.
+ */
+ public var AnimateAtlas = 'animateatlas';
+
+ /**
+ * Renders the character using a custom method.
+ */
+ public var Custom = 'custom';
}
+/**
+ * The JSON data schema used to define a character.
+ */
typedef CharacterData =
{
/**
@@ -580,6 +651,9 @@ typedef CharacterData =
var flipX:Null;
};
+/**
+ * The JSON data schema used to define the health icon for a character.
+ */
typedef HealthIconData =
{
/**
diff --git a/source/funkin/play/character/MultiSparrowCharacter.hx b/source/funkin/play/character/MultiSparrowCharacter.hx
index 98dda41d0..974a1c431 100644
--- a/source/funkin/play/character/MultiSparrowCharacter.hx
+++ b/source/funkin/play/character/MultiSparrowCharacter.hx
@@ -3,6 +3,7 @@ package funkin.play.character;
import flixel.graphics.frames.FlxFramesCollection;
import funkin.modding.events.ScriptEvent;
import funkin.util.assets.FlxAnimationUtil;
+import funkin.play.character.CharacterData.CharacterRenderType;
/**
* For some characters which use Sparrow atlases, the spritesheets need to be split
@@ -37,7 +38,7 @@ class MultiSparrowCharacter extends BaseCharacter
public function new(id:String)
{
- super(id);
+ super(id, CharacterRenderType.MultiSparrow);
}
override function onCreate(event:ScriptEvent):Void
@@ -48,7 +49,7 @@ class MultiSparrowCharacter extends BaseCharacter
super.onCreate(event);
}
- function buildSprites()
+ function buildSprites():Void
{
buildSpritesheets();
buildAnimations();
@@ -63,8 +64,11 @@ class MultiSparrowCharacter extends BaseCharacter
}
}
- function buildSpritesheets()
+ function buildSpritesheets():Void
{
+ // TODO: This currently works by creating like 5 frame collections and switching between them.
+ // It would be better to refactor this to simply concatenate the frame collections together.
+
// Build the list of asset paths to use.
// Ignore nulls and duplicates.
var assetList = [_data.assetPath];
diff --git a/source/funkin/play/character/PackerCharacter.hx b/source/funkin/play/character/PackerCharacter.hx
index 00469964f..3ee276eb1 100644
--- a/source/funkin/play/character/PackerCharacter.hx
+++ b/source/funkin/play/character/PackerCharacter.hx
@@ -1,9 +1,9 @@
package funkin.play.character;
-import funkin.modding.events.ScriptEvent;
import flixel.graphics.frames.FlxFramesCollection;
+import funkin.modding.events.ScriptEvent;
+import funkin.play.character.CharacterData.CharacterRenderType;
import funkin.util.assets.FlxAnimationUtil;
-import funkin.play.character.BaseCharacter.CharacterType;
/**
* A PackerCharacter is a Character which is rendered by
@@ -13,7 +13,7 @@ class PackerCharacter extends BaseCharacter
{
public function new(id:String)
{
- super(id);
+ super(id, CharacterRenderType.Packer);
}
override function onCreate(event:ScriptEvent):Void
@@ -26,7 +26,7 @@ class PackerCharacter extends BaseCharacter
super.onCreate(event);
}
- function loadSpritesheet()
+ function loadSpritesheet():Void
{
trace('[PACKERCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
@@ -51,7 +51,7 @@ class PackerCharacter extends BaseCharacter
this.setScale(_data.scale);
}
- function loadAnimations()
+ function loadAnimations():Void
{
trace('[PACKERCHAR] Loading ${_data.animations.length} animations for ${characterId}');
diff --git a/source/funkin/play/character/ScriptedCharacter.hx b/source/funkin/play/character/ScriptedCharacter.hx
index 2760cb42c..3d9ad7ed4 100644
--- a/source/funkin/play/character/ScriptedCharacter.hx
+++ b/source/funkin/play/character/ScriptedCharacter.hx
@@ -1,10 +1,5 @@
package funkin.play.character;
-import funkin.play.character.MultiSparrowCharacter;
-import funkin.play.character.PackerCharacter;
-import funkin.play.character.SparrowCharacter;
-import polymod.hscript.HScriptedClass;
-
/**
* A script that can be tied to a BaseCharacter, which persists across states.
* Create a scripted class that extends BaseCharacter to use this.
@@ -13,7 +8,7 @@ import polymod.hscript.HScriptedClass;
* and can't use one of the built-in render modes.
*/
@:hscriptClass
-class ScriptedBaseCharacter extends BaseCharacter implements HScriptedClass {}
+class ScriptedBaseCharacter extends BaseCharacter implements polymod.hscript.HScriptedClass {}
/**
* A script that can be tied to a SparrowCharacter, which persists across states.
@@ -21,7 +16,7 @@ class ScriptedBaseCharacter extends BaseCharacter implements HScriptedClass {}
* then call `super('charId')` in the constructor to use this.
*/
@:hscriptClass
-class ScriptedSparrowCharacter extends SparrowCharacter implements HScriptedClass {}
+class ScriptedSparrowCharacter extends SparrowCharacter implements polymod.hscript.HScriptedClass {}
/**
* A script that can be tied to a MultiSparrowCharacter, which persists across states.
@@ -29,7 +24,7 @@ class ScriptedSparrowCharacter extends SparrowCharacter implements HScriptedClas
* then call `super('charId')` in the constructor to use this.
*/
@:hscriptClass
-class ScriptedMultiSparrowCharacter extends MultiSparrowCharacter implements HScriptedClass {}
+class ScriptedMultiSparrowCharacter extends MultiSparrowCharacter implements polymod.hscript.HScriptedClass {}
/**
* A script that can be tied to a PackerCharacter, which persists across states.
@@ -37,4 +32,12 @@ class ScriptedMultiSparrowCharacter extends MultiSparrowCharacter implements HSc
* then call `super('charId')` in the constructor to use this.
*/
@:hscriptClass
-class ScriptedPackerCharacter extends PackerCharacter implements HScriptedClass {}
+class ScriptedPackerCharacter extends PackerCharacter implements polymod.hscript.HScriptedClass {}
+
+/**
+ * A script that can be tied to an AnimateAtlasCharacter, which persists across states.
+ * Create a scripted class that extends AnimateAtlasCharacter,
+ * then call `super('charId')` in the constructor to use this.
+ */
+@:hscriptClass
+class ScriptedAnimateAtlasCharacter extends AnimateAtlasCharacter implements polymod.hscript.HScriptedClass {}
diff --git a/source/funkin/play/character/SparrowCharacter.hx b/source/funkin/play/character/SparrowCharacter.hx
index e9e9cf423..1db14fe23 100644
--- a/source/funkin/play/character/SparrowCharacter.hx
+++ b/source/funkin/play/character/SparrowCharacter.hx
@@ -3,6 +3,7 @@ package funkin.play.character;
import funkin.modding.events.ScriptEvent;
import funkin.util.assets.FlxAnimationUtil;
import flixel.graphics.frames.FlxFramesCollection;
+import funkin.play.character.CharacterData.CharacterRenderType;
/**
* A SparrowCharacter is a Character which is rendered by
@@ -15,7 +16,7 @@ class SparrowCharacter extends BaseCharacter
{
public function new(id:String)
{
- super(id);
+ super(id, CharacterRenderType.Sparrow);
}
override function onCreate(event:ScriptEvent):Void
diff --git a/source/funkin/play/cutscene/VanillaCutscenes.hx b/source/funkin/play/cutscene/VanillaCutscenes.hx
new file mode 100644
index 000000000..99d0bd0d8
--- /dev/null
+++ b/source/funkin/play/cutscene/VanillaCutscenes.hx
@@ -0,0 +1,125 @@
+package funkin.play.cutscene;
+
+import hxcodec.flixel.FlxVideoSprite;
+import hxcodec.flixel.FlxCutsceneState;
+import flixel.FlxSprite;
+import flixel.tweens.FlxEase;
+import flixel.tweens.FlxTween;
+import flixel.util.FlxColor;
+import flixel.util.FlxTimer;
+
+/**
+ * Static methods for playing cutscenes in the PlayState.
+ * TODO: Un-hardcode this shit!!!!!1!
+ */
+class VanillaCutscenes
+{
+ /**
+ * Well, well, well, what have we got here?
+ */
+ public static function playUghCutscene():Void
+ {
+ playVideoCutscene('music/ughCutscene.mp4');
+ }
+
+ /**
+ * Nice bars for an ugly, boring teenager!
+ */
+ public static function playGunsCutscene():Void
+ {
+ playVideoCutscene('music/gunsCutscene.mp4');
+ }
+
+ /**
+ * Don't you have a school to shoot up?
+ */
+ public static function playStressCutscene():Void
+ {
+ playVideoCutscene('music/stressCutscene.mp4');
+ }
+
+ static var blackScreen:FlxSprite;
+
+ /**
+ * Plays a cutscene from a video file, then starts the countdown once the video is done.
+ * TODO: Cutscene is currently skipped on native platforms.
+ */
+ static function playVideoCutscene(path:String):Void
+ {
+ // Tell PlayState to stop the song until the video is done.
+ PlayState.isInCutscene = true;
+ PlayState.instance.camHUD.visible = false;
+
+ // Display a black screen to hide the game while the video is playing.
+ blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
+ blackScreen.scrollFactor.set(0, 0);
+ blackScreen.cameras = [PlayState.instance.camCutscene];
+ PlayState.instance.add(blackScreen);
+
+ #if html5
+ // Video displays OVER the FlxState.
+ vid = new FlxVideo(path);
+ vid.finishCallback = finishCutscene;
+ #else
+ // Video displays OVER the FlxState.
+ vid = new FlxVideoSprite(0, 0);
+
+ vid.cameras = [PlayState.instance.camCutscene];
+
+ PlayState.instance.add(vid);
+
+ vid.playVideo(Paths.file(path), false);
+ vid.onEndReached.add(finishCutscene.bind(0.5));
+ #end
+ }
+
+ static var vid:#if html5 FlxVideo #else FlxVideoSprite #end;
+
+ /**
+ * Does the cleanup to start the countdown after the video is done.
+ * Gets called immediately if the video can't be played.
+ */
+ public static function finishCutscene(?transitionTime:Float = 2.5):Void
+ {
+ trace('ALERT: Finish cutscene called!');
+
+ #if html5
+ #else
+ vid.stop();
+ PlayState.instance.remove(vid);
+ #end
+
+ PlayState.instance.camHUD.visible = true;
+
+ FlxTween.tween(blackScreen, {alpha: 0}, transitionTime,
+ {
+ ease: FlxEase.quadInOut,
+ onComplete: function(twn:FlxTween) {
+ PlayState.instance.remove(blackScreen);
+ blackScreen = null;
+ }
+ });
+ FlxTween.tween(FlxG.camera, {zoom: PlayState.defaultCameraZoom}, transitionTime,
+ {
+ ease: FlxEase.quadInOut,
+ onComplete: function(twn:FlxTween) {
+ PlayState.instance.startCountdown();
+ }
+ });
+ }
+
+ /**
+ * FNF corruption mod???
+ */
+ public static function playHorrorStartCutscene():Void
+ {
+ PlayState.isInCutscene = true;
+ PlayState.instance.camHUD.visible = false;
+
+ blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
+ blackScreen.scrollFactor.set(0, 0);
+ PlayState.instance.add(blackScreen);
+
+ new FlxTimer().start(0.1, _ -> finishCutscene(2.5));
+ }
+}
diff --git a/source/funkin/play/event/FocusCameraSongEvent.hx b/source/funkin/play/event/FocusCameraSongEvent.hx
index e674a1e24..29d2e80e0 100644
--- a/source/funkin/play/event/FocusCameraSongEvent.hx
+++ b/source/funkin/play/event/FocusCameraSongEvent.hx
@@ -47,17 +47,17 @@ class FocusCameraSongEvent extends SongEvent
super('FocusCamera');
}
- public override function handleEvent(data:SongEventData)
+ public override function handleEvent(data:SongEventData):Void
{
// Does nothing if there is no PlayState camera or stage.
if (PlayState.instance == null || PlayState.instance.currentStage == null) return;
- var posX = data.getFloat('x');
+ var posX:Null = data.getFloat('x');
if (posX == null) posX = 0.0;
- var posY = data.getFloat('y');
+ var posY:Null = data.getFloat('y');
if (posY == null) posY = 0.0;
- var char = data.getInt('char');
+ var char:Null = data.getInt('char');
if (char == null) char = cast data.value;
@@ -65,29 +65,45 @@ class FocusCameraSongEvent extends SongEvent
{
case -1: // Position
trace('Focusing camera on static position.');
- var xTarget = posX;
- var yTarget = posY;
+ var xTarget:Float = posX;
+ var yTarget:Float = posY;
PlayState.instance.cameraFollowPoint.setPosition(xTarget, yTarget);
case 0: // Boyfriend
// Focus the camera on the player.
+ if (PlayState.instance.currentStage.getBoyfriend() == null)
+ {
+ trace('No BF to focus on.');
+ return;
+ }
trace('Focusing camera on player.');
- var xTarget = PlayState.instance.currentStage.getBoyfriend().cameraFocusPoint.x + posX;
- var yTarget = PlayState.instance.currentStage.getBoyfriend().cameraFocusPoint.y + posY;
+ var xTarget:Float = PlayState.instance.currentStage.getBoyfriend().cameraFocusPoint.x + posX;
+ var yTarget:Float = PlayState.instance.currentStage.getBoyfriend().cameraFocusPoint.y + posY;
PlayState.instance.cameraFollowPoint.setPosition(xTarget, yTarget);
case 1: // Dad
// Focus the camera on the dad.
+ if (PlayState.instance.currentStage.getDad() == null)
+ {
+ trace('No dad to focus on.');
+ return;
+ }
trace('Focusing camera on dad.');
- var xTarget = PlayState.instance.currentStage.getDad().cameraFocusPoint.x + posX;
- var yTarget = PlayState.instance.currentStage.getDad().cameraFocusPoint.y + posY;
+ trace(PlayState.instance.currentStage.getDad());
+ var xTarget:Float = PlayState.instance.currentStage.getDad().cameraFocusPoint.x + posX;
+ var yTarget:Float = PlayState.instance.currentStage.getDad().cameraFocusPoint.y + posY;
PlayState.instance.cameraFollowPoint.setPosition(xTarget, yTarget);
case 2: // Girlfriend
// Focus the camera on the girlfriend.
+ if (PlayState.instance.currentStage.getGirlfriend() == null)
+ {
+ trace('No GF to focus on.');
+ return;
+ }
trace('Focusing camera on girlfriend.');
- var xTarget = PlayState.instance.currentStage.getGirlfriend().cameraFocusPoint.x + posX;
- var yTarget = PlayState.instance.currentStage.getGirlfriend().cameraFocusPoint.y + posY;
+ var xTarget:Float = PlayState.instance.currentStage.getGirlfriend().cameraFocusPoint.x + posX;
+ var yTarget:Float = PlayState.instance.currentStage.getGirlfriend().cameraFocusPoint.y + posY;
PlayState.instance.cameraFollowPoint.setPosition(xTarget, yTarget);
default:
@@ -97,7 +113,7 @@ class FocusCameraSongEvent extends SongEvent
public override function getTitle():String
{
- return "Focus Camera";
+ return 'Focus Camera';
}
/**
diff --git a/source/funkin/play/scoring/Scoring.hx b/source/funkin/play/scoring/Scoring.hx
index 1e2ff28bb..75d002cb5 100644
--- a/source/funkin/play/scoring/Scoring.hx
+++ b/source/funkin/play/scoring/Scoring.hx
@@ -1,5 +1,8 @@
package funkin.play.scoring;
+/**
+ * Which system to use when scoring and judging notes.
+ */
enum abstract ScoringSystem(String)
{
/**
@@ -19,9 +22,6 @@ enum abstract ScoringSystem(String)
* Scores the player based on the offset based on timing, represented by a sigmoid function.
*/
var PBOT1;
-
- // WIFE1
- // WIFE3
}
/**
@@ -35,161 +35,229 @@ class Scoring
* @param scoringSystem The scoring system to use.
* @return The score the note receives.
*/
- public static function scoreNote(msTiming:Float, scoringSystem:ScoringSystem = PBOT1)
+ public static function scoreNote(msTiming:Float, scoringSystem:ScoringSystem = PBOT1):Int
{
- switch (scoringSystem)
+ return switch (scoringSystem)
{
- case LEGACY:
- return scoreNote_LEGACY(msTiming);
- case WEEK7:
- return scoreNote_WEEK7(msTiming);
- case PBOT1:
- return scoreNote_PBOT1(msTiming);
+ case LEGACY: scoreNoteLEGACY(msTiming);
+ case WEEK7: scoreNoteWEEK7(msTiming);
+ case PBOT1: scoreNotePBOT1(msTiming);
default:
trace('ERROR: Unknown scoring system: ' + scoringSystem);
- return 0;
+ 0;
}
}
/**
* Determine the judgement a note receives under a given scoring system.
* @param msTiming The difference between the note's time and when it was hit.
+ * @param scoringSystem The scoring system to use.
* @return The judgement the note receives.
*/
public static function judgeNote(msTiming:Float, scoringSystem:ScoringSystem = PBOT1):String
{
- switch (scoringSystem)
+ return switch (scoringSystem)
{
- case LEGACY:
- return judgeNote_LEGACY(msTiming);
- case WEEK7:
- return judgeNote_WEEK7(msTiming);
- case PBOT1:
- return judgeNote_PBOT1(msTiming);
+ case LEGACY: judgeNoteLEGACY(msTiming);
+ case WEEK7: judgeNoteWEEK7(msTiming);
+ case PBOT1: judgeNotePBOT1(msTiming);
default:
trace('ERROR: Unknown scoring system: ' + scoringSystem);
- return 'miss';
+ 'miss';
}
}
/**
- * The maximum score received.
+ * The maximum score a note can receive.
*/
- public static var PBOT1_MAX_SCORE = 350;
+ public static final PBOT1_MAX_SCORE:Int = 500;
/**
- * The minimum score received.
+ * The offset of the sigmoid curve for the scoring function.
*/
- public static var PBOT1_MIN_SCORE = 0;
+ public static final PBOT1_SCORING_OFFSET:Float = 54.99;
+
+ /**
+ * The slope of the sigmoid curve for the scoring function.
+ */
+ public static final PBOT1_SCORING_SLOPE:Float = 0.080;
+
+ /**
+ * The minimum score a note can receive while still being considered a hit.
+ */
+ public static final PBOT1_MIN_SCORE:Float = 9.0;
+
+ /**
+ * The score a note receives when it is missed.
+ */
+ public static final PBOT1_MISS_SCORE:Int = 0;
/**
* The threshold at which a note hit is considered perfect and always given the max score.
- **/
- public static var PBOT1_PERFECT_THRESHOLD = 5.0; // 5ms.
+ */
+ public static final PBOT1_PERFECT_THRESHOLD:Float = 5.0; // 5ms
/**
- * The threshold at which a note hit is considered missed and always given the min score.
- **/
- public static var PBOT1_MISS_THRESHOLD = (10 / 60) * 1000; // ~166ms
+ * The threshold at which a note hit is considered missed.
+ * `160ms`
+ */
+ public static final PBOT1_MISS_THRESHOLD:Float = 160.0;
- // Magic numbers used to tweak the shape of the scoring function.
- public static var PBOT1_SCORING_SLOPE:Float = 0.052;
- public static var PBOT1_SCORING_OFFSET:Float = 80.0;
+ /**
+ * The time within which a note is considered to have been hit with the Killer judgement.
+ * `~7.5% of the hit window, or 12.5ms`
+ */
+ public static final PBOT1_KILLER_THRESHOLD:Float = 12.5;
- static function scoreNote_PBOT1(msTiming:Float):Int
+ /**
+ * The time within which a note is considered to have been hit with the Sick judgement.
+ * `~25% of the hit window, or 45ms`
+ */
+ public static final PBOT1_SICK_THRESHOLD:Float = 45.0;
+
+ /**
+ * The time within which a note is considered to have been hit with the Good judgement.
+ * `~55% of the hit window, or 90ms`
+ */
+ public static final PBOT1_GOOD_THRESHOLD:Float = 90.0;
+
+ /**
+ * The time within which a note is considered to have been hit with the Bad judgement.
+ * `~85% of the hit window, or 135ms`
+ */
+ public static final PBOT1_BAD_THRESHOLD:Float = 135.0;
+
+ /**
+ * The time within which a note is considered to have been hit with the Shit judgement.
+ * `100% of the hit window, or 160ms`
+ */
+ public static final PBOT1_SHIT_THRESHOLD:Float = 160.0;
+
+ static function scoreNotePBOT1(msTiming:Float):Int
{
// Absolute value because otherwise late hits are always given the max score.
- var absTiming = Math.abs(msTiming);
- if (absTiming > PBOT1_MISS_THRESHOLD)
- {
- return PBOT1_MIN_SCORE;
- }
- else if (absTiming < PBOT1_PERFECT_THRESHOLD)
- {
- return PBOT1_MAX_SCORE;
- }
- else
- {
- // Calculate the score based on the timing using a sigmoid function.
- var factor:Float = 1.0 - (1.0 / (1.0 + Math.exp(-PBOT1_SCORING_SLOPE * (absTiming - PBOT1_SCORING_OFFSET))));
+ var absTiming:Float = Math.abs(msTiming);
- var score = Std.int(PBOT1_MAX_SCORE * factor);
+ return switch (absTiming)
+ {
+ case(_ > PBOT1_MISS_THRESHOLD) => true:
+ PBOT1_MISS_SCORE;
+ case(_ < PBOT1_PERFECT_THRESHOLD) => true:
+ PBOT1_MAX_SCORE;
+ default:
+ var factor:Float = 1.0 - (1.0 / (1.0 + Math.exp(-PBOT1_SCORING_SLOPE * (absTiming - PBOT1_SCORING_OFFSET))));
+ var score:Int = Std.int(PBOT1_MAX_SCORE * factor + PBOT1_MIN_SCORE);
- return score;
+ score;
}
}
- static function judgeNote_PBOT1(msTiming:Float):String
+ static function judgeNotePBOT1(msTiming:Float):String
{
- return judgeNote_WEEK7(msTiming);
+ var absTiming:Float = Math.abs(msTiming);
+
+ return switch (absTiming)
+ {
+ case(_ < PBOT1_KILLER_THRESHOLD) => true:
+ 'killer';
+ case(_ < PBOT1_SICK_THRESHOLD) => true:
+ 'sick';
+ case(_ < PBOT1_GOOD_THRESHOLD) => true:
+ 'good';
+ case(_ < PBOT1_BAD_THRESHOLD) => true:
+ 'bad';
+ case(_ < PBOT1_SHIT_THRESHOLD) => true:
+ 'shit';
+ default:
+ 'miss';
+ }
}
/**
* The window of time in which a note is considered to be hit, on the Funkin Legacy scoring system.
* Currently equal to 10 frames at 60fps, or ~166ms.
*/
- public static var LEGACY_HIT_WINDOW:Float = (10 / 60) * 1000; // 166.67 ms hit window (10 frames at 60fps)
+ public static final LEGACY_HIT_WINDOW:Float = (10 / 60) * 1000; // 166.67 ms hit window (10 frames at 60fps)
/**
- * The threshold at which a note is considered a "Bad" hit rather than a "Shit" hit.
+ * The threshold at which a note is considered a "Sick" hit rather than another judgement.
* Represented as a percentage of the total hit window.
*/
- public static var LEGACY_BAD_THRESHOLD:Float = 0.9;
+ public static final LEGACY_SICK_THRESHOLD:Float = 0.2;
- public static var LEGACY_GOOD_THRESHOLD:Float = 0.75;
- public static var LEGACY_SICK_THRESHOLD:Float = 0.2;
- public static var LEGACY_SHIT_SCORE = 50;
- public static var LEGACY_BAD_SCORE = 100;
- public static var LEGACY_GOOD_SCORE = 200;
- public static var LEGACY_SICK_SCORE = 350;
+ /**
+ * The threshold at which a note is considered a "Good" hit rather than another judgement.
+ * Represented as a percentage of the total hit window.
+ */
+ public static final LEGACY_GOOD_THRESHOLD:Float = 0.75;
- static function scoreNote_LEGACY(msTiming:Float):Int
+ /**
+ * The threshold at which a note is considered a "Bad" hit rather than another judgement.
+ * Represented as a percentage of the total hit window.
+ */
+ public static final LEGACY_BAD_THRESHOLD:Float = 0.9;
+
+ /**
+ * The score a note receives when hit within the Shit threshold, rather than a miss.
+ * Represented as a percentage of the total hit window.
+ */
+ public static final LEGACY_SHIT_THRESHOLD:Float = 1.0;
+
+ /**
+ * The score a note receives when hit within the Sick threshold.
+ */
+ public static final LEGACY_SICK_SCORE:Int = 350;
+
+ /**
+ * The score a note receives when hit within the Good threshold.
+ */
+ public static final LEGACY_GOOD_SCORE:Int = 200;
+
+ /**
+ * The score a note receives when hit within the Bad threshold.
+ */
+ public static final LEGACY_BAD_SCORE:Int = 100;
+
+ /**
+ * The score a note receives when hit within the Shit threshold.
+ */
+ public static final LEGACY_SHIT_SCORE:Int = 50;
+
+ static function scoreNoteLEGACY(msTiming:Float):Int
{
- var absTiming = Math.abs(msTiming);
- if (absTiming < LEGACY_HIT_WINDOW * LEGACY_SICK_THRESHOLD)
+ var absTiming:Float = Math.abs(msTiming);
+
+ return switch (absTiming)
{
- return LEGACY_SICK_SCORE;
- }
- else if (absTiming < LEGACY_HIT_WINDOW * LEGACY_GOOD_THRESHOLD)
- {
- return LEGACY_GOOD_SCORE;
- }
- else if (absTiming < LEGACY_HIT_WINDOW * LEGACY_BAD_THRESHOLD)
- {
- return LEGACY_BAD_SCORE;
- }
- else if (absTiming < LEGACY_HIT_WINDOW)
- {
- return LEGACY_SHIT_SCORE;
- }
- else
- {
- return 0;
+ case(_ < LEGACY_HIT_WINDOW * LEGACY_SICK_THRESHOLD) => true:
+ LEGACY_SICK_SCORE;
+ case(_ < LEGACY_HIT_WINDOW * LEGACY_GOOD_THRESHOLD) => true:
+ LEGACY_GOOD_SCORE;
+ case(_ < LEGACY_HIT_WINDOW * LEGACY_BAD_THRESHOLD) => true:
+ LEGACY_BAD_SCORE;
+ case(_ < LEGACY_HIT_WINDOW * LEGACY_SHIT_THRESHOLD) => true:
+ LEGACY_SHIT_SCORE;
+ default:
+ 0;
}
}
- static function judgeNote_LEGACY(msTiming:Float):String
+ static function judgeNoteLEGACY(msTiming:Float):String
{
- var absTiming = Math.abs(msTiming);
- if (absTiming < LEGACY_HIT_WINDOW * LEGACY_SICK_THRESHOLD)
+ var absTiming:Float = Math.abs(msTiming);
+
+ return switch (absTiming)
{
- return 'sick';
- }
- else if (absTiming < LEGACY_HIT_WINDOW * LEGACY_GOOD_THRESHOLD)
- {
- return 'good';
- }
- else if (absTiming < LEGACY_HIT_WINDOW * LEGACY_BAD_THRESHOLD)
- {
- return 'bad';
- }
- else if (absTiming < LEGACY_HIT_WINDOW)
- {
- return 'shit';
- }
- else
- {
- return 'miss';
+ case(_ < LEGACY_HIT_WINDOW * LEGACY_SICK_THRESHOLD) => true:
+ 'sick';
+ case(_ < LEGACY_HIT_WINDOW * LEGACY_GOOD_THRESHOLD) => true:
+ 'good';
+ case(_ < LEGACY_HIT_WINDOW * LEGACY_BAD_THRESHOLD) => true:
+ 'bad';
+ case(_ < LEGACY_HIT_WINDOW * LEGACY_SHIT_THRESHOLD) => true:
+ 'shit';
+ default:
+ 'miss';
}
}
@@ -197,19 +265,34 @@ class Scoring
* The window of time in which a note is considered to be hit, on the Funkin Classic scoring system.
* Same as L 10 frames at 60fps, or ~166ms.
*/
- public static var WEEK7_HIT_WINDOW = LEGACY_HIT_WINDOW;
+ public static final WEEK7_HIT_WINDOW:Float = LEGACY_HIT_WINDOW;
- public static var WEEK7_BAD_THRESHOLD = 0.8; // 80% of the hit window, or ~125ms
- public static var WEEK7_GOOD_THRESHOLD = 0.55; // 55% of the hit window, or ~91ms
- public static var WEEK7_SICK_THRESHOLD = 0.2; // 20% of the hit window, or ~33ms
- public static var WEEK7_SHIT_SCORE = 50;
- public static var WEEK7_BAD_SCORE = 100;
- public static var WEEK7_GOOD_SCORE = 200;
- public static var WEEK7_SICK_SCORE = 350;
+ public static final WEEK7_BAD_THRESHOLD:Float = 0.8; // 80% of the hit window, or ~125ms
+ public static final WEEK7_GOOD_THRESHOLD:Float = 0.55; // 55% of the hit window, or ~91ms
+ public static final WEEK7_SICK_THRESHOLD:Float = 0.2; // 20% of the hit window, or ~33ms
+ public static final WEEK7_SHIT_SCORE:Int = 50;
+ public static final WEEK7_BAD_SCORE:Int = 100;
+ public static final WEEK7_GOOD_SCORE:Int = 200;
+ public static final WEEK7_SICK_SCORE:Int = 350;
- static function scoreNote_WEEK7(msTiming:Float):Int
+ static function scoreNoteWEEK7(msTiming:Float):Int
{
- var absTiming = Math.abs(msTiming);
+ var absTiming:Float = Math.abs(msTiming);
+
+ return switch (absTiming)
+ {
+ case(_ < WEEK7_HIT_WINDOW * WEEK7_SICK_THRESHOLD) => true:
+ LEGACY_SICK_SCORE;
+ case(_ < WEEK7_HIT_WINDOW * WEEK7_GOOD_THRESHOLD) => true:
+ LEGACY_GOOD_SCORE;
+ case(_ < WEEK7_HIT_WINDOW * WEEK7_BAD_THRESHOLD) => true:
+ LEGACY_BAD_SCORE;
+ case(_ < WEEK7_HIT_WINDOW) => true:
+ LEGACY_SHIT_SCORE;
+ default:
+ 0;
+ }
+
if (absTiming < WEEK7_HIT_WINDOW * WEEK7_SICK_THRESHOLD)
{
return WEEK7_SICK_SCORE;
@@ -232,7 +315,7 @@ class Scoring
}
}
- static function judgeNote_WEEK7(msTiming:Float):String
+ static function judgeNoteWEEK7(msTiming:Float):String
{
var absTiming = Math.abs(msTiming);
if (absTiming < WEEK7_HIT_WINDOW * WEEK7_SICK_THRESHOLD)
diff --git a/source/funkin/play/song/SongData.hx b/source/funkin/play/song/SongData.hx
index 3666885bd..60ae32ec1 100644
--- a/source/funkin/play/song/SongData.hx
+++ b/source/funkin/play/song/SongData.hx
@@ -18,9 +18,9 @@ class SongDataParser
*/
static final songCache:Map = new Map();
- static final DEFAULT_SONG_ID = 'UNKNOWN';
- static final SONG_DATA_PATH = 'songs/';
- static final SONG_DATA_SUFFIX = '-metadata.json';
+ static final DEFAULT_SONG_ID:String = 'UNKNOWN';
+ static final SONG_DATA_PATH:String = 'songs/';
+ static final SONG_DATA_SUFFIX:String = '-metadata.json';
/**
* Parses and preloads the game's song metadata and scripts when the game starts.
@@ -30,7 +30,7 @@ class SongDataParser
public static function loadSongCache():Void
{
clearSongCache();
- trace("Loading song cache...");
+ trace('Loading song cache...');
//
// SCRIPTED SONGS
@@ -54,12 +54,10 @@ class SongDataParser
//
// UNSCRIPTED SONGS
//
- var songIdList:Array = DataAssets.listDataFilesInPath(SONG_DATA_PATH, SONG_DATA_SUFFIX).map(function(songDataPath:String):String
- {
+ var songIdList:Array = DataAssets.listDataFilesInPath(SONG_DATA_PATH, SONG_DATA_SUFFIX).map(function(songDataPath:String):String {
return songDataPath.split('/')[0];
});
- var unscriptedSongIds:Array = songIdList.filter(function(songId:String):Bool
- {
+ var unscriptedSongIds:Array = songIdList.filter(function(songId:String):Bool {
return !songCache.exists(songId);
});
trace(' Instantiating ${unscriptedSongIds.length} non-scripted songs...');
@@ -67,7 +65,7 @@ class SongDataParser
{
try
{
- var song = new Song(songId);
+ var song:Song = new Song(songId);
if (song != null)
{
trace(' Loaded song data: ${song.songId}');
@@ -88,6 +86,8 @@ class SongDataParser
/**
* Retrieves a particular song from the cache.
+ * @param songId The ID of the song to retrieve.
+ * @return The song, or null if it was not found.
*/
public static function fetchSong(songId:String):Null
{
@@ -331,7 +331,7 @@ typedef RawSongNoteData =
abstract SongNoteData(RawSongNoteData)
{
- public function new(time:Float, data:Int, length:Float = 0, kind:String = "")
+ public function new(time:Float, data:Int, length:Float = 0, kind:String = '')
{
this =
{
diff --git a/source/funkin/play/stage/Bopper.hx b/source/funkin/play/stage/Bopper.hx
index 757893d7f..1c03cb7bc 100644
--- a/source/funkin/play/stage/Bopper.hx
+++ b/source/funkin/play/stage/Bopper.hx
@@ -40,7 +40,7 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
* Add a suffix to the `idle` animation (or `danceLeft` and `danceRight` animations)
* that this bopper will play.
*/
- public var idleSuffix(default, set):String = "";
+ public var idleSuffix(default, set):String = '';
/**
* Whether this bopper should bop every beat. By default it's true, but when used
@@ -60,7 +60,7 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
*/
public var globalOffsets(default, set):Array = [0, 0];
- function set_globalOffsets(value:Array)
+ function set_globalOffsets(value:Array):Array
{
if (globalOffsets == null) globalOffsets = [0, 0];
if (globalOffsets == value) return value;
@@ -70,14 +70,15 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
this.x += xDiff;
this.y += yDiff;
- return animOffsets = value;
+ globalOffsets = value;
+ return value;
}
var animOffsets(default, set):Array = [0, 0];
public var originalPosition:FlxPoint = new FlxPoint(0, 0);
- function set_animOffsets(value:Array)
+ function set_animOffsets(value:Array):Array
{
if (animOffsets == null) animOffsets = [0, 0];
if (animOffsets == value) return value;
@@ -102,8 +103,11 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
super();
this.danceEvery = danceEvery;
- this.animation.callback = this.onAnimationFrame;
- this.animation.finishCallback = this.onAnimationFinished;
+ if (this.animation != null)
+ {
+ this.animation.callback = this.onAnimationFrame;
+ this.animation.finishCallback = this.onAnimationFinished;
+ }
}
/**
diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx
index 3dd167fd6..e58a9fa84 100644
--- a/source/funkin/play/stage/Stage.hx
+++ b/source/funkin/play/stage/Stage.hx
@@ -114,7 +114,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
* The default stage construction routine. Called when the stage is going to be played in.
* Instantiates each prop and adds it to the stage, while setting its parameters.
*/
- function buildStage()
+ function buildStage():Void
{
trace('Building stage for display: ${this.stageId}');
@@ -143,9 +143,9 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
// Initalize sprite frames.
switch (dataProp.animType)
{
- case "packer":
+ case 'packer':
propSprite.frames = Paths.getPackerAtlas(dataProp.assetPath);
- default: // "sparrow"
+ default: // 'sparrow'
propSprite.frames = Paths.getSparrowAtlas(dataProp.assetPath);
}
}
@@ -189,7 +189,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
switch (dataProp.animType)
{
- case "packer":
+ case 'packer':
for (propAnim in dataProp.animations)
{
propSprite.animation.add(propAnim.name, propAnim.frameIndices);
@@ -199,7 +199,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
cast(propSprite, Bopper).setAnimationOffsets(propAnim.name, propAnim.offsets[0], propAnim.offsets[1]);
}
}
- default: // "sparrow"
+ default: // 'sparrow'
FlxAnimationUtil.addAtlasAnimations(propSprite, dataProp.animations);
if (Std.isOfType(propSprite, Bopper))
{
@@ -302,7 +302,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
/**
* Used by the PlayState to add a character to the stage.
*/
- public function addCharacter(character:BaseCharacter, charType:CharacterType)
+ public function addCharacter(character:BaseCharacter, charType:CharacterType):Void
{
if (character == null) return;
@@ -325,16 +325,16 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
switch (charType)
{
case BF:
- this.characters.set("bf", character);
+ this.characters.set('bf', character);
charData = _data.characters.bf;
character.flipX = !character.getDataFlipX();
character.initHealthIcon(false);
case GF:
- this.characters.set("gf", character);
+ this.characters.set('gf', character);
charData = _data.characters.gf;
character.flipX = character.getDataFlipX();
case DAD:
- this.characters.set("dad", character);
+ this.characters.set('dad', character);
charData = _data.characters.dad;
character.flipX = character.getDataFlipX();
character.initHealthIcon(true);
@@ -414,11 +414,11 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
{
if (pop)
{
- var boyfriend:BaseCharacter = getCharacter("bf");
+ var boyfriend:BaseCharacter = getCharacter('bf');
// Remove the character from the stage.
this.remove(boyfriend);
- this.characters.remove("bf");
+ this.characters.remove('bf');
return boyfriend;
}
diff --git a/source/funkin/ui/animDebugShit/FlxAnimateTest.hx b/source/funkin/ui/animDebugShit/FlxAnimateTest.hx
new file mode 100644
index 000000000..738e109ef
--- /dev/null
+++ b/source/funkin/ui/animDebugShit/FlxAnimateTest.hx
@@ -0,0 +1,48 @@
+package funkin.ui.animDebugShit;
+
+import flixel.FlxG;
+import funkin.graphics.adobeanimate.FlxAtlasSprite;
+
+/**
+ * A simple test of FlxAnimate.
+ * Delete this later?
+ */
+class FlxAnimateTest extends MusicBeatState
+{
+ var sprite:FlxAtlasSprite;
+
+ public function new()
+ {
+ super();
+ this.bgColor = 0xFF999999;
+ }
+
+ public override function create():Void
+ {
+ super.create();
+
+ sprite = new FlxAtlasSprite(0, 0, 'shared:assets/shared/images/characters/tankman');
+ add(sprite);
+
+ sprite.playAnimation('idle');
+ }
+
+ public override function update(elapsed:Float):Void
+ {
+ super.update(elapsed);
+
+ if (FlxG.keys.justPressed.SPACE) sprite.playAnimation('idle');
+
+ if (FlxG.keys.justPressed.W) sprite.playAnimation('singUP');
+
+ if (FlxG.keys.justPressed.A) sprite.playAnimation('singLEFT');
+
+ if (FlxG.keys.justPressed.S) sprite.playAnimation('singDOWN');
+
+ if (FlxG.keys.justPressed.D) sprite.playAnimation('singRIGHT');
+
+ if (FlxG.keys.justPressed.J) sprite.playAnimation('hehPrettyGood');
+
+ if (FlxG.keys.justPressed.K) sprite.playAnimation('ugh');
+ }
+}