From 890602c7d0535abca034cb4953c114efb889bd76 Mon Sep 17 00:00:00 2001
From: Yoshman29 <81191621+YoshiCrafter29@users.noreply.github.com>
Date: Mon, 7 Nov 2022 13:34:39 +0100
Subject: [PATCH] animate atlas stuff todo
---
Project.xml | 1 +
source/animateatlas/AtlasFrameMaker.hx | 149 +++++
source/animateatlas/HelperEnums.hx | 21 +
source/animateatlas/JSONData.hx | 157 +++++
source/animateatlas/Main.hx | 109 ++++
.../displayobject/SpriteAnimationLibrary.hx | 225 ++++++++
.../displayobject/SpriteMovieClip.hx | 117 ++++
.../displayobject/SpriteSymbol.hx | 540 ++++++++++++++++++
.../tilecontainer/TileAnimationLibrary.hx | 226 ++++++++
.../tilecontainer/TileContainerMovieClip.hx | 135 +++++
.../tilecontainer/TileContainerSymbol.hx | 475 +++++++++++++++
source/funkin/system/CoolUtil.hx | 6 +
source/funkin/system/Main.hx | 2 +
update.bat | 1 +
14 files changed, 2164 insertions(+)
create mode 100644 source/animateatlas/AtlasFrameMaker.hx
create mode 100644 source/animateatlas/HelperEnums.hx
create mode 100644 source/animateatlas/JSONData.hx
create mode 100644 source/animateatlas/Main.hx
create mode 100644 source/animateatlas/displayobject/SpriteAnimationLibrary.hx
create mode 100644 source/animateatlas/displayobject/SpriteMovieClip.hx
create mode 100644 source/animateatlas/displayobject/SpriteSymbol.hx
create mode 100644 source/animateatlas/tilecontainer/TileAnimationLibrary.hx
create mode 100644 source/animateatlas/tilecontainer/TileContainerMovieClip.hx
create mode 100644 source/animateatlas/tilecontainer/TileContainerSymbol.hx
diff --git a/Project.xml b/Project.xml
index f8495d3a..91d8510e 100644
--- a/Project.xml
+++ b/Project.xml
@@ -74,6 +74,7 @@
+
diff --git a/source/animateatlas/AtlasFrameMaker.hx b/source/animateatlas/AtlasFrameMaker.hx
new file mode 100644
index 00000000..d553a5c1
--- /dev/null
+++ b/source/animateatlas/AtlasFrameMaker.hx
@@ -0,0 +1,149 @@
+package animateatlas;
+import flixel.util.FlxDestroyUtil;
+import openfl.geom.Rectangle;
+import flixel.math.FlxPoint;
+import flixel.math.FlxRect;
+import openfl.Assets;
+import haxe.Json;
+import openfl.display.BitmapData;
+import animateatlas.JSONData.AtlasData;
+import animateatlas.JSONData.AnimationData;
+import animateatlas.displayobject.SpriteAnimationLibrary;
+import animateatlas.displayobject.SpriteMovieClip;
+import flixel.graphics.FlxGraphic;
+import flixel.graphics.frames.FlxFramesCollection;
+import flixel.graphics.frames.FlxFrame;
+using StringTools;
+class AtlasFrameMaker extends FlxFramesCollection{
+
+
+ //public static var widthoffset:Int = 0;
+ //public static var heightoffset:Int = 0;
+ //public static var excludeArray:Array;
+ /**
+
+ * Creates Frames from TextureAtlas(very early and broken ok) Originally made for FNF HD by Smokey and Rozebud
+ *
+ * @param key The file path.
+ * @param _excludeArray Use this to only create selected animations. Keep null to create all of them.
+ *
+ */
+
+ public static function construct(key:String,?_excludeArray:Array = null):FlxFramesCollection{
+
+ // widthoffset = _widthoffset;
+ // heightoffset = _heightoffset;
+
+
+
+ var frameCollection:FlxFramesCollection;
+ var frameArray:Array> = [];
+ var animationData:AnimationData = Json.parse(Assets.getText(key + "/Animation.json"));
+ var atlasData:AtlasData = Json.parse(Assets.getText(key + "/spritemap.json").replace("\uFEFF", ""));//FIXED UTF8 w/ BOM error
+ var bitmapData:BitmapData = Assets.getBitmapData(key + "/spritemap.png");
+ var ss = new SpriteAnimationLibrary(animationData, atlasData, bitmapData);
+ var t = ss.createAnimation();
+ if(_excludeArray == null){
+ _excludeArray = t.getFrameLabels();
+ }
+ frameCollection = new FlxFramesCollection(FlxGraphic.fromBitmapData(bitmapData),FlxFrameCollectionType.IMAGE);
+
+
+ for(x in t.getFrameLabels()){
+
+ frameArray.push(getFramesArray(t, x,_excludeArray));
+
+
+ }
+
+ for(x in frameArray){
+ for(y in x){
+ frameCollection.pushFrame(y);
+ }
+ }
+
+ return frameCollection;
+
+ }
+
+ @:noCompletion static function getFramesArray(t:SpriteMovieClip,animation:String,_excludeArray:Array):Array
+ {
+ var sizeInfo:Rectangle = new Rectangle(0,0);
+ t.currentLabel = animation;
+ var bitMapArray:Array = [];
+ var daFramez:Array = [];
+ var firstPass = true;
+ var frameSize:FlxPoint = new FlxPoint(0,0);
+
+ for (i in t.getFrame(animation)...t.numFrames){
+ t.currentFrame = i;
+ if (t.currentLabel == animation){
+ if (_excludeArray.contains(animation)){
+ sizeInfo = t.getBounds(t);
+
+ var bitmapShit:BitmapData = new BitmapData(
+ Std.int(sizeInfo.width + Math.abs(sizeInfo.x)),Std.int(sizeInfo.height +Math.abs(sizeInfo.y)),true,0);
+
+ bitmapShit.draw(t,null,null,null,null,true);
+ bitMapArray.push(bitmapShit);
+
+ if (firstPass){
+ frameSize.set(bitmapShit.width,bitmapShit.height);
+ firstPass = false;
+ }
+ }
+ }
+ else break;
+ }
+
+ for (i in 0...bitMapArray.length){
+ var b = FlxGraphic.fromBitmapData(bitMapArray[i]);
+ var theFrame = new FlxFrame(b);
+ theFrame.parent = b;
+ theFrame.name = animation + i;
+ theFrame.sourceSize.set(frameSize.x,frameSize.y);
+ theFrame.frame = new FlxRect(0, 0, bitMapArray[i].width, bitMapArray[i].height);
+ daFramez.push(theFrame);
+
+ //trace(daFramez);
+ }
+ return daFramez;
+ }
+
+ /*public static function renderTest(key:String, animation:String, ?frame:Int = 0):Void
+ {
+ var animationData:AnimationData = Json.parse(Assets.getText(key + "/Animation.json"));
+ var atlasData:AtlasData = Json.parse(Assets.getText(key + "/spritemap.json"));
+ var bitmapData:BitmapData = Assets.getBitmapData(key + "/spritemap.png");
+ var ss = new SpriteAnimationLibrary(animationData, atlasData, bitmapData);
+ var t = ss.createAnimation();
+ var bitMapArray:Array = [];
+
+ for (i in 0...t.numFrames){
+ t.currentFrame = i;
+ if (t.currentLabel == animation){
+ //trace(t.currentFrame);
+ var bitmapShit:BitmapData = new BitmapData(Std.int(t.width),Std.int(t.height),true,0);
+ //bitmapShit.drawWithQuality(t,null,null,null,null,true);
+ bitmapShit.draw(t, new Matrix(1, 0, 0, 1, t.width/2, t.height/2), null, null, null, false);
+ bitMapArray.push(bitmapShit);
+ }
+ }
+
+ saveImage(bitMapArray[frame]);
+
+ }
+
+ public static function saveImage(bitmapData:BitmapData)
+ {
+ var b:ByteArray = new ByteArray();
+ b = bitmapData.encode(bitmapData.rect, new PNGEncoderOptions(true), b);
+ new FileDialog().save(b, "png", null, "file");
+ }
+ */
+
+
+
+
+
+}
diff --git a/source/animateatlas/HelperEnums.hx b/source/animateatlas/HelperEnums.hx
new file mode 100644
index 00000000..23f05870
--- /dev/null
+++ b/source/animateatlas/HelperEnums.hx
@@ -0,0 +1,21 @@
+package animateatlas;
+
+enum abstract LoopMode(String) from String to String {
+ public static inline var LOOP:String = "loop";
+ public static inline var PLAY_ONCE:String = "playonce";
+ public static inline var SINGLE_FRAME:String = "singleframe";
+
+ public static function isValid(value:String):Bool {
+ return value == LOOP || value == PLAY_ONCE || value == SINGLE_FRAME;
+ }
+}
+
+enum abstract SymbolType(String) from String to String {
+ public static inline var GRAPHIC:String = "graphic";
+ public static inline var MOVIE_CLIP:String = "movieclip";
+ public static inline var BUTTON:String = "button";
+
+ public static function isValid(value:String):Bool {
+ return value == GRAPHIC || value == MOVIE_CLIP || value == BUTTON;
+ }
+}
diff --git a/source/animateatlas/JSONData.hx b/source/animateatlas/JSONData.hx
new file mode 100644
index 00000000..fbb44e85
--- /dev/null
+++ b/source/animateatlas/JSONData.hx
@@ -0,0 +1,157 @@
+package animateatlas;
+
+/**
+ * All data needed for the json importer + some extra for after parsing.
+ * Stolen mostly from https://github.com/TomByrne/Starling-Extension-Adobe-Animate-Hx/blob/master/hx/src/starling/extensions/animate/AnimationAtlasData.hx
+ */
+typedef AnimationData = {
+ ?ANIMATION:SymbolData,
+ ?SYMBOL_DICTIONARY:{
+ Symbols:Array
+ },
+ ?metadata:{
+ ?framerate:Null
+ }
+}
+
+typedef AtlasData = {
+ ?ATLAS:{
+ SPRITES:Array
+ },
+ ?meta:{
+ app:String,
+ version:String,
+ image:String,
+ format:String,
+ size:{w:Int, h:Int},
+ scale:String,
+ }
+}
+
+typedef SpriteDummy = {
+ SPRITE:SpriteData
+}
+
+typedef SpriteData = {
+ name:String,
+ x:Int,
+ y:Int,
+ w:Int,
+ h:Int,
+ rotated:Bool
+}
+
+typedef SymbolData = {
+ ?name:String,
+ SYMBOL_name:String,
+ ?TIMELINE:SymbolTimelineData
+}
+
+typedef SymbolTimelineData = {
+ ?sortedForRender:Bool,
+ LAYERS:Array
+}
+
+typedef LayerData = {
+ Layer_name:String,
+ Frames:Array,
+ FrameMap:Map
+}
+
+typedef LayerFrameData = {
+ index:Int,
+ ?name:String,
+ duration:Int,
+ elements:Array
+}
+
+typedef ElementData = {
+ ?ATLAS_SPRITE_instance:Dynamic,
+ ?SYMBOL_Instance:SymbolInstanceData
+}
+
+typedef SymbolInstanceData = {
+ SYMBOL_name:String,
+ Instance_Name:String,
+ bitmap:BitmapPosData,
+ symbolType:String,
+ transformationPoint:PointData,
+ Matrix3D:Matrix3DData,
+ ?DecomposedMatrix:Decomposed3DData,
+ ?color:ColorData,
+
+ ?loop:String,
+ firstFrame:Int,
+ ?filters:FilterData
+}
+
+typedef ColorData = {
+ mode:String,
+
+ ?RedMultiplier:Float,
+ ?greenMultiplier:Float,
+ ?blueMultiplier:Float,
+ ?alphaMultiplier:Float,
+ ?redOffset:Float,
+ ?greenOffset:Float,
+ ?blueOffset:Float,
+ ?AlphaOffset:Float,
+ ?tintColor:String,
+ ?tintMultiplier:Float
+}
+
+typedef BitmapPosData = {
+ name:String,
+ Position:PointData,
+}
+
+typedef PointData = {
+ x:Int,
+ y:Int
+}
+
+typedef Matrix3DData = {
+ m00:Float,
+ m01:Float,
+ m02:Float,
+ m03:Float,
+ m10:Float,
+ m11:Float,
+ m12:Float,
+ m13:Float,
+ m20:Float,
+ m21:Float,
+ m22:Float,
+ m23:Float,
+ m30:Float,
+ m31:Float,
+ m32:Float,
+ m33:Float,
+}
+//tryna add more support gimme a sec
+typedef FilterData = {
+ ?BlurFilter: {
+ blurX:Float,
+ blurY:Float,
+ quality:Int
+ },
+
+ ?GlowFilter: {
+ blurX: Float,
+ blurY: Float,
+ color: Int,
+ alpha: Int,
+ quality: Int,
+ strength: Int,
+ knockout: Bool,
+ inner: Bool
+ }
+
+
+}
+
+typedef Decomposed3DData = {
+ Position:{x:Float, y:Float, z:Float},
+ Rotation:{x:Float, y:Float, z:Float},
+ Scaling:{x:Float, y:Float, z:Float},
+}
diff --git a/source/animateatlas/Main.hx b/source/animateatlas/Main.hx
new file mode 100644
index 00000000..8bbe8105
--- /dev/null
+++ b/source/animateatlas/Main.hx
@@ -0,0 +1,109 @@
+package;
+
+import animateatlas.JSONData.AtlasData;
+import openfl.display.BitmapData;
+import animateatlas.JSONData.AnimationData;
+import openfl.display.FPS;
+import openfl.Lib;
+import openfl.events.MouseEvent;
+import animateatlas.HelperEnums.LoopMode;
+import openfl.events.Event;
+import openfl.display.Tilemap;
+import openfl.display.Tileset;
+import openfl.Assets;
+import haxe.Json;
+import animateatlas.tilecontainer.TileAnimationLibrary;
+import animateatlas.tilecontainer.TileContainerMovieClip;
+import animateatlas.displayobject.SpriteAnimationLibrary;
+import animateatlas.displayobject.SpriteMovieClip;
+import openfl.display.Sprite;
+
+class Main extends Sprite {
+ var aa:TileAnimationLibrary;
+ var ss:SpriteAnimationLibrary;
+
+ //
+ var tileSymbols:Array;
+
+ var spriteSymbols:Array;
+
+ var renderer:Tilemap;
+
+ public function new() {
+ super();
+ graphics.beginFill(0x333333);
+ graphics.drawRect(0, 0, Lib.current.stage.stageWidth, Lib.current.stage.stageHeight);
+
+ var animationData:AnimationData = Json.parse(Assets.getText("assets/TEST/Animation.json"));
+ var atlasData:AtlasData = Json.parse(Assets.getText("assets/TEST/spritemap.json"));
+ var bitmapData:BitmapData = Assets.getBitmapData("assets/TEST/spritemap.png");
+
+ aa = new TileAnimationLibrary(animationData, atlasData, bitmapData);
+ ss = new SpriteAnimationLibrary(animationData, atlasData, bitmapData);
+
+ renderer = new Tilemap(Lib.current.stage.stageWidth, Lib.current.stage.stageHeight, null, true);
+
+ renderer.tileAlphaEnabled = false;
+ renderer.tileBlendModeEnabled = false;
+ renderer.tileColorTransformEnabled = false;
+
+ addChild(renderer);
+ addChild(new FPS(10, 10, 0xFFFFFF));
+
+ tileSymbols = [];
+ spriteSymbols = [];
+
+ addEventListener(Event.ENTER_FRAME, update);
+ // addEventListener(MouseEvent.CLICK, addSpriteGirl);
+ addEventListener(MouseEvent.CLICK, addTileGirl);
+ }
+
+ var prev:Int = 0;
+ var dt:Int = 0;
+ var curr:Int = 0;
+
+ public function update(_) {
+ // making a dt
+ curr = Lib.getTimer();
+ dt = curr - prev;
+ prev = curr;
+
+ for (symbol in tileSymbols) {
+ symbol.update(dt);
+ }
+ for (symbol in spriteSymbols) {
+ symbol.update(dt);
+ }
+ }
+
+ public function addSpriteGirl(_) {
+ for (i in 0...1) {
+ var t = ss.createAnimation();
+ t.x = mouseX + i * 20 * (-1 * i % 2);
+ t.y = mouseY + i * 20 * (-1 * i % 2);
+
+ addChild(t);
+ t.loopMode = LoopMode.SINGLE_FRAME;
+
+ t.currentLabel = t.getFrameLabels()[Std.random(t.getFrameLabels().length)];
+ spriteSymbols.push(t);
+ trace(spriteSymbols.length);
+ }
+ }
+
+ public function addTileGirl(_) {
+ for (i in 0...1) {
+ var t = aa.createAnimation();
+ t.x = mouseX + i * 5 * (-1 * i % 2);
+ t.y = mouseY + i * 5 * (-1 * i % 2);
+
+ renderer.addTile(t);
+ t.loopMode = LoopMode.SINGLE_FRAME;
+
+ t.currentLabel = t.getFrameLabels()[Std.random(t.getFrameLabels().length)];
+ tileSymbols.push(t);
+
+ trace(tileSymbols.length);
+ }
+ }
+}
diff --git a/source/animateatlas/displayobject/SpriteAnimationLibrary.hx b/source/animateatlas/displayobject/SpriteAnimationLibrary.hx
new file mode 100644
index 00000000..2f835ab4
--- /dev/null
+++ b/source/animateatlas/displayobject/SpriteAnimationLibrary.hx
@@ -0,0 +1,225 @@
+package animateatlas.displayobject;
+
+import openfl.display.BitmapData;
+import animateatlas.JSONData.AnimationData;
+import animateatlas.JSONData.ElementData;
+import animateatlas.JSONData.LayerFrameData;
+import animateatlas.JSONData.LayerData;
+import animateatlas.JSONData.SymbolTimelineData;
+import animateatlas.JSONData.Matrix3DData;
+import animateatlas.JSONData.AtlasData;
+import animateatlas.JSONData.SymbolData;
+import animateatlas.JSONData.SpriteData;
+import animateatlas.HelperEnums.LoopMode;
+import animateatlas.HelperEnums.SymbolType;
+import openfl.errors.ArgumentError;
+
+/**
+ * Performance will be REALLY BAD.
+ * Consider using TileAnimationLibrary whenever possible.
+ */
+class SpriteAnimationLibrary {
+ public var frameRate:Float;
+
+ private var _atlas:Map;
+ private var _symbolData:Map;
+ private var _symbolPool:Map>;
+ private var _defaultSymbolName:String;
+ private var _texture:BitmapData;
+
+ public static inline var BITMAP_SYMBOL_NAME:String = "___atlas_sprite___";
+
+ private static var STD_MATRIX3D_DATA:Matrix3DData = {
+ m00: 1,
+ m01: 0,
+ m02: 0,
+ m03: 0,
+ m10: 0,
+ m11: 1,
+ m12: 0,
+ m13: 0,
+ m20: 0,
+ m21: 0,
+ m22: 1,
+ m23: 0,
+ m30: 0,
+ m31: 0,
+ m32: 0,
+ m33: 1
+ };
+
+ public function new(data:AnimationData, atlas:AtlasData, texture:BitmapData) {
+ parseAnimationData(data);
+ parseAtlasData(atlas);
+ _texture = texture;
+ _symbolPool = new Map();
+ }
+
+ public function hasAnimation(name:String):Bool {
+ return hasSymbol(name);
+ }
+
+ public function createAnimation(symbol:String = null):SpriteMovieClip {
+ symbol = (symbol != null) ? symbol : _defaultSymbolName;
+ if (!hasSymbol(symbol)) {
+ throw new ArgumentError("Symbol not found: " + symbol);
+ }
+ return new SpriteMovieClip(getSymbol(symbol));
+ }
+
+ public function getAnimationNames(prefix:String = ""):Array {
+ var out = new Array();
+
+ for (name in _symbolData.keys()) {
+ if (name != BITMAP_SYMBOL_NAME && name.indexOf(prefix) == 0) {
+ out.push(name);
+ }
+ }
+
+ // but... why?
+ out.sort(function(a1, a2):Int {
+ a1 = a1.toLowerCase();
+ a2 = a2.toLowerCase();
+ if (a1 < a2) {
+ return -1;
+ } else if (a1 > a2) {
+ return 1;
+ } else {
+ return 0;
+ }
+ });
+ return out;
+ }
+
+ private function getSpriteData(name:String):SpriteData {
+ return _atlas.get(name);
+ }
+
+ private function hasSymbol(name:String):Bool {
+ return _symbolData.exists(name);
+ }
+
+ // # region Pooling
+ // todo migrate this to lime pool
+
+ @:access(animateatlas)
+ @:allow(AtlasFrameMaker)
+ private function getSymbol(name:String):SpriteSymbol {
+ var pool:Array = getSymbolPool(name);
+ if (pool.length == 0) {
+ return new SpriteSymbol(getSymbolData(name), this, _texture);
+ } else {
+ return pool.pop();
+ }
+ }
+
+ private function putSymbol(symbol:SpriteSymbol):Void {
+ symbol.reset();
+ var pool:Array = getSymbolPool(symbol.symbolName);
+ pool.push(symbol);
+ symbol.currentFrame = 0;
+ }
+
+ private function getSymbolPool(name:String):Array {
+ var pool:Array = _symbolPool.get(name);
+ if (pool == null) {
+ pool = [];
+ _symbolPool.set(name, pool);
+ }
+ return pool;
+ }
+
+ // # end region
+ // # region helpers
+ private function parseAnimationData(data:AnimationData):Void {
+ var metaData = data.metadata;
+
+ if (metaData != null && metaData.framerate != null && metaData.framerate > 0) {
+ frameRate = (metaData.framerate);
+ } else {
+ frameRate = 24;
+ }
+
+ _symbolData = new Map();
+
+ // the actual symbol dictionary
+ var symbols = data.SYMBOL_DICTIONARY.Symbols;
+ for (symbolData in symbols) {
+ _symbolData[symbolData.SYMBOL_name] = preprocessSymbolData(symbolData);
+ }
+
+ // the main animation
+ var defaultSymbolData:SymbolData = preprocessSymbolData(data.ANIMATION);
+ _defaultSymbolName = defaultSymbolData.SYMBOL_name;
+ _symbolData.set(_defaultSymbolName, defaultSymbolData);
+
+ // a purely internal symbol for bitmaps - simplifies their handling
+ _symbolData.set(BITMAP_SYMBOL_NAME, {
+ SYMBOL_name: BITMAP_SYMBOL_NAME,
+ TIMELINE: {
+ LAYERS: []
+ }
+ });
+ }
+
+ private function preprocessSymbolData(symbolData:SymbolData):SymbolData {
+ var timeLineData:SymbolTimelineData = symbolData.TIMELINE;
+ var layerDates:Array = timeLineData.LAYERS;
+
+ // In Animate CC, layers are sorted front to back.
+ // In Starling, it's the other way round - so we simply reverse the layer data.
+
+ if (!timeLineData.sortedForRender) {
+ timeLineData.sortedForRender = true;
+ layerDates.reverse();
+ }
+
+ // We replace all "ATLAS_SPRITE_instance" elements with symbols of the same contents.
+ // That way, we are always only dealing with symbols.
+
+ for (layerData in layerDates) {
+ var frames:Array = layerData.Frames;
+
+ for (frame in frames) {
+ var elements:Array = frame.elements;
+ for (e in 0...elements.length) {
+ var element:ElementData = elements[e];
+ if (element.ATLAS_SPRITE_instance != null) {
+ element = elements[e] = {
+ SYMBOL_Instance: {
+ SYMBOL_name: BITMAP_SYMBOL_NAME,
+ Instance_Name: "InstName",
+ bitmap: element.ATLAS_SPRITE_instance,
+ symbolType: SymbolType.GRAPHIC,
+ firstFrame: 0,
+ loop: LoopMode.LOOP,
+ transformationPoint: {
+ x: 0,
+ y: 0
+ },
+ Matrix3D: STD_MATRIX3D_DATA
+ }
+ };
+ }
+ }
+ }
+ }
+
+ return symbolData;
+ }
+
+ private function parseAtlasData(atlas:AtlasData):Void {
+ _atlas = new Map();
+ if (atlas.ATLAS != null && atlas.ATLAS.SPRITES != null) {
+ for (s in atlas.ATLAS.SPRITES) {
+ _atlas.set(s.SPRITE.name, s.SPRITE);
+ }
+ }
+ }
+
+ private function getSymbolData(name:String):SymbolData {
+ return _symbolData.get(name);
+ }
+
+ // # end region
+}
diff --git a/source/animateatlas/displayobject/SpriteMovieClip.hx b/source/animateatlas/displayobject/SpriteMovieClip.hx
new file mode 100644
index 00000000..2b2b746d
--- /dev/null
+++ b/source/animateatlas/displayobject/SpriteMovieClip.hx
@@ -0,0 +1,117 @@
+package animateatlas.displayobject;
+
+import openfl.display.Sprite;
+import animateatlas.HelperEnums.LoopMode;
+import animateatlas.HelperEnums.SymbolType;
+
+@:access(animateatlas.displayobject.SpriteSymbol)
+class SpriteMovieClip extends Sprite {
+ public var framerate(get, set):Float;
+ public var currentLabel(get, set):String;
+ public var currentFrame(get, set):Int;
+ public var type(get, set):String;
+ public var loopMode(get, set):String;
+ public var symbolName(get, never):String;
+ public var numLayers(get, never):Int;
+ public var numFrames(get, never):Int;
+ public var layers(get, never):Array; // ! Dangerous AF.
+
+ private var symbol:SpriteSymbol;
+ private var _framerate:Null = null;
+
+ private var frameElapsed:Float = 0;
+
+ public function new(symbol:SpriteSymbol) {
+ super();
+ this.symbol = symbol;
+ addChild(this.symbol);
+ }
+
+ public function update(dt:Int) {
+ //
+ var frameDuration:Float = 1000 / framerate;
+ frameElapsed += dt;
+
+ while (frameElapsed > frameDuration) {
+ frameElapsed -= frameDuration;
+ symbol.nextFrame();
+ }
+ while (frameElapsed < -frameDuration) {
+ frameElapsed += frameDuration;
+ symbol.prevFrame();
+ }
+ }
+
+ public function getFrameLabels():Array {
+ return symbol.getFrameLabels();
+ }
+
+ public function getFrame(label:String):Int {
+ return symbol.getFrame(label);
+ }
+
+ // # region Property setter and getter
+
+ private function set_currentLabel(value:String):String {
+ symbol.currentFrame = symbol.getFrame(value);
+ return value;
+ }
+
+ private function get_currentLabel():String {
+ return symbol.currentLabel;
+ }
+
+
+ private function set_currentFrame(value:Int):Int {
+ symbol.currentFrame = value;
+ return value;
+ }
+
+ private function get_currentFrame():Int {
+ return symbol.currentFrame;
+ }
+
+ private function set_type(value:SymbolType):SymbolType {
+ symbol.type = value;
+ return value;
+ }
+
+ private function get_type():SymbolType {
+ return symbol.type;
+ }
+
+ private function set_loopMode(value:LoopMode):LoopMode {
+ symbol.loopMode = value;
+ return value;
+ }
+
+ private function get_loopMode():LoopMode {
+ return symbol.loopMode;
+ }
+
+ private function get_symbolName():String {
+ return symbol.symbolName;
+ }
+
+ private function get_numLayers():Int {
+ return symbol.numLayers;
+ }
+
+ private function get_numFrames():Int {
+ return symbol.numFrames;
+ }
+
+ private function get_layers():Array {
+ return symbol._layers;
+ }
+
+ private function set_framerate(value:Float):Float {
+ return _framerate = value;
+ }
+
+ private function get_framerate():Float {
+ return _framerate == null ? symbol._library.frameRate : _framerate;
+ }
+
+ // # end region
+}
diff --git a/source/animateatlas/displayobject/SpriteSymbol.hx b/source/animateatlas/displayobject/SpriteSymbol.hx
new file mode 100644
index 00000000..4f813c42
--- /dev/null
+++ b/source/animateatlas/displayobject/SpriteSymbol.hx
@@ -0,0 +1,540 @@
+package animateatlas.displayobject;
+
+import flixel.util.FlxColor;
+import openfl.filters.GlowFilter;
+import openfl.filters.BlurFilter;
+import openfl.display.PixelSnapping;
+import openfl.geom.Point;
+import openfl.display.BitmapData;
+import openfl.display.Bitmap;
+import openfl.display.Sprite;
+import openfl.errors.ArgumentError;
+import openfl.geom.Rectangle;
+import openfl.errors.Error;
+import animateatlas.JSONData.ElementData;
+import animateatlas.HelperEnums.LoopMode;
+import animateatlas.HelperEnums.SymbolType;
+import openfl.display.FrameLabel;
+import animateatlas.JSONData.SymbolData;
+import animateatlas.JSONData.SymbolInstanceData;
+import animateatlas.JSONData.LayerData;
+import animateatlas.JSONData.BitmapPosData;
+import animateatlas.JSONData.Matrix3DData;
+import animateatlas.JSONData.LayerFrameData;
+import animateatlas.JSONData.ColorData;
+import openfl.geom.Matrix;
+import openfl.geom.ColorTransform;
+import animateatlas.JSONData.FilterData;
+
+class SpriteSymbol extends Sprite {
+ public var currentLabel(get, never):String;
+ public var currentFrame(get, set):Int;
+ public var type(get, set):String;
+ public var loopMode(get, set):String;
+ public var symbolName(get, never):String;
+ public var numLayers(get, never):Int;
+ public var numFrames(get, never):Int;
+
+ private var _data:SymbolData;
+ private var _library:SpriteAnimationLibrary;
+ private var _symbolName:String;
+ private var _type:String;
+ private var _loopMode:String;
+
+ private var _currentFrame:Int;
+ private var _composedFrame:Int;
+ private var _bitmap:Bitmap;
+ private var _numFrames:Int;
+ private var _numLayers:Int;
+ private var _frameLabels:Array;
+ private var _colorTransform:ColorTransform;
+ private var _layers:Array;
+ private var _texture:BitmapData;
+ private var _tempRect = new Rectangle();
+ private var _zeroPoint = new Point(0, 0);
+ private var filterHelper:BitmapData;
+ public var smoothing:Bool = true;
+
+ private static var sMatrix:Matrix = new Matrix();
+
+ private function new(data:SymbolData, library:SpriteAnimationLibrary, texture:BitmapData) {
+ super();
+ _data = data;
+ _library = library;
+ _composedFrame = -1;
+ _numLayers = data.TIMELINE.LAYERS.length;
+ _numFrames = getNumFrames();
+ _frameLabels = _getFrameLabels();
+ _symbolName = data.SYMBOL_name;
+ _type = SymbolType.GRAPHIC;
+ _loopMode = LoopMode.LOOP;
+ _texture = texture;
+
+ createLayers();
+
+ // Create FrameMap caches if don't exist
+ for (layer in data.TIMELINE.LAYERS) {
+ if (layer.FrameMap != null)
+ return;
+
+ var map = new Map();
+
+ for (i in 0...layer.Frames.length) {
+ var frame = layer.Frames[i];
+ for (j in 0...frame.duration) {
+ map.set(i + j, frame);
+ }
+ }
+
+ layer.FrameMap = map;
+ }
+ }
+
+ public function reset():Void {
+ sMatrix.identity();
+ transform.matrix = sMatrix.clone();
+ alpha = 1.0;
+ _currentFrame = 0;
+ _composedFrame = -1;
+ }
+
+ public function nextFrame():Void {
+ if (_loopMode != LoopMode.SINGLE_FRAME) {
+ currentFrame += 1;
+ }
+
+ moveMovieclip_MovieClips(1);
+ }
+
+ public function prevFrame():Void {
+ if (_loopMode != LoopMode.SINGLE_FRAME) {
+ currentFrame -= 1;
+ }
+
+ moveMovieclip_MovieClips(-1);
+ }
+
+ /** Moves all movie clips n frames, recursively. */
+ private function moveMovieclip_MovieClips(direction:Int = 1):Void {
+ if (_type == SymbolType.MOVIE_CLIP) {
+ currentFrame += direction;
+ }
+
+ for (l in 0..._numLayers) {
+ var layer:Sprite = getLayer(l);
+ var numElements:Int = layer.numChildren;
+
+ for (e in 0...numElements) {
+ (try cast(layer.getChildAt(e), SpriteSymbol) catch (e:Dynamic) null).moveMovieclip_MovieClips(direction);
+ }
+ }
+ }
+
+ public function update():Void {
+ for (i in 0..._numLayers) {
+ updateLayer(i);
+ }
+
+ _composedFrame = _currentFrame;
+ }
+
+ @:access(animateatlas)
+ private function updateLayer(layerIndex:Int):Void {
+ var layer:Sprite = getLayer(layerIndex);
+ var frameData:LayerFrameData = getFrameData(layerIndex, _currentFrame);
+ var elements:Array = (frameData != null) ? frameData.elements : null;
+ var numElements:Int = (elements != null) ? elements.length : 0;
+ for (i in 0...numElements) {
+ var elementData:SymbolInstanceData = elements[i].SYMBOL_Instance;
+
+ if (elementData == null) {
+ continue;
+ }
+
+ // this is confusing but needed :(
+ var oldSymbol:SpriteSymbol = (layer.numChildren > i) ? try
+ cast(layer.getChildAt(i), SpriteSymbol)
+ catch (e:Dynamic)
+ null : null;
+
+ var newSymbol:SpriteSymbol = null;
+
+ var symbolName:String = elementData.SYMBOL_name;
+
+ if (!_library.hasSymbol(symbolName)) {
+ symbolName = SpriteAnimationLibrary.BITMAP_SYMBOL_NAME;
+ }
+
+ if (oldSymbol != null && oldSymbol._symbolName == symbolName) {
+ newSymbol = oldSymbol;
+ } else {
+ if (oldSymbol != null) {
+ if (oldSymbol.parent != null)
+ oldSymbol.removeChild(oldSymbol);
+ _library.putSymbol(oldSymbol);
+ }
+
+ newSymbol = cast(_library.getSymbol(symbolName));
+ layer.addChildAt(newSymbol, i);
+ }
+
+ newSymbol.setTransformationMatrix(elementData.Matrix3D);
+ newSymbol.setBitmap(elementData.bitmap);
+ newSymbol.setFilterData(elementData.filters);
+ newSymbol.setColor(elementData.color);
+ newSymbol.setLoop(elementData.loop);
+ newSymbol.setType(elementData.symbolType);
+
+
+
+ if (newSymbol.type == SymbolType.GRAPHIC) {
+ var firstFrame:Int = elementData.firstFrame;
+ var frameAge:Int = Std.int(_currentFrame - frameData.index);
+
+ if (newSymbol.loopMode == LoopMode.SINGLE_FRAME) {
+ newSymbol.currentFrame = firstFrame;
+ } else if (newSymbol.loopMode == LoopMode.LOOP) {
+ newSymbol.currentFrame = (firstFrame + frameAge) % newSymbol._numFrames;
+ } else {
+ newSymbol.currentFrame = firstFrame + frameAge;
+ }
+ }
+
+ }
+
+ var numObsoleteSymbols:Int = (layer.numChildren - numElements);
+
+ for (i in 0...numObsoleteSymbols) {
+ try {
+ var oldSymbol = cast(layer.removeChildAt(numElements), SpriteSymbol);
+ if (oldSymbol != null)
+ _library.putSymbol(oldSymbol);
+ } catch (e:Dynamic) {};
+ }
+ }
+
+ private function createLayers():Void {
+ // todo safety check for not initialiing twice
+ if (_layers != null) {
+ throw new Error("You must not call this twice");
+ }
+ _layers = new Array();
+
+ if (_numLayers <= 1) {
+ _layers.push(this);
+ } else {
+ for (i in 0..._numLayers) {
+ var layer:Sprite = new Sprite();
+ layer.name = getLayerData(i).Layer_name;
+ addChild(layer);
+ _layers.push(layer);
+ }
+ }
+ }
+
+ @:access(animateatlas)
+ public function setBitmap(data:BitmapPosData):Void {
+ if (data != null) {
+ var spriteData = _library.getSpriteData(data.name + "");
+
+ if (_bitmap == null) {
+ _bitmap = new Bitmap(new BitmapData(1, 1), PixelSnapping.AUTO, smoothing);
+ addChild(_bitmap);
+ }
+
+ if (_tempRect.x != spriteData.x || _tempRect.y != spriteData.y || _tempRect.width != spriteData.w || _tempRect.height != spriteData.h) {
+ var clippedTexture = new BitmapData(spriteData.w, spriteData.h);
+ _tempRect.setTo(spriteData.x, spriteData.y, spriteData.w, spriteData.h);
+ clippedTexture.copyPixels(_texture, _tempRect, _zeroPoint);
+ _bitmap.bitmapData = clippedTexture;
+ _bitmap.smoothing = smoothing;
+
+
+ }
+ // aditional checks for rotation
+ if (spriteData.rotated) {
+ _bitmap.rotation = -90;
+ _bitmap.x = data.Position.x;
+ _bitmap.y = data.Position.y + spriteData.w;
+ } else {
+ _bitmap.rotation = 0;
+ _bitmap.x = data.Position.x;
+ _bitmap.y = data.Position.y;
+ }
+
+ addChildAt(_bitmap, 0);
+ } else if (_bitmap != null) {
+ if (_bitmap.parent != null)
+ _bitmap.parent.removeChild(_bitmap);
+ }
+ }
+ @:access(animateatlas)
+ private function setFilterData(data:FilterData):Void{
+ var blur:BlurFilter;
+ var glow:GlowFilter;
+ if (data != null){
+ if (data.BlurFilter != null){
+ blur = new BlurFilter();
+ blur.blurX = data.BlurFilter.blurX;
+ blur.blurY = data.BlurFilter.blurY;
+ blur.quality = data.BlurFilter.quality;
+ //_bitmap.bitmapData.applyFilter(_bitmap.bitmapData,new Rectangle(0,0,_bitmap.bitmapData.width,_bitmap.bitmapData.height),new Point(0,0),blur);
+ //filters.push(blur);
+ }
+ if (data.GlowFilter != null){
+ //trace('GLOW' + data.GlowFilter);
+ //glow = new GlowFilter();
+ //glow.blurX = data.GlowFilter.blurX;
+ //glow.blurY = data.GlowFilter.blurY;
+ //glow.color = data.GlowFilter.color;
+ //glow.alpha = data.GlowFilter.alpha;
+ //glow.quality = data.GlowFilter.quality;
+ //glow.strength = data.GlowFilter.strength;
+ //glow.knockout = data.GlowFilter.knockout;
+ //glow.inner = data.GlowFilter.inner;
+ //filters.push(glow);
+
+
+
+
+ }
+
+ }
+
+ }
+
+ private function setTransformationMatrix(data:Matrix3DData):Void {
+ sMatrix.setTo(data.m00, data.m01, data.m10, data.m11, data.m30, data.m31);
+ if (sMatrix.a != transform.matrix.a || sMatrix.b != transform.matrix.b || sMatrix.c != transform.matrix.c || sMatrix.d != transform.matrix.d
+ || sMatrix.tx != transform.matrix.tx || sMatrix.ty != transform.matrix.ty)
+ transform.matrix = sMatrix.clone(); // todo stop the cloning :(
+ }
+
+ private function setColor(data:ColorData):Void {
+ var newTransform = new ColorTransform();
+ if (data != null) {
+ newTransform.redOffset = (data.redOffset == null ? 0 : data.redOffset);
+ newTransform.greenOffset = (data.greenOffset == null ? 0 : data.greenOffset);
+ newTransform.blueOffset = (data.blueOffset == null ? 0 : data.blueOffset);
+ newTransform.alphaOffset = (data.AlphaOffset == null ? 0 : data.AlphaOffset);
+
+ newTransform.redMultiplier = (data.RedMultiplier == null ? 1 : data.RedMultiplier);
+ newTransform.greenMultiplier = (data.greenMultiplier == null ? 1 : data.greenMultiplier);
+ newTransform.blueMultiplier = (data.blueMultiplier == null ? 1 : data.blueMultiplier);
+ newTransform.alphaMultiplier = (data.alphaMultiplier == null ? 1 : data.alphaMultiplier);
+
+
+ if (data.mode == "Tint"){//Tint color effect
+ var color:FlxColor = Std.parseInt(StringTools.replace(data.tintColor, "#", "0xFF"));
+
+
+ newTransform.redMultiplier = 0;
+ newTransform.greenMultiplier = 0;//(data.greenMultiplier == null ? 1 : data.greenMultiplier);
+ newTransform.blueMultiplier = 0;//(data.blueMultiplier == null ? 1 : data.blueMultiplier);
+ newTransform.redOffset = color.red*data.tintMultiplier;
+ newTransform.greenOffset = color.green * data.tintMultiplier;//(data.greenMultiplier == null ? 1 : data.greenMultiplier);
+ newTransform.blueOffset = color.blue * data.tintMultiplier;//(data.blueMultiplier == null ? 1 : data.blueMultiplier);
+ }
+
+ }
+ transform.colorTransform = newTransform;
+
+ }
+
+ private function setLoop(data:String):Void {
+ if (data != null) {
+ _loopMode = data;
+ } else {
+ _loopMode = LoopMode.LOOP;
+ }
+ }
+
+ private function setType(data:String):Void {
+ if (data != null) {
+ _type = data;
+ }
+ }
+
+ private function getNumFrames():Int {
+ var numFrames:Int = 0;
+
+ for (i in 0..._numLayers) {
+ var layer = getLayerData(i);
+ var frameDates:Array = (layer == null ? [] : layer.Frames);
+ var numFrameDates:Int = (frameDates != null) ? frameDates.length : 0;
+ var layerNumFrames:Int = (numFrameDates != 0) ? frameDates[0].index : 0;
+
+ for (j in 0...numFrameDates) {
+ layerNumFrames += frameDates[j].duration;
+ }
+
+ if (layerNumFrames > numFrames) {
+ numFrames = layerNumFrames;
+ }
+ }
+
+ return numFrames == 0 ? 1 : numFrames;
+ }
+
+ private function _getFrameLabels():Array {
+ var labels:Array = [];
+
+ for (i in 0..._numLayers) {
+ var layer = getLayerData(i);
+ var frameDates:Array = (layer == null ? [] : layer.Frames);
+ var numFrameDates:Int = (frameDates != null) ? frameDates.length : 0;
+
+ for (j in 0...numFrameDates) {
+ var frameData:LayerFrameData = frameDates[j];
+ if (frameData.name != null) {
+ labels.push(new FrameLabel(frameData.name, frameData.index));
+ }
+ }
+ }
+ labels.sort(sortLabels);
+ return labels;
+ }
+
+ public function getFrameLabels():Array {
+ return _frameLabels.map(f -> f.name); // Inlining. I feel a js
+ }
+
+
+
+ function sortLabels(i1:FrameLabel, i2:FrameLabel):Int {
+ var f1 = i1.frame;
+ var f2 = i2.frame;
+ if (f1 < f2) {
+ return -1;
+ } else if (f1 > f2) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ private function getLayer(layerIndex:Int):Sprite {
+ return _layers[layerIndex];
+ }
+
+ public function getTexture():BitmapData{
+ //THIS GETS THE ENTIRE THING I'M RETARDED LOL
+ return _texture;
+ }
+
+ public function getNextLabel(afterLabel:String = null):String {
+ var numLabels:Int = _frameLabels.length;
+ var startFrame:Int = getFrame(afterLabel == null ? currentLabel : afterLabel);
+
+ for (i in 0...numLabels) {
+ var label:FrameLabel = _frameLabels[i];
+ if (label.frame > startFrame) {
+ return label.name;
+ }
+ }
+
+ return (_frameLabels != null) ? _frameLabels[0].name : null;
+ }
+
+ private function get_currentLabel():String {
+ var numLabels:Int = _frameLabels.length;
+ var highestLabel:FrameLabel = (numLabels != 0) ? _frameLabels[0] : null;
+
+ for (i in 1...numLabels) {
+ var label:FrameLabel = _frameLabels[i];
+
+ if (label.frame <= _currentFrame) {
+ highestLabel = label;
+ } else {
+ break;
+ }
+ }
+
+ return (highestLabel != null) ? highestLabel.name : null;
+ }
+
+ public function getFrame(label:String):Int {
+ var numLabels:Int = _frameLabels.length;
+ for (i in 0...numLabels) {
+ var frameLabel:FrameLabel = _frameLabels[i];
+ if (frameLabel.name == label) {
+ return frameLabel.frame;
+ }
+ }
+ return -1;
+ }
+
+ private function get_currentFrame():Int {
+ return _currentFrame;
+ }
+
+ private function set_currentFrame(value:Int):Int {
+ while (value < 0) {
+ value += _numFrames;
+ }
+
+ if (_loopMode == LoopMode.PLAY_ONCE) {
+ _currentFrame = Std.int(Math.min(Math.max(value, 0), _numFrames - 1));
+ } else {
+ _currentFrame = Std.int(Math.abs(value % _numFrames));
+ }
+
+ if (_composedFrame != _currentFrame) {
+ update();
+ }
+ return value;
+ }
+
+ private function get_type():String {
+ return _type;
+ }
+
+ private function set_type(value:String):String {
+ if (SymbolType.isValid(value)) {
+ _type = value;
+ } else {
+ throw new ArgumentError("Invalid symbol type: " + value);
+ }
+ return value;
+ }
+
+ private function get_loopMode():String {
+ return _loopMode;
+ }
+
+ private function set_loopMode(value:String):String {
+ if (LoopMode.isValid(value)) {
+ _loopMode = value;
+ } else {
+ throw new ArgumentError("Invalid loop mode: " + value);
+ }
+ return value;
+ }
+
+ private function get_symbolName():String {
+ return _symbolName;
+ }
+
+ private function get_numLayers():Int {
+ return _numLayers;
+ }
+
+ private function get_numFrames():Int {
+ return _numFrames;
+ }
+
+ // data access
+
+ private function getLayerData(layerIndex:Int):LayerData {
+ return _data.TIMELINE.LAYERS[layerIndex];
+ }
+
+ private function getFrameData(layerIndex:Int, frameIndex:Int):LayerFrameData {
+ var layer = getLayerData(layerIndex);
+ if (layer == null)
+ return null;
+
+ return layer.FrameMap.get(frameIndex);
+ }
+
+}
diff --git a/source/animateatlas/tilecontainer/TileAnimationLibrary.hx b/source/animateatlas/tilecontainer/TileAnimationLibrary.hx
new file mode 100644
index 00000000..008f0e36
--- /dev/null
+++ b/source/animateatlas/tilecontainer/TileAnimationLibrary.hx
@@ -0,0 +1,226 @@
+package animateatlas.tilecontainer;
+
+import openfl.display.Tileset;
+import haxe.Constraints.Constructible;
+import openfl.display.BitmapData;
+import animateatlas.JSONData.AnimationData;
+import animateatlas.JSONData.ElementData;
+import animateatlas.JSONData.LayerFrameData;
+import animateatlas.JSONData.LayerData;
+import animateatlas.JSONData.SymbolTimelineData;
+import animateatlas.JSONData.Matrix3DData;
+import animateatlas.JSONData.AtlasData;
+import animateatlas.JSONData.SymbolData;
+import animateatlas.JSONData.SpriteData;
+import animateatlas.HelperEnums.LoopMode;
+import animateatlas.HelperEnums.SymbolType;
+import openfl.errors.ArgumentError;
+
+/**
+ * Since we can extract symbols from the exported timeline and instance them separatedly, this keeps track of all symbols.
+ * Also, this is a "more readable" way of understanding the AnimationData
+ */
+class TileAnimationLibrary {
+ public var frameRate:Float;
+
+ private var _atlas:Map;
+ private var _symbolData:Map;
+ private var _symbolPool:Map>;
+ private var _defaultSymbolName:String;
+ private var _texture:Tileset;
+
+ public static inline var BITMAP_SYMBOL_NAME:String = "___atlas_sprite___";
+
+ private static var STD_MATRIX3D_DATA:Matrix3DData = {
+ m00: 1,
+ m01: 0,
+ m02: 0,
+ m03: 0,
+ m10: 0,
+ m11: 1,
+ m12: 0,
+ m13: 0,
+ m20: 0,
+ m21: 0,
+ m22: 1,
+ m23: 0,
+ m30: 0,
+ m31: 0,
+ m32: 0,
+ m33: 1
+ };
+
+ public function new(data:AnimationData, atlas:AtlasData, texture:BitmapData) {
+ parseAnimationData(data);
+ parseAtlasData(atlas);
+ _texture = new Tileset(texture);
+ _symbolPool = new Map();
+ }
+
+ public function hasAnimation(name:String):Bool {
+ return hasSymbol(name);
+ }
+
+ public function createAnimation(symbol:String = null):TileContainerMovieClip {
+ symbol = (symbol != null) ? symbol : _defaultSymbolName;
+ if (!hasSymbol(symbol)) {
+ throw new ArgumentError("Symbol not found: " + symbol);
+ }
+ return new TileContainerMovieClip(getSymbol(symbol));
+ }
+
+ public function getAnimationNames(prefix:String = ""):Array {
+ var out = new Array();
+
+ for (name in _symbolData.keys()) {
+ if (name != BITMAP_SYMBOL_NAME && name.indexOf(prefix) == 0) {
+ out.push(name);
+ }
+ }
+
+ // but... why?
+ out.sort(function(a1, a2):Int {
+ a1 = a1.toLowerCase();
+ a2 = a2.toLowerCase();
+ if (a1 < a2) {
+ return -1;
+ } else if (a1 > a2) {
+ return 1;
+ } else {
+ return 0;
+ }
+ });
+ return out;
+ }
+
+ private function getSpriteData(name:String):SpriteData {
+ return _atlas.get(name);
+ }
+
+ private function hasSymbol(name:String):Bool {
+ return _symbolData.exists(name);
+ }
+
+ // # region Pooling
+ // todo migrate this to lime pool
+
+ @:access(animateatlas)
+ private function getSymbol(name:String):TileContainerSymbol {
+ var pool:Array = getSymbolPool(name);
+ if (pool.length == 0) {
+ return new TileContainerSymbol(getSymbolData(name), this, _texture);
+ } else {
+ return pool.pop();
+ }
+ }
+
+ private function putSymbol(symbol:TileContainerSymbol):Void {
+ symbol.reset();
+ var pool:Array = getSymbolPool(symbol.symbolName);
+ pool.push(symbol);
+ symbol.currentFrame = 0;
+ }
+
+ private function getSymbolPool(name:String):Array {
+ var pool:Array = _symbolPool.get(name);
+ if (pool == null) {
+ pool = [];
+ _symbolPool.set(name, pool);
+ }
+ return pool;
+ }
+
+ // # end region
+ // # region helpers
+ private function parseAnimationData(data:AnimationData):Void {
+ var metaData = data.metadata;
+
+ if (metaData != null && metaData.framerate != null && metaData.framerate > 0) {
+ frameRate = (metaData.framerate);
+ } else {
+ frameRate = 24;
+ }
+
+ _symbolData = new Map();
+
+ // the actual symbol dictionary
+ var symbols = data.SYMBOL_DICTIONARY.Symbols;
+ for (symbolData in symbols) {
+ _symbolData[symbolData.SYMBOL_name] = preprocessSymbolData(symbolData);
+ }
+
+ // the main animation
+ var defaultSymbolData:SymbolData = preprocessSymbolData(data.ANIMATION);
+ _defaultSymbolName = defaultSymbolData.SYMBOL_name;
+ _symbolData.set(_defaultSymbolName, defaultSymbolData);
+
+ // a purely internal symbol for bitmaps - simplifies their handling
+ _symbolData.set(BITMAP_SYMBOL_NAME, {
+ SYMBOL_name: BITMAP_SYMBOL_NAME,
+ TIMELINE: {
+ LAYERS: []
+ }
+ });
+ }
+
+ private function preprocessSymbolData(symbolData:SymbolData):SymbolData {
+ var timeLineData:SymbolTimelineData = symbolData.TIMELINE;
+ var layerDates:Array = timeLineData.LAYERS;
+
+ // In Animate CC, layers are sorted front to back.
+ // In Starling, it's the other way round - so we simply reverse the layer data.
+
+ if (!timeLineData.sortedForRender) {
+ timeLineData.sortedForRender = true;
+ layerDates.reverse();
+ }
+
+ // We replace all "ATLAS_SPRITE_instance" elements with symbols of the same contents.
+ // That way, we are always only dealing with symbols.
+
+ for (layerData in layerDates) {
+ var frames:Array = layerData.Frames;
+
+ for (frame in frames) {
+ var elements:Array = frame.elements;
+ for (e in 0...elements.length) {
+ var element:ElementData = elements[e];
+ if (element.ATLAS_SPRITE_instance != null) {
+ element = elements[e] = {
+ SYMBOL_Instance: {
+ SYMBOL_name: BITMAP_SYMBOL_NAME,
+ Instance_Name: "InstName",
+ bitmap: element.ATLAS_SPRITE_instance,
+ symbolType: SymbolType.GRAPHIC,
+ firstFrame: 0,
+ loop: LoopMode.LOOP,
+ transformationPoint: {
+ x: 0,
+ y: 0
+ },
+ Matrix3D: STD_MATRIX3D_DATA
+ }
+ };
+ }
+ }
+ }
+ }
+
+ return symbolData;
+ }
+
+ private function parseAtlasData(atlas:AtlasData):Void {
+ _atlas = new Map();
+ if (atlas.ATLAS != null && atlas.ATLAS.SPRITES != null) {
+ for (s in atlas.ATLAS.SPRITES) {
+ _atlas.set(s.SPRITE.name, s.SPRITE);
+ }
+ }
+ }
+
+ private function getSymbolData(name:String):SymbolData {
+ return _symbolData.get(name);
+ }
+
+ // # end region
+}
diff --git a/source/animateatlas/tilecontainer/TileContainerMovieClip.hx b/source/animateatlas/tilecontainer/TileContainerMovieClip.hx
new file mode 100644
index 00000000..66de4ded
--- /dev/null
+++ b/source/animateatlas/tilecontainer/TileContainerMovieClip.hx
@@ -0,0 +1,135 @@
+package animateatlas.tilecontainer;
+
+import animateatlas.HelperEnums.LoopMode;
+import animateatlas.HelperEnums.SymbolType;
+import openfl.display.TileContainer;
+
+@:access(animateatlas.tilecontainer.TileContainerSymbol)
+class TileContainerMovieClip extends TileContainer {
+ public var framerate(get, set):Float;
+ public var currentLabel(get, set):String;
+ public var currentFrame(get, set):Int;
+ public var type(get, set):String;
+ public var loopMode(get, set):String;
+ public var symbolName(get, never):String;
+ public var numLayers(get, never):Int;
+ public var numFrames(get, never):Int;
+ public var layers(get, never):Array; // ! Dangerous AF.
+
+ private var symbol:TileContainerSymbol;
+ private var _framerate:Null = null;
+
+ private var frameElapsed:Float = 0;
+
+ public function new(symbol:TileContainerSymbol) {
+ super();
+ this.symbol = symbol;
+ addTile(this.symbol);
+ }
+
+ public function update(dt:Int) {
+ var frameDuration:Float = 1000 / framerate;
+ frameElapsed += dt;
+
+ while (frameElapsed > frameDuration) {
+ frameElapsed -= frameDuration;
+ symbol.nextFrame();
+ }
+ while (frameElapsed < -frameDuration) {
+ frameElapsed += frameDuration;
+ symbol.prevFrame();
+ }
+ }
+
+ public function getFrameLabels():Array {
+ return symbol.getFrameLabels();
+ }
+
+ public function getFrame(label:String):Int {
+ return symbol.getFrame(label);
+ }
+ public function getFramesofAnim(label:String):Int {
+ var framesArray:Array;
+ var uncalculatedArray:Array = [];
+ var uncalculatedFrames:Int = 0;
+
+ for (i in 0...getFrameLabels().length){
+ uncalculatedArray.push(getFrame(getFrameLabels()[i]));
+ }
+
+
+ uncalculatedFrames = uncalculatedArray[0]+uncalculatedArray.length;
+
+ return uncalculatedFrames;
+ }
+
+ // # region Property setter and getter
+
+ private function set_currentLabel(value:String):String {
+ symbol.currentFrame = symbol.getFrame(value);
+ return value;
+ }
+
+ private function get_currentLabel():String {
+ return symbol.currentLabel;
+ }
+
+ private function set_currentFrame(value:Int):Int {
+ symbol.currentFrame = value;
+ return value;
+ }
+
+ public function get_animFrames():Int{
+ return symbol.get_numFrames();
+ }
+
+
+ private function get_currentFrame():Int {
+ return symbol.currentFrame;
+ }
+
+ private function set_type(value:SymbolType):SymbolType {
+ symbol.type = value;
+ return value;
+ }
+
+ private function get_type():SymbolType {
+ return symbol.type;
+ }
+
+ private function set_loopMode(value:LoopMode):LoopMode {
+ symbol.loopMode = value;
+ return value;
+ }
+
+ private function get_loopMode():LoopMode {
+ return symbol.loopMode;
+ }
+
+ private function get_symbolName():String {
+ return symbol.symbolName;
+ }
+
+ public function get_numLayers():Int {
+ return symbol.numLayers;
+ }
+
+ private function get_numFrames():Int {
+ return symbol.numFrames;
+ }
+
+
+ public function get_layers():Array {
+ return symbol._layers;
+ }
+
+ private function set_framerate(value:Float):Float {
+ return _framerate = value;
+ }
+
+ private function get_framerate():Float {
+ return _framerate == null ? symbol._library.frameRate : _framerate;
+ }
+
+ // # end region
+}
diff --git a/source/animateatlas/tilecontainer/TileContainerSymbol.hx b/source/animateatlas/tilecontainer/TileContainerSymbol.hx
new file mode 100644
index 00000000..316dddfa
--- /dev/null
+++ b/source/animateatlas/tilecontainer/TileContainerSymbol.hx
@@ -0,0 +1,475 @@
+package animateatlas.tilecontainer;
+
+import openfl.display.Tileset;
+import animateatlas.JSONData.PointData;
+import openfl.errors.ArgumentError;
+import openfl.geom.Rectangle;
+import openfl.errors.Error;
+import animateatlas.JSONData.ElementData;
+import animateatlas.HelperEnums.LoopMode;
+import animateatlas.HelperEnums.SymbolType;
+import openfl.display.FrameLabel;
+import animateatlas.JSONData.SymbolData;
+import animateatlas.JSONData.SymbolInstanceData;
+import animateatlas.JSONData.LayerData;
+import animateatlas.JSONData.BitmapPosData;
+import animateatlas.JSONData.Matrix3DData;
+import animateatlas.JSONData.LayerFrameData;
+import animateatlas.JSONData.ColorData;
+import openfl.geom.Matrix;
+import openfl.geom.ColorTransform;
+import openfl.display.DisplayObjectContainer;
+import openfl.display.TileContainer;
+import openfl.display.Tile;
+
+class TileContainerSymbol extends TileContainer {
+ public var currentLabel(get, never):String;
+ public var currentFrame(get, set):Int;
+ public var type(get, set):String;
+ public var loopMode(get, set):String;
+ public var symbolName(get, never):String;
+ public var numLayers(get, never):Int;
+ public var numFrames(get, never):Int;
+
+ private var _data:SymbolData;
+ private var _library:TileAnimationLibrary;
+ private var _symbolName:String;
+ private var _type:String;
+ private var _loopMode:String;
+ private var _currentFrame:Int;
+ private var _composedFrame:Int;
+ private var _bitmap:Tile;
+ private var _numFrames:Int;
+ private var _numLayers:Int;
+ private var _frameLabels:Array;
+ private var _colorTransform:ColorTransform;
+ private var _layers:Array;
+
+ private function new(data:SymbolData, library:TileAnimationLibrary, tileset:Tileset) {
+ super();
+ this.tileset = tileset;
+ _data = data;
+ _library = library;
+ _composedFrame = -1;
+ _numLayers = data.TIMELINE.LAYERS.length;
+ _numFrames = getNumFrames();
+ _frameLabels = _getFrameLabels();
+ _symbolName = data.SYMBOL_name;
+ _type = SymbolType.GRAPHIC;
+ _loopMode = LoopMode.LOOP;
+
+ createLayers();
+
+ // Create FrameMap caches if don't exist
+ for (layer in data.TIMELINE.LAYERS) {
+ if (layer.FrameMap != null)
+ return;
+
+ var map = new Map();
+
+ for (i in 0...layer.Frames.length) {
+ var frame = layer.Frames[i];
+ for (j in 0...frame.duration) {
+ map.set(i + j, frame);
+ }
+ }
+
+ layer.FrameMap = map;
+ }
+ }
+
+ public function reset():Void {
+ matrix.identity();
+
+ // copied from the setter for tile so we don't create a new matrix.
+ __rotation = null;
+ __scaleX = null;
+ __scaleY = null;
+ __setRenderDirty();
+
+ alpha = 1.0;
+ _currentFrame = 0;
+ _composedFrame = -1;
+ }
+
+ public function nextFrame():Void {
+ if (_loopMode != LoopMode.SINGLE_FRAME) {
+ currentFrame += 1;
+ }
+
+ moveMovieclip_MovieClips(1);
+ }
+
+ public function prevFrame():Void {
+ if (_loopMode != LoopMode.SINGLE_FRAME) {
+ currentFrame -= 1;
+ }
+
+ moveMovieclip_MovieClips(-1);
+ }
+
+ /** Moves all movie clips n frames, recursively. */
+ private function moveMovieclip_MovieClips(direction:Int = 1):Void {
+ if (_type == SymbolType.MOVIE_CLIP) {
+ currentFrame += direction;
+ }
+
+ for (l in 0..._numLayers) {
+ var layer:TileContainer = getLayer(l);
+ var numElements:Int = layer.numTiles;
+
+ for (e in 0...numElements) {
+ (try cast(layer.getTileAt(e), TileContainerSymbol) catch (e:Dynamic) null).moveMovieclip_MovieClips(direction);
+ }
+ }
+ }
+
+ public function update():Void {
+ for (i in 0..._numLayers) {
+ updateLayer(i);
+ }
+
+ _composedFrame = _currentFrame;
+ }
+
+ @:access(animateatlas)
+ private function updateLayer(layerIndex:Int):Void {
+ var layer:TileContainer = getLayer(layerIndex);
+ var frameData:LayerFrameData = getFrameData(layerIndex, _currentFrame);
+ var elements:Array = (frameData != null) ? frameData.elements : null;
+ var numElements:Int = (elements != null) ? elements.length : 0;
+ for (i in 0...numElements) {
+ var elementData:SymbolInstanceData = elements[i].SYMBOL_Instance;
+ if (elementData == null) {
+ continue;
+ }
+ // this is confusing but needed :(
+ var oldSymbol:TileContainerSymbol = (layer.numTiles > i) ? try
+ cast(layer.getTileAt(i), TileContainerSymbol)
+ catch (e:Dynamic)
+ null : null;
+
+ var newSymbol:TileContainerSymbol = null;
+ var symbolName:String = elementData.SYMBOL_name;
+
+ if (!_library.hasSymbol(symbolName)) {
+ symbolName = TileAnimationLibrary.BITMAP_SYMBOL_NAME;
+ }
+
+ if (oldSymbol != null && oldSymbol._symbolName == symbolName) {
+ newSymbol = oldSymbol;
+ } else {
+ if (oldSymbol != null) {
+ if (oldSymbol.parent != null)
+ oldSymbol.removeTile(oldSymbol);
+ _library.putSymbol(oldSymbol);
+ }
+
+ newSymbol = cast(_library.getSymbol(symbolName));
+ layer.addTileAt(newSymbol, i);
+ }
+
+ newSymbol.setTransformationMatrix(elementData.Matrix3D);
+ newSymbol.setBitmap(elementData.bitmap);
+ newSymbol.setColor(elementData.color);
+ newSymbol.setLoop(elementData.loop);
+ newSymbol.setType(elementData.symbolType);
+
+ if (newSymbol.type == SymbolType.GRAPHIC) {
+ var firstFrame:Int = elementData.firstFrame;
+ var frameAge:Int = Std.int(_currentFrame - frameData.index);
+
+ if (newSymbol.loopMode == LoopMode.SINGLE_FRAME) {
+ newSymbol.currentFrame = firstFrame;
+ } else if (newSymbol.loopMode == LoopMode.LOOP) {
+ newSymbol.currentFrame = (firstFrame + frameAge) % newSymbol._numFrames;
+ } else {
+ newSymbol.currentFrame = firstFrame + frameAge;
+ }
+ }
+ }
+
+ var numObsoleteSymbols:Int = (layer.numTiles - numElements);
+
+ for (i in 0...numObsoleteSymbols) {
+ try {
+ var oldSymbol = cast(layer.removeTileAt(numElements), TileContainerSymbol);
+ if (oldSymbol != null)
+ _library.putSymbol(oldSymbol);
+ } catch (e:Dynamic) {};
+ }
+ }
+
+ private function createLayers():Void {
+ // todo safety check for not initialiing twice
+ if (_layers != null) {
+ throw new Error("You must not call this twice");
+ }
+ _layers = new Array();
+
+ if (_numLayers <= 1) {
+ _layers.push(this);
+ } else {
+ for (i in 0..._numLayers) {
+ var layer:TileContainer = new TileContainer();
+ if (layer.data == null) {
+ layer.data = {layerName: getLayerData(i).Layer_name};
+ } else {
+ layer.data.layerName = getLayerData(i).Layer_name;
+ }
+ addTile(layer);
+ _layers.push(layer);
+ }
+ }
+ }
+
+ @:access(animateatlas)
+ public function setBitmap(data:BitmapPosData):Void {
+ if (data != null) {
+ var spriteData = _library.getSpriteData(data.name + "");
+
+ if (_bitmap == null) {
+ _bitmap = new Tile(-1);
+ _bitmap.rect = new Rectangle();
+ addTile(_bitmap);
+ }
+
+ _bitmap.rect.setTo(spriteData.x, spriteData.y, spriteData.w, spriteData.h);
+ _bitmap.__setRenderDirty(); // setTo() doesn't trigger the renderdirty
+
+ // aditional checks for rotation
+ if (spriteData.rotated) {
+ _bitmap.rotation = -90;
+ _bitmap.x = data.Position.x;
+ _bitmap.y = data.Position.y + spriteData.w;
+ } else {
+ _bitmap.rotation = 0;
+ _bitmap.x = data.Position.x;
+ _bitmap.y = data.Position.y;
+ }
+
+ addTileAt(_bitmap, 0);
+ } else if (_bitmap != null) {
+ if (_bitmap.parent != null)
+ _bitmap.parent.removeTile(_bitmap);
+ }
+ }
+
+ private function setTransformationMatrix(data:Matrix3DData):Void {
+ if (data.m00 != matrix.a || data.m01 != matrix.b || data.m10 != matrix.c || data.m11 != matrix.d || data.m30 != matrix.tx || data.m31 != matrix.ty) {
+ matrix.setTo(data.m00, data.m01, data.m10, data.m11, data.m30, data.m31);
+
+ // copied from the setter for tile so we don't create a new matrix.
+ __rotation = null;
+ __scaleX = null;
+ __scaleY = null;
+ __setRenderDirty();
+ }
+ }
+
+ private function setColor(data:ColorData):Void {
+ var newTransform = new ColorTransform();
+ if (data != null) {
+ newTransform.redOffset = (data.redOffset == null ? 0 : data.redOffset);
+ newTransform.greenOffset = (data.greenOffset == null ? 0 : data.greenOffset);
+ newTransform.blueOffset = (data.blueOffset == null ? 0 : data.blueOffset);
+ newTransform.alphaOffset = (data.AlphaOffset == null ? 0 : data.AlphaOffset);
+
+ newTransform.redMultiplier = (data.RedMultiplier == null ? 1 : data.RedMultiplier);
+ newTransform.greenMultiplier = (data.greenMultiplier == null ? 1 : data.greenMultiplier);
+ newTransform.blueMultiplier = (data.blueMultiplier == null ? 1 : data.blueMultiplier);
+ newTransform.alphaMultiplier = (data.alphaMultiplier == null ? 1 : data.alphaMultiplier);
+ }
+ colorTransform = newTransform;
+ }
+
+ private function setLoop(data:String):Void {
+ if (data != null) {
+ _loopMode = data;
+ } else {
+ _loopMode = LoopMode.LOOP;
+ }
+ }
+
+ private function setType(data:String):Void {
+ if (data != null) {
+ _type = data;
+ }
+ }
+
+ private function getNumFrames():Int {
+ var numFrames:Int = 0;
+
+ for (i in 0..._numLayers) {
+ var layer = getLayerData(i);
+ var frameDates:Array = (layer == null ? [] : layer.Frames);
+ var numFrameDates:Int = (frameDates != null) ? frameDates.length : 0;
+ var layerNumFrames:Int = (numFrameDates != 0) ? frameDates[0].index : 0;
+
+ for (j in 0...numFrameDates) {
+ layerNumFrames += frameDates[j].duration;
+ }
+
+ if (layerNumFrames > numFrames) {
+ numFrames = layerNumFrames;
+ }
+ }
+
+ return numFrames == 0 ? 1 : numFrames;
+ }
+
+ private function _getFrameLabels():Array {
+ var labels:Array = [];
+
+ for (i in 0..._numLayers) {
+ var layer = getLayerData(i);
+ var frameDates:Array = (layer == null ? [] : layer.Frames);
+ var numFrameDates:Int = (frameDates != null) ? frameDates.length : 0;
+
+ for (j in 0...numFrameDates) {
+ var frameData:LayerFrameData = frameDates[j];
+ if (frameData.name != null) {
+ labels.push(new FrameLabel(frameData.name, frameData.index));
+ }
+ }
+ }
+ labels.sort(sortLabels);
+ return labels;
+ }
+
+ public function getFrameLabels():Array {
+ return _frameLabels.map(f -> f.name); // Inlining. I feel a js
+ }
+
+ function sortLabels(i1:FrameLabel, i2:FrameLabel):Int {
+ var f1 = i1.frame;
+ var f2 = i2.frame;
+ if (f1 < f2) {
+ return -1;
+ } else if (f1 > f2) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ private function getLayer(layerIndex:Int):TileContainer {
+ return _layers[layerIndex];
+ }
+
+ public function getNextLabel(afterLabel:String = null):String {
+ var numLabels:Int = _frameLabels.length;
+ var startFrame:Int = getFrame(afterLabel == null ? currentLabel : afterLabel);
+
+ for (i in 0...numLabels) {
+ var label:FrameLabel = _frameLabels[i];
+ if (label.frame > startFrame) {
+ return label.name;
+ }
+ }
+
+ return (_frameLabels != null) ? _frameLabels[0].name : null;
+ }
+
+ private function get_currentLabel():String {
+ var numLabels:Int = _frameLabels.length;
+ var highestLabel:FrameLabel = (numLabels != 0) ? _frameLabels[0] : null;
+
+ for (i in 1...numLabels) {
+ var label:FrameLabel = _frameLabels[i];
+
+ if (label.frame <= _currentFrame) {
+ highestLabel = label;
+ } else {
+ break;
+ }
+ }
+
+ return (highestLabel != null) ? highestLabel.name : null;
+ }
+
+ public function getFrame(label:String):Int {
+ var numLabels:Int = _frameLabels.length;
+ for (i in 0...numLabels) {
+ var frameLabel:FrameLabel = _frameLabels[i];
+ if (frameLabel.name == label) {
+ return frameLabel.frame;
+ }
+ }
+ return -1;
+ }
+
+
+ private function get_currentFrame():Int {
+ return _currentFrame;
+ }
+
+ private function set_currentFrame(value:Int):Int {
+ while (value < 0) {
+ value += _numFrames;
+ }
+
+ if (_loopMode == LoopMode.PLAY_ONCE) {
+ _currentFrame = Std.int(Math.min(Math.max(value, 0), _numFrames - 1));
+ } else {
+ _currentFrame = Std.int(Math.abs(value % _numFrames));
+ }
+
+ if (_composedFrame != _currentFrame) {
+ update();
+ }
+ return value;
+ }
+
+ private function get_type():String {
+ return _type;
+ }
+
+ private function set_type(value:String):String {
+ if (SymbolType.isValid(value)) {
+ _type = value;
+ } else {
+ throw new ArgumentError("Invalid symbol type: " + value);
+ }
+ return value;
+ }
+
+ private function get_loopMode():String {
+ return _loopMode;
+ }
+
+ private function set_loopMode(value:String):String {
+ if (LoopMode.isValid(value)) {
+ _loopMode = value;
+ } else {
+ throw new ArgumentError("Invalid loop mode: " + value);
+ }
+ return value;
+ }
+
+ private function get_symbolName():String {
+ return _symbolName;
+ }
+
+ public function get_numLayers():Int {
+ return _numLayers;
+ }
+
+ public function get_numFrames():Int {
+ return _numFrames;
+ }
+
+ // data access
+
+ private function getLayerData(layerIndex:Int):LayerData {
+ return _data.TIMELINE.LAYERS[layerIndex];
+ }
+
+ private function getFrameData(layerIndex:Int, frameIndex:Int):LayerFrameData {
+ var layer = getLayerData(layerIndex);
+ if (layer == null)
+ return null;
+
+ return layer.FrameMap.get(frameIndex);
+ }
+}
diff --git a/source/funkin/system/CoolUtil.hx b/source/funkin/system/CoolUtil.hx
index 458c4332..65c76da8 100644
--- a/source/funkin/system/CoolUtil.hx
+++ b/source/funkin/system/CoolUtil.hx
@@ -1,5 +1,6 @@
package funkin.system;
+import animateatlas.AtlasFrameMaker;
import haxe.Json;
import funkin.menus.StoryMenuState.WeekData;
import flixel.graphics.frames.FlxAtlasFrames;
@@ -268,6 +269,11 @@ class CoolUtil
public static function loadFrames(path:String, Unique:Bool = false, Key:String = null):FlxFramesCollection {
var noExt = Path.withoutExtension(path);
+ if (Assets.exists('$noExt/Animation.json')
+ && Assets.exists('$noExt/spritemap.json')
+ && Assets.exists('$noExt/spritemap.png')) {
+ // TODO: Credit Smokey555!!
+ return AtlasFrameMaker.construct(noExt);
if (Assets.exists('$noExt.xml')) {
return Paths.getSparrowAtlasAlt(noExt);
} else if (Assets.exists('$noExt.txt')) {
diff --git a/source/funkin/system/Main.hx b/source/funkin/system/Main.hx
index c18d8e5b..66bc9ab8 100644
--- a/source/funkin/system/Main.hx
+++ b/source/funkin/system/Main.hx
@@ -32,6 +32,8 @@ import funkin.mods.ModsFolder;
class Main extends Sprite
{
+ // TODO: CREDIT SMOKEY FOR ATLAS STUFF!!
+
var gameWidth:Int = 1280; // Width of the game in pixels (might be less / more in actual pixels depending on your zoom).
var gameHeight:Int = 720; // Height of the game in pixels (might be less / more in actual pixels depending on your zoom).
var initialState:Class = TitleState; // The FlxState the game starts with.
diff --git a/update.bat b/update.bat
index 1866d461..178086fb 100644
--- a/update.bat
+++ b/update.bat
@@ -1,3 +1,4 @@
haxelib git flixel-yc29 https://www.github.com/YoshiCrafter29/flixel
haxelib git hscript-improved https://www.github.com/YoshiCrafter29/hscript-improved
+haxelib git Flixel-TextureAtlas https://github.com/Smokey555/Flixel-TextureAtlas
haxelib install hxCodec
\ No newline at end of file