mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-15 03:35:18 -05:00
Merge branch 'feature/flxanimate'
This commit is contained in:
commit
e035adab72
34 changed files with 1870 additions and 1673 deletions
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
|
@ -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
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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`
|
||||
|
|
22
Project.xml
22
Project.xml
|
@ -115,21 +115,21 @@
|
|||
<assets path="assets/fonts" embed='true' />
|
||||
<!-- _______________________________ Libraries ______________________________ -->
|
||||
|
||||
<haxelib name="openfl" />
|
||||
<haxelib name="flixel" />
|
||||
<haxelib name="lime" /> <!-- Game engine backend -->
|
||||
<haxelib name="openfl" /> <!-- Game engine backend -->
|
||||
<haxelib name="flixel" /> <!-- Game engine -->
|
||||
<haxedev set='webgl' />
|
||||
|
||||
<!--In case you want to use the addons package-->
|
||||
<haxelib name="flixel-addons" />
|
||||
<haxelib name="hscript" />
|
||||
<haxelib name="haxeui-core"/>
|
||||
<haxelib name="haxeui-flixel"/>
|
||||
<haxelib name="flixel-addons" /> <!-- Additional utilities for Flixel -->
|
||||
<haxelib name="hscript" /> <!-- Scripting -->
|
||||
|
||||
<haxelib name="flixel-ui" />
|
||||
<haxelib name="haxeui-core"/>
|
||||
<haxelib name="haxeui-flixel"/>
|
||||
<haxelib name="polymod" />
|
||||
<haxelib name="flxanimate" />
|
||||
<haxelib name="flixel-ui" /> <!-- UI framework (deprecate this? -->
|
||||
<haxelib name="haxeui-core"/> <!-- UI framework -->
|
||||
<haxelib name="haxeui-flixel"/> <!-- Integrate HaxeUI with Flixel -->
|
||||
<haxelib name="polymod" /> <!-- Modding framework -->
|
||||
<haxelib name="flxanimate" /> <!-- Texture atlas rendering -->
|
||||
<haxelib name="hxcodec" /> <!-- Video playback -->
|
||||
|
||||
<haxelib name="thx.semver" />
|
||||
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
10
hmm.json
10
hmm.json
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"dependencies": [{
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "discord_rpc",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
|
@ -63,6 +64,13 @@
|
|||
"type": "haxelib",
|
||||
"version": "2.5.0"
|
||||
},
|
||||
{
|
||||
"name": "hxcodec",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "master",
|
||||
"url": "https://github.com/EliteMasterEric/hxCodec"
|
||||
},
|
||||
{
|
||||
"name": "hxcpp",
|
||||
"type": "haxelib",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<FlxSprite>
|
||||
{
|
||||
// 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];
|
||||
*/
|
||||
}
|
||||
}
|
|
@ -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<FlxSymbol>;
|
||||
|
||||
public function new(x:Float, y:Float)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
sprGrp = new FlxTypedGroup<FlxSymbol>();
|
||||
|
||||
var tests:Array<String> = ['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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<String, Symbol> = new Map();
|
||||
public static var actualSprites:Map<String, Sprite> = new Map();
|
||||
|
||||
var _atlas:Map<String, Sprite>;
|
||||
var _symbolData:Map<String, Symbol>;
|
||||
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<String, Sprite>();
|
||||
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<String, Sprite>
|
||||
{
|
||||
var sprShitty:Spritemap = cast CoolUtil.coolJSON(json);
|
||||
var sprMap:Map<String, Sprite> = 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<Symbol>)
|
||||
{
|
||||
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<Layer> = timelineData.L;
|
||||
|
||||
if (!timelineData.sortedForRender)
|
||||
{
|
||||
timelineData.sortedForRender = true;
|
||||
layerData.reverse();
|
||||
}
|
||||
|
||||
for (layerStuff in layerData)
|
||||
{
|
||||
var frames:Array<Frame> = layerStuff.FR;
|
||||
|
||||
for (frame in frames)
|
||||
{
|
||||
var elements:Array<Element> = 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<Array<VALIDFRAME>> = [];
|
||||
|
||||
// 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<Array<Array<Float>>> = [];
|
||||
|
||||
public static var trpHelpIDK:Array<Array<Array<Float>>> = [];
|
||||
|
||||
public static var loopedFrameShit:Int = 0;
|
||||
|
||||
public static var funnyMatrix:Matrix = new Matrix();
|
||||
public static var matrixFlipper:Array<Matrix> = [];
|
||||
|
||||
// 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<Int> = [];
|
||||
var frameMap:Map<Int, Frame> = 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<Array<Float>>,
|
||||
trpArray:Array<Array<Float>>,
|
||||
fullMatrix:Matrix
|
||||
}
|
||||
|
||||
typedef AnimJson =
|
||||
{
|
||||
AN:Animation,
|
||||
SD:SymbolDictionary,
|
||||
MD:MetaData
|
||||
}
|
||||
|
||||
typedef Animation =
|
||||
{
|
||||
N:String,
|
||||
SN:String,
|
||||
TL:Timeline
|
||||
}
|
||||
|
||||
typedef SymbolDictionary =
|
||||
{
|
||||
S:Array<Symbol>
|
||||
}
|
||||
|
||||
typedef Symbol =
|
||||
{
|
||||
/**Symbol name*/
|
||||
SN:String,
|
||||
|
||||
TL:Timeline
|
||||
}
|
||||
|
||||
typedef Timeline =
|
||||
{
|
||||
?sortedForRender:Bool,
|
||||
L:Array<Layer>
|
||||
}
|
||||
|
||||
typedef Layer =
|
||||
{
|
||||
LN:String,
|
||||
FR:Array<Frame>
|
||||
}
|
||||
|
||||
typedef Frame =
|
||||
{
|
||||
E:Array<Element>,
|
||||
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<Float>,
|
||||
|
||||
TRP:
|
||||
{
|
||||
x:Float, y:Float
|
||||
}
|
||||
}
|
||||
|
||||
typedef AlsoSymbolInstance =
|
||||
{
|
||||
N:String,
|
||||
M3D:Array<Float>
|
||||
}
|
||||
|
||||
typedef MetaData =
|
||||
{
|
||||
/**
|
||||
* Framerate
|
||||
*/
|
||||
FRT:Int
|
||||
}
|
||||
|
||||
// SPRITEMAP BULLSHIT
|
||||
typedef Spritemap =
|
||||
{
|
||||
ATLAS:
|
||||
{
|
||||
SPRITES:Array<SpriteBullshit>
|
||||
},
|
||||
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
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -29,8 +29,7 @@ class FlxAudioGroup extends FlxTypedGroup<FlxSound>
|
|||
|
||||
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<FlxSound>
|
|||
|
||||
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<FlxSound>
|
|||
{
|
||||
#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<FlxSound>
|
|||
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<FlxSound>
|
|||
*/
|
||||
public function pause()
|
||||
{
|
||||
forEachAlive(function(sound:FlxSound)
|
||||
{
|
||||
forEachAlive(function(sound:FlxSound) {
|
||||
sound.pause();
|
||||
});
|
||||
}
|
||||
|
@ -142,8 +137,7 @@ class FlxAudioGroup extends FlxTypedGroup<FlxSound>
|
|||
*/
|
||||
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<FlxSound>
|
|||
*/
|
||||
public function resume()
|
||||
{
|
||||
forEachAlive(function(sound:FlxSound)
|
||||
{
|
||||
forEachAlive(function(sound:FlxSound) {
|
||||
sound.resume();
|
||||
});
|
||||
}
|
||||
|
@ -164,8 +157,7 @@ class FlxAudioGroup extends FlxTypedGroup<FlxSound>
|
|||
*/
|
||||
public function stop()
|
||||
{
|
||||
forEachAlive(function(sound:FlxSound)
|
||||
{
|
||||
forEachAlive(function(sound:FlxSound) {
|
||||
sound.stop();
|
||||
});
|
||||
}
|
||||
|
@ -188,8 +180,7 @@ class FlxAudioGroup extends FlxTypedGroup<FlxSound>
|
|||
{
|
||||
var deviation:Float = 0;
|
||||
|
||||
forEachAlive(function(sound:FlxSound)
|
||||
{
|
||||
forEachAlive(function(sound:FlxSound) {
|
||||
if (targetTime == null) targetTime = sound.time;
|
||||
else
|
||||
{
|
||||
|
|
174
source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx
Normal file
174
source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx
Normal file
|
@ -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<String, flxanimate.animate.FlxAnim.ButtonSettings>,
|
||||
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:FlxTypedSignal<String->Void> = new FlxTypedSignal<String->Void>();
|
||||
|
||||
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<String>
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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':
|
||||
|
|
|
@ -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,15 @@ 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;
|
||||
<<<<<<< HEAD
|
||||
import funkin.play.character.CharacterData;
|
||||
=======
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.cutscene.VanillaCutscenes;
|
||||
>>>>>>> feature/flxanimate
|
||||
import funkin.play.event.SongEvent.SongEventParser;
|
||||
import funkin.play.scoring.Scoring;
|
||||
import funkin.play.song.Song;
|
||||
|
@ -37,7 +41,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 +52,9 @@ import lime.ui.Haptic;
|
|||
import Discord.DiscordClient;
|
||||
#end
|
||||
|
||||
/**
|
||||
* The gameplay state, where all the rhythm gaming happens.
|
||||
*/
|
||||
class PlayState extends MusicBeatState
|
||||
{
|
||||
/**
|
||||
|
@ -81,14 +88,20 @@ 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;
|
||||
|
||||
/**
|
||||
<<<<<<< HEAD
|
||||
* Whether the inputs should be disabled for whatever reason... used for the stage edit lol!
|
||||
*/
|
||||
public static var disableKeys:Bool = false;
|
||||
=======
|
||||
* Whether the game is currently in dialog, and gameplay should be stopped.
|
||||
*/
|
||||
public static var isInDialog:Bool = false;
|
||||
>>>>>>> feature/flxanimate
|
||||
|
||||
/**
|
||||
* Whether the game is currently in the countdown before the song resumes.
|
||||
|
@ -188,6 +201,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 +262,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 +297,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 +312,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 +480,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 +505,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 +516,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 +531,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 +539,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 +559,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 +581,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 +824,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 +844,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 +908,10 @@ class PlayState extends MusicBeatState
|
|||
|
||||
// Add the stage to the scene.
|
||||
this.add(currentStage);
|
||||
|
||||
#if debug
|
||||
FlxG.console.registerObject('stage', currentStage);
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -941,7 +981,7 @@ class PlayState extends MusicBeatState
|
|||
{
|
||||
if (dialogueBox != null)
|
||||
{
|
||||
isInCutscene = true;
|
||||
isInDialog = true;
|
||||
|
||||
if (currentSong.song.toLowerCase() == 'thorns')
|
||||
{
|
||||
|
@ -1016,7 +1056,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 +1070,6 @@ class PlayState extends MusicBeatState
|
|||
vocalsFinished = true;
|
||||
};
|
||||
|
||||
trace(vocals);
|
||||
|
||||
activeNotes = new FlxTypedGroup<Note>();
|
||||
activeNotes.zIndex = 1000;
|
||||
add(activeNotes);
|
||||
|
@ -1381,10 +1419,14 @@ class PlayState extends MusicBeatState
|
|||
|
||||
startingSong = true;
|
||||
|
||||
// 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();
|
||||
|
||||
|
@ -1521,7 +1563,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);
|
||||
|
@ -1540,7 +1582,6 @@ class PlayState extends MusicBeatState
|
|||
switch (Conductor.currentBeat)
|
||||
{
|
||||
case 16:
|
||||
camZooming = true;
|
||||
gfSpeed = 2;
|
||||
case 48:
|
||||
gfSpeed = 1;
|
||||
|
@ -1551,7 +1592,7 @@ class PlayState extends MusicBeatState
|
|||
}
|
||||
}
|
||||
|
||||
if ((!isInCutscene && !disableKeys) && !_exiting)
|
||||
if (!isInCutscene && !isInDialog && !disableKeys && !_exiting)
|
||||
{
|
||||
// RESET = Quick Game Over Screen
|
||||
if (controls.RESET)
|
||||
|
@ -1664,8 +1705,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);
|
||||
|
||||
|
@ -1737,14 +1776,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<FlxKey> = [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'))
|
||||
{
|
||||
|
@ -1896,7 +1946,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;
|
||||
|
@ -2289,25 +2339,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;
|
||||
|
@ -2425,7 +2472,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.
|
||||
|
@ -2451,7 +2498,7 @@ class PlayState extends MusicBeatState
|
|||
* Function called before closing the current substate.
|
||||
* @param subState
|
||||
*/
|
||||
override function closeSubState()
|
||||
public override function closeSubState()
|
||||
{
|
||||
if (isGamePaused)
|
||||
{
|
||||
|
@ -2461,7 +2508,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();
|
||||
|
@ -2481,12 +2528,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;
|
||||
|
||||
|
@ -2508,6 +2557,8 @@ class PlayState extends MusicBeatState
|
|||
if (currentStage != null) currentStage.dispatchToCharacters(event);
|
||||
|
||||
// TODO: Dispatch event to song script
|
||||
|
||||
// TODO: Dispatch event to note script
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
722
source/funkin/play/character/AnimateAtlasCharacter.hx
Normal file
722
source/funkin/play/character/AnimateAtlasCharacter.hx
Normal file
|
@ -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<Array<Float>>,
|
||||
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<String, AnimateAtlasAnimation> = new Map<String, AnimateAtlasAnimation>();
|
||||
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<AnimateAtlasAnimation> = 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<V>(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<FlxCamera>):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<FlxCamera>):Array<FlxCamera>
|
||||
{
|
||||
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 {}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<String, CharacterData> = new Map<String, CharacterData>();
|
||||
static final characterScriptedClass:Map<String, String> = new Map<String, String>();
|
||||
|
@ -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<String> = DataAssets.listDataFilesInPath('characters/');
|
||||
var unscriptedCharIds:Array<String> = charIdList.filter(function(charId:String):Bool
|
||||
{
|
||||
var unscriptedCharIds:Array<String> = charIdList.filter(function(charId:String):Bool {
|
||||
return !characterCache.exists(charId);
|
||||
});
|
||||
trace(' Fetching data for ${unscriptedCharIds.length} characters...');
|
||||
|
@ -85,9 +80,17 @@ class CharacterDataParser
|
|||
trace(' Instantiating ${scriptedCharClassNames1.length} (Sparrow) scripted characters...');
|
||||
for (charCls in scriptedCharClassNames1)
|
||||
{
|
||||
var character = ScriptedSparrowCharacter.init(charCls, DEFAULT_CHAR_ID);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var scriptedCharClassNames2:Array<String> = ScriptedPackerCharacter.listScriptClasses();
|
||||
|
@ -96,9 +99,17 @@ class CharacterDataParser
|
|||
trace(' Instantiating ${scriptedCharClassNames2.length} (Packer) scripted characters...');
|
||||
for (charCls in scriptedCharClassNames2)
|
||||
{
|
||||
var character = ScriptedPackerCharacter.init(charCls, DEFAULT_CHAR_ID);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var scriptedCharClassNames3:Array<String> = ScriptedMultiSparrowCharacter.listScriptClasses();
|
||||
|
@ -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<String> = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Only instantiate the ones not populated above.
|
||||
// ScriptedBaseCharacter.listScriptClasses() will pick up scripts extending the other classes.
|
||||
var scriptedCharClassNames:Array<String> = 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,16 +182,23 @@ 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<BaseCharacter>
|
||||
{
|
||||
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);
|
||||
|
||||
|
@ -168,33 +208,43 @@ class CharacterDataParser
|
|||
{
|
||||
switch (charData.renderType)
|
||||
{
|
||||
case CharacterRenderType.MULTISPARROW:
|
||||
case CharacterRenderType.AnimateAtlas:
|
||||
char = ScriptedAnimateAtlasCharacter.init(charScriptClass, charId);
|
||||
case CharacterRenderType.MultiSparrow:
|
||||
char = ScriptedMultiSparrowCharacter.init(charScriptClass, charId);
|
||||
case CharacterRenderType.SPARROW:
|
||||
case CharacterRenderType.Sparrow:
|
||||
char = ScriptedSparrowCharacter.init(charScriptClass, charId);
|
||||
case CharacterRenderType.PACKER:
|
||||
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);
|
||||
char = ScriptedBaseCharacter.init(charScriptClass, charId, CharacterRenderType.Custom);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (charData.renderType)
|
||||
{
|
||||
case CharacterRenderType.MULTISPARROW:
|
||||
case CharacterRenderType.AnimateAtlas:
|
||||
char = new AnimateAtlasCharacter(charId);
|
||||
case CharacterRenderType.MultiSparrow:
|
||||
char = new MultiSparrowCharacter(charId);
|
||||
case CharacterRenderType.SPARROW:
|
||||
case CharacterRenderType.Sparrow:
|
||||
char = new SparrowCharacter(charId);
|
||||
case CharacterRenderType.PACKER:
|
||||
case CharacterRenderType.Packer:
|
||||
char = new PackerCharacter(charId);
|
||||
default:
|
||||
trace('[WARN] Creating character with undefined renderType ${charData.renderType}');
|
||||
char = new BaseCharacter(charId);
|
||||
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.
|
||||
|
@ -202,30 +252,31 @@ class CharacterDataParser
|
|||
|
||||
return char;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Failed to build character, not found in cache: ${charId}');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<CharacterData>
|
||||
{
|
||||
if (characterCache.exists(charId))
|
||||
{
|
||||
return characterCache.get(charId);
|
||||
}
|
||||
else
|
||||
{
|
||||
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<String>
|
||||
{
|
||||
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<CharacterData>
|
||||
{
|
||||
// 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<Float> = [0, 0];
|
||||
static final DEFAULT_HEALTHICON_OFFSETS:Array<Int> = [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<Float> = [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<Bool>;
|
||||
};
|
||||
|
||||
/**
|
||||
* The JSON data schema used to define the health icon for a character.
|
||||
*/
|
||||
typedef HealthIconData =
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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}');
|
||||
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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
|
||||
|
|
125
source/funkin/play/cutscene/VanillaCutscenes.hx
Normal file
125
source/funkin/play/cutscene/VanillaCutscenes.hx
Normal file
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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<Float> = data.getFloat('x');
|
||||
if (posX == null) posX = 0.0;
|
||||
var posY = data.getFloat('y');
|
||||
var posY:Null<Float> = data.getFloat('y');
|
||||
if (posY == null) posY = 0.0;
|
||||
|
||||
var char = data.getInt('char');
|
||||
var char:Null<Int> = 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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
var absTiming:Float = Math.abs(msTiming);
|
||||
|
||||
return switch (absTiming)
|
||||
{
|
||||
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.
|
||||
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);
|
||||
|
||||
var score = Std.int(PBOT1_MAX_SCORE * factor);
|
||||
|
||||
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)
|
||||
|
|
|
@ -18,9 +18,9 @@ class SongDataParser
|
|||
*/
|
||||
static final songCache:Map<String, Song> = new Map<String, Song>();
|
||||
|
||||
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<String> = DataAssets.listDataFilesInPath(SONG_DATA_PATH, SONG_DATA_SUFFIX).map(function(songDataPath:String):String
|
||||
{
|
||||
var songIdList:Array<String> = DataAssets.listDataFilesInPath(SONG_DATA_PATH, SONG_DATA_SUFFIX).map(function(songDataPath:String):String {
|
||||
return songDataPath.split('/')[0];
|
||||
});
|
||||
var unscriptedSongIds:Array<String> = songIdList.filter(function(songId:String):Bool
|
||||
{
|
||||
var unscriptedSongIds:Array<String> = 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<Song>
|
||||
{
|
||||
|
@ -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 =
|
||||
{
|
||||
|
|
|
@ -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<Float> = [0, 0];
|
||||
|
||||
function set_globalOffsets(value:Array<Float>)
|
||||
function set_globalOffsets(value:Array<Float>):Array<Float>
|
||||
{
|
||||
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<Float> = [0, 0];
|
||||
|
||||
public var originalPosition:FlxPoint = new FlxPoint(0, 0);
|
||||
|
||||
function set_animOffsets(value:Array<Float>)
|
||||
function set_animOffsets(value:Array<Float>):Array<Float>
|
||||
{
|
||||
if (animOffsets == null) animOffsets = [0, 0];
|
||||
if (animOffsets == value) return value;
|
||||
|
@ -102,9 +103,12 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
|||
super();
|
||||
this.danceEvery = danceEvery;
|
||||
|
||||
if (this.animation != null)
|
||||
{
|
||||
this.animation.callback = this.onAnimationFrame;
|
||||
this.animation.finishCallback = this.onAnimationFinished;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an animation finishes.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
48
source/funkin/ui/animDebugShit/FlxAnimateTest.hx
Normal file
48
source/funkin/ui/animDebugShit/FlxAnimateTest.hx
Normal file
|
@ -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');
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue