Replace FlxAnimate with flixel-animate

Co-Authored-By: Hyper_ <40342021+NotHyper-474@users.noreply.github.com>
Co-Authored-By: MaybeMaru <97055307+MaybeMaru@users.noreply.github.com>
Co-Authored-By: Trayfellow <48515908+trayfellow@users.noreply.github.com>
This commit is contained in:
Abnormal
2025-11-21 18:53:42 -07:00
committed by Hundrec
co-authored by Hyper_ MaybeMaru Trayfellow
parent fe6f5eb821
commit fcfde61b24
51 changed files with 2071 additions and 2457 deletions
+1 -1
Submodule assets updated: d5de6a05fa...91af5e79b2
+3 -3
View File
@@ -56,11 +56,11 @@
"url": "https://github.com/FunkinCrew/flixel-addons"
},
{
"name": "flxanimate",
"name": "flixel-animate",
"type": "git",
"dir": null,
"ref": "49214278b9124823582cdcecd94f4a1de9a4b36b",
"url": "https://github.com/FunkinCrew/flxanimate"
"ref": "9aacdf7afe5ffdcd086a2898ebb7abb966b95a3d",
"url": "https://github.com/MaybeMaru/flixel-animate"
},
{
"name": "format",
+1 -1
View File
@@ -1304,7 +1304,7 @@ class Project extends HXProject
}
addHaxelib('polymod'); // Modding framework
addHaxelib('flxanimate'); // Texture atlas rendering
addHaxelib('flixel-animate'); // Texture atlas rendering
addHaxelib('json2object'); // JSON parsing
addHaxelib('jsonpath'); // JSON parsing
+2 -1
View File
@@ -1,13 +1,14 @@
package funkin;
import openfl.utils.Future;
import funkin.util.macro.ConsoleMacro;
/**
* A wrapper around `openfl.utils.Assets` which disallows access to the harmful functions.
* Later we'll add Funkin-specific caching to this.
*/
@:nullSafety
class Assets
class Assets implements ConsoleClass
{
/**
* The assets cache.
+13 -1
View File
@@ -111,6 +111,16 @@ class FunkinMemory
///// TEXTURES /////
/**
* Determine whether the texture with the given key is cached.
* @param key The key of the texture to check.
* @return Whether the texture is cached.
*/
public static function isTextureCached(key:String):Bool
{
return FlxG.bitmap.get(key) != null;
}
/**
* Ensures a texture with the given key is cached.
* @param key The key of the texture to cache.
@@ -233,6 +243,7 @@ class FunkinMemory
if (graphic != null)
{
FlxG.bitmap.remove(graphic);
graphic.persist = false;
graphic.destroy();
previousCachedTextures.remove(graphicKey);
Assets.cache.clear(graphicKey);
@@ -250,7 +261,7 @@ class FunkinMemory
{
var obj:Null<FlxGraphic> = FlxG.bitmap.get(key);
if (obj == null || obj.persist || permanentCachedTextures.exists(key) || key.contains("fonts"))
if (obj == null || (obj.persist && permanentCachedTextures.exists(key)) || key.contains("fonts"))
{
continue;
}
@@ -262,6 +273,7 @@ class FunkinMemory
if (key.contains(purgeEntry))
{
FlxG.bitmap.removeKey(key);
obj.persist = false;
obj.destroy();
}
}
+2 -1
View File
@@ -2,13 +2,14 @@ package funkin;
import flixel.graphics.frames.FlxAtlasFrames;
import openfl.utils.AssetType;
import funkin.util.macro.ConsoleMacro;
import haxe.io.Path;
/**
* A core class which handles determining asset paths.
*/
@:nullSafety
class Paths
class Paths implements ConsoleClass
{
static var currentLevel:Null<String> = null;
+10 -1
View File
@@ -83,7 +83,7 @@ typedef UnnamedAnimationData =
/**
* Optionally specify an asset path to use for this specific animation.
* ONLY for use by MultiSparrow characters.
* ONLY for use by MultiSparrow and MultiAnimateAtlas characters.
* @default The assetPath of the parent sprite
*/
@:optional
@@ -138,4 +138,13 @@ typedef UnnamedAnimationData =
@:default([])
@:optional
var frameIndices:Null<Array<Int>>;
/**
* The type of animation to use.
* Only available for texture atlases.
* Options: "framelabel", "symbol"
*/
@:default("framelabel")
@:optional
var animType:String;
}
+77 -2
View File
@@ -6,12 +6,14 @@ import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.character.ScriptedCharacter.ScriptedAnimateAtlasCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedBaseCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedMultiSparrowCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedMultiAnimateAtlasCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedPackerCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedSparrowCharacter;
import funkin.play.character.AnimateAtlasCharacter;
import funkin.play.character.BaseCharacter;
import funkin.play.character.SparrowCharacter;
import funkin.play.character.MultiSparrowCharacter;
import funkin.play.character.MultiAnimateAtlasCharacter;
import funkin.play.character.PackerCharacter;
import funkin.util.assets.DataAssets;
import funkin.util.VersionUtil;
@@ -160,6 +162,25 @@ class CharacterDataParser
}
}
var scriptedCharClassNames5:Array<String> = ScriptedMultiAnimateAtlasCharacter.listScriptClasses();
if (scriptedCharClassNames5.length > 0)
{
trace(' Instantiating ${scriptedCharClassNames5.length} (Multi-Animate Atlas) scripted characters...');
for (charCls in scriptedCharClassNames5)
{
try
{
var character:MultiAnimateAtlasCharacter = ScriptedMultiAnimateAtlasCharacter.init(charCls, DEFAULT_CHAR_ID);
characterScriptedClass.set(character.characterId, charCls);
}
catch (e)
{
trace(' FAILED to instantiate scripted Multi-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();
@@ -167,7 +188,8 @@ class CharacterDataParser
return !(scriptedCharClassNames1.contains(charCls)
|| scriptedCharClassNames2.contains(charCls)
|| scriptedCharClassNames3.contains(charCls)
|| scriptedCharClassNames4.contains(charCls));
|| scriptedCharClassNames4.contains(charCls)
|| scriptedCharClassNames5.contains(charCls));
});
if (scriptedCharClassNames.length > 0)
@@ -226,6 +248,8 @@ class CharacterDataParser
char = ScriptedSparrowCharacter.init(charScriptClass, charId);
case CharacterRenderType.Packer:
char = ScriptedPackerCharacter.init(charScriptClass, charId);
case CharacterRenderType.MultiAnimateAtlas:
char = ScriptedMultiAnimateAtlasCharacter.init(charScriptClass, charId);
default:
// We're going to assume that the script class does the rendering.
char = ScriptedBaseCharacter.init(charScriptClass, charId, CharacterRenderType.Custom);
@@ -243,6 +267,8 @@ class CharacterDataParser
char = new SparrowCharacter(charId);
case CharacterRenderType.Packer:
char = new PackerCharacter(charId);
case CharacterRenderType.MultiAnimateAtlas:
char = new MultiAnimateAtlasCharacter(charId);
default:
trace(' WARNING '.bold().bg_yellow() + ' Creating character with undefined renderType ${charData.renderType}');
char = new BaseCharacter(charId, CharacterRenderType.Custom);
@@ -431,6 +457,15 @@ class CharacterDataParser
public static final DEFAULT_SCALE:Float = 1;
public static final DEFAULT_SCROLL:Array<Float> = [0, 0];
public static final DEFAULT_STARTINGANIM:String = 'idle';
public static final DEFAULT_APPLYSTAGEMATRIX:Bool = false;
public static final DEFAULT_ANIMTYPE:String = "framelabel";
public static final DEFAULT_ATLASSETTINGS:funkin.data.stage.StageData.TextureAtlasData =
{
swfMode: true,
cacheOnLoad: false,
filterQuality: 1,
applyStageMatrix: false
};
/**
* Set unspecified parameters to their defaults.
@@ -559,6 +594,16 @@ class CharacterDataParser
input.flipX = DEFAULT_FLIPX;
}
if (input.applyStageMatrix == null)
{
input.applyStageMatrix = DEFAULT_APPLYSTAGEMATRIX;
}
if (input.atlasSettings == null)
{
input.atlasSettings = DEFAULT_ATLASSETTINGS;
}
if (input.animations.length == 0 && input.startingAnimation != null)
{
return null;
@@ -596,6 +641,11 @@ class CharacterDataParser
{
inputAnimation.flipY = DEFAULT_FLIPY;
}
if (inputAnimation.animType == null)
{
inputAnimation.animType = DEFAULT_ANIMTYPE;
}
}
// All good!
@@ -624,10 +674,15 @@ enum abstract CharacterRenderType(String) from String to String
public var MultiSparrow = 'multisparrow';
/**
* Renders the character using a spritesheet of symbols and JSON data.
* Renders the character using a single spritesheet of symbols and JSON data.
*/
public var AnimateAtlas = 'animateatlas';
/**
* Renders the character using multiple spritesheets of symbols and JSON data.
*/
public var MultiAnimateAtlas = 'multianimateatlas';
/**
* Renders the character using a custom method.
*/
@@ -674,6 +729,9 @@ typedef CharacterData =
*/
var healthIcon:Null<HealthIconData>;
/**
* Optional data about the death animation for the character.
*/
var death:Null<DeathData>;
/**
@@ -734,6 +792,23 @@ typedef CharacterData =
* @default false
*/
var flipX:Null<Bool>;
/**
* NOTE: This only applies to animate atlas characters.
*
* Whether to apply the stage matrix, if it was exported from a symbol instance.
* Also positions the Texture Atlas as it displays in Animate.
* Turning this on is only recommended if you prepositioned the character in Animate.
* For other cases, it should be turned off to act similarly to a normal FlxSprite.
*/
var applyStageMatrix:Null<Bool>;
/**
* Various settings for the prop.
* Only available for texture atlases.
*/
@:optional
var atlasSettings:funkin.data.stage.StageData.TextureAtlasData;
};
/**
@@ -103,6 +103,10 @@ class PlayerFreeplayDJData
var assetPath:String;
var animations:Array<AnimationData>;
@:optional
@:default(false)
var applyStageMatrix:Bool;
@:optional
@:default("BOYFRIEND")
var text1:String;
@@ -158,11 +162,16 @@ class PlayerFreeplayDJData
}
public inline function getAssetPath():String
return assetPath; // return Paths.animateAtlas(assetPath);
return assetPath; // return assetPath;
public inline function getAnimationsList():Array<AnimationData>
return animations;
public function useApplyStageMatrix():Bool
{
return applyStageMatrix;
}
public function getFreeplayDJText(index:Int):String
{
switch (index)
@@ -343,6 +352,10 @@ typedef PlayerResultsAnimationData =
*/
var renderType:String;
@:optional
@:default(false)
var applyStageMatrix:Bool;
@:optional
var assetPath:Null<String>;
+47 -1
View File
@@ -196,7 +196,7 @@ typedef StageDataProp =
/**
* The animation type to use.
* Options: "sparrow", "packer"
* Options: "sparrow", "packer", "animateatlas"
* @default "sparrow"
*/
@:default("sparrow")
@@ -228,6 +228,52 @@ typedef StageDataProp =
@:default("#FFFFFF")
@:optional
var color:String;
/**
* Various settings for the prop.
* Only available for texture atlases.
*/
@:optional
var atlasSettings:TextureAtlasData;
};
typedef TextureAtlasData =
{
/**
* If true, the texture atlas will behave as if it was exported as an SWF file.
* Notably, this allows MovieClip symbols to play.
*/
@:optional
var swfMode:Bool;
/**
* If true, filters and masks will be cached when the atlas is loaded, instead of during runtime.
*/
@:optional
var cacheOnLoad:Bool;
/**
* The filter quality.
* Available values are: HIGH, MEDIUM, LOW, and RUDY.
*
* If you're making an atlas sprite in HScript, you pass an Int instead:
*
* HIGH - 0
* MEDIUM - 1
* LOW - 2
* RUDY - 3
*/
@:optional
var filterQuality:Int;
/**
* Whether to apply the stage matrix, if it was exported from a symbol instance.
* Also positions the Texture Atlas as it displays in Animate.
* Turning this on is only recommended if you prepositioned the character in Animate.
* For other cases, it should be turned off to act similarly to a normal FlxSprite.
*/
@:optional
var applyStageMatrix:Bool;
};
typedef StageDataCharacter =
@@ -0,0 +1,35 @@
package funkin.graphics;
import funkin.graphics.FunkinSprite;
import animate.FlxAnimateController;
class FunkinAnimationController extends FlxAnimateController
{
/**
* The sprite that this animation controller is attached to.
*/
var _parentSprite:FunkinSprite;
public function new(sprite:FunkinSprite)
{
super(sprite);
_parentSprite = sprite;
}
/**
* We override `FlxAnimationController`'s `play` method to account for texture atlases.
*/
public override function play(animName:String, force = false, reversed = false, frame = 0):Void
{
if (animName == null || animName == '') animName = _parentSprite.getDefaultSymbol();
if (!_parentSprite.hasAnimation(animName))
{
// Skip if the animation doesn't exist
trace('Animation ${animName} does not exist!');
return;
}
super.play(animName, force, reversed, frame);
}
}
+432 -135
View File
@@ -9,20 +9,97 @@ import funkin.graphics.framebuffer.FixedBitmapData;
import openfl.display.BitmapData;
import flixel.math.FlxRect;
import flixel.math.FlxPoint;
import flixel.graphics.frames.FlxFrame.FlxFrameAngle;
import flixel.math.FlxMatrix;
import flixel.graphics.frames.FlxFrame;
import flixel.FlxCamera;
import openfl.system.System;
import flixel.system.FlxAssets.FlxGraphicAsset;
import funkin.FunkinMemory;
import animate.internal.SymbolItem;
import animate.internal.elements.Element;
import animate.internal.elements.AtlasInstance;
import animate.internal.elements.SymbolInstance;
import animate.FlxAnimate;
import animate.FlxAnimateFrames;
using StringTools;
typedef AtlasSpriteSettings =
{
/**
* If true, the texture atlas will behave as if it was exported as an SWF file.
* Notably, this allows MovieClip symbols to play.
*/
@:optional
var swfMode:Bool;
/**
* If true, filters and masks will be cached when the atlas is loaded, instead of during runtime.
*/
@:optional
var cacheOnLoad:Bool;
/**
* The filter quality.
* Available values are: HIGH, MEDIUM, LOW, and RUDY.
*
* If you're making an atlas sprite in HScript, you pass an Int instead:
*
* HIGH - 0
* MEDIUM - 1
* LOW - 2
* RUDY - 3
*/
@:optional
var filterQuality:FilterQuality;
/**
* Optional, an array of spritemaps for the atlas to load.
*/
@:optional
var spritemaps:Array<SpritemapInput>;
/**
* Optional, string of the metadata.json contents.
*/
@:optional
var metadataJson:String;
/**
* Optional, force the cache to use a specific key to index the texture atlas.
*/
@:optional
var cacheKey:String;
/**
* If true, the texture atlas will use a new slot in the cache.
*/
@:optional
var uniqueInCache:Bool;
/**
* Optional callback for when a symbol is created.
*/
@:optional
var onSymbolCreate:animate.internal.SymbolItem->Void;
/**
* Whether to apply the stage matrix, if it was exported from a symbol instance.
* Also positions the Texture Atlas as it displays in Animate.
* Turning this on is only recommended if you prepositioned the character in Animate.
* For other cases, it should be turned off to act similarly to a normal FlxSprite.
*/
@:optional
var applyStageMatrix:Bool;
}
/**
* An FlxSprite with additional functionality.
* - A more efficient method for creating solid color sprites.
* - TODO: Better cache handling for textures.
*/
@:nullSafety
class FunkinSprite extends FlxSprite
class FunkinSprite extends FlxAnimate
{
/**
* @param x Starting X position
@@ -33,6 +110,16 @@ class FunkinSprite extends FlxSprite
super(x, y);
}
override function initVars():Void
{
super.initVars();
var newController:FunkinAnimationController = new FunkinAnimationController(this);
animation = newController;
anim = newController;
}
/**
* Create a new FunkinSprite with a static texture.
* @param x The starting X position.
@@ -75,6 +162,20 @@ class FunkinSprite extends FlxSprite
return sprite;
}
/**
* Create a new FunkinSprite with an Adobe Animate texture atlas.
* @param x The starting X position.
* @param y The starting Y position.
* @param key The key of the texture to load.
* @return The new FunkinSprite.
*/
public static function createTextureAtlas(x:Float = 0.0, y:Float = 0.0, key:String, ?assetLibrary:Null<String>, ?settings:AtlasSpriteSettings):FunkinSprite
{
var sprite:FunkinSprite = new FunkinSprite(x, y);
sprite.loadTextureAtlas(key, assetLibrary ?? "", settings);
return sprite;
}
/**
* Load a static image as the sprite's texture.
* @param key The key of the texture to load.
@@ -83,7 +184,17 @@ class FunkinSprite extends FlxSprite
public function loadTexture(key:String):FunkinSprite
{
var graphicKey:String = Paths.image(key);
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
if (!Assets.exists(graphicKey, IMAGE))
{
FlxG.log.error('Texture not found, check your path! $graphicKey');
return this;
}
if (!FunkinMemory.isTextureCached(graphicKey))
{
FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
}
loadGraphic(graphicKey);
@@ -163,6 +274,63 @@ class FunkinSprite extends FlxSprite
return loadBitmapData(inputBitmap);
}
/**
* Loads an Adobe Animate texture atlas as the sprite's texture.
* @param key The key of the texture to load.
* @param settings Additional settings for loading the atlas.
* @return This sprite, for chaining.
*/
public function loadTextureAtlas(key:Null<String>, ?assetLibrary:Null<String>, ?settings:AtlasSpriteSettings):FunkinSprite
{
if (key == null)
{
throw 'Null path specified for loadTextureAtlas()!';
}
var validatedSettings:AtlasSpriteSettings =
{
swfMode: settings?.swfMode ?? false,
cacheOnLoad: settings?.cacheOnLoad ?? false,
filterQuality: settings?.filterQuality ?? MEDIUM,
spritemaps: settings?.spritemaps ?? null,
metadataJson: settings?.metadataJson ?? null,
cacheKey: settings?.cacheKey ?? null,
uniqueInCache: settings?.uniqueInCache ?? false,
onSymbolCreate: settings?.onSymbolCreate ?? null,
applyStageMatrix: settings?.applyStageMatrix ?? false
};
var assetLibrary:String = assetLibrary ?? "";
var graphicKey:String = "";
if (assetLibrary != "")
{
graphicKey = Paths.animateAtlas(key, assetLibrary);
}
else
{
graphicKey = Paths.animateAtlas(key);
}
// Validate asset path.
if (!Assets.exists('${graphicKey}/Animation.json'))
{
throw 'No Animation.json file exists at the specified path (${graphicKey})';
}
this.applyStageMatrix = validatedSettings.applyStageMatrix ?? false;
frames = FlxAnimateFrames.fromAnimate(graphicKey, validatedSettings.spritemaps, validatedSettings.metadataJson, validatedSettings.cacheKey,
validatedSettings.uniqueInCache, {
swfMode: validatedSettings.swfMode,
cacheOnLoad: validatedSettings.cacheOnLoad,
filterQuality: validatedSettings.filterQuality,
onSymbolCreate: validatedSettings.onSymbolCreate
});
return this;
}
/**
* Load an animated texture (Sparrow atlas spritesheet) as the sprite's texture.
* @param key The key of the texture to load.
@@ -171,7 +339,7 @@ class FunkinSprite extends FlxSprite
public function loadSparrow(key:String):FunkinSprite
{
var graphicKey:String = Paths.image(key);
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
if (!FunkinMemory.isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
this.frames = Paths.getSparrowAtlas(key);
@@ -186,73 +354,13 @@ class FunkinSprite extends FlxSprite
public function loadPacker(key:String):FunkinSprite
{
var graphicKey:String = Paths.image(key);
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
if (!FunkinMemory.isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
this.frames = Paths.getPackerAtlas(key);
return this;
}
/**
* Determine whether the texture with the given key is cached.
* @param key The key of the texture to check.
* @return Whether the texture is cached.
*/
public static function isTextureCached(key:String):Bool
{
return FlxG.bitmap.get(key) != null;
}
@:deprecated("Use FunkinMemory.cacheTexture() instead")
public static function cacheTexture(key:String):Void
{
FunkinMemory.cacheTexture(Paths.image(key));
}
@:deprecated("Use FunkinMemory.permanentCacheTexture() instead")
public static function permanentCacheTexture(key:String):Void
{
@:privateAccess FunkinMemory.permanentCacheTexture(Paths.image(key));
}
@:deprecated("Use FunkinMemory.cacheTexture() instead")
public static function cacheSparrow(key:String):Void
{
FunkinMemory.cacheTexture(Paths.image(key));
}
@:deprecated("Use FunkinMemory.cacheTexture() instead")
public static function cachePacker(key:String):Void
{
FunkinMemory.cacheTexture(Paths.image(key));
}
@:deprecated("Use FunkinMemory.preparePurgeTextureCache() instead")
public static function preparePurgeCache():Void
{
FunkinMemory.preparePurgeTextureCache();
}
@:deprecated("Use FunkinMemory.purgeCache() instead")
public static function purgeCache():Void
{
FunkinMemory.purgeCache();
}
static function isGraphicCached(graphic:FlxGraphic):Bool
{
var result = null;
if (graphic == null) return false;
result = FlxG.bitmap.get(graphic.key);
if (result == null) return false;
if (result != graphic)
{
FlxG.log.warn('Cached graphic does not match original: ${graphic.key}');
return false;
}
return true;
}
/**
* @param id The animation ID to check.
* @return Whether the animation is dynamic (has multiple frames). `false` for static, one-frame animations.
@@ -266,6 +374,67 @@ class FunkinSprite extends FlxSprite
return animData.numFrames > 1;
}
/**
* Whether or not this sprite has an animation with the given ID.
* @param id The ID of the animation to check.
*/
public function hasAnimation(id:String):Bool
{
var animationList:Array<String> = this.animation.getNameList();
if (animationList.contains(id))
{
return true;
}
else if (this.isAnimate && !animationList.contains(id))
{
return addAnimationIfMissing(id);
}
return false;
}
/**
* Adds an animation if it doesn't exist.
* @param id The animation ID to check.
*/
function addAnimationIfMissing(id:String):Bool
{
@:privateAccess
var symbols:Array<String> = this.library.dictionary.keys().array();
var frameLabels:Array<String> = listAnimations();
if (frameLabels.contains(id))
{
// Animation exists as a frame label but wasn't added, so we add it
anim.addByFrameLabel(id, id, this.library.frameRate, false);
return true;
}
else if (symbols.contains(id))
{
// Animation exists as a symbol but wasn't added, so we add it
anim.addBySymbol(id, id, this.library.frameRate, false);
return true;
}
return false;
}
/**
* Gets the current animation ID.
*/
public function getCurrentAnimation():String
{
return this.animation.curAnim?.name ?? '';
}
/**
* Whether or not the current animation is finished.
*/
public function isAnimationFinished():Bool
{
return this.animation?.finished ?? false;
}
/**
* Acts similarly to `makeGraphic`, but with improved memory usage,
* at the expense of not being able to paint onto the resulting sprite.
@@ -286,6 +455,201 @@ class FunkinSprite extends FlxSprite
return this;
}
/**
* @return A list of all the animations this sprite has available.
*/
public function listAnimations():Array<String>
{
var frameLabels:Array<String> = getFrameLabelList();
var animationList:Array<String> = this.animation.getNameList();
return frameLabels.concat(animationList);
}
/**
* TEXTURE ATLAS-EXCLUSIVE FUNCTIONS
* These functions only work if the sprite's texture is an Adobe Animate texture atlas.
* Calling these functions on non-texture atlases will do nothing.
*/
/**
* Gets a list of frame labels from the default timeline.
*/
public function getFrameLabelList():Array<String>
{
if (!this.isAnimate)
{
trace('WARNING: getFrameLabelList() only works texture atlases!');
return [];
}
var foundLabels:Array<String> = [];
var mainTimeline:Null<animate.internal.Timeline> = this.library.timeline;
for (layer in mainTimeline.layers)
{
@:nullSafety(Off)
for (frame in layer.frames)
{
if (frame.name.rtrim() != '')
{
foundLabels.push(frame.name);
}
}
}
return foundLabels;
}
/**
* Gets a frame label by its name.
* @param name The name of the frame label to retrieve.
* @return The frame label, or null if it doesn't exist.
*/
public function getFrameLabel(name:String):Null<animate.internal.Frame>
{
if (!this.isAnimate)
{
trace('WARNING: getFrameLabel() only works texture atlases!');
return null;
}
for (layer in this.timeline.layers)
{
@:nullSafety(Off)
for (frame in layer.frames)
{
if (frame.name == name)
{
return frame;
}
}
}
return null;
}
/**
* Returns the default symbol in the atlas.
*/
public function getDefaultSymbol():String
{
if (!this.isAnimate)
{
trace('WARNING: getDefaultSymbol() only works texture atlases!');
return '';
}
return library.timeline.name;
}
/**
* Replaces the graphic of a symbol in the atlas.
* @param symbol The symbol to replace.
* @param graphic The new graphic to use.
* @param adjustScale Whether to adjust the scale of new frame to match the old one.
*/
public function replaceSymbolGraphic(symbol:String, ?graphic:Null<FlxGraphicAsset>, ?adjustScale:Bool = true):Void
{
if (!this.isAnimate)
{
trace('WARNING: replaceSymbolGraphic() only works texture atlases!');
return;
}
var elements:Array<Element> = getSymbolElements(symbol);
for (element in elements)
{
var atlasInstance:AtlasInstance = element.toAtlasInstance();
var frame:Null<FlxFrame> = graphic != null ? FlxG.bitmap.add(graphic).imageFrame.frame : null;
atlasInstance.replaceFrame(frame, adjustScale);
element = atlasInstance;
}
}
/**
* Returns the first element of a symbol in the atlas.
* @param symbol The symbol to get elements from.
* @return The first element of the symbol. WARNING: Can be null.
*/
public function getFirstElement(symbol:String):Null<Element>
{
if (!this.isAnimate)
{
trace('WARNING: getFirstElement() only works texture atlases!');
return null;
}
var symbolElements:Array<Element> = getSymbolElements(symbol);
return symbolElements.length > 0 ? symbolElements[0] : null;
}
/**
* Returns the elements of a symbol in the atlas.
* @param symbol The symbol to get elements from.
*/
public function getSymbolElements(symbol:String):Array<Element>
{
if (!this.isAnimate)
{
trace('WARNING: getSymbolElements() only works texture atlases!');
return [];
}
var symbolInstance:Null<SymbolItem> = this.library.getSymbol(symbol);
if (symbolInstance == null)
{
throw 'Symbol not found in atlas: ${symbol}';
return [];
}
var elements:Array<Element> = symbolInstance.timeline.getElementsAtIndex(0);
if (elements?.length == 0)
{
trace('WARNING: No Atlas Elements found for "$symbol" symbol.');
}
return elements ?? [];
}
/**
* Scales an element by a certain multiplier.
* @param element The element to scale.
* @param scale The scale multiplier.
* @param positionOffset The offset to apply to `tx` and `ty` after scaling.
* (Or in other words, the position of the element.)
*/
public function scaleElement(element:Element, scale:Float, positionOffset:Float = 0, scaleEverything:Bool = false):Void
{
if (!this.isAnimate)
{
trace('WARNING: scaleElement() only works texture atlases!');
return;
}
var elementMatrix:FlxMatrix = element.matrix;
if (scaleEverything)
{
elementMatrix.scale(scale, scale);
return;
}
var symbolInstance:SymbolInstance = element.parentFrame.convertToSymbol(0, 1);
var transformPoint:FlxPoint = symbolInstance.transformationPoint;
elementMatrix.a += scale;
elementMatrix.d += scale;
elementMatrix.tx -= transformPoint.x * scale;
elementMatrix.ty -= transformPoint.y * scale;
elementMatrix.tx -= positionOffset;
elementMatrix.ty -= positionOffset;
}
/**
* Ensure scale is applied when cloning a sprite.R
* The default `clone()` method acts kinda weird TBH.
@@ -325,77 +689,10 @@ class FunkinSprite extends FlxSprite
return _rect;
}
/**
* Returns the screen position of this object.
*
* @param result Optional arg for the returning point
* @param camera The desired "screen" coordinate space. If `null`, `FlxG.camera` is used.
* @return The screen position of this object.
*/
public override function getScreenPosition(?result:FlxPoint, ?camera:FlxCamera):FlxPoint
override function preparePixelPerfectMatrix(matrix:FlxMatrix)
{
if (result == null) result = FlxPoint.get();
if (camera == null) camera = FlxG.camera;
result.set(x, y);
if (pixelPerfectPosition)
{
_rect.width = _rect.width / this.scale.x;
_rect.height = _rect.height / this.scale.y;
_rect.x = _rect.x / this.scale.x;
_rect.y = _rect.y / this.scale.y;
_rect.round();
_rect.x = _rect.x * this.scale.x;
_rect.y = _rect.y * this.scale.y;
_rect.width = _rect.width * this.scale.x;
_rect.height = _rect.height * this.scale.y;
}
return result.subtract(camera.scroll.x * scrollFactor.x, camera.scroll.y * scrollFactor.y);
}
override function drawSimple(camera:FlxCamera):Void
{
getScreenPosition(_point, camera).subtractPoint(offset);
if (isPixelPerfectRender(camera))
{
_point.x = _point.x / this.scale.x;
_point.y = _point.y / this.scale.y;
_point.round();
_point.x = _point.x * this.scale.x;
_point.y = _point.y * this.scale.y;
}
_point.copyToFlash(_flashPoint);
camera.copyPixels(_frame, framePixels, _flashRect, _flashPoint, colorTransform, blend, antialiasing);
}
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 (bakedRotationAngle <= 0)
{
updateTrig();
if (angle != 0) _matrix.rotateWithTrig(_cosAngle, _sinAngle);
}
getScreenPosition(_point, camera).subtractPoint(offset);
_point.add(origin.x, origin.y);
_matrix.translate(_point.x, _point.y);
if (isPixelPerfectRender(camera))
{
_matrix.tx = Math.round(_matrix.tx / this.scale.x) * this.scale.x;
_matrix.ty = Math.round(_matrix.ty / this.scale.y) * this.scale.y;
}
camera.drawPixels(_frame, framePixels, _matrix, colorTransform, blend, antialiasing, shader);
matrix.tx = Math.round(matrix.tx / this.scale.x) * this.scale.x;
matrix.ty = Math.round(matrix.ty / this.scale.y) * this.scale.y;
}
public override function destroy():Void
@@ -1,414 +0,0 @@
package funkin.graphics.adobeanimate;
import flixel.util.FlxSignal.FlxTypedSignal;
import flxanimate.FlxAnimate;
import flxanimate.FlxAnimate.Settings;
import flixel.graphics.frames.FlxFrame;
import flixel.system.FlxAssets.FlxGraphicAsset;
import flixel.math.FlxPoint;
import flxanimate.animate.FlxKeyFrame;
/**
* A sprite which provides convenience functions for rendering a texture atlas with animations.
*/
@:nullSafety
class FlxAtlasSprite extends FlxAnimate
{
static final SETTINGS:Settings =
{
// ?ButtonSettings:Map<String, flxanimate.animate.FlxAnim.ButtonSettings>,
FrameRate: 24.0,
Reversed: false,
// ?OnComplete:Void -> Void,
ShowPivot: false,
Antialiasing: true,
ScrollFactor: null,
// Offset: new FlxPoint(0, 0), // This is just FlxSprite.offset
};
/**
* Signal dispatched when an animation advances to the next frame.
*/
public var onAnimationFrame:FlxTypedSignal<String->Int->Void> = new FlxTypedSignal();
/**
* Signal dispatched when a non-looping animation finishes playing.
*/
public var onAnimationComplete:FlxTypedSignal<String->Void> = new FlxTypedSignal();
/**
* Signal dispatched when a looping animation finishes playing.
*/
public var onAnimationLoop:FlxTypedSignal<String->Void> = new FlxTypedSignal();
var currentAnimation:String = '';
var canPlayOtherAnims:Bool = true;
@:nullSafety(Off) // null safety HATES new classes atm, it'll be fixed in haxe 4.0.0?
public function new(x:Float, y:Float, ?path:String, ?settings:Settings, ?pathSafety:Bool = true)
{
if (settings == null) settings = SETTINGS;
if (path == null && pathSafety) throw 'Null path specified for FlxAtlasSprite!';
// Validate asset path.
if (!Assets.exists('${path}/Animation.json')
&& pathSafety) throw 'FlxAtlasSprite does not have an Animation.json file at the specified path (${path})';
super(x, y, path, settings);
if (this.anim.stageInstance == null && pathSafety) throw 'FlxAtlasSprite not initialized properly. Are you sure the path (${path}) exists?';
onAnimationComplete.add(cleanupAnimation);
// This defaults the sprite to play the first animation in the atlas,
// then pauses it. This ensures symbols are intialized properly.
if (this.anim.curInstance != null)
{
this.anim.play('');
this.anim.pause();
}
this.anim.onComplete.add(_onAnimationComplete);
this.anim.onFrame.add(_onAnimationFrame);
}
/**
* @return A list of all the animations this sprite has available.
*/
public function listAnimations():Array<String>
{
var mainSymbol = this.anim.symbolDictionary[this.anim.stageInstance.symbol.name];
if (mainSymbol == null)
{
FlxG.log.error('FlxAtlasSprite does not have its main symbol!');
return [];
}
return mainSymbol.getFrameLabels().map(keyFrame -> keyFrame.name).filterNull();
}
/**
* @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 || anim.symbolDictionary.exists(id);
}
/**
* @return The current animation being played.
*/
public function getCurrentAnimation():String
{
return this.currentAnimation;
}
var _completeAnim:Bool = false;
var fr:Null<FlxKeyFrame> = null;
var looping:Bool = false;
public var ignoreExclusionPref:Array<String> = [];
/**
* 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
* @param loop Whether to loop the animation
* @param startFrame The frame to start the animation on
* NOTE: `loop` and `ignoreOther` are not compatible with each other!
*/
public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false, loop:Bool = false, startFrame:Int = 0):Void
{
// Skip if not allowed to play animations.
if ((!canPlayOtherAnims))
{
if (this.currentAnimation == id && restart) {}
else if (ignoreExclusionPref != null && ignoreExclusionPref.length > 0)
{
var detected:Bool = false;
for (entry in ignoreExclusionPref)
{
if (StringTools.startsWith(id, entry))
{
detected = true;
break;
}
}
if (!detected) return;
}
else
return;
}
if (anim == null) return;
if (id == null || id == '') id = this.currentAnimation;
if (this.currentAnimation == id && !restart)
{
if (!anim.isPlaying)
{
if (fr != null) anim.curFrame = fr.index + startFrame;
else
anim.curFrame = startFrame;
// Resume animation if it's paused.
anim.resume();
}
return;
}
else if (!hasAnimation(id))
{
// Skip if the animation doesn't exist
trace('Animation ' + id + ' not found');
return;
}
this.currentAnimation = id;
anim.onComplete.removeAll();
anim.onComplete.add(function() {
_onAnimationComplete();
});
looping = loop;
// Prevent other animations from playing if `ignoreOther` is true.
if (ignoreOther) canPlayOtherAnims = false;
// Move to the first frame of the animation.
// goToFrameLabel(id);
// trace('Playing animation $id');
if ((id == null || id == "") || this.anim.symbolDictionary.exists(id) || (this.anim.getByName(id) != null))
{
this.anim.play(id, restart, false, startFrame);
this.currentAnimation = anim.curSymbol.name;
fr = null;
}
var frameLabelNames = getFrameLabelNames();
// Only call goToFrameLabel if there is a frame label with that name. This prevents annoying warnings!
if (frameLabelNames != null && frameLabelNames.indexOf(id) != -1)
{
goToFrameLabel(id);
fr = anim.getFrameLabel(id);
anim.curFrame += startFrame;
// Resume animation if it's paused.
anim.resume();
}
}
override public function update(elapsed:Float):Void
{
super.update(elapsed);
}
/**
* Returns true if the animation has finished playing.
* @return Whether the animation has finished playing.
*/
public function isAnimationFinished():Bool
{
return isLoopComplete();
}
/**
* Returns true if the animation has reached the last frame.
* Can be true even if animation is configured to loop.
* @return Whether the animation has reached the last frame.
*/
public function isLoopComplete():Bool
{
if (this.anim == null) return false;
if (!this.anim.isPlaying) return false;
if (fr != null)
{
var curFrame = anim.curFrame;
var startFrame = fr.index;
var endFrame = (fr.index + fr.duration);
return (anim.reversed) ? (curFrame < startFrame) : (curFrame >= endFrame);
}
return (anim.reversed && anim.curFrame == 0 || !(anim.reversed) && (anim.curFrame) >= (anim.length - 1));
}
/**
* 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);
}
function goToFrameLabel(label:String):Void
{
this.anim.goToFrameLabel(label);
}
function getFrameLabelNames(?layer:haxe.extern.EitherType<Int, String>):Null<Array<String>>
{
var labels = this.anim.getFrameLabels(layer);
var array = [];
for (label in labels)
{
array.push(label.name);
}
return array;
}
function getNextFrameLabel(label:String):String
{
return listAnimations()[(getLabelIndex(label) + 1) % listAnimations().length];
}
function getLabelIndex(label:String):Int
{
return listAnimations().indexOf(label);
}
function goToFrameIndex(index:Int):Void
{
this.anim.curFrame = index;
}
public function cleanupAnimation(_:String):Void
{
canPlayOtherAnims = true;
// this.currentAnimation = null;
this.anim.pause();
}
function _onAnimationFrame(frame:Int):Void
{
if (currentAnimation != null)
{
onAnimationFrame.dispatch(currentAnimation, frame);
if (isLoopComplete())
{
anim.pause();
if (looping)
{
anim.curFrame = (fr != null) ? fr.index : 0;
anim.resume();
_onAnimationLoop();
}
else if (fr != null && anim.curFrame != anim.length - 1)
{
anim.curFrame--;
_onAnimationComplete();
}
}
}
}
function _onAnimationComplete():Void
{
if (currentAnimation != null)
{
onAnimationComplete.dispatch(currentAnimation);
}
else
{
onAnimationComplete.dispatch('');
}
}
function _onAnimationLoop():Void
{
if (currentAnimation != null)
{
onAnimationLoop.dispatch(currentAnimation);
}
else
{
onAnimationLoop.dispatch('');
}
}
var prevFrames:Map<Int, FlxFrame> = [];
public function replaceFrameGraphic(index:Int, ?graphic:FlxGraphicAsset):Void
{
var cond = false;
if (graphic == null) cond = true;
else
{
if ((graphic is String)) cond = !Assets.exists(graphic)
else
cond = false;
}
if (cond)
{
var prevFrame:Null<FlxFrame> = prevFrames.get(index);
if (prevFrame == null) return;
prevFrame.copyTo(frames.getByIndex(index));
return;
}
var prevFrame:FlxFrame = prevFrames.get(index) ?? frames.getByIndex(index).copyTo();
prevFrames.set(index, prevFrame);
@:nullSafety(Off) // TODO: Remove this once flixel.system.frontEnds.BitmapFrontEnd has been null safed
var frame = FlxG.bitmap.add(graphic).imageFrame.frame;
frame.copyTo(frames.getByIndex(index));
// Additional sizing fix.
@:privateAccess
if (true)
{
var frame = frames.getByIndex(index);
frame.tileMatrix.a = prevFrame.frame.width / frame.frame.width;
frame.tileMatrix.d = prevFrame.frame.height / frame.frame.height;
}
}
public function getBasePosition():Null<FlxPoint>
{
// var stagePos = new FlxPoint(anim.stageInstance.matrix.tx, anim.stageInstance.matrix.ty);
var instancePos = new FlxPoint(anim.curInstance.matrix.tx, anim.curInstance.matrix.ty);
var firstElement = anim.curSymbol.timeline?.get(0)?.get(0)?.get(0);
if (firstElement == null) return instancePos;
var firstElementPos = new FlxPoint(firstElement.matrix.tx, firstElement.matrix.ty);
return instancePos + firstElementPos;
}
public function getPivotPosition():Null<FlxPoint>
{
return anim.curInstance.symbol.transformationPoint;
}
public override function destroy():Void
{
for (prevFrameId in prevFrames.keys())
{
replaceFrameGraphic(prevFrameId, null);
}
super.destroy();
}
}
+5
View File
@@ -274,6 +274,11 @@ class PolymodHandler
Polymod.addImportAlias('funkin.play.character.CharacterDataParser', funkin.data.character.CharacterData.CharacterDataParser);
Polymod.addImportAlias('funkin.play.character.CharacterData.CharacterDataParser', funkin.data.character.CharacterData.CharacterDataParser);
// `FlxAtlasSprite` was merged into `FunkinSprite` and then removed.
// We add the import alias here so mods don't error out as much.
Polymod.addImportAlias('funkin.graphics.adobeanimate.FlxAtlasSprite', funkin.graphics.FunkinSprite);
Polymod.addImportAlias('funkin.modding.base.ScriptedFlxAtlasSprite', funkin.graphics.ScriptedFunkinSprite);
// `funkin.util.FileUtil` has unrestricted access to the file system.
Polymod.addImportAlias('funkin.util.FileUtil', funkin.util.FileUtilSandboxed);
@@ -1,8 +0,0 @@
package funkin.modding.base;
/**
* A script that can be tied to an FlxAtlasSprite
* Create a scripted class that extends FlxAtlasSprite to use this.
*/
@:hscriptClass
class ScriptedFlxAtlasSprite extends funkin.graphics.adobeanimate.FlxAtlasSprite implements HScriptedClass {}
+18 -14
View File
@@ -20,11 +20,9 @@ import funkin.audio.FunkinSound;
import funkin.data.freeplay.player.PlayerData.PlayerResultsAnimationData;
import funkin.data.freeplay.player.PlayerRegistry;
import funkin.data.song.SongRegistry;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.FunkinCamera;
import funkin.graphics.FunkinSprite;
import funkin.graphics.shaders.LeftMaskShader;
import funkin.modding.base.ScriptedFlxAtlasSprite;
import funkin.play.components.ClearPercentCounter;
import funkin.play.components.TallyCounter;
import funkin.play.scoring.Scoring;
@@ -78,7 +76,7 @@ class ResultState extends MusicBeatSubState
var characterAtlasAnimations:Array<
{
sprite:FlxAtlasSprite,
sprite:FunkinSprite,
delay:Float,
forceLoop:Bool,
startFrameLabel:String,
@@ -193,7 +191,7 @@ class ResultState extends MusicBeatSubState
add(soundSystem);
// Fetch playable character data. Default to BF on the results screen if we can't find it.
playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(params.characterId);
playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(params.characterId) ?? params.characterId;
playerCharacter = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? 'bf');
trace('Got playable character: ${playerCharacter?.getName()}');
@@ -222,17 +220,22 @@ class ResultState extends MusicBeatSubState
{
case 'animateatlas':
@:nullSafety(Off)
var animation:FlxAtlasSprite = null;
var animation:FunkinSprite = null;
var xPos = offsets[0] + (FullScreenScaleMode.gameCutoutSize.x / 2);
var yPos = offsets[1];
if (animData.scriptClass != null) animation = ScriptedFlxAtlasSprite.init(animData.scriptClass, xPos, yPos);
if (animData.scriptClass != null) animation = ScriptedFunkinSprite.init(animData.scriptClass, xPos, yPos);
else
animation = new FlxAtlasSprite(xPos, yPos, Paths.animateAtlas(animPath, animLibrary));
animation = FunkinSprite.createTextureAtlas(xPos, yPos, animPath, animLibrary);
if (animation == null) continue;
if (animData?.applyStageMatrix ?? false)
{
animation.applyStageMatrix = true;
}
animation.zIndex = animData.zIndex ?? 500;
animation.scale.set(animData.scale ?? 1.0, animData.scale ?? 1.0);
@@ -240,7 +243,7 @@ class ResultState extends MusicBeatSubState
if (!(animData.looped ?? true))
{
// Animation is not looped.
animation.onAnimationComplete.add((_name:String) -> {
animation.anim.onFinish.add((_name:String) -> {
if (animation != null)
{
animation.anim.pause();
@@ -249,23 +252,24 @@ class ResultState extends MusicBeatSubState
}
else if (animData.loopFrameLabel != null)
{
animation.onAnimationComplete.add((_name:String) -> {
animation.anim.onFinish.add((_name:String) -> {
if (animation != null)
{
animation.playAnimation(animData.loopFrameLabel ?? '', true, false, true); // unpauses this anim, since it's on PlayOnce!
animation.anim.play(animData.loopFrameLabel ?? '', true); // unpauses this anim, since it's on PlayOnce!
animation.anim.curAnim.looped = true;
}
});
}
else if (animData.loopFrame != null)
{
animation.onAnimationComplete.add((_name:String) -> {
animation.anim.onFinish.add((_name:String) -> {
if (animation != null)
{
animation.anim.curFrame = animData.loopFrame ?? 0;
animation.anim.play(); // unpauses this anim, since it's on PlayOnce!
animation.anim.play("", true, false, animData.loopFrame ?? 0); // unpauses this anim, since it's on PlayOnce!
}
});
}
// Hide until ready to play.
animation.visible = false;
// Queue to play.
@@ -660,7 +664,7 @@ class ResultState extends MusicBeatSubState
new FlxTimer().start(atlas.delay, _ -> {
if (atlas.sprite == null) return;
atlas.sprite.visible = true;
atlas.sprite.playAnimation(atlas.startFrameLabel);
atlas.sprite.anim.play(atlas.startFrameLabel);
if (atlas.sound != "")
{
var sndPath:String = Paths.stripLibrary(atlas.sound);
@@ -1,35 +1,11 @@
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 funkin.graphics.FunkinSprite;
import flixel.system.FlxAssets.FlxGraphicAsset;
import flixel.util.FlxColor;
import flixel.util.FlxDestroyUtil;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.util.assets.FlxAnimationUtil;
import funkin.modding.events.ScriptEvent;
import funkin.data.animation.AnimationData;
import funkin.data.character.CharacterData.CharacterRenderType;
import flixel.util.FlxDirectionFlags;
import openfl.display.BitmapData;
import openfl.display.BlendMode;
/**
* Individual animation data for an AnimateAtlasCharacter.
*/
typedef AnimateAtlasAnimation =
{
name:String,
prefix:String,
offsets:Null<Array<Float>>,
looped:Bool,
}
import flixel.math.FlxPoint;
/**
* An AnimateAtlasCharacter is a Character which is rendered by
@@ -40,39 +16,13 @@ typedef AnimateAtlasAnimation =
*/
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 currentAnimName:Null<String> = null;
var animFinished:Bool = false;
var originalSizes(default, never):FlxPoint = new FlxPoint(0, 0);
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
{
// Display a custom scope for debugging purposes.
@@ -83,756 +33,71 @@ class AnimateAtlasCharacter extends BaseCharacter
try
{
trace('Loading assets for Animate Atlas character "${characterId}"', flixel.util.FlxColor.fromString("#89CFF0"));
var atlasSprite:FlxAtlasSprite = loadAtlasSprite();
setSprite(atlasSprite);
loadAtlas();
loadAnimations();
}
catch (e)
{
throw "Exception thrown while building FlxAtlasSprite: " + e;
throw "Exception thrown while building sprite: " + e;
}
trace('[ATLASCHAR] Successfully loaded texture atlas for ${characterId} with ${_data.animations.length} animations.');
super.onCreate(event);
originalSizes.set(this.width, this.height);
}
public override function playAnimation(name:String, restart:Bool = false, ignoreOther:Bool = false, reverse:Bool = false):Void
{
var correctName = correctAnimationName(name);
if (correctName == null)
{
trace('$characterName Could not find Atlas animation: ' + name);
return;
}
var animData = getAnimationData(correctName);
currentAnimName = correctName;
var prefix:String = animData.prefix;
if (prefix == null) prefix = correctName;
var loop:Bool = animData.looped;
this.mainSprite.playAnimation(prefix, restart, ignoreOther, loop);
}
public override function hasAnimation(name:String):Bool
{
return getAnimationData(name) != null;
}
/**
* Returns true if the animation has finished playing.
* Never true if animation is configured to loop.
*/
public override function isAnimationFinished():Bool
{
return mainSprite?.isAnimationFinished() ?? false;
}
function loadAtlasSprite():FlxAtlasSprite
function loadAtlas()
{
trace('[ATLASCHAR] Loading sprite atlas for ${characterId}.');
var assetLibrary:String = Paths.getLibrary(_data.assetPath);
var assetPath:String = Paths.stripLibrary(_data.assetPath);
var animLibrary:String = Paths.getLibrary(_data.assetPath);
var animPath:String = Paths.stripLibrary(_data.assetPath);
var assetPath:String = Paths.animateAtlas(animPath, animLibrary);
loadTextureAtlas(assetPath, assetLibrary, cast _data.atlasSettings);
var sprite:FlxAtlasSprite = new FlxAtlasSprite(0, 0, assetPath);
// sprite.onAnimationComplete.removeAll();
sprite.onAnimationComplete.add(this.onAnimationFinished);
return sprite;
}
override function onAnimationFinished(prefix:String):Void
{
super.onAnimationFinished(prefix);
if (!getCurrentAnimation().endsWith(Constants.ANIMATION_HOLD_SUFFIX)
&& hasAnimation(getCurrentAnimation() + Constants.ANIMATION_HOLD_SUFFIX))
if (_data.isPixel)
{
playAnimation(getCurrentAnimation() + Constants.ANIMATION_HOLD_SUFFIX);
}
if (getAnimationData() != null && getAnimationData().looped)
{
if (StringTools.endsWith(prefix, "-hold")) trace(prefix);
playAnimation(prefix, true, false);
this.isPixel = true;
this.antialiasing = false;
}
else
{
// Make the game hold on the last frame.
this.mainSprite.cleanupAnimation(prefix);
// currentAnimName = null;
// Fallback to idle!
// playAnimation('idle', true, false);
this.isPixel = false;
this.antialiasing = true;
}
this.setScale(_data.scale);
}
function setSprite(sprite:FlxAtlasSprite):Void
function loadAnimations()
{
trace('[ATLASCHAR] Applying sprite properties to ${characterId}');
trace('[ATLASCHAR] Loading ${_data.animations.length} animations for ${characterId}');
this.mainSprite = sprite;
FlxAnimationUtil.addTextureAtlasAnimations(this, _data.animations);
mainSprite.ignoreExclusionPref = ["sing"];
// This forces the atlas to recalcuate its width and height
this.mainSprite.alpha = 0.0001;
this.mainSprite.draw();
this.mainSprite.alpha = 1.0;
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] Attempting to load ${_data.animations.length} animations for ${characterId}');
var animData:Array<AnimateAtlasAnimation> = cast _data.animations;
for (anim in animData)
for (anim in _data.animations)
{
// Validate the animation before adding.
var prefix = anim.prefix;
if (!this.mainSprite.hasAnimation(prefix))
if (anim.offsets == null)
{
FlxG.log.warn('[ATLASCHAR] Animation ${prefix} not found in Animate Atlas ${_data.assetPath}');
trace('[ATLASCHAR] Animation ${prefix} not found in Animate Atlas ${_data.assetPath}');
continue;
setAnimationOffsets(anim.name, 0, 0);
}
else
{
setAnimationOffsets(anim.name, anim.offsets[0], anim.offsets[1]);
}
animations.set(anim.name, anim);
trace('[ATLASCHAR] - Successfully loaded animation ${anim.name} to ${characterId}');
}
trace('[ATLASCHAR] Loaded ${animations.size()} animations for ${characterId}');
}
public override function getCurrentAnimation():String
{
// return this.mainSprite.getCurrentAnimation();
return currentAnimName;
}
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;
if (this.mainSprite != null)
{
this.mainSprite.kill();
this.mainSprite = null;
}
}
/**
* 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:FlxDirectionFlags):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);
}
}
var resS:FlxPoint = new FlxPoint();
/**
* Reset the character so it can be used at the start of the level.
* Call this when restarting the level.
*/
override public function resetCharacter(resetCamera:Bool = true):Void
{
trace("RESETTING ATLAS " + characterName);
// Reset the animation offsets. This will modify x and y to be the absolute position of the character.
// this.animOffsets = [0, 0];
// Now we can set the x and y to be their original values without having to account for animOffsets.
this.resetPosition();
mainSprite.setPosition(originalPosition.x, originalPosition.y);
// Then reapply animOffsets...
// applyAnimationOffsets(getCurrentAnimation());
// Make sure we are playing the idle animation
// ...then update the hitbox so that this.width and this.height are correct.
mainSprite.scale.set(1, 1);
mainSprite.alpha = 0.0001;
mainSprite.width = 0;
mainSprite.height = 0;
this.dance(true); // Force to avoid the old animation playing with the wrong offset at the start of the song.
mainSprite.draw(); // refresh frame
if (resS.x == 0)
{
resS.x = mainSprite.width; // clunky bizz
resS.y = mainSprite.height;
}
mainSprite.alpha = alpha;
mainSprite.width = resS.x;
mainSprite.height = resS.y;
frameWidth = 0;
frameHeight = 0;
scaleCallback(scale);
this.updateHitbox();
// Reset the camera focus point while we're at it.
if (resetCamera) this.resetCameraFocusPoint();
}
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
return x = value;
}
override function set_y(value:Float):Float
{
if (exists && y != value) transformChildren(yTransform, value - y); // offset
return y = value;
}
override function set_angle(value:Float):Float
{
if (exists && angle != value) transformChildren(angleTransform, value - angle); // offset
return angle = value;
}
override function set_alpha(value:Float):Float
{
value = value.clamp(0, 1);
if (exists && alpha != value)
{
transformChildren(directAlphaTransform, value);
}
return alpha = value;
}
override function set_facing(value:FlxDirectionFlags):FlxDirectionFlags
{
if (exists && facing != value) transformChildren(facingTransform, value);
return facing = value;
}
override function set_flipX(value:Bool):Bool
{
if (exists && flipX != value) transformChildren(flipXTransform, value);
return flipX = value;
}
override function set_flipY(value:Bool):Bool
{
if (exists && flipY != value) transformChildren(flipYTransform, value);
return flipY = value;
}
override function set_moves(value:Bool):Bool
{
if (exists && moves != value) transformChildren(movesTransform, value);
return moves = value;
}
override function set_immovable(value:Bool):Bool
{
if (exists && immovable != value) transformChildren(immovableTransform, value);
return immovable = value;
}
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);
return color = value;
}
override function set_blend(value:BlendMode):BlendMode
{
if (exists && blend != value) transformChildren(blendTransform, value);
return blend = value;
}
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;
var animNames = this.anim.getNameList();
trace('[ATLASCHAR] Successfully loaded ${animNames.length} animations for ${characterId}');
}
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;
return originalSizes.x;
}
override function get_height():Float
{
if (this.mainSprite == null) return 0;
return this.mainSprite.height;
return originalSizes.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 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):FunkinSprite
{
#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 {}
}
@@ -0,0 +1,129 @@
package funkin.play.character;
import funkin.graphics.FunkinSprite;
import funkin.util.assets.FlxAnimationUtil;
import animate.FlxAnimateFrames;
import funkin.modding.events.ScriptEvent;
import funkin.data.animation.AnimationData;
import funkin.data.character.CharacterData.CharacterRenderType;
import flixel.math.FlxPoint;
/**
* This render type is the most complex, and is used by characters which use
* multiple Adobe Animate texture atlases. This render type concatenates multiple
* texture atlases into a single sprite.
*
* BaseCharacter has game logic, MultiAnimateAtlasCharacter has only rendering logic.
* KEEP THEM SEPARATE!
*/
class MultiAnimateAtlasCharacter extends BaseCharacter
{
var originalSizes(default, never):FlxPoint = new FlxPoint(0, 0);
public function new(id:String)
{
super(id, CharacterRenderType.MultiAnimateAtlas);
}
override function onCreate(event:ScriptEvent):Void
{
// Display a custom scope for debugging purposes.
#if FEATURE_DEBUG_TRACY
cpp.vm.tracy.TracyProfiler.zoneScoped('MultiAnimateAtlasCharacter.create(${this.characterId})');
#end
try
{
trace('Loading assets for Multi-Animate Atlas character "${characterId}"', flixel.util.FlxColor.fromString("#89CFF0"));
loadAtlases();
loadAnimations();
}
catch (e)
{
throw "Exception thrown while building sprite: " + e;
}
trace('[MULTIATLASCHAR] Successfully loaded texture atlases for ${characterId} with ${_data.animations.length} animations.');
super.onCreate(event);
originalSizes.set(this.width, this.height);
}
function loadAtlases()
{
trace('[MULTIATLASCHAR] Loading sprite atlases for ${characterId}.');
var assetList:Array<String> = [];
for (anim in _data.animations)
{
if (anim.assetPath != null && !assetList.contains(anim.assetPath))
{
assetList.push(anim.assetPath);
}
}
var baseAssetLibrary:String = Paths.getLibrary(_data.assetPath);
var baseAssetPath:String = Paths.stripLibrary(_data.assetPath);
loadTextureAtlas(baseAssetPath, baseAssetLibrary, cast _data.atlasSettings);
for (asset in assetList)
{
var subAssetLibrary:String = Paths.getLibrary(asset);
var subAssetPath:String = Paths.stripLibrary(asset);
var clone:FunkinSprite = FunkinSprite.createTextureAtlas(0, 0, subAssetPath, subAssetLibrary, cast _data.atlasSettings);
var subTexture:FlxAnimateFrames = clone.library;
trace('Concatenating texture atlas: ${asset}');
subTexture.parent.destroyOnNoUse = false;
this.library.addAtlas(subTexture);
}
if (_data.isPixel)
{
this.isPixel = true;
this.antialiasing = false;
}
else
{
this.isPixel = false;
this.antialiasing = true;
}
this.setScale(_data.scale);
}
function loadAnimations()
{
trace('[MULTIATLASCHAR] Loading ${_data.animations.length} animations for ${characterId}');
FlxAnimationUtil.addTextureAtlasAnimations(this, _data.animations);
for (anim in _data.animations)
{
if (anim.offsets == null)
{
setAnimationOffsets(anim.name, 0, 0);
}
else
{
setAnimationOffsets(anim.name, anim.offsets[0], anim.offsets[1]);
}
}
var animNames = this.anim.getNameList();
trace('[MULTIATLASCHAR] Successfully loaded ${animNames.length} animations for ${characterId}');
}
override function get_width():Float
{
return originalSizes.x;
}
override function get_height():Float
{
return originalSizes.y;
}
}
@@ -126,9 +126,4 @@ class MultiSparrowCharacter extends BaseCharacter
var animNames = this.animation.getNameList();
trace('[MULTISPARROWCHAR] Successfully loaded ${animNames.length} animations for ${characterId}');
}
public override function playAnimation(name:String, restart:Bool = false, ignoreOther:Bool = false, reverse:Bool = false):Void
{
super.playAnimation(name, restart, ignoreOther, reverse);
}
}
@@ -26,6 +26,14 @@ class ScriptedSparrowCharacter extends SparrowCharacter implements polymod.hscri
@:hscriptClass
class ScriptedMultiSparrowCharacter extends MultiSparrowCharacter implements polymod.hscript.HScriptedClass {}
/**
* A script that can be tied to a MultiAnimateAtlasCharacter, which persists across states.
* Create a scripted class that extends MultiAnimateAtlasCharacter,
* then call `super('charId')` in the constructor to use this.
*/
@:hscriptClass
class ScriptedMultiAnimateAtlasCharacter extends MultiAnimateAtlasCharacter implements polymod.hscript.HScriptedClass {}
/**
* A script that can be tied to a PackerCharacter, which persists across states.
* Create a scripted class that extends PackerCharacter,
@@ -49,8 +49,6 @@ class SparrowCharacter extends BaseCharacter
{
this.isPixel = true;
this.antialiasing = false;
// pixelPerfectRender = true;
// pixelPerfectPosition = true;
}
else
{
@@ -438,35 +438,6 @@ class HealthIcon extends FunkinSprite
this.antialiasing = !isPixel;
}
/**
* @return Name of the current animation being played by this health icon.
*/
public function getCurrentAnimation():String
{
if (this.animation == null || this.animation.curAnim == null) return "";
return this.animation.curAnim.name;
}
/**
* @param id The name of the animation to check for.
* @return Whether this sprite posesses the given animation.
* Only true if the animation was successfully loaded from the XML.
*/
public function hasAnimation(id:String):Bool
{
if (this.animation == null) return false;
return this.animation.getByName(id) != null;
}
/**
* @return Whether the current animation is in the finished state.
*/
public function isAnimationFinished():Bool
{
return this.animation.finished;
}
/**
* Plays the animation with the given name.
* @param name The name of the animation to play.
@@ -201,12 +201,6 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe
applyAnimationOffsets(correctName);
}
public function getCurrentAnimation():String
{
if (this.animation == null || this.animation.curAnim == null) return "";
return this.animation.curAnim.name;
}
/**
* Ensure that a given animation exists before playing it.
* Will gracefully check for name, then name with stripped suffixes, then 'idle', then fail to play.
-16
View File
@@ -175,22 +175,6 @@ class StrumlineNote extends FunkinSprite
}
}
/**
* Returns the name of the animation that is currently playing.
* If no animation is playing (usually this means the sprite is BROKEN!),
* returns an empty string to prevent NPEs.
*/
public function getCurrentAnimation():String
{
if (this.animation == null || this.animation.curAnim == null) return "";
return this.animation.curAnim.name;
}
public function isAnimationFinished():Bool
{
return this.animation.finished;
}
static final DEFAULT_OFFSET:Int = 13;
/**
@@ -115,7 +115,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
return null;
}
if (!FunkinSprite.isTextureCached(Paths.image(noteAssetPath)))
if (!FunkinMemory.isTextureCached(Paths.image(noteAssetPath)))
{
FlxG.log.warn('Note texture is not cached: ${noteAssetPath}');
}
@@ -896,7 +896,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
return null;
}
if (!FunkinSprite.isTextureCached(Paths.image(splashAssetPath)))
if (!FunkinMemory.isTextureCached(Paths.image(splashAssetPath)))
{
FlxG.log.warn('Note Splash texture not cached: ${splashAssetPath}');
}
@@ -1046,7 +1046,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
return null;
}
if (!FunkinSprite.isTextureCached(Paths.image(holdCoverAssetPath)))
if (!FunkinMemory.isTextureCached(Paths.image(holdCoverAssetPath)))
{
FlxG.log.warn('Hold Note Cover texture not cached: ${holdCoverAssetPath}');
}
-23
View File
@@ -203,13 +203,6 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
}
}
public function hasAnimation(id:String):Bool
{
if (this.animation == null) return false;
return this.animation.getByName(id) != null;
}
/**
* Ensure that a given animation exists before playing it.
* Will gracefully check for name, then name with stripped suffixes, then fail to play.
@@ -327,27 +320,11 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
this.animOffsets = offsets;
}
public function isAnimationFinished():Bool
{
return this.animation?.finished ?? false;
}
public function setAnimationOffsets(name:String, xOffset:Float, yOffset:Float):Void
{
animationOffsets.set(name, [xOffset, yOffset]);
}
/**
* Returns the name of the animation that is currently playing.
* If no animation is playing (usually this means the character is BROKEN!),
* returns an empty string to prevent NPEs.
*/
public function getCurrentAnimation():String
{
if (this.animation == null || this.animation.curAnim == null) return "";
return this.animation.curAnim.name;
}
// override getScreenPosition (used by FlxSprite's draw method) to account for animation offsets.
override function getScreenPosition(?result:FlxPoint, ?camera:FlxCamera):FlxPoint
{
+4
View File
@@ -183,6 +183,8 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
{
case 'packer':
propSprite.loadPacker(dataProp.assetPath);
case 'animateatlas':
propSprite.loadTextureAtlas(dataProp.assetPath, _data.directory, cast dataProp.atlasSettings);
default: // 'sparrow'
propSprite.loadSparrow(dataProp.assetPath);
}
@@ -268,6 +270,8 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
cast(propSprite, Bopper).setAnimationOffsets(propAnim.name, propAnim.offsets[0], propAnim.offsets[1]);
}
}
case 'animateatlas':
FlxAnimationUtil.addTextureAtlasAnimations(propSprite, dataProp.animations);
default: // 'sparrow'
FlxAnimationUtil.addAtlasAnimations(propSprite, dataProp.animations);
if (Std.isOfType(propSprite, Bopper))
-18
View File
@@ -1,18 +0,0 @@
package funkin.ui.charSelect;
import flixel.FlxSprite;
@:nullSafety
class CharIcon extends FlxSprite
{
public var locked:Bool = false;
public function new(x:Float, y:Float, locked:Bool = false)
{
super(x, y);
this.locked = locked;
makeGraphic(128, 128);
}
}
@@ -1,50 +0,0 @@
package funkin.ui.charSelect;
import openfl.display.BitmapData;
import openfl.filters.DropShadowFilter;
import openfl.filters.ConvolutionFilter;
import funkin.graphics.shaders.StrokeShader;
@:nullSafety
class CharIconCharacter extends CharIcon
{
// public var dropShadowFilter:DropShadowFilter;
var matrixFilter:Array<Float> = [
1, 1, 1,
1, 1, 1,
1, 1, 1
];
var divisor:Int = 1;
var bias:Int = 0;
// var convolutionFilter:ConvolutionFilter;
// public var noDropShadow:BitmapData;
// public var withDropShadow:BitmapData;
// var strokeShader:StrokeShader;
public function new(path:String)
{
super(0, 0, false);
loadGraphic(Paths.image('freeplay/icons/' + path + 'pixel'));
setGraphicSize(128, 128);
updateHitbox();
antialiasing = false;
// strokeShader = new StrokeShader();
// shader = strokeShader;
// noDropShadow = pixels.clone();
// dropShadowFilter = new DropShadowFilter(5, 45, 0, 1, 0, 0);
// convolutionFilter = new ConvolutionFilter(3, 3, matrixFilter, divisor, bias);
// pixels.applyFilter(pixels, pixels.rect, new openfl.geom.Point(0, 0), dropShadowFilter);
// pixels.applyFilter(pixels, pixels.rect, new openfl.geom.Point(0, 0), convolutionFilter);
// withDropShadow = pixels.clone();
// pixels = noDropShadow.clone();
}
}
@@ -1,4 +0,0 @@
package funkin.ui.charSelect;
@:nullSafety
class CharIconLocked extends CharIcon {}
@@ -0,0 +1,45 @@
package funkin.ui.charSelect;
import animate.FlxAnimateFrames;
import flixel.FlxG;
/**
* Utility class for handling the atlases loaded by CharSelect & co. in an efficient way.
* TODO: Maybe this should be a general utility class instead?
*/
class CharSelectAtlasHandler
{
static final framesCache:Map<String, FlxAnimateFrames> = [];
public static function loadAtlas(path:String, ?settings:FlxAnimateSettings):Null<FlxAnimateFrames>
{
if (framesCache.exists(path)) return framesCache.get(path);
var result:FlxAnimateFrames = FlxAnimateFrames.fromAnimate(Paths.animateAtlas(path),
{
swfMode: settings?.swfMode ?? true,
filterQuality: settings?.filterQuality ?? MEDIUM,
cacheOnLoad: settings?.cacheOnLoad ?? false
});
if (result == null)
{
FlxG.log.error('Failed to load atlas at path $path!');
return null;
}
result.parent.destroyOnNoUse = false;
framesCache.set(path, result);
return result;
}
public static function clearAtlasCache():Void
{
for (frames in framesCache.iterator())
{
// NOTE: Doing this already calls checkUseCount!
frames.parent.destroyOnNoUse = true;
}
framesCache.clear();
}
}
+36 -115
View File
@@ -1,67 +1,34 @@
package funkin.ui.charSelect;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import flixel.math.FlxMath;
import funkin.util.FramesJSFLParser;
import funkin.util.FramesJSFLParser.FramesJSFLInfo;
import funkin.util.FramesJSFLParser.FramesJSFLFrame;
import funkin.graphics.FunkinSprite;
import funkin.modding.IScriptedClass.IBPMSyncedScriptedClass;
import funkin.modding.events.ScriptEvent;
import funkin.vis.dsp.SpectralAnalyzer;
import funkin.data.freeplay.player.PlayerRegistry;
import funkin.ui.FullScreenScaleMode;
import flixel.math.FlxPoint;
@:nullSafety
class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
class CharSelectGF extends FunkinSprite implements IBPMSyncedScriptedClass
{
var fadeTimer:Float = 0;
var fadingStatus:FadeStatus = OFF;
var fadeAnimIndex:Int = 0;
var animInInfo:Null<FramesJSFLInfo>;
var animOutInfo:Null<FramesJSFLInfo>;
var intendedYPos:Float = 0;
var intendedAlpha:Float = 0;
var list:Array<Null<String>> = [];
var analyzer:Null<SpectralAnalyzer>;
var analyzerLevelsCache:Array<Bar> = new Array<Bar>();
var currentGFPath:Null<String>;
var currentGFPath:String = "";
var enableVisualizer:Bool = false;
public function new()
{
super(0, 0, Paths.animateAtlas("charSelect/gfChill"));
var danceEvery:Int = 2;
list = anim.curSymbol.getFrameLabelNames();
public function new(x:Float, y:Float)
{
super(x, y);
this.applyStageMatrix = true;
switchGF(Constants.DEFAULT_CHARACTER);
}
override public function update(elapsed:Float):Void
{
super.update(elapsed);
switch (fadingStatus)
{
case OFF:
// do nothing if it's off!
// or maybe force position to be 0,0?
// maybe reset timers?
resetFadeAnimParams();
case FADE_OUT:
if (animOutInfo != null) doFade(animOutInfo);
case FADE_IN:
if (animInInfo != null) doFade(animInInfo);
default:
}
}
public function onStepHit(event:SongTimeScriptEvent):Void {}
var danceEvery:Int = 2;
public function onBeatHit(event:SongTimeScriptEvent):Void
{
// TODO: There's a minor visual bug where there's a little stutter.
@@ -72,7 +39,7 @@ class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
if (getCurrentAnimation() == "idle" && (event.beat % danceEvery == 0))
{
trace('GF beat hit');
playAnimation("idle", true, false, false);
anim.play("idle", true);
}
};
@@ -87,9 +54,8 @@ class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
if (enableVisualizer && analyzer != null)
{
analyzerLevelsCache = analyzer.getLevels(analyzerLevelsCache);
var frame = anim.curSymbol.timeline.get("VIZ_bars").get(anim.curFrame);
if (frame == null) return;
var elements = frame.getList();
var frame:Null<animate.internal.Frame> = this.timeline.getLayer("VIZ_bars")?.getFrameAtIndex(anim.curAnim.curFrame) ?? null;
var elements:Array<animate.internal.elements.Element> = frame?.elements ?? [];
var len:Int = cast Math.min(elements.length, 7);
for (i in 0...len)
@@ -107,73 +73,31 @@ class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
animFrame = Std.int(Math.abs(animFrame - 12)); // shitty dumbass flip, cuz dave got da shit backwards lol!
elements[i].symbol.firstFrame = animFrame;
var convertedSymbol = elements[i].toSymbolInstance();
convertedSymbol.firstFrame = animFrame;
elements[i] = convertedSymbol;
}
}
}
/**
* @param animInfo Should not be confused with animInInfo!
* This is merely a local var for the function!
*/
function doFade(animInfo:Null<FramesJSFLInfo>):Void
{
if (animInfo == null)
{
return;
}
fadeTimer += FlxG.elapsed;
if (fadeTimer >= 1 / 24)
{
fadeTimer -= FlxG.elapsed;
// only inc the index for the first frame, used for reference of where to "start"
if (fadeAnimIndex == 0)
{
fadeAnimIndex++;
return;
}
var curFrame:FramesJSFLFrame = animInfo.frames[fadeAnimIndex];
var prevFrame:FramesJSFLFrame = animInfo.frames[fadeAnimIndex - 1];
var xDiff:Float = curFrame.x - prevFrame.x;
var yDiff:Float = curFrame.y - prevFrame.y;
var alphaDiff:Float = curFrame.alpha - prevFrame.alpha;
alphaDiff /= 100; // flash exports alpha as a whole number
alpha += alphaDiff;
alpha = alpha.clamp(0, 1);
x += xDiff;
y += yDiff;
fadeAnimIndex++;
}
if (fadeAnimIndex >= animInfo.frames.length) fadingStatus = OFF;
}
function resetFadeAnimParams()
{
fadeTimer = 0;
fadeAnimIndex = 0;
}
/**
* For switching between "GFs" such as gf, nene, etc
* @param bf Which BF we are selecting, so that we know the accompyaning GF
*/
public function switchGF(bf:String):Void
{
var previousGFPath = currentGFPath;
var previousGFPath:String = currentGFPath;
var bfObj = PlayerRegistry.instance.fetchEntry(bf);
var gfData = bfObj?.getCharSelectData()?.gf;
if (gfData != null && gfData.assetPath != null) currentGFPath = Paths.animateAtlas(gfData.assetPath);
var assetPath:Null<String> = gfData?.assetPath ?? "";
currentGFPath = assetPath;
// We don't need to update any anims if we didn't change GF
trace('currentGFPath(${currentGFPath})');
if (currentGFPath == null)
if (currentGFPath == "")
{
this.visible = false;
return;
@@ -181,20 +105,24 @@ class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
else if (previousGFPath != currentGFPath)
{
this.visible = true;
loadAtlas(currentGFPath);
var path:String = currentGFPath;
var texture:Null<animate.FlxAnimateFrames> = CharSelectAtlasHandler.loadAtlas(path, {swfMode: true});
if (texture != null)
{
frames = texture;
}
else
{
this.visible = false;
currentGFPath = "";
return;
}
enableVisualizer = gfData?.visualizer ?? false;
var animInfoPath = Paths.file('images/${gfData?.animInfoPath}');
animInInfo = FramesJSFLParser.parse(animInfoPath + '/In.txt');
animOutInfo = FramesJSFLParser.parse(animInfoPath + '/Out.txt');
if (animInInfo == null) trace(" ERROR ".bg_red().bold() + " Failed to load data for animInInfo, is the path provided correct?");
if (animOutInfo == null) trace(" ERROR ".bg_red().bold() + " Failed to load data for animOutInfo, is the path provided correct?");
}
playAnimation("idle", true, false, false);
anim.play("idle", true);
updateHitbox();
}
@@ -207,10 +135,3 @@ class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
public function onUpdate(event:UpdateScriptEvent):Void {};
}
enum FadeStatus
{
OFF;
FADE_OUT;
FADE_IN;
}
+40 -27
View File
@@ -1,41 +1,60 @@
package funkin.ui.charSelect;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.FunkinSprite;
import funkin.modding.IScriptedClass.IBPMSyncedScriptedClass;
import funkin.modding.events.ScriptEvent;
@:nullSafety
class CharSelectPlayer extends FlxAtlasSprite implements IBPMSyncedScriptedClass
class CharSelectPlayer extends FunkinSprite implements IBPMSyncedScriptedClass
{
static final DEFAULT_PATH = "charSelect/bfChill";
var initialX:Float = 0;
var initialY:Float = 0;
var currentBFPath:Null<String>;
public function new(x:Float, y:Float)
{
initialX = x;
initialY = y;
super(x, y, Paths.animateAtlas("charSelect/bfChill"));
super(x, y);
onAnimationComplete.add(function(animLabel:String) {
loadTextureAtlas(DEFAULT_PATH,
{
applyStageMatrix: true,
swfMode: true
});
anim.onFinish.add(function(animLabel:String) {
switch (animLabel)
{
case "slidein":
if (hasAnimation("slidein idle point"))
{
playAnimation("slidein idle point", true, false, false);
anim.play("slidein idle point", true);
}
else
{
playAnimation("idle", true, false, false);
anim.play("idle", true);
anim.curAnim.looped = true;
}
case "deselect":
playAnimation("deselect loop start", true, false, true);
anim.play("deselect loop start", true);
case "slidein idle point", "cannot select Label", "unlock":
playAnimation("idle", true, false, false);
anim.play("idle", true);
case "idle":
trace('Waiting for onBeatHit');
// TODO: once char select data is refactored, add a `shouldBop` field or something IDK
if (currentBFPath != null)
{
if (currentBFPath.endsWith("locked"))
{
anim.curAnim.looped = true;
}
}
}
});
}
@@ -50,36 +69,30 @@ class CharSelectPlayer extends FlxAtlasSprite implements IBPMSyncedScriptedClass
// but isAnimationFinished() and isLoopComplete() both don't work! What the hell?
// danceEvery isn't necessary if that gets fixed.
//
if (getCurrentAnimation() == "idle")
if (getCurrentAnimation() == "idle" && isAnimationFinished())
{
playAnimation("idle", true, false, false);
anim.play("idle", true);
}
};
public function updatePosition(str:String)
public function switchChar(str:String, playSlideAnim:Bool = true):Void
{
switch (str)
{
case "bf" | 'pico' | "random":
x = initialX;
y = initialY;
}
}
var texture:Null<animate.FlxAnimateFrames> = CharSelectAtlasHandler.loadAtlas('charSelect/${str}Chill');
public function switchChar(str:String, playSlideAnim:Bool = true)
{
switch str
if (texture != null)
{
default:
loadAtlas(Paths.animateAtlas("charSelect/" + str + "Chill"));
frames = texture;
}
else
{
trace('Failed to load character atlas for ${str}');
return;
}
final animName:String = playSlideAnim ? "slidein" : "idle";
playAnimation(animName, true, false, false);
anim.play(animName, true);
updateHitbox();
updatePosition(str);
}
public function onScriptEvent(event:ScriptEvent):Void {};
+160 -185
View File
@@ -1,8 +1,6 @@
package funkin.ui.charSelect;
import openfl.filters.BitmapFilter;
import flixel.FlxObject;
import flixel.FlxSprite;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup;
import flixel.math.FlxPoint;
@@ -11,33 +9,34 @@ import flixel.system.debug.watch.Tracker.TrackerProfile;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.util.FlxTimer;
import flixel.util.FlxColor;
import funkin.audio.FunkinSound;
import funkin.data.freeplay.player.PlayerData.PlayerCharSelectData;
import funkin.data.freeplay.player.PlayerRegistry;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import openfl.filters.DropShadowFilter;
import funkin.graphics.FunkinCamera;
import funkin.graphics.FunkinSprite;
import funkin.graphics.shaders.BlueFade;
import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.stage.Stage;
import funkin.save.Save;
import funkin.ui.freeplay.charselect.PlayableCharacter;
import funkin.ui.freeplay.FreeplayState;
import funkin.ui.freeplay.charselect.PlayableCharacter;
import funkin.ui.PixelatedIcon;
import funkin.util.FramesJSFLParser;
import funkin.util.FramesJSFLParser.FramesJSFLInfo;
import funkin.util.HapticUtil;
import funkin.util.MathUtil;
import funkin.vis.dsp.SpectralAnalyzer;
import openfl.display.BlendMode;
import openfl.filters.ShaderFilter;
import funkin.util.FramesJSFLParser;
import funkin.util.FramesJSFLParser.FramesJSFLInfo;
import funkin.graphics.FunkinSprite;
import openfl.filters.BitmapFilter;
import openfl.filters.DropShadowFilter;
#if FEATURE_NEWGROUNDS
import funkin.api.newgrounds.Medals;
#end
#if FEATURE_TOUCH_CONTROLS
import funkin.util.TouchUtil;
import funkin.util.SwipeUtil;
import funkin.util.HapticUtil;
#end
@:nullSafety
class CharSelectSubState extends MusicBeatSubState
@@ -45,33 +44,30 @@ class CharSelectSubState extends MusicBeatSubState
// what the actual hell
// having a hard time trying to make my changes work so i chose to be less stubborn and just remove them for now. - Zack
// Left this here so somebody can remind me
var cursor:FlxSprite = new FlxSprite(0, 0);
var cursor:FunkinSprite;
var cursorBlue:FlxSprite = new FlxSprite(0, 0);
var cursorDarkBlue:FlxSprite = new FlxSprite(0, 0);
var grpCursors:FlxTypedGroup<FlxSprite> = new FlxTypedGroup<FlxSprite>();
var cursorConfirmed:FlxSprite = new FlxSprite(0, 0);
var cursorDenied:FlxSprite = new FlxSprite(0, 0);
var cursorBlue:FunkinSprite;
var cursorDarkBlue:FunkinSprite;
var grpCursors:FlxTypedGroup<FunkinSprite>;
var cursorConfirmed:FunkinSprite;
var cursorDenied:FunkinSprite;
var cursorX:Int = 0;
var cursorY:Int = 0;
var cursorFactor:Float = 110;
var cursorOffsetX:Float = -16;
var cursorOffsetY:Float = -48;
var cursorLocIntended:FlxPoint = new FlxPoint(0, 0);
var tmrFrames:Int = 60;
// var currentStage:Stage;
var playerChill:CharSelectPlayer = new CharSelectPlayer(0, 0);
var playerChillOut:CharSelectPlayer = new CharSelectPlayer(0, 0);
var gfChill:CharSelectGF = new CharSelectGF();
// var gfChillOut:CharSelectGF;
var barthing:FlxAtlasSprite = new FlxAtlasSprite(0, 0, Paths.animateAtlas("charSelect/barThing"));
var dipshitBacking:FlxSprite = new FlxSprite(423, -17);
var chooseDipshit:FlxSprite = new FlxSprite(426, -13);
var dipshitBlur:FlxSprite = new FlxSprite(419, -65);
var transitionGradient:FlxSprite = new FlxSprite(0, 0);
var playerChill:CharSelectPlayer;
var playerChillOut:CharSelectPlayer;
var gfChill:CharSelectGF;
var barthing:FunkinSprite;
var dipshitBacking:FunkinSprite;
var chooseDipshit:FunkinSprite;
var dipshitBlur:FunkinSprite;
var transitionGradient:FunkinSprite;
var curChar(default, set):String = Constants.DEFAULT_CHARACTER;
var rememberedChar:String;
var nametag:Nametag = new Nametag(Constants.DEFAULT_CHARACTER);
var nametag:Nametag;
var camFollow:FlxObject = new FlxObject(0, 0, 1, 1);
var autoFollow:Bool = false;
var availableChars:Map<Int, String> = new Map<Int, String>();
@@ -98,11 +94,41 @@ class CharSelectSubState extends MusicBeatSubState
var cutoutSize:Float = 0;
var fadeShader:BlueFade = new BlueFade();
public function new(?params:CharSelectSubStateParams)
{
super();
rememberedChar = params?.character ?? Constants.DEFAULT_CHARACTER;
loadAvailableCharacters();
rememberedChar = params?.character ?? "";
grpCursors = new FlxTypedGroup<FunkinSprite>();
grpHitboxes = new FlxTypedGroup<FlxObject>();
gfChill = new CharSelectGF(cutoutSize, 0);
playerChillOut = new CharSelectPlayer(cutoutSize, 0);
playerChill = new CharSelectPlayer(cutoutSize, 0);
dipshitBlur = new FunkinSprite(cutoutSize + 419, -65);
dipshitBacking = new FunkinSprite(cutoutSize + 423, -17);
chooseDipshit = new FunkinSprite(cutoutSize + 426, -13);
nametag = new Nametag(curChar);
cursor = new FunkinSprite(0, 0);
cursorBlue = new FunkinSprite(0, 0);
cursorDarkBlue = new FunkinSprite(0, 0);
cursorConfirmed = new FunkinSprite(0, 0);
cursorDenied = new FunkinSprite(0, 0);
charHitbox = new FlxObject(FlxG.width * 0.65, FlxG.height * 0.2, 300, 500);
transitionGradient = new FunkinSprite(0, 0);
barthing = new FunkinSprite(0, 0);
selectSound = new FunkinSound();
unlockSound = new FunkinSound();
lockedSound = new FunkinSound();
staticSound = new FunkinSound();
}
function loadAvailableCharacters():Void
@@ -111,9 +137,7 @@ class CharSelectSubState extends MusicBeatSubState
for (playerId in playerIds)
{
var player:Null<PlayableCharacter> = PlayerRegistry.instance.fetchEntry(playerId);
if (player == null) continue;
var playerData = player.getCharSelectData();
var playerData:Null<PlayerCharSelectData> = PlayerRegistry.instance.fetchEntry(playerId)?.getCharSelectData();
if (playerData == null) continue;
var targetPosition:Int = playerData.position ?? 0;
@@ -124,15 +148,26 @@ class CharSelectSubState extends MusicBeatSubState
trace('Placing player ${playerId} at position ${targetPosition}');
availableChars.set(targetPosition, playerId);
}
}
var fadeShader:BlueFade = new BlueFade();
CharSelectAtlasHandler.loadAtlas('charSelect/${playerId}Chill');
var gfPath:Null<String> = playerData.gf?.assetPath;
if (gfPath != null)
{
CharSelectAtlasHandler.loadAtlas(gfPath);
}
}
// Mr. Static also needs some caching...
CharSelectAtlasHandler.loadAtlas('charSelect/lockedChill', {filterQuality: LOW, cacheOnLoad: true});
}
override public function create():Void
{
super.create();
loadAvailableCharacters();
cutoutSize = FullScreenScaleMode.gameCutoutSize.x / 2;
bopInfo = FramesJSFLParser.parse(Paths.file("images/charSelect/iconBopInfo/iconBopInfo.txt"));
@@ -141,35 +176,39 @@ class CharSelectSubState extends MusicBeatSubState
trace(" ERROR ".bg_red().bold() + " Failed to load data for bopInfo, is the path provided correct?");
}
var bg:FlxSprite = new FlxSprite(cutoutSize + -153, -140);
var bg:FunkinSprite = new FunkinSprite(cutoutSize + -153, -140);
bg.loadGraphic(Paths.image('charSelect/charSelectBG'));
bg.scrollFactor.set(0.1, 0.1);
add(bg);
var crowd:FlxAtlasSprite = new FlxAtlasSprite(cutoutSize, 0, Paths.animateAtlas("charSelect/crowd"));
crowd.anim.play();
crowd.anim.onComplete.add(function() {
crowd.anim.play();
});
var crowd:FunkinSprite = FunkinSprite.createTextureAtlas(cutoutSize, 0, "charSelect/crowd",
{
applyStageMatrix: true
});
crowd.anim.play('');
crowd.anim.curAnim.looped = true;
crowd.scrollFactor.set(0.3, 0.3);
add(crowd);
var stageSpr:FlxAtlasSprite = new FlxAtlasSprite(cutoutSize + -2, 1, Paths.animateAtlas("charSelect/charSelectStage"));
stageSpr.anim.play("");
stageSpr.anim.onComplete.add(function() {
stageSpr.anim.play("");
});
var stageSpr:FunkinSprite = FunkinSprite.createTextureAtlas(cutoutSize - 2, 1, "charSelect/charSelectStage",
{
applyStageMatrix: true
});
stageSpr.anim.play('');
stageSpr.anim.curAnim.looped = true;
add(stageSpr);
var curtains:FlxSprite = new FlxSprite(cutoutSize + (-47 - 165), -49 - 50);
var curtains:FunkinSprite = new FunkinSprite(cutoutSize + -212, -99);
curtains.loadGraphic(Paths.image('charSelect/curtains'));
curtains.scrollFactor.set(1.4, 1.4);
add(curtains);
barthing.anim.play("");
barthing.anim.onComplete.add(function() {
barthing.anim.play("");
});
barthing.loadTextureAtlas("charSelect/barThing",
{
applyStageMatrix: true
});
barthing.anim.play('');
barthing.anim.curAnim.looped = true;
barthing.blend = BlendMode.MULTIPLY;
barthing.scale.x = 2.5;
barthing.scrollFactor.set(0, 0);
@@ -178,27 +217,23 @@ class CharSelectSubState extends MusicBeatSubState
barthing.y += 80;
FlxTween.tween(barthing, {y: barthing.y - 80}, 1.3, {ease: FlxEase.expoOut});
var charLight:FlxSprite = new FlxSprite(cutoutSize + 800, 250);
var charLight:FunkinSprite = new FunkinSprite(cutoutSize + 800, 250);
charLight.loadGraphic(Paths.image('charSelect/charLight'));
add(charLight);
var charLightGF:FlxSprite = new FlxSprite(cutoutSize + 180, 240);
var charLightGF:FunkinSprite = new FunkinSprite(cutoutSize + 180, 240);
charLightGF.loadGraphic(Paths.image('charSelect/charLight'));
add(charLightGF);
function setupPlayerChill(character:String)
{
gfChill = new CharSelectGF();
gfChill.switchGF(character);
gfChill.x += cutoutSize;
add(gfChill);
playerChillOut = new CharSelectPlayer(cutoutSize, 0);
playerChillOut.switchChar(character, false);
playerChillOut.visible = false;
add(playerChillOut);
playerChill = new CharSelectPlayer(cutoutSize, 0);
playerChill.switchChar(character, false);
add(playerChill);
}
@@ -221,28 +256,27 @@ class CharSelectSubState extends MusicBeatSubState
else
setupPlayerChill(Constants.DEFAULT_CHARACTER);
var speakers:FlxAtlasSprite = new FlxAtlasSprite(cutoutSize - 10, 0, Paths.animateAtlas("charSelect/charSelectSpeakers"));
speakers.anim.play("");
speakers.anim.onComplete.add(function() {
speakers.anim.play("");
});
var speakers:FunkinSprite = FunkinSprite.createTextureAtlas(cutoutSize - 10, 0, "charSelect/charSelectSpeakers",
{
applyStageMatrix: true
});
speakers.anim.play('');
speakers.anim.curAnim.looped = true;
speakers.scrollFactor.set(1.8, 1.8);
speakers.scale.set(1.05, 1.05);
add(speakers);
var fgBlur:FlxSprite = new FlxSprite(cutoutSize + -125, 170);
var fgBlur:FunkinSprite = new FunkinSprite(cutoutSize + -125, 170);
fgBlur.loadGraphic(Paths.image('charSelect/foregroundBlur'));
fgBlur.blend = openfl.display.BlendMode.MULTIPLY;
fgBlur.blend = BlendMode.MULTIPLY;
add(fgBlur);
dipshitBlur = new FlxSprite(cutoutSize + 419, -65);
dipshitBlur.frames = Paths.getSparrowAtlas("charSelect/dipshitBlur");
dipshitBlur.animation.addByPrefix('idle', "CHOOSE vertical offset instance 1", 24, true);
dipshitBlur.blend = BlendMode.ADD;
dipshitBlur.animation.play("idle");
add(dipshitBlur);
dipshitBacking = new FlxSprite(cutoutSize + 423, -17);
dipshitBacking.frames = Paths.getSparrowAtlas("charSelect/dipshitBacking");
dipshitBacking.animation.addByPrefix('idle', "CHOOSE horizontal offset instance 1", 24, true);
dipshitBacking.blend = BlendMode.ADD;
@@ -252,7 +286,6 @@ class CharSelectSubState extends MusicBeatSubState
dipshitBacking.y += 210;
FlxTween.tween(dipshitBacking, {y: dipshitBacking.y - 210}, 1.1, {ease: FlxEase.expoOut});
chooseDipshit = new FlxSprite(cutoutSize + 426, -13);
chooseDipshit.loadGraphic(Paths.image('charSelect/chooseDipshit'));
add(chooseDipshit);
@@ -266,7 +299,6 @@ class CharSelectSubState extends MusicBeatSubState
dipshitBacking.scrollFactor.set();
dipshitBlur.scrollFactor.set();
nametag = new Nametag(curChar);
nametag.midpointX += cutoutSize;
add(nametag);
@@ -278,31 +310,14 @@ class CharSelectSubState extends MusicBeatSubState
nametag.scrollFactor.set();
FlxG.debugger.addTrackerProfile(new TrackerProfile(FlxSprite, ["x", "y", "alpha", "scale", "blend"]));
FlxG.debugger.addTrackerProfile(new TrackerProfile(FlxAtlasSprite, ["x", "y"]));
FlxG.debugger.addTrackerProfile(new TrackerProfile(FunkinSprite, ["x", "y", "alpha", "scale", "blend"]));
FlxG.debugger.addTrackerProfile(new TrackerProfile(FlxSound, ["pitch", "volume"]));
// FlxG.debugger.track(crowd);
// FlxG.debugger.track(stageSpr, "stageSpr");
// FlxG.debugger.track(bfChill, "bf chill");
// FlxG.debugger.track(playerChill, "player");
// FlxG.debugger.track(nametag, "nametag");
// FlxG.debugger.track(selectSound, "selectSound");
// FlxG.debugger.track(chooseDipshit, "choose dipshit");
// FlxG.debugger.track(barthing, "barthing");
// FlxG.debugger.track(fgBlur, "fgBlur");
// FlxG.debugger.track(dipshitBlur, "dipshitBlur");
// FlxG.debugger.track(dipshitBacking, "dipshitBacking");
// FlxG.debugger.track(charLightGF, "charLight");
// FlxG.debugger.track(gfChill, "gfChill");
add(grpCursors);
cursor.loadGraphic(Paths.image('charSelect/charSelector'));
cursor.color = 0xFFFFFF00;
// FFCC00
cursorBlue.loadGraphic(Paths.image('charSelect/charSelector'));
cursorBlue.color = 0xFF3EBBFF;
@@ -328,7 +343,6 @@ class CharSelectSubState extends MusicBeatSubState
grpCursors.add(cursorBlue);
grpCursors.add(cursor);
charHitbox = new FlxObject(FlxG.width * 0.65, FlxG.height * 0.2, 300, 500);
charHitbox.active = false;
charHitbox.scrollFactor.set();
@@ -389,25 +403,17 @@ class CharSelectSubState extends MusicBeatSubState
FlxTween.color(cursor, 0.2, 0xFFFFFF00, 0xFFFFCC00, {type: PINGPONG});
// FlxG.debugger.track(cursor);
FlxG.debugger.addTrackerProfile(new TrackerProfile(CharSelectSubState, ["curChar", "grpXSpread", "grpYSpread"]));
FlxG.debugger.track(this);
add(camFollow);
camFollow.screenCenter();
// FlxG.camera.follow(camFollow, LOCKON, 0.01);
FlxG.camera.follow(camFollow, LOCKON);
var fadeShaderFilter:ShaderFilter = new ShaderFilter(fadeShader);
FlxG.camera.filters = [fadeShaderFilter];
var temp:FlxSprite = new FlxSprite();
temp.loadGraphic(Paths.image('charSelect/placement'));
add(temp);
temp.alpha = 0.0;
Conductor.stepHit.add(spamOnStep);
// FlxG.debugger.track(temp, "tempBG");
@@ -462,6 +468,7 @@ class CharSelectSubState extends MusicBeatSubState
FlxG.sound.list.add(introSound);
openSubState(new IntroSubState());
subStateClosed.addOnce((_) -> {
remove(blackScreen);
if (!Save.instance.oldChar.value)
@@ -477,6 +484,12 @@ class CharSelectSubState extends MusicBeatSubState
});
}
override public function destroy():Void
{
CharSelectAtlasHandler.clearAtlasCache();
super.destroy();
}
function checkNewChar():Void
{
if (nonLocks.length > 0) selectTimer.start(2, (_) -> {
@@ -511,7 +524,7 @@ class CharSelectSubState extends MusicBeatSubState
}
var grpIcons:FlxSpriteGroup = new FlxSpriteGroup();
var grpHitboxes:FlxTypedGroup<FlxObject> = new FlxTypedGroup<FlxObject>();
var grpHitboxes:FlxTypedGroup<FlxObject>;
var grpXSpread(default, set):Float = 107;
var grpYSpread(default, set):Float = 127;
var nonLocks = [];
@@ -521,7 +534,6 @@ class CharSelectSubState extends MusicBeatSubState
add(grpIcons);
FlxG.debugger.addTrackerProfile(new TrackerProfile(FlxSpriteGroup, ["x", "y"]));
// FlxG.debugger.track(grpIcons, "iconGrp");
for (i in 0...9)
{
@@ -542,12 +554,13 @@ class CharSelectSubState extends MusicBeatSubState
var isPlayerUnlocked:Bool = player?.isUnlocked() ?? false;
if (availableChars.exists(i) && isPlayerUnlocked) nonLocks.push(i);
var temp:Lock = new Lock(0, 0, i);
temp.ID = 1;
var temp:Lock = new Lock(0, 0, i,
{
swfMode: true,
uniqueInCache: true
});
// temp.onAnimationComplete.add(function(anim) {
// if (anim == "unlock") playerChill.playAnimation("unlock", true);
// });
temp.ID = 1;
grpIcons.add(temp);
}
@@ -582,6 +595,7 @@ class CharSelectSubState extends MusicBeatSubState
var xThing = (copy - index - 2) * -1;
// Look, I'd write better code but I had better aneurysms, my bad - Cheems
// felt - Zack
// Krue - Abnormal
cursorY = yThing;
cursorX = xThing;
@@ -592,26 +606,21 @@ class CharSelectSubState extends MusicBeatSubState
selectTimer.start(0.5, function(_) {
var lock:Lock = cast grpIcons.group.members[index];
lock.anim.getFrameLabel("unlockAnim").add(function() {
playerChillOut.playAnimation("death");
lock.anim.play("unlock");
lock.anim.onFrameChange.add(function(animName:String, frame:Int, index:Int) {
if (frame == 40)
{
playerChillOut.anim.play("death");
}
});
lock.playAnimation("unlock");
unlockSound.volume = 0.7;
unlockSound.play(true);
// Do not sync the lock, because otherwise the animation will be out of sync!
/*syncLock = lock;
sync = true; */
lock.onAnimationComplete.addOnce(function(_) {
// syncLock = null;
var char:Null<String> = availableChars.get(index) ?? Constants.DEFAULT_CHARACTER;
lock.anim.onFinish.addOnce(function(_) {
var char:String = availableChars.get(index) ?? Constants.DEFAULT_CHARACTER;
camera.flash(0xFFFFFFFF, 0.1);
playerChill.playAnimation("unlock");
playerChill.anim.play("unlock");
playerChill.visible = true;
var id = grpIcons.members.indexOf(lock);
@@ -631,7 +640,7 @@ class CharSelectSubState extends MusicBeatSubState
bopPlay = true;
updateIconPositions();
playerChillOut.onAnimationComplete.addOnce((_) -> if (_ == "death")
playerChillOut.anim.onFinish.addOnce((_) -> if (_ == "death")
{
// sync = false;
playerChillOut.visible = false;
@@ -671,7 +680,7 @@ class CharSelectSubState extends MusicBeatSubState
});
}
else
playerChill.onAnimationComplete.addOnce((_) -> unLock());
playerChill.anim.onFinish.addOnce((_) -> unLock());
});
playerChill.visible = false;
@@ -685,6 +694,7 @@ class CharSelectSubState extends MusicBeatSubState
{
grpIcons.x = cutoutSize + 450;
grpIcons.y = 120;
for (index => member in grpIcons.members)
{
var posX:Float = (index % 3);
@@ -710,34 +720,6 @@ class CharSelectSubState extends MusicBeatSubState
}
}
var sync:Bool = false;
var syncLock:Null<Lock>;
var audioBizz:Float = 0;
function syncAudio(elapsed:Float):Void
{
@:privateAccess
if (sync && unlockSound.time > 0)
{
// if (playerChillOut.anim.framerate > 0)
// {
// if (syncLock != null) syncLock.anim.framerate = 0;
// playerChillOut.anim.framerate = 0;
// }
playerChillOut.anim._tick = 0;
if (syncLock != null) syncLock.anim._tick = 0;
if ((unlockSound.time - audioBizz) >= ((delay) * 100))
{
if (syncLock != null) syncLock.anim._tick = delay;
playerChillOut.anim._tick = delay;
audioBizz += delay * 100;
}
}
}
function goToFreeplay():Void
{
allowInput = false;
@@ -762,7 +744,6 @@ class CharSelectSubState extends MusicBeatSubState
FlxTween.tween(dipshitBlur, {y: dipshitBlur.y + 220}, 0.8, {ease: FlxEase.backIn});
for (index => member in grpIcons.members)
{
// member.y += 300;
FlxTween.tween(member, {y: member.y + 300}, 0.8, {ease: FlxEase.backIn});
}
FlxG.camera.follow(camFollow, LOCKON);
@@ -813,8 +794,6 @@ class CharSelectSubState extends MusicBeatSubState
if (controls.UI_UP_R || controls.UI_DOWN_R || controls.UI_LEFT_R || controls.UI_RIGHT_R #if FEATURE_TOUCH_CONTROLS || TouchUtil.justReleased #end)
selectSound.pitch = 1;
syncAudio(elapsed);
if (allowInput && !pressedSelect)
{
#if FEATURE_TOUCH_CONTROLS
@@ -938,10 +917,10 @@ class CharSelectSubState extends MusicBeatSubState
cursorY = -1;
}
if (availableChars.exists(getCurrentSelected())
&& PlayerRegistry.instance.isCharacterSeen(availableChars[getCurrentSelected()] ?? Constants.DEFAULT_CHARACTER))
var currentCharacter:String = availableChars[getCurrentSelected()] ?? Constants.DEFAULT_CHARACTER;
if (availableChars.exists(getCurrentSelected()) && Save.instance.charactersSeen.value.contains(currentCharacter))
{
var charId = availableChars.get(getCurrentSelected());
var charId:String = availableChars.get(getCurrentSelected()) ?? Constants.DEFAULT_CHARACTER;
if (charId != null) curChar = charId;
if (allowInput && pressedSelect && (controls.BACK #if FEATURE_TOUCH_CONTROLS || (mobileDeny && TouchUtil.justReleased) #end))
@@ -961,8 +940,8 @@ class CharSelectSubState extends MusicBeatSubState
FlxTween.globalManager.cancelTweensOf(FlxG.sound.music);
FlxTween.tween(FlxG.sound.music, {pitch: 1.0, volume: 1.0}, 1, {ease: FlxEase.quartInOut});
playerChill.playAnimation("deselect");
gfChill.playAnimation("deselect");
playerChill.anim.play("deselect");
gfChill.anim.play("deselect");
pressedSelect = false;
FlxTween.tween(FlxG.sound.music, {pitch: 1.0}, 1,
{
@@ -970,8 +949,10 @@ class CharSelectSubState extends MusicBeatSubState
onComplete: (_) -> {
if (playerChill.getCurrentAnimation() == "deselect loop start" || playerChill.getCurrentAnimation() == "deselect")
{
playerChill.playAnimation("idle", true, false, true);
gfChill.playAnimation("idle", true, false, true);
playerChill.anim.play("idle", true);
playerChill.anim.curAnim.looped = true;
gfChill.anim.play("idle", true);
gfChill.anim.curAnim.looped = true;
}
}
});
@@ -1004,8 +985,9 @@ class CharSelectSubState extends MusicBeatSubState
FlxTween.tween(FlxG.sound.music, {pitch: 0.1}, 1, {ease: FlxEase.quadInOut});
FlxTween.tween(FlxG.sound.music, {volume: 0.0}, 1.5, {ease: FlxEase.quadInOut});
playerChill.playAnimation("select");
gfChill.playAnimation("confirm", true, false, true);
playerChill.anim.play("select");
gfChill.anim.play("confirm", true);
gfChill.anim.curAnim.looped = true;
pressedSelect = true;
selectTimer.start(1.5, (_) -> {
goToFreeplay();
@@ -1027,7 +1009,7 @@ class CharSelectSubState extends MusicBeatSubState
{
cursorDenied.visible = true;
playerChill.playAnimation("cannot select Label", true);
playerChill.anim.play("cannot select Label", true);
lockedSound.play(true);
@@ -1140,7 +1122,6 @@ class CharSelectSubState extends MusicBeatSubState
{
if (spamUp || spamDown || spamLeft || spamRight)
{
// selectSound.changePitchBySemitone(1);
if (selectSound.pitch > 5) selectSound.pitch = 5;
selectSound.play(true);
@@ -1182,22 +1163,20 @@ class CharSelectSubState extends MusicBeatSubState
switch (lock.getCurrentAnimation())
{
case "idle":
lock.playAnimation("selected");
lock.anim.play("selected");
case "selected" | "clicked":
if (controls.ACCEPT) lock.playAnimation("clicked", true);
if (controls.ACCEPT) lock.anim.play("clicked", true);
}
}
else
{
lock.playAnimation("idle");
lock.anim.play("idle");
}
case 0:
var memb:PixelatedIcon = cast member;
if (index == getCurrentSelected())
{
// memb.pixels = memb.withDropShadow.clone();
if (bopPlay)
{
if (bopRefX == 0)
@@ -1216,19 +1195,15 @@ class CharSelectSubState extends MusicBeatSubState
if (autoFollow && !pressedSelect && memb.animation.curAnim?.name != 'idle')
{
memb.animation.play("confirm", false, true);
@:nullSafety(Off)
var onFinish:String->Void = null;
onFinish = (_) -> {
var onFinish:String->Void = (_) -> {
member.animation.play('idle');
member.animation.onFinish.remove(onFinish);
member.animation.onFinish.removeAll();
};
member.animation.onFinish.add(onFinish);
}
}
else
{
// memb.pixels = memb.noDropShadow.clone();
@:nullSafety(Off)
memb.filters = null;
memb.scale.set(2, 2);
}
@@ -1279,23 +1254,23 @@ class CharSelectSubState extends MusicBeatSubState
gfChill.visible = false;
playerChill.visible = false;
playerChillOut.visible = true;
playerChillOut.playAnimation("slideout");
var index = playerChillOut.anim.getFrameLabel("slideout").index;
playerChillOut.onAnimationFrame.removeAll();
playerChillOut.onAnimationFrame.add((_, frame:Int) -> {
if (frame >= index + 1)
playerChillOut.anim.play("slideout");
playerChillOut.anim.onFrameChange.removeAll();
playerChillOut.anim.onFrameChange.add(function(animName:String, frameNumber:Int, index:Int) {
if (!playerChill.visible)
{
playerChill.visible = true;
playerChill.switchChar(value);
gfChill.switchGF(value);
gfChill.visible = true;
}
if (frame >= index + 2)
{
playerChillOut.switchChar(value);
playerChillOut.visible = false;
playerChillOut.onAnimationFrame.removeAll();
}
});
playerChillOut.anim.onFinish.addOnce(function(animName:String) {
playerChillOut.switchChar(value);
playerChillOut.visible = false;
playerChillOut.anim.onFrameChange.removeAll();
});
return value;
@@ -1321,5 +1296,5 @@ class CharSelectSubState extends MusicBeatSubState
*/
typedef CharSelectSubStateParams =
{
?character:String, // ?fromFreeplaySelect:Bool,
?character:String
};
+29 -23
View File
@@ -1,38 +1,44 @@
package funkin.ui.charSelect;
import flixel.util.FlxColor;
import flxanimate.effects.FlxTint;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.FunkinSprite;
import flixel.FlxCamera;
import flixel.math.FlxPoint;
@:nullSafety
class Lock extends FlxAtlasSprite
class Lock extends FunkinSprite
{
var colors:Array<FlxColor> = [
0x31F2A5, 0x20ECCD, 0x24D9E8,
0x20ECCD, 0x20C8D4, 0x209BDD,
0x209BDD, 0x2362C9, 0x243FB9
]; // lock colors, in a nx3 matrix format
0xFF31F2A5, 0xFF20ECCD, 0xFF24D9E8,
0xFF20ECCD, 0xFF20C8D4, 0xFF209BDD,
0xFF209BDD, 0xFF2362C9, 0xFF243FB9
];
public function new(x:Float = 0, y:Float = 0, index:Int)
public function new(x:Float = 0, y:Float = 0, index:Int, settings:AtlasSpriteSettings)
{
super(x, y, Paths.animateAtlas("charSelect/lock"));
var tint:FlxColor = colors[index];
var tint:FlxTint = new FlxTint(colors[index], 1);
super(x, y);
var arr:Array<String> = ["lock", "lock top 1", "lock top 2", "lock top 3", "lock base fuck it"];
loadTextureAtlas("charSelect/lock",
{
swfMode: settings.swfMode,
uniqueInCache: settings.uniqueInCache,
onSymbolCreate: (symbol) -> {
if (symbol.timeline.getLayer("color") != null)
{
var colorSymbol:Null<animate.internal.elements.SymbolInstance> = symbol.timeline.getLayer("color")?.getFrameAtIndex(0)?.convertToSymbol(0,
1) ?? null;
var func = function(name) {
var symbol = anim.symbolDictionary[name];
if (symbol != null && symbol.timeline.get("color") != null) {
var color = symbol.timeline.get("color").get(0);
if (color != null) color.colorEffect = tint;
}
}
for (symbol in arr)
{
func(symbol);
}
if (colorSymbol != null)
{
colorSymbol.setColorTransform(0, 0, 0, 1, tint.red, tint.green, tint.blue, 0);
}
}
}
});
playAnimation("idle");
anim.play("idle");
offset.set(230, 110);
}
}
@@ -51,7 +51,7 @@ class DebugBoundingState extends FlxState
var offsetView:FlxGroup;
var dropDownSetup:Bool = false;
var onionSkinChar:FlxSprite;
var onionSkinChar:BaseCharacter;
var txtOffsetShit:FlxText;
var offsetEditorDialog:CollapsibleDialog;
@@ -177,16 +177,25 @@ class DebugBoundingState extends FlxState
if (swagChar == null) return;
if (swagChar.hasAnimation("idle")) swagChar.playAnimation("idle", true);
onionSkinChar.loadGraphicFromSprite(swagChar);
onionSkinChar.frame = swagChar.frame;
onionSkinChar.alpha = 0.6;
if (onionSkinChar.hasAnimation("idle"))
{
onionSkinChar.playAnimation("idle", true);
}
else
{
onionSkinChar.playAnimation(currentAnimationName, true);
}
onionSkinChar.animation.pause();
onionSkinChar.flipX = swagChar.flipX;
onionSkinChar.scale.set(swagChar.scale.x, swagChar.scale.y);
onionSkinChar.updateHitbox();
onionSkinChar.offset.x = swagChar.offset.x + (swagChar.animOffsets[0] - swagChar.globalOffsets[0]) * swagChar.scale.x;
onionSkinChar.offset.y = swagChar.offset.y + (swagChar.animOffsets[1] - swagChar.globalOffsets[1]) * swagChar.scale.y;
swagChar.playAnimation(currentAnimationName, true); // reset animation to the one it should be
swagChar.playAnimation(currentAnimationName, true);
}
function initOffsetView():Void
@@ -194,10 +203,6 @@ class DebugBoundingState extends FlxState
offsetView = new FlxGroup();
add(offsetView);
onionSkinChar = new FlxSprite().makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.TRANSPARENT);
onionSkinChar.visible = false;
offsetView.add(onionSkinChar);
txtOffsetShit = new FlxText(20, 20, 0, "", 20);
txtOffsetShit.setFormat(Paths.font("vcr.ttf"), 26, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
txtOffsetShit.cameras = [hudCam];
@@ -205,10 +210,6 @@ class DebugBoundingState extends FlxState
offsetView.add(txtOffsetShit);
var characters:Array<String> = CharacterDataParser.listCharacterIds();
characters = characters.filter(function(charId:String) {
var char = CharacterDataParser.fetchCharacterData(charId);
return char.renderType != AnimateAtlas;
});
characters.sort(SortUtil.alphabetically);
var charDropdown:DropDown = offsetEditorDialog.findComponent('characterDropdown', DropDown);
@@ -520,10 +521,23 @@ class DebugBoundingState extends FlxState
swagChar.destroy();
}
if (onionSkinChar != null)
{
offsetView.remove(onionSkinChar);
onionSkinChar.destroy();
}
swagChar = CharacterDataParser.fetchCharacter(char);
swagChar.x = onionSkinChar.x = 100;
swagChar.y = onionSkinChar.y = 100;
swagChar.x = 100;
swagChar.y = 100;
swagChar.debug = true;
onionSkinChar = CharacterDataParser.fetchCharacter(char);
onionSkinChar.x = swagChar.x;
onionSkinChar.y = swagChar.y;
onionSkinChar.debug = true;
offsetView.add(onionSkinChar);
offsetView.add(swagChar);
if (swagChar == null || swagChar.frames == null)
+21 -8
View File
@@ -1,16 +1,18 @@
package funkin.ui.debug.anim;
import flixel.FlxG;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.FunkinSprite;
import funkin.ui.MusicBeatState;
import funkin.util.macro.ConsoleMacro;
import funkin.Paths;
/**
* A simple test of FlxAnimate.
* Delete this later?
*/
class FlxAnimateTest extends MusicBeatState
class FlxAnimateTest extends MusicBeatState implements ConsoleClass
{
var sprite:FlxAtlasSprite;
var sprite:FunkinSprite;
public function new()
{
@@ -22,18 +24,29 @@ class FlxAnimateTest extends MusicBeatState
{
super.create();
sprite = new FlxAtlasSprite(-100, 0, 'week3:assets/week3/images/philly/erect/picoFlowers');
sprite = FunkinSprite.createTextureAtlas(0, 0, "charSelect/bfChill",
{
swfMode: false, // If to render like in a SWF file, rather than the Animate editor.
cacheOnLoad: true, // If to precache all animation filters and masks at once, rather than at runtime.
filterQuality: MEDIUM // Level of quality used to render filters. (HIGH, MEDIUM, LOW, RUDY)
});
add(sprite);
sprite.playAnimation(sprite.anim.stageInstance.symbol.name, false, false, true);
}
public override function update(elapsed:Float):Void
{
super.update(elapsed);
if (FlxG.keys.justPressed.SPACE) ((sprite.anim.isPlaying) ? sprite.anim.pause() : sprite.playAnimation(null, false, false, true));
if (FlxG.keys.justPressed.SPACE) (!(sprite.isAnimationFinished()) ? sprite.anim.pause() : sprite.anim.resume());
if (FlxG.keys.anyJustPressed([A, LEFT])) sprite.anim.curFrame--;
if (FlxG.keys.anyJustPressed([D, RIGHT])) sprite.anim.curFrame++;
if (FlxG.keys.anyJustPressed([A, LEFT])) sprite.anim.curAnim.curFrame--;
if (FlxG.keys.anyJustPressed([D, RIGHT])) sprite.anim.curAnim.curFrame++;
if (FlxG.keys.justPressed.Q) sprite.anim.play("slidein idle point", true);
if (FlxG.keys.justPressed.W) sprite.anim.play("slidein", true);
if (FlxG.keys.justPressed.E) sprite.anim.play("death", true);
if (FlxG.keys.justPressed.R) sprite.anim.play("cannot select Label", true);
if (FlxG.keys.justPressed.T) sprite.anim.play("idle", true);
}
}
@@ -54,7 +54,7 @@ class ResultsDebugSubState extends MusicBeatSubState
FlxG.switchState(() -> new funkin.play.ResultState(resultsParams));
});
});
createToggleListItem("Character", PlayerRegistry.instance.listBaseGameEntryIds(), function(result:String) {
createToggleListItem("Character", PlayerRegistry.instance.listEntryIds(), function(result:String) {
resultsParams.characterId = result;
});
createToggleListItem("Results Mode", ["Debug", "Story", "Freeplay"], function(result:String) {
+12 -25
View File
@@ -1,6 +1,6 @@
package funkin.ui.freeplay;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.FunkinSprite;
import flixel.group.FlxSpriteGroup;
import flixel.util.FlxSort;
import flixel.util.FlxTimer;
@@ -32,7 +32,9 @@ class AlbumRoll extends FlxSpriteGroup
return value;
}
var newAlbumArt:FlxAtlasSprite;
final ALBUM_ART_SYMBOL:String = "album art placeholder";
var newAlbumArt:FunkinSprite;
var albumTitle:Null<FunkinSprite> = null;
var difficultyStars:DifficultyStars;
@@ -41,26 +43,14 @@ class AlbumRoll extends FlxSpriteGroup
var albumData:Null<Album> = null;
final animNames:Map<String, String> = [
"volume1-active" => "ENTRANCE",
"volume2-active" => "ENTRANCE VOL2",
"volume3-active" => "ENTRANCE VOL3",
"volume1-trans" => "VOL1 TRANS",
"volume2-trans" => "VOL2 TRANS",
"volume3-trans" => "VOL3 TRANS",
"volume1-idle" => "VOL1 STILL",
"volume2-idle" => "VOL2 STILL",
"volume3-idle" => "VOL3 STILL",
];
public function new()
{
super();
newAlbumArt = new FlxAtlasSprite((FlxG.width - 640) - FullScreenScaleMode.gameNotchSize.x, 360, Paths.animateAtlas("freeplay/albumRoll/freeplayAlbum"));
newAlbumArt = FunkinSprite.createTextureAtlas((FlxG.width + -360) - FullScreenScaleMode.gameNotchSize.x, 220, "freeplay/albumRoll/freeplayAlbum");
newAlbumArt.visible = false;
difficultyStars = new DifficultyStars((FlxG.width - 1140) - FullScreenScaleMode.gameNotchSize.x, 39);
difficultyStars = new DifficultyStars((FlxG.width - 330) - FullScreenScaleMode.gameNotchSize.x, 209);
difficultyStars.visible = false;
add(newAlbumArt);
@@ -69,7 +59,7 @@ class AlbumRoll extends FlxSpriteGroup
buildAlbumTitle("freeplay/albumRoll/volume1-text");
if (albumTitle != null) albumTitle.visible = false;
newAlbumArt.onAnimationComplete.add(onAlbumFinish);
newAlbumArt.anim.onFinish.add(onAlbumFinish);
}
function onAlbumFinish(animName:String):Void
@@ -77,11 +67,8 @@ class AlbumRoll extends FlxSpriteGroup
// Play the idle animation for the current album.
if (animName != "idle")
{
newAlbumArt.playAnimation('idle', true, false, true);
}
else
{
newAlbumArt.cleanupAnimation('idle');
newAlbumArt.anim.play('idle', true);
newAlbumArt.anim.curAnim.looped = true;
}
}
@@ -108,7 +95,7 @@ class AlbumRoll extends FlxSpriteGroup
// Update the album art.
var albumGraphic = Paths.image(albumData.getAlbumArtAssetKey());
newAlbumArt.replaceFrameGraphic(0, albumGraphic);
newAlbumArt.replaceSymbolGraphic(ALBUM_ART_SYMBOL, albumGraphic);
buildAlbumTitle(albumData.getAlbumTitleAssetKey(), albumData.getAlbumTitleOffsets());
applyExitMovers();
@@ -174,7 +161,7 @@ class AlbumRoll extends FlxSpriteGroup
if (albumTitle != null) albumTitle.visible = false;
newAlbumArt.visible = true;
newAlbumArt.playAnimation('intro', true);
newAlbumArt.anim.play('intro', true);
difficultyStars.visible = false;
difficultyStars.flameCheck();
@@ -190,7 +177,7 @@ class AlbumRoll extends FlxSpriteGroup
{
this.visible = true;
// Weird workaround
newAlbumArt.playAnimation('switch', true);
newAlbumArt.anim.play('switch', true);
if (albumTitle != null) albumTitle.animation.play('switch');
}
+6 -5
View File
@@ -1,7 +1,7 @@
package funkin.ui.freeplay;
import flixel.group.FlxSpriteGroup;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.FunkinSprite;
import funkin.graphics.shaders.HSVShader;
@:nullSafety
@@ -18,7 +18,7 @@ class DifficultyStars extends FlxSpriteGroup
*/
public var difficulty(default, set):Int = 1;
public var stars:FlxAtlasSprite;
public var stars:FunkinSprite;
public var flames:FreeplayFlames;
@@ -32,7 +32,7 @@ class DifficultyStars extends FlxSpriteGroup
flames = new FreeplayFlames(0, 0);
stars = new FlxAtlasSprite(0, 0, Paths.animateAtlas("freeplay/freeplayStars"));
stars = FunkinSprite.createTextureAtlas(0, 0, "freeplay/freeplayStars");
stars.anim.play("diff stars");
add(flames);
@@ -56,7 +56,7 @@ class DifficultyStars extends FlxSpriteGroup
// ......
// 1300-1499: 15 stars
// 1500 : 0 stars
if (curDifficulty < 15 && stars.anim.curFrame >= (curDifficulty + 1) * 100)
if (curDifficulty < 15 && stars.anim.curAnim.curFrame >= (curDifficulty + 1) * 100)
{
stars.anim.play("diff stars", true, false, curDifficulty * 100);
}
@@ -97,6 +97,7 @@ class DifficultyStars extends FlxSpriteGroup
function set_curDifficulty(value:Int):Int
{
curDifficulty = value;
if (curDifficulty == 15)
{
stars.anim.play("diff stars", true, false, 1500);
@@ -104,7 +105,7 @@ class DifficultyStars extends FlxSpriteGroup
}
else
{
stars.anim.curFrame = Std.int(curDifficulty * 100);
stars.anim.curAnim.curFrame = Std.int(curDifficulty * 100);
stars.anim.play("diff stars", true, false, curDifficulty * 100);
}
+7 -16
View File
@@ -1564,21 +1564,7 @@ class FreeplayState extends MusicBeatSubState
wait: 0.1
});
add(transitionGradient);
// FlxTween.tween(transitionGradient, {alpha: 0}, 1, {ease: FlxEase.circIn});
// for (index => capsule in grpCapsules.members)
// {
// var distFromSelected:Float = Math.abs(index - curSelected) - 1;
// if (distFromSelected < 5)
// {
// capsule.doLerp = false;
// exitMoversCharSel.set([capsule],
// {
// y: -250,
// speed: 0.8,
// wait: 0.1
// });
// }
// }
funnyCam.filtersEnabled = true;
fadeShader.fade(0.0, 1.0, 0.8, {ease: FlxEase.quadIn, onComplete: (twn) -> funnyCam.filtersEnabled = false});
@@ -1609,6 +1595,10 @@ class FreeplayState extends MusicBeatSubState
});
}
}
// TODO: i have no fucking idea why the dj position is shitty when entering from char select
// the easiest way is to just snap them back into place
if (dj != null) dj.setPosition((CUTOUT_WIDTH * DJ_POS_MULTI) + 640, 366);
}
var spamTimer:Float = 0;
@@ -1672,7 +1662,8 @@ class FreeplayState extends MusicBeatSubState
if ((controls.FREEPLAY_CHAR_SELECT && !fromCharSelect #if FEATURE_TOUCH_CONTROLS
|| (TouchUtil.pressAction(djHitbox, funnyCam, false) && !SwipeUtil.swipeAny) #end)
&& controls.active)
&& controls.active
&& !FlxG.debugger.visible)
{
tryOpenCharSelect();
}
+30 -11
View File
@@ -12,7 +12,9 @@ import flixel.util.FlxTimer;
import funkin.input.Controls;
import funkin.util.SwipeUtil;
import funkin.util.TouchUtil;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import flixel.math.FlxPoint;
import flixel.FlxCamera;
import funkin.graphics.FunkinSprite;
import funkin.audio.FunkinSound;
class LetterSort extends FlxSpriteGroup
@@ -53,7 +55,7 @@ class LetterSort extends FlxSpriteGroup
for (i in 0...5)
{
var letter:FreeplayLetter = new FreeplayLetter(i * 80, 0, i);
var letter:FreeplayLetter = new FreeplayLetter(i * 80, 0, i, curSelection);
letter.x += 50;
letter.y += 50;
// letter.visible = false;
@@ -247,9 +249,9 @@ class LetterSort extends FlxSpriteGroup
}
/**
* The actual FlxAtlasSprite for the letters, with their animation code stuff and regex stuff
* The actual FunkinSprite for the letters, with their animation code stuff and regex stuff
*/
class FreeplayLetter extends FlxAtlasSprite
class FreeplayLetter extends FunkinSprite
{
/**
* A preformatted array of letter strings, for use when doing regex
@@ -268,9 +270,11 @@ class FreeplayLetter extends FlxAtlasSprite
*/
public var curLetter:Int = 0;
public function new(x:Float, y:Float, ?letterInd:Int)
public function new(x:Float, y:Float, ?letterInd:Int, curSelected:Int = 0)
{
super(x, y, Paths.animateAtlas("freeplay/sortedLetters"));
super(x, y);
loadTextureAtlas("freeplay/sortedLetters");
// this is used for the regex
// /^[OR].*/gi doesn't work for showing the song Pico, so now it's
@@ -290,11 +294,16 @@ class FreeplayLetter extends FlxAtlasSprite
if (letterInd != null)
{
this.anim.play(animLetters[letterInd] + " move");
this.anim.pause();
this.anim.play(animLetters[letterInd] + " move", true);
curLetter = letterInd;
this.anim.onComplete.add(function() {
this.anim.play(animLetters[curLetter] + " move");
if (curSelected != curLetter)
{
this.anim.pause();
}
this.anim.onFinish.add(function(name:String) {
this.anim.play(animLetters[curLetter] + " move", true);
});
}
}
@@ -328,6 +337,16 @@ class FreeplayLetter extends FlxAtlasSprite
{
this.anim.pause();
}
// updateHitbox();
}
/**
* Offset the letter.
*/
override function getScreenPosition(?result:FlxPoint, ?camera:FlxCamera):FlxPoint
{
var output:FlxPoint = super.getScreenPosition(result, camera);
output.x -= 50;
output.y -= 60;
return output;
}
}
@@ -6,7 +6,6 @@ import flixel.math.FlxPoint;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.util.FlxSpriteUtil;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.FunkinSprite;
import funkin.ui.freeplay.charselect.PlayableCharacter;
import openfl.display.BlendMode;
@@ -24,7 +23,7 @@ import funkin.util.BitmapUtil;
*/
class BackingCard extends FlxSpriteGroup implements IBPMSyncedScriptedClass implements IStateChangingScriptedClass implements IFreeplayScriptedClass
{
public var backingTextYeah:FlxAtlasSprite;
public var backingTextYeah:FunkinSprite;
public var orangeBackShit:FunkinSprite;
public var alsoOrangeLOL:FunkinSprite;
public var pinkBack:FunkinSprite;
@@ -58,15 +57,7 @@ class BackingCard extends FlxSpriteGroup implements IBPMSyncedScriptedClass impl
orangeBackShit = new FunkinSprite(84, 440).makeSolidColor(Std.int(pinkBack.width), 75, 0xFFFEDA00);
alsoOrangeLOL = new FunkinSprite(0, orangeBackShit.y).makeSolidColor(100, Std.int(orangeBackShit.height), 0xFFFFD400);
confirmGlow2 = new FlxSprite(confirmGlow.x, confirmGlow.y).loadGraphic(Paths.image('freeplay/confirmGlow2'));
backingTextYeah = new FlxAtlasSprite((FreeplayState.CUTOUT_WIDTH * FreeplayState.DJ_POS_MULTI) + 640, 370,
Paths.animateAtlas("freeplay/backing-text-yeah"), {
FrameRate: 24.0,
Reversed: false,
// ?OnComplete:Void -> Void,
ShowPivot: false,
Antialiasing: true,
ScrollFactor: new FlxPoint(1, 1),
});
backingTextYeah = FunkinSprite.createTextureAtlas((FreeplayState.CUTOUT_WIDTH * FreeplayState.DJ_POS_MULTI) + -320, 120, "freeplay/backing-text-yeah",);
pinkBack.color = 0xFFFFD4E9; // sets it to pink!
pinkBack.x -= pinkBack.width;
@@ -6,15 +6,12 @@ import flixel.FlxSprite;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.modding.events.ScriptEvent;
import openfl.display.BlendMode;
import funkin.util.BitmapUtil;
class NewCharacterCard extends BackingCard
{
var confirmAtlas:FlxAtlasSprite;
var darkBg:FlxSprite;
var lightLayer:FlxSprite;
var multiply1:FlxSprite;
@@ -261,10 +258,4 @@ class NewCharacterCard extends BackingCard
cardGlow.scale.set(1, 1);
FlxTween.tween(cardGlow, {alpha: 0, "scale.x": 1.2, "scale.y": 1.2}, 0.25, {ease: FlxEase.sineOut});
}
override public function confirm():Void
{
// confirmAtlas.visible = true;
// confirmAtlas.anim.play("");
}
}
@@ -1,7 +1,5 @@
package funkin.ui.freeplay.dj;
import flixel.util.FlxSignal;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.data.freeplay.player.PlayerRegistry;
/**
@@ -11,15 +9,31 @@ import funkin.data.freeplay.player.PlayerRegistry;
@:hscriptClass
class ScriptedAnimateAtlasFreeplayDJ extends AnimateAtlasFreeplayDJ implements polymod.hscript.HScriptedClass {}
/**
* An AnimateAtlasFreeplayDJ is a Freeplay DJ which is rendered by
* displaying an animation derived from an Adobe Animate texture atlas spritesheet file.
*
* BaseFreeplayDJ has game logic, AnimateAtlasFreeplayDJ has only rendering logic.
* KEEP THEM SEPARATE!
*/
class AnimateAtlasFreeplayDJ extends BaseFreeplayDJ
{
public function new(x:Float, y:Float, characterId:String)
{
super(x, y, characterId);
loadAtlas(Paths.animateAtlas(playableCharData.getAssetPath()));
onAnimationComplete.add(onFinishAnim);
onAnimationLoop.add(onFinishAnim);
loadTextureAtlas(playableCharData?.getAssetPath(),
{
swfMode: true
});
if (playableCharData?.useApplyStageMatrix() ?? false)
{
this.applyStageMatrix = true;
}
animation.onFinish.add(onFinishAnim);
animation.onLoop.add(onFinishAnim);
}
public override function update(elapsed:Float):Void
@@ -29,7 +43,7 @@ class AnimateAtlasFreeplayDJ extends BaseFreeplayDJ
case Intro:
// Play the intro animation then leave this state immediately.
var animPrefix = playableCharData?.getAnimationPrefix('intro');
if (animPrefix != null && (getCurrentAnimation() != animPrefix || !this.anim.isPlaying))
if (animPrefix != null && (getCurrentAnimation() != animPrefix))
{
playFlashAnimation(animPrefix, true);
}
@@ -63,7 +77,7 @@ class AnimateAtlasFreeplayDJ extends BaseFreeplayDJ
if (getCurrentAnimation() == animPrefixA)
{
var endFrame = playableCharData?.getFistPumpIntroEndFrame() ?? 0;
if (endFrame > -1 && anim.curFrame >= endFrame)
if (endFrame > -1 && anim.curAnim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixA, true, false, false, playableCharData?.getFistPumpIntroStartFrame());
}
@@ -71,7 +85,7 @@ class AnimateAtlasFreeplayDJ extends BaseFreeplayDJ
else if (getCurrentAnimation() == animPrefixB)
{
var endFrame = playableCharData?.getFistPumpIntroBadEndFrame() ?? 0;
if (endFrame > -1 && anim.curFrame >= endFrame)
if (endFrame > -1 && anim.curAnim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixB, true, false, false, playableCharData?.getFistPumpIntroBadStartFrame());
}
@@ -88,7 +102,7 @@ class AnimateAtlasFreeplayDJ extends BaseFreeplayDJ
if (getCurrentAnimation() == animPrefixA)
{
var endFrame = playableCharData?.getFistPumpLoopEndFrame() ?? 0;
if (endFrame > -1 && anim.curFrame >= endFrame)
if (endFrame > -1 && anim.curAnim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixA, true, false, false, playableCharData?.getFistPumpLoopStartFrame());
}
@@ -96,7 +110,7 @@ class AnimateAtlasFreeplayDJ extends BaseFreeplayDJ
else if (getCurrentAnimation() == animPrefixB)
{
var endFrame = playableCharData?.getFistPumpLoopBadEndFrame() ?? 0;
if (endFrame > -1 && anim.curFrame >= endFrame)
if (endFrame > -1 && anim.curAnim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixB, true, false, false, playableCharData?.getFistPumpLoopBadStartFrame());
}
@@ -131,14 +145,12 @@ class AnimateAtlasFreeplayDJ extends BaseFreeplayDJ
}
// Call the superclass function AFTER updating the current state and playing the next animation.
// This ensures that FlxAnimate starts rendering the new animation immediately.
// This ensures that flixel-animate starts rendering the new animation immediately.
super.update(elapsed);
}
override function onFinishAnim(name:String):Void
{
// var name = anim.curSymbol.name;
if (name == playableCharData?.getAnimationPrefix('intro'))
{
if (PlayerRegistry.instance.hasNewCharacter())
@@ -183,25 +195,6 @@ class AnimateAtlasFreeplayDJ extends BaseFreeplayDJ
// trace('Finished loss reaction');
currentState = Idle;
}
else if (name == playableCharData?.getAnimationPrefix('cartoon'))
{
// trace('Finished cartoon');
var frame:Int = FlxG.random.bool(33) ? (playableCharData?.getCartoonLoopBlinkFrame() ?? 0) : (playableCharData?.getCartoonLoopFrame() ?? 0);
// Character switches channels when the video ends, or at a 10% chance each time his idle loops.
if (FlxG.random.bool(5))
{
frame = playableCharData?.getCartoonChannelChangeFrame() ?? 0;
// boyfriend switches channel code?
// Transefer into bf.hxc in scripts/freeplay/dj
// runTvLogic();
}
trace('Replay idle: ${frame}');
var animPrefix = playableCharData?.getAnimationPrefix('cartoon');
if (animPrefix != null) playFlashAnimation(animPrefix, true, false, false, frame);
// trace('Finished confirm');
}
else if (name == playableCharData?.getAnimationPrefix('newUnlock'))
{
// Animation should loop.
@@ -216,25 +209,14 @@ class AnimateAtlasFreeplayDJ extends BaseFreeplayDJ
}
}
override public function listAnimations():Array<String>
{
var anims:Array<String> = [];
@:privateAccess
for (animKey in anim.symbolDictionary)
anims.push(animKey.name);
return anims;
}
override public function getCurrentAnimation():String
{
if (this.anim == null || this.anim.curSymbol == null) return "";
return this.anim.curSymbol.name;
}
override public function playFlashAnimation(id:String, Force:Bool = false, Reverse:Bool = false, Loop:Bool = false, Frame:Int = 0):Void
{
playAnimation(id, Force, Reverse, Loop, Frame);
applyAnimOffset();
animation.play(id, Force, Reverse, Frame);
if (animation.curAnim != null)
{
animation.curAnim.looped = Loop;
}
applyAnimationOffset();
}
}
+29 -371
View File
@@ -1,16 +1,11 @@
package funkin.ui.freeplay.dj;
import flixel.graphics.frames.FlxFrame;
import flixel.FlxCamera;
import flixel.FlxBasic;
import flixel.util.FlxSignal;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.audio.FunkinSound;
import funkin.graphics.FunkinSprite;
import funkin.data.freeplay.player.PlayerRegistry;
import funkin.data.freeplay.player.PlayerData.PlayerFreeplayDJData;
import funkin.util.assets.FlxAnimationUtil;
import funkin.modding.events.ScriptEvent;
import funkin.modding.IScriptedClass.IFreeplayScriptedClass;
import funkin.modding.events.ScriptEvent;
enum FreeplayDJState
{
@@ -64,7 +59,7 @@ enum FreeplayDJState
}
@:nullSafety
class BaseFreeplayDJ extends FlxAtlasSprite implements IFreeplayScriptedClass
class BaseFreeplayDJ extends FunkinSprite implements IFreeplayScriptedClass
{
public var IDLE_EGG_PERIOD:Float = 60.0;
public var IDLE_CARTOON_PERIOD:Float = 120.0;
@@ -96,7 +91,7 @@ class BaseFreeplayDJ extends FlxAtlasSprite implements IFreeplayScriptedClass
final playableChar = PlayerRegistry.instance.fetchEntry(characterId);
playableCharData = playableChar?.getFreeplayDJData();
super(x, y, null, null, false);
super(x, y);
}
function onFinishAnim(name:String):Void {}
@@ -109,7 +104,7 @@ class BaseFreeplayDJ extends FlxAtlasSprite implements IFreeplayScriptedClass
public function playFlashAnimation(id:String, Force:Bool = false, Reverse:Bool = false, Loop:Bool = false, Frame:Int = 0):Void
{
// playAnimationSimple(id, Force, Reverse, Loop, Frame);
applyAnimOffset();
applyAnimationOffset();
}
public function onPlayerAction():Void
@@ -213,32 +208,36 @@ class BaseFreeplayDJ extends FlxAtlasSprite implements IFreeplayScriptedClass
if (animPrefix != null) playFlashAnimation(animPrefix, true, false, false, playableCharData?.getFistPumpLoopBadStartFrame());
}
override public function listAnimations():Array<String>
function applyAnimationOffset():Void
{
// this is base dj dum dum, there's no animations
return [];
}
var animationName:String = getCurrentAnimation();
var animationOffsets:Null<Array<Float>> = playableCharData?.getAnimationOffsetsByPrefix(animationName);
var globalOffsets:Array<Float> = [this.x, this.y];
override public function getCurrentAnimation():String
{
// this is base dj dum dum, there's no animations
return "";
}
function applyAnimOffset()
{
var animName = getCurrentAnimation();
trace(animName);
var daOffset = playableCharData?.getAnimationOffsetsByPrefix(animName);
trace(daOffset);
if (daOffset != null)
if (animationOffsets != null)
{
final xValue = daOffset[0];
final yValue = daOffset[1];
offset.set(xValue, yValue);
var finalOffsetX:Float = 0;
var finalOffsetY:Float = 0;
if (this.applyStageMatrix)
{
finalOffsetX = animationOffsets[0];
finalOffsetY = animationOffsets[1];
}
else
{
finalOffsetX = globalOffsets[0] - animationOffsets[0] - (FreeplayState.CUTOUT_WIDTH * FreeplayState.DJ_POS_MULTI);
finalOffsetY = globalOffsets[1] - animationOffsets[1];
}
trace('Successfully applied offset ($animationName): ' + animationOffsets[0] + ', ' + animationOffsets[1]);
offset.set(finalOffsetX, finalOffsetY);
}
else
{
trace('No offset found ($animationName), defaulting to: 0, 0');
offset.set(0, 0);
}
}
public function onScriptEvent(event:ScriptEvent) {}
@@ -299,344 +298,3 @@ class BaseFreeplayDJ extends FlxAtlasSprite implements IFreeplayScriptedClass
*/
public function onFreeplayClose(event:FreeplayScriptEvent):Void {}
}
// Class for all non-atalas DJ's
class FlixelFramedFreeplayDJ extends BaseFreeplayDJ
{
public function new(x:Float, y:Float, characterId:String)
{
super(x, y, characterId);
loadFrames();
loadAnimations();
animation.onFinish.add(onFinishAnim);
animation.onLoop.add(onFinishAnim);
// animation.onFrameChange.add((name, num, index) -> trace('name:$name, num:$num, index:$index'));
}
public function loadFrames():Void
{
trace("OVERRIDE ME");
trace("LOADING FRAMES FUNC");
}
public function loadAnimations():Void
{
trace('[SPARROWCHAR] Loading ${playableCharData.getAnimationsList().length} animations for ${characterId}');
FlxAnimationUtil.addAtlasAnimations(this, playableCharData.getAnimationsList());
var animNames = this.animation.getNameList();
trace('[SPARROWCHAR] Successfully loaded ${animNames.length} animations for ${characterId}');
}
override function applyAnimOffset()
{
var animName = getCurrentAnimation();
trace(animName);
var daOffset = playableCharData?.getAnimationOffsets(animName);
trace(daOffset);
if (daOffset != null)
{
final xValue = daOffset[0];
final yValue = daOffset[1];
offset.set(xValue, yValue);
}
else
offset.set(0, 0);
}
override public function playFlashAnimation(id:String, Force:Bool = false, Reverse:Bool = false, Loop:Bool = false, Frame:Int = 0):Void
{
// No LOOP logic, because default Flixel's FlxAnimationController can normaly work with LOOPED animations
animation.play(id, Force, Reverse, Frame);
applyAnimOffset();
}
override public function listAnimations():Array<String>
{
return animation.getNameList() ?? [];
}
override public function getCurrentAnimation():String
{
return animation?.curAnim?.name ?? "";
}
override function updateAnimation(elapsed:Float):Void
{
animation.update(elapsed);
}
override public function hasAnimation(anim:String):Bool
{
return listAnimations().contains(anim);
}
override public function toCharSelect():Void
{
if (hasAnimation('charSelect'))
{
currentState = CharSelect;
playFlashAnimation('charSelect', true, false, false, 0);
}
else
{
FlxG.log.warn("Freeplay character does not have 'charSelect' animation!");
currentState = Confirm;
// Call this immediately; otherwise, we get locked out of Character Select.
onCharSelectComplete();
}
}
override public function fistPumpIntro():Void
{
// We really don't want to play anything but the new character animation here.
if (PlayerRegistry.instance.hasNewCharacter())
{
currentState = NewUnlock;
return;
}
currentState = FistPumpIntro;
playFlashAnimation('fistPumpIntro', true, false, false);
}
override public function fistPump():Void
{
// We really don't want to play anything but the new character animation here.
if (PlayerRegistry.instance.hasNewCharacter())
{
currentState = NewUnlock;
return;
}
currentState = FistPump;
playFlashAnimation('fistPump', true, false, false);
}
override public function fistPumpLossIntro():Void
{
// We really don't want to play anything but the new character animation here.
if (PlayerRegistry.instance.hasNewCharacter())
{
currentState = NewUnlock;
return;
}
currentState = FistPumpIntro;
playFlashAnimation('lossIntro', true, false, false);
}
override public function fistPumpLoss():Void
{
// We really don't want to play anything but the new character animation here.
if (PlayerRegistry.instance.hasNewCharacter())
{
currentState = NewUnlock;
return;
}
currentState = FistPump;
playFlashAnimation('loss', true, false, false);
}
public override function update(elapsed:Float):Void
{
switch (currentState)
{
case Intro:
// Play the intro animation then leave this state immediately.
if (hasAnimation('intro') && (getCurrentAnimation() != 'intro' || this.animation.finished)) playFlashAnimation('intro', true);
timeIdling = 0;
case Idle:
// We are in this state the majority of the time.
if (hasAnimation('idle') && getCurrentAnimation() != 'idle') playFlashAnimation('idle', true, false, true);
timeIdling += elapsed;
case NewUnlock:
if (!hasAnimation('newUnlock'))
{
currentState = Idle;
}
if (hasAnimation('newUnlock') && getCurrentAnimation() != 'newUnlock')
{
playFlashAnimation('newUnlock', true, false, true);
}
case Confirm:
if (hasAnimation('confirm') && getCurrentAnimation() != 'confirm') playFlashAnimation('confirm', false);
timeIdling = 0;
case FistPumpIntro:
// I shit my self - PurSnake
case FistPump:
// Twice
case IdleEasterEgg:
if (hasAnimation('idleEasterEgg') && getCurrentAnimation() != 'idleEasterEgg')
{
onIdleEasterEgg.dispatch();
playFlashAnimation('idleEasterEgg', false);
seenIdleEasterEgg = true;
}
timeIdling = 0;
case Cartoon:
if (!hasAnimation('cartoon'))
{
currentState = IdleEasterEgg;
}
else
{
if (getCurrentAnimation() != 'cartoon') playFlashAnimation('cartoon', true);
timeIdling = 0;
}
default:
// I shit myself.
}
super.update(elapsed);
}
override function onFinishAnim(name:String):Void
{
if (name == 'intro')
{
if (PlayerRegistry.instance.hasNewCharacter())
{
currentState = NewUnlock;
}
else
{
currentState = Idle;
}
onIntroDone.dispatch();
}
else if (name == 'idle')
{
// trace('Finished idle')
if (timeIdling >= IDLE_EGG_PERIOD && !seenIdleEasterEgg)
{
currentState = IdleEasterEgg;
}
else if (timeIdling >= IDLE_CARTOON_PERIOD)
{
currentState = Cartoon;
}
}
else if (name == 'confirm')
{
// trace('Finished confirm');
}
else if (name == 'fistPump')
{
// trace('Finished fist pump');
currentState = Idle;
}
else if (name == 'idleEasterEgg')
{
// trace('Finished spook');
currentState = Idle;
}
else if (name == 'loss')
{
// trace('Finished loss reaction');
currentState = Idle;
}
else if (name == 'cartoon')
{
// trace('Finished cartoon');
// var frame:Int = FlxG.random.bool(33) ? (playableCharData?.getCartoonLoopBlinkFrame() ?? 0) : (playableCharData?.getCartoonLoopFrame() ?? 0);
// Character switches channels when the video ends, or at a 10% chance each time his idle loops.
/*if (FlxG.random.bool(5))
{
frame = playableCharData?.getCartoonChannelChangeFrame() ?? 0;
// boyfriend switches channel code?
// Transefer into bf.hxc in scripts/freeplay/dj
// runTvLogic();
}
trace('Replay idle: ${frame}'); */
playFlashAnimation('cartoon', true, false, false);
// YOU BETTER REDO THIS IN YOUR SCRIPT WITH DIFFERENT ANIMATIONS
// trace('Finished confirm');
}
else if (name == 'newUnlock')
{
// Animation should loop.
}
else if (name == 'charSelect')
{
onCharSelectComplete();
}
else
{
trace('Finished ${name}');
}
}
/// Draw - Logic
override public function draw():Void
{
checkEmptyFrame();
if (alpha == 0 || _frame.type == FlxFrameType.EMPTY) return;
if (dirty) // rarely
calcFrame(useFramePixels);
for (camera in getCamerasLegacy())
{
if (!camera.visible || !camera.exists || !isOnScreen(camera)) continue;
if (isSimpleRender(camera)) drawSimple(camera);
else
drawComplex(camera);
#if FLX_DEBUG
FlxBasic.visibleCount++;
#end
}
#if FLX_DEBUG
if (FlxG.debugger.drawDebug) drawDebug();
#end
}
@:noCompletion
override function drawSimple(camera:FlxCamera):Void
{
getScreenPosition(_point, camera).subtract(offset);
if (isPixelPerfectRender(camera)) _point.floor();
_point.copyTo(_flashPoint);
camera.copyPixels(_frame, framePixels, _flashRect, _flashPoint, colorTransform, blend, antialiasing);
}
@:noCompletion
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 (bakedRotationAngle <= 0)
{
updateTrig();
if (angle != 0) _matrix.rotateWithTrig(_cosAngle, _sinAngle);
}
getScreenPosition(_point, camera).subtract(offset);
_point.add(origin.x, origin.y);
_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);
}
}
@@ -2,10 +2,8 @@ package funkin.ui.freeplay.dj;
import flixel.graphics.frames.FlxFramesCollection;
import flixel.graphics.frames.FlxAtlasFrames;
import funkin.util.assets.FlxAnimationUtil;
import funkin.data.freeplay.player.PlayerRegistry;
import flixel.graphics.frames.FlxFrame;
import flixel.FlxCamera;
import flixel.FlxBasic;
/**
* A script that can be tied to a MultiSparrowFreeplayDJ.
@@ -14,14 +12,27 @@ import flixel.FlxBasic;
@:hscriptClass
class ScriptedMultiSparrowFreeplayDJ extends MultiSparrowFreeplayDJ implements polymod.hscript.HScriptedClass {}
class MultiSparrowFreeplayDJ extends BaseFreeplayDJ.FlixelFramedFreeplayDJ
/**
* For some Freeplay DJs which use Sparrow atlases, the spritesheets need to be split
* into multiple files. This Freeplay DJ renderer concatenates these together into a single sprite.
*
* BaseFreeplayDJ has game logic, MultiSparrowFreeplayDJ has only rendering logic.
* KEEP THEM SEPARATE!
*/
class MultiSparrowFreeplayDJ extends BaseFreeplayDJ
{
public function new(x:Float, y:Float, characterId:String)
{
super(x, y, characterId);
loadFrames();
loadAnimations();
animation.onFinish.add(onFinishAnim);
animation.onLoop.add(onFinishAnim);
}
override public function loadFrames():Void
public function loadFrames():Void
{
trace('Loading assets for Multi-Sparrow character "${characterId}"', flixel.util.FlxColor.fromString("#89CFF0"));
@@ -57,4 +68,198 @@ class MultiSparrowFreeplayDJ extends BaseFreeplayDJ.FlixelFramedFreeplayDJ
}
this.frames = texture;
}
public function loadAnimations():Void
{
trace('[MULTISPARROWDJ] Loading ${playableCharData.getAnimationsList().length} animations for ${characterId}');
FlxAnimationUtil.addAtlasAnimations(this, playableCharData.getAnimationsList());
var animationList:Array<String> = this.animation.getNameList();
trace('[MULTISPARROWDJ] Successfully loaded ${animationList.length} animations for ${characterId}');
}
public override function update(elapsed:Float):Void
{
switch (currentState)
{
case Intro:
// Play the intro animation then leave this state immediately.
var animPrefix = playableCharData?.getAnimationPrefix('intro');
if (animPrefix != null && (getCurrentAnimation() != animPrefix))
{
playFlashAnimation(animPrefix, true);
}
timeIdling = 0;
case Idle:
// We are in this state the majority of the time.
var animPrefix = playableCharData?.getAnimationPrefix('idle');
if (animPrefix != null && getCurrentAnimation() != animPrefix)
{
playFlashAnimation(animPrefix, true, false, true);
}
timeIdling += elapsed;
case NewUnlock:
var animPrefix = playableCharData?.getAnimationPrefix('newUnlock');
if (animPrefix != null && !hasAnimation(animPrefix))
{
currentState = Idle;
}
if (animPrefix != null && getCurrentAnimation() != animPrefix)
{
playFlashAnimation(animPrefix, true, false, true);
}
case Confirm:
var animPrefix = playableCharData?.getAnimationPrefix('confirm');
if (animPrefix != null && getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, false);
timeIdling = 0;
case FistPumpIntro:
var animPrefixA = playableCharData?.getAnimationPrefix('fistPump');
var animPrefixB = playableCharData?.getAnimationPrefix('loss');
if (getCurrentAnimation() == animPrefixA)
{
var endFrame = playableCharData?.getFistPumpIntroEndFrame() ?? 0;
if (endFrame > -1 && anim.curAnim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixA, true, false, false, playableCharData?.getFistPumpIntroStartFrame());
}
}
else if (getCurrentAnimation() == animPrefixB)
{
var endFrame = playableCharData?.getFistPumpIntroBadEndFrame() ?? 0;
if (endFrame > -1 && anim.curAnim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixB, true, false, false, playableCharData?.getFistPumpIntroBadStartFrame());
}
}
else
{
FlxG.log.warn("Unrecognized animation in FistPumpIntro: " + getCurrentAnimation());
}
case FistPump:
var animPrefixA = playableCharData?.getAnimationPrefix('fistPump');
var animPrefixB = playableCharData?.getAnimationPrefix('loss');
if (getCurrentAnimation() == animPrefixA)
{
var endFrame = playableCharData?.getFistPumpLoopEndFrame() ?? 0;
if (endFrame > -1 && anim.curAnim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixA, true, false, false, playableCharData?.getFistPumpLoopStartFrame());
}
}
else if (getCurrentAnimation() == animPrefixB)
{
var endFrame = playableCharData?.getFistPumpLoopBadEndFrame() ?? 0;
if (endFrame > -1 && anim.curAnim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixB, true, false, false, playableCharData?.getFistPumpLoopBadStartFrame());
}
}
else
{
FlxG.log.warn("Unrecognized animation in FistPump: " + getCurrentAnimation());
}
case IdleEasterEgg:
var animPrefix = playableCharData?.getAnimationPrefix('idleEasterEgg');
if (animPrefix != null && getCurrentAnimation() != animPrefix)
{
onIdleEasterEgg.dispatch();
playFlashAnimation(animPrefix, false);
seenIdleEasterEgg = true;
}
timeIdling = 0;
case Cartoon:
var animPrefix = playableCharData?.getAnimationPrefix('cartoon');
if (animPrefix == null)
{
currentState = IdleEasterEgg;
}
else
{
if (getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, true);
timeIdling = 0;
}
default:
// I shit myself.
}
// Call the superclass function AFTER updating the current state and playing the next animation.
// This ensures that flixel-animate starts rendering the new animation immediately.
super.update(elapsed);
}
override function onFinishAnim(name:String):Void
{
if (name == playableCharData?.getAnimationPrefix('intro'))
{
if (PlayerRegistry.instance.hasNewCharacter())
{
currentState = NewUnlock;
}
else
{
currentState = Idle;
}
onIntroDone.dispatch();
}
else if (name == playableCharData?.getAnimationPrefix('idle'))
{
// trace('Finished idle');
if (timeIdling >= IDLE_EGG_PERIOD && !seenIdleEasterEgg)
{
currentState = IdleEasterEgg;
}
else if (timeIdling >= IDLE_CARTOON_PERIOD)
{
currentState = Cartoon;
}
}
else if (name == playableCharData?.getAnimationPrefix('confirm'))
{
// trace('Finished confirm');
}
else if (name == playableCharData?.getAnimationPrefix('fistPump'))
{
// trace('Finished fist pump');
currentState = Idle;
}
else if (name == playableCharData?.getAnimationPrefix('idleEasterEgg'))
{
// trace('Finished spook');
currentState = Idle;
}
else if (name == playableCharData?.getAnimationPrefix('loss'))
{
// trace('Finished loss reaction');
currentState = Idle;
}
else if (name == playableCharData?.getAnimationPrefix('newUnlock'))
{
// Animation should loop.
}
else if (name == playableCharData?.getAnimationPrefix('charSelect'))
{
onCharSelectComplete();
}
else
{
trace('Finished ${name}');
}
}
override public function playFlashAnimation(id:String, Force:Bool = false, Reverse:Bool = false, Loop:Bool = false, Frame:Int = 0):Void
{
animation.play(id, Force, Reverse, Frame);
if (animation.curAnim != null)
{
animation.curAnim.looped = Loop;
}
applyAnimationOffset();
}
}
@@ -1,10 +1,8 @@
package funkin.ui.freeplay.dj;
import flixel.graphics.frames.FlxFramesCollection;
import funkin.util.assets.FlxAnimationUtil;
import funkin.data.freeplay.player.PlayerRegistry;
import flixel.graphics.frames.FlxFrame;
import flixel.FlxCamera;
import flixel.FlxBasic;
/**
* A script that can be tied to a PackerFreeplayDJ.
@@ -13,21 +11,228 @@ import flixel.FlxBasic;
@:hscriptClass
class ScriptedPackerFreeplayDJ extends PackerFreeplayDJ implements polymod.hscript.HScriptedClass {}
class PackerFreeplayDJ extends BaseFreeplayDJ.FlixelFramedFreeplayDJ
/**
* A PackerFreeplayDJ is a Freeplay DJ which is rendered by
* displaying an animation derived from a Packer spritesheet file.
*
* BaseFreeplayDJ has game logic, PackerFreeplayDJ has only rendering logic.
* KEEP THEM SEPARATE!
*/
class PackerFreeplayDJ extends BaseFreeplayDJ
{
public function new(x:Float, y:Float, characterId:String)
{
super(x, y, characterId);
loadFrames();
loadAnimations();
animation.onFinish.add(onFinishAnim);
animation.onLoop.add(onFinishAnim);
}
override public function loadFrames()
public function loadFrames():Void
{
final tex:FlxFramesCollection = Paths.getPackerAtlas(playableCharData.getAssetPath());
if (tex == null)
{
trace('Could not load Packer sprite: ${playableCharData.getAssetPath()}');
trace('Could not load sparrow sprite: ' + playableCharData.getAssetPath());
return;
}
this.frames = tex;
}
public function loadAnimations():Void
{
trace('[PACKERDJ] Loading ${playableCharData.getAnimationsList().length} animations for ${characterId}');
FlxAnimationUtil.addAtlasAnimations(this, playableCharData.getAnimationsList());
var animationList:Array<String> = this.animation.getNameList();
trace('[PACKERDJ] Successfully loaded ${animationList.length} animations for ${characterId}');
}
public override function update(elapsed:Float):Void
{
switch (currentState)
{
case Intro:
// Play the intro animation then leave this state immediately.
var animPrefix = playableCharData?.getAnimationPrefix('intro');
if (animPrefix != null && (getCurrentAnimation() != animPrefix))
{
playFlashAnimation(animPrefix, true);
}
timeIdling = 0;
case Idle:
// We are in this state the majority of the time.
var animPrefix = playableCharData?.getAnimationPrefix('idle');
if (animPrefix != null && getCurrentAnimation() != animPrefix)
{
playFlashAnimation(animPrefix, true, false, true);
}
timeIdling += elapsed;
case NewUnlock:
var animPrefix = playableCharData?.getAnimationPrefix('newUnlock');
if (animPrefix != null && !hasAnimation(animPrefix))
{
currentState = Idle;
}
if (animPrefix != null && getCurrentAnimation() != animPrefix)
{
playFlashAnimation(animPrefix, true, false, true);
}
case Confirm:
var animPrefix = playableCharData?.getAnimationPrefix('confirm');
if (animPrefix != null && getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, false);
timeIdling = 0;
case FistPumpIntro:
var animPrefixA = playableCharData?.getAnimationPrefix('fistPump');
var animPrefixB = playableCharData?.getAnimationPrefix('loss');
if (getCurrentAnimation() == animPrefixA)
{
var endFrame = playableCharData?.getFistPumpIntroEndFrame() ?? 0;
if (endFrame > -1 && anim.curAnim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixA, true, false, false, playableCharData?.getFistPumpIntroStartFrame());
}
}
else if (getCurrentAnimation() == animPrefixB)
{
var endFrame = playableCharData?.getFistPumpIntroBadEndFrame() ?? 0;
if (endFrame > -1 && anim.curAnim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixB, true, false, false, playableCharData?.getFistPumpIntroBadStartFrame());
}
}
else
{
FlxG.log.warn("Unrecognized animation in FistPumpIntro: " + getCurrentAnimation());
}
case FistPump:
var animPrefixA = playableCharData?.getAnimationPrefix('fistPump');
var animPrefixB = playableCharData?.getAnimationPrefix('loss');
if (getCurrentAnimation() == animPrefixA)
{
var endFrame = playableCharData?.getFistPumpLoopEndFrame() ?? 0;
if (endFrame > -1 && anim.curAnim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixA, true, false, false, playableCharData?.getFistPumpLoopStartFrame());
}
}
else if (getCurrentAnimation() == animPrefixB)
{
var endFrame = playableCharData?.getFistPumpLoopBadEndFrame() ?? 0;
if (endFrame > -1 && anim.curAnim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixB, true, false, false, playableCharData?.getFistPumpLoopBadStartFrame());
}
}
else
{
FlxG.log.warn("Unrecognized animation in FistPump: " + getCurrentAnimation());
}
case IdleEasterEgg:
var animPrefix = playableCharData?.getAnimationPrefix('idleEasterEgg');
if (animPrefix != null && getCurrentAnimation() != animPrefix)
{
onIdleEasterEgg.dispatch();
playFlashAnimation(animPrefix, false);
seenIdleEasterEgg = true;
}
timeIdling = 0;
case Cartoon:
var animPrefix = playableCharData?.getAnimationPrefix('cartoon');
if (animPrefix == null)
{
currentState = IdleEasterEgg;
}
else
{
if (getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, true);
timeIdling = 0;
}
default:
// I shit myself.
}
// Call the superclass function AFTER updating the current state and playing the next animation.
// This ensures that flixel-animate starts rendering the new animation immediately.
super.update(elapsed);
}
override function onFinishAnim(name:String):Void
{
if (name == playableCharData?.getAnimationPrefix('intro'))
{
if (PlayerRegistry.instance.hasNewCharacter())
{
currentState = NewUnlock;
}
else
{
currentState = Idle;
}
onIntroDone.dispatch();
}
else if (name == playableCharData?.getAnimationPrefix('idle'))
{
// trace('Finished idle');
if (timeIdling >= IDLE_EGG_PERIOD && !seenIdleEasterEgg)
{
currentState = IdleEasterEgg;
}
else if (timeIdling >= IDLE_CARTOON_PERIOD)
{
currentState = Cartoon;
}
}
else if (name == playableCharData?.getAnimationPrefix('confirm'))
{
// trace('Finished confirm');
}
else if (name == playableCharData?.getAnimationPrefix('fistPump'))
{
// trace('Finished fist pump');
currentState = Idle;
}
else if (name == playableCharData?.getAnimationPrefix('idleEasterEgg'))
{
// trace('Finished spook');
currentState = Idle;
}
else if (name == playableCharData?.getAnimationPrefix('loss'))
{
// trace('Finished loss reaction');
currentState = Idle;
}
else if (name == playableCharData?.getAnimationPrefix('newUnlock'))
{
// Animation should loop.
}
else if (name == playableCharData?.getAnimationPrefix('charSelect'))
{
onCharSelectComplete();
}
else
{
trace('Finished ${name}');
}
}
override public function playFlashAnimation(id:String, Force:Bool = false, Reverse:Bool = false, Loop:Bool = false, Frame:Int = 0):Void
{
animation.play(id, Force, Reverse, Frame);
if (animation.curAnim != null)
{
animation.curAnim.looped = Loop;
}
applyAnimationOffset();
}
}
@@ -1,10 +1,8 @@
package funkin.ui.freeplay.dj;
import flixel.graphics.frames.FlxFramesCollection;
import funkin.util.assets.FlxAnimationUtil;
import funkin.data.freeplay.player.PlayerRegistry;
import flixel.graphics.frames.FlxFrame;
import flixel.FlxCamera;
import flixel.FlxBasic;
/**
* A script that can be tied to a SparrowFreeplayDJ.
@@ -13,21 +11,228 @@ import flixel.FlxBasic;
@:hscriptClass
class ScriptedSparrowFreeplayDJ extends SparrowFreeplayDJ implements polymod.hscript.HScriptedClass {}
class SparrowFreeplayDJ extends BaseFreeplayDJ.FlixelFramedFreeplayDJ
/**
* A SparrowFreeplayDJ is a Freeplay DJ which is rendered by
* displaying an animation derived from a SparrowV2 atlas spritesheet file.
*
* BaseFreeplayDJ has game logic, SparrowFreeplayDJ has only rendering logic.
* KEEP THEM SEPARATE!
*/
class SparrowFreeplayDJ extends BaseFreeplayDJ
{
public function new(x:Float, y:Float, characterId:String)
{
super(x, y, characterId);
loadFrames();
loadAnimations();
animation.onFinish.add(onFinishAnim);
animation.onLoop.add(onFinishAnim);
}
override public function loadFrames()
public function loadFrames():Void
{
final tex:FlxFramesCollection = Paths.getSparrowAtlas(playableCharData.getAssetPath());
if (tex == null)
{
trace('Could not load Sparrow sprite: ' + playableCharData.getAssetPath());
trace('Could not load sparrow sprite: ' + playableCharData.getAssetPath());
return;
}
this.frames = tex;
}
public function loadAnimations():Void
{
trace('[SPARROWDJ] Loading ${playableCharData.getAnimationsList().length} animations for ${characterId}');
FlxAnimationUtil.addAtlasAnimations(this, playableCharData.getAnimationsList());
var animationList:Array<String> = this.animation.getNameList();
trace('[SPARROWDJ] Successfully loaded ${animationList.length} animations for ${characterId}');
}
public override function update(elapsed:Float):Void
{
switch (currentState)
{
case Intro:
// Play the intro animation then leave this state immediately.
var animPrefix = playableCharData?.getAnimationPrefix('intro');
if (animPrefix != null && (getCurrentAnimation() != animPrefix))
{
playFlashAnimation(animPrefix, true);
}
timeIdling = 0;
case Idle:
// We are in this state the majority of the time.
var animPrefix = playableCharData?.getAnimationPrefix('idle');
if (animPrefix != null && getCurrentAnimation() != animPrefix)
{
playFlashAnimation(animPrefix, true, false, true);
}
timeIdling += elapsed;
case NewUnlock:
var animPrefix = playableCharData?.getAnimationPrefix('newUnlock');
if (animPrefix != null && !hasAnimation(animPrefix))
{
currentState = Idle;
}
if (animPrefix != null && getCurrentAnimation() != animPrefix)
{
playFlashAnimation(animPrefix, true, false, true);
}
case Confirm:
var animPrefix = playableCharData?.getAnimationPrefix('confirm');
if (animPrefix != null && getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, false);
timeIdling = 0;
case FistPumpIntro:
var animPrefixA = playableCharData?.getAnimationPrefix('fistPump');
var animPrefixB = playableCharData?.getAnimationPrefix('loss');
if (getCurrentAnimation() == animPrefixA)
{
var endFrame = playableCharData?.getFistPumpIntroEndFrame() ?? 0;
if (endFrame > -1 && anim.curAnim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixA, true, false, false, playableCharData?.getFistPumpIntroStartFrame());
}
}
else if (getCurrentAnimation() == animPrefixB)
{
var endFrame = playableCharData?.getFistPumpIntroBadEndFrame() ?? 0;
if (endFrame > -1 && anim.curAnim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixB, true, false, false, playableCharData?.getFistPumpIntroBadStartFrame());
}
}
else
{
FlxG.log.warn("Unrecognized animation in FistPumpIntro: " + getCurrentAnimation());
}
case FistPump:
var animPrefixA = playableCharData?.getAnimationPrefix('fistPump');
var animPrefixB = playableCharData?.getAnimationPrefix('loss');
if (getCurrentAnimation() == animPrefixA)
{
var endFrame = playableCharData?.getFistPumpLoopEndFrame() ?? 0;
if (endFrame > -1 && anim.curAnim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixA, true, false, false, playableCharData?.getFistPumpLoopStartFrame());
}
}
else if (getCurrentAnimation() == animPrefixB)
{
var endFrame = playableCharData?.getFistPumpLoopBadEndFrame() ?? 0;
if (endFrame > -1 && anim.curAnim.curFrame >= endFrame)
{
playFlashAnimation(animPrefixB, true, false, false, playableCharData?.getFistPumpLoopBadStartFrame());
}
}
else
{
FlxG.log.warn("Unrecognized animation in FistPump: " + getCurrentAnimation());
}
case IdleEasterEgg:
var animPrefix = playableCharData?.getAnimationPrefix('idleEasterEgg');
if (animPrefix != null && getCurrentAnimation() != animPrefix)
{
onIdleEasterEgg.dispatch();
playFlashAnimation(animPrefix, false);
seenIdleEasterEgg = true;
}
timeIdling = 0;
case Cartoon:
var animPrefix = playableCharData?.getAnimationPrefix('cartoon');
if (animPrefix == null)
{
currentState = IdleEasterEgg;
}
else
{
if (getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, true);
timeIdling = 0;
}
default:
// I shit myself.
}
// Call the superclass function AFTER updating the current state and playing the next animation.
// This ensures that flixel-animate starts rendering the new animation immediately.
super.update(elapsed);
}
override function onFinishAnim(name:String):Void
{
if (name == playableCharData?.getAnimationPrefix('intro'))
{
if (PlayerRegistry.instance.hasNewCharacter())
{
currentState = NewUnlock;
}
else
{
currentState = Idle;
}
onIntroDone.dispatch();
}
else if (name == playableCharData?.getAnimationPrefix('idle'))
{
// trace('Finished idle');
if (timeIdling >= IDLE_EGG_PERIOD && !seenIdleEasterEgg)
{
currentState = IdleEasterEgg;
}
else if (timeIdling >= IDLE_CARTOON_PERIOD)
{
currentState = Cartoon;
}
}
else if (name == playableCharData?.getAnimationPrefix('confirm'))
{
// trace('Finished confirm');
}
else if (name == playableCharData?.getAnimationPrefix('fistPump'))
{
// trace('Finished fist pump');
currentState = Idle;
}
else if (name == playableCharData?.getAnimationPrefix('idleEasterEgg'))
{
// trace('Finished spook');
currentState = Idle;
}
else if (name == playableCharData?.getAnimationPrefix('loss'))
{
// trace('Finished loss reaction');
currentState = Idle;
}
else if (name == playableCharData?.getAnimationPrefix('newUnlock'))
{
// Animation should loop.
}
else if (name == playableCharData?.getAnimationPrefix('charSelect'))
{
onCharSelectComplete();
}
else
{
trace('Finished ${name}');
}
}
override public function playFlashAnimation(id:String, Force:Bool = false, Reverse:Bool = false, Loop:Bool = false, Frame:Int = 0):Void
{
animation.play(id, Force, Reverse, Frame);
if (animation.curAnim != null)
{
animation.curAnim.looped = Loop;
}
applyAnimationOffset();
}
}
+53 -8
View File
@@ -1,6 +1,7 @@
package funkin.util.assets;
import flixel.FlxSprite;
import funkin.graphics.FunkinSprite;
import flixel.graphics.frames.FlxFramesCollection;
import funkin.data.animation.AnimationData;
@@ -13,22 +14,55 @@ class FlxAnimationUtil
public static function addAtlasAnimation(target:FlxSprite, anim:AnimationData):Void
{
if (anim.prefix == null) return;
var frameRate = anim.frameRate == null ? 24 : anim.frameRate;
var looped = anim.looped == null ? false : anim.looped;
var flipX = anim.flipX == null ? false : anim.flipX;
var flipY = anim.flipY == null ? false : anim.flipY;
var frameRate:Int = anim.frameRate ?? 24;
var looped:Bool = anim.looped ?? false;
var flipX:Bool = anim.flipX ?? false;
var flipY:Bool = anim.flipY ?? false;
if (anim.frameIndices != null && anim.frameIndices.length > 0)
{
// trace('addByIndices(${anim.name}, ${anim.prefix}, ${anim.frameIndices}, ${frameRate}, ${looped}, ${flipX}, ${flipY})');
target.animation.addByIndices(anim.name, anim.prefix, anim.frameIndices, '', frameRate, looped, flipX, flipY);
// trace('RESULT:${target.animation.getAnimationList()}');
}
else
{
// trace('addByPrefix(${anim.name}, ${anim.prefix}, ${frameRate}, ${looped}, ${flipX}, ${flipY})');
target.animation.addByPrefix(anim.name, anim.prefix, frameRate, looped, flipX, flipY);
// trace('RESULT:${target.animation.getAnimationList()}');
}
}
/**
* Properly adds an animation to a texture atlas sprite based on the provided animation data.
*/
public static function addTextureAtlasAnimation(target:FunkinSprite, anim:AnimationData):Void
{
if (!target.isAnimate) return;
if (anim.prefix == null) return;
var frameRate:Int = anim.frameRate ?? 24;
var looped:Bool = anim.looped ?? false;
var flipX:Bool = anim.flipX ?? false;
var flipY:Bool = anim.flipY ?? false;
var animType:String = anim.animType ?? "framelabel";
if (anim.frameIndices != null && anim.frameIndices.length > 0)
{
switch (animType)
{
case "framelabel":
target.anim.addByFrameLabelIndices(anim.name, anim.prefix, anim.frameIndices, frameRate, looped, flipX, flipY);
case "symbol":
target.anim.addBySymbolIndices(anim.name, anim.prefix, anim.frameIndices, frameRate, looped, flipX, flipY);
}
}
else
{
switch (animType)
{
case "framelabel":
target.anim.addByFrameLabel(anim.name, anim.prefix, frameRate, looped, flipX, flipY);
case "symbol":
target.anim.addBySymbol(anim.name, anim.prefix, frameRate, looped, flipX, flipY);
}
}
}
@@ -43,6 +77,17 @@ class FlxAnimationUtil
}
}
/**
* Properly adds multiple animations to a texture atlas sprite based on the provided animation data.
*/
public static function addTextureAtlasAnimations(target:FunkinSprite, animations:Array<AnimationData>):Void
{
for (anim in animations)
{
addTextureAtlasAnimation(target, anim);
}
}
/**
* Combine two FlxFramesCollection objects into one.
* @param a The first FlxFramesCollection
@@ -6,24 +6,28 @@ import flixel.group.FlxContainer.FlxTypedContainer;
import flixel.text.FlxText;
import funkin.audio.FunkinSound;
import flixel.graphics.FlxGraphic;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.FunkinSprite;
import flixel.math.FlxRect;
import funkin.api.newgrounds.Medals;
import funkin.util.macro.ConsoleMacro;
import funkin.ui.FullScreenScaleMode;
@:nullSafety
class NewgroundsMedalPlugin extends FlxTypedContainer<FlxBasic>
class NewgroundsMedalPlugin extends FlxTypedContainer<FlxBasic> implements ConsoleClass
{
var medal:FlxAtlasSprite;
public static var instance:Null<NewgroundsMedalPlugin> = null;
var medal:FunkinSprite;
var points:FlxText;
var name:FlxText;
public static var instance:Null<NewgroundsMedalPlugin> = null;
var moveText:Bool = false;
var medalQueue:Array<Void->Void> = [];
var tween:Bool = false;
var textSpeed:Float = 20;
var am:Float = 20;
var funcs:Array<Void->Void> = [];
final MEDAL_X = (FlxG.width - 250) * 0.5;
final MEDAL_Y = FlxG.height - 100;
public function new()
{
@@ -34,12 +38,15 @@ class NewgroundsMedalPlugin extends FlxTypedContainer<FlxBasic>
FlxG.console.registerClass(Medals);
#end
var ecs = (FlxG.width - 250) * 0.5;
var why = FlxG.height - 100;
FlxGraphic.defaultPersist = true;
medal = new FlxAtlasSprite(ecs, why, Paths.animateAtlas("ui/medal"));
points = new FlxText(171 + ecs, 10 + why, 50, 12, false);
medal = FunkinSprite.createTextureAtlas((MEDAL_X) + (FullScreenScaleMode.gameCutoutSize.x / 2), MEDAL_Y, "ui/medal",
{
swfMode: true,
filterQuality: HIGH
});
points = new FlxText((171 + MEDAL_X) + (FullScreenScaleMode.gameCutoutSize.x / 2), 17 + MEDAL_Y, 50, 12, false);
points.fieldHeight = 18;
points.systemFont = "Arial";
points.bold = true;
@@ -50,64 +57,73 @@ class NewgroundsMedalPlugin extends FlxTypedContainer<FlxBasic>
points.visible = false;
points.scrollFactor.set();
name = new FlxText(73 + ecs, 34 + why, 0, 26);
name = new FlxText((73 + MEDAL_X) + (FullScreenScaleMode.gameCutoutSize.x / 2), 37 + MEDAL_Y, 0, 26);
name.font = Paths.font("ShareTechMono-Regular.ttf");
name.letterSpacing = -2;
name.text = "Ono Boners Deluxe";
name.clipRect = FlxRect.get(0, 0, 164, 35.2);
name.visible = false;
name.scrollFactor.set();
medal.scrollFactor.set();
medal.anim.swfRender = true;
medal.visible = false;
var fr = medal.anim.curSymbol.timeline.get(0).get(0);
if (fr != null) fr.name = "START"; // woerkaround
// fr.add(() -> FunkinSound.playOnce(Paths.sound('NGFadeIn'), 1.));
medal.anim.getFrameLabel("show").add(function() {
points.visible = true;
name.visible = true;
if (name.width > name.clipRect.width)
medal.anim.onFrameLabel.add(function(label:String) {
switch (label)
{
// TODO: Remove this once FlxText.get_size deals with TextFormat's nullable size properly
@:nullSafety(Off)
am = (name.text.length * (name.size + 2) * 1.25) / name.clipRect.width * 10;
tween = true;
// FlxTimer.wait(0.3, () -> tween = true);
case "show":
points.visible = true;
name.visible = true;
if (name.width > name.clipRect.width)
{
@:nullSafety(Off)
textSpeed = (name.text.length * (name.size + 2) * 1.25) / name.clipRect.width * 10;
moveText = true;
}
case "fade":
FunkinSound.playOnce(Paths.sound('NGFadeOut'), 1.0);
case "hide":
points.visible = false;
name.visible = false;
moveText = false;
name.offset.x = 0;
name.clipRect.x = 0;
name.resetFrame();
}
});
// medal.anim.getFrameLabel("idle").add(() -> medal.anim.pause());
medal.anim.getFrameLabel("fade").add(() -> FunkinSound.playOnce(Paths.sound('NGFadeOut'), 1.));
medal.anim.getFrameLabel("hide").add(function() {
points.visible = false;
name.visible = false;
tween = false;
name.offset.x = 0;
name.clipRect.x = 0;
name.resetFrame();
medal.replaceFrameGraphic(3, null);
medal.anim.onFinish.add(function(name:String) {
medal.visible = false;
});
add(medal);
add(points);
add(name);
FlxGraphic.defaultPersist = false;
}
/**
* Update the positions of the medal atlas in case the resolution changes!
*/
function updatePositions():Void
{
medal.x = MEDAL_X + (FullScreenScaleMode.gameCutoutSize.x / 2);
points.x = (175 + MEDAL_X) + (FullScreenScaleMode.gameCutoutSize.x / 2);
name.x = (79 + MEDAL_X) + (FullScreenScaleMode.gameCutoutSize.x / 2);
}
override public function update(elapsed:Float)
{
super.update(elapsed);
if (tween)
if (moveText)
{
var val = am * elapsed;
name.offset.x += val;
name.clipRect.x += val;
var textX:Float = textSpeed * elapsed;
name.offset.x += textX;
name.clipRect.x += textX;
name.resetFrame();
}
}
@@ -120,10 +136,10 @@ class NewgroundsMedalPlugin extends FlxTypedContainer<FlxBasic>
// instance is defined above so there's no need to worry about null safety here
@:nullSafety(Off)
instance.medal.anim?.onComplete.add(function() {
if (instance.funcs.length > 0)
instance.medal.anim.onFinish.add(function(name:String) {
if (instance.medalQueue.length > 0)
{
instance.funcs.shift()();
instance.medalQueue.shift()();
}
});
}
@@ -131,22 +147,24 @@ class NewgroundsMedalPlugin extends FlxTypedContainer<FlxBasic>
public static function play(points:Int = 100, name:String = "I LOVE CUM I LOVE CUM I LOVE CUM I LOVE CUM", ?graphic:FlxGraphic)
{
if (instance == null) return;
var func = function() {
var playMedal:Void->Void = function() {
instance.points.visible = false;
instance.name.visible = false;
instance.points.text = Std.string(points);
instance.name.text = name;
instance.medal.visible = true;
instance.medal.replaceFrameGraphic(3, graphic);
instance.medal.anim.play(true);
instance.updatePositions();
FunkinSound.playOnce(Paths.sound('NGFadeIn'), 1.0);
instance.medal.anim.play("");
instance.medal.visible = true;
instance.medal.replaceSymbolGraphic("NGMEDAL", graphic);
}
if (!instance.medal.anim.isPlaying && instance.funcs.length == 0) func();
if (instance.medal.isAnimationFinished() && instance.medalQueue.length == 0) playMedal();
else
instance.funcs.push(func);
instance.medalQueue.push(playMedal);
}
}
#end