moved utils (update your scripts!!!!)

This commit is contained in:
Yoshman29
2023-01-11 14:21:04 +01:00
parent cda6268573
commit 6dd2854d2c
17 changed files with 30 additions and 24 deletions
+387
View File
@@ -0,0 +1,387 @@
package funkin.utils;
import funkin.system.Song;
import funkin.system.Conductor;
import flixel.system.FlxSoundGroup;
import animateatlas.AtlasFrameMaker;
import haxe.Json;
import funkin.menus.StoryMenuState.WeekData;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.system.FlxAssets.FlxGraphicAsset;
import flixel.graphics.FlxGraphic;
import haxe.io.Path;
import haxe.xml.Access;
import flixel.input.keyboard.FlxKey;
import lime.utils.Assets;
import flixel.animation.FlxAnimation;
import flixel.util.FlxColor;
import flixel.math.FlxMath;
import flixel.FlxG;
import flixel.FlxSprite;
import flixel.FlxObject;
import flixel.FlxCamera;
import flixel.util.FlxAxes;
import flixel.graphics.frames.FlxFrame;
import flixel.graphics.frames.FlxFramesCollection;
import flash.geom.ColorTransform;
using StringTools;
class CoolUtil
{
/*
* Returns `v` if not null, `defaultValue` otherwise.
* @param v The value
* @param defaultValue The default value
* @return The return value
*/
public static inline function getDefault<T>(v:Null<T>, defaultValue:T):Null<T> {
return (v == null || isNaN(v)) ? defaultValue : v;
}
public static function parseJson(assetPath:String) {
return Json.parse(Assets.getText(assetPath));
}
public inline static function parseJsonString(str:String)
return Json.parse(str);
public static inline function isNaN(v:Dynamic) {
if (v is Float || v is Int)
return Math.isNaN(cast(v, Float));
return false;
}
public static inline function last<T>(array:Array<T>):T {
return array[array.length - 1];
}
public static function setFieldDefault<T>(v:Dynamic, name:String, defaultValue:T):T {
if (Reflect.hasField(v, name)) {
var f:Null<Dynamic> = Reflect.field(v, name);
if (f != null)
return cast f;
}
Reflect.setField(v, name, defaultValue);
return defaultValue;
}
public static inline function addZeros(str:String, num:Int) {
while(str.length < num) str = '0${str}';
return str;
}
public static function getSizeString(size:Float):String {
var labels = ["B", "KB", "MB", "GB", "TB"];
var rSize:Float = size;
var label:Int = 0;
while(rSize > 1024 && label < labels.length-1) {
label++;
rSize /= 1024;
}
return '${Std.int(rSize) + "." + addZeros(Std.string(Std.int((rSize % 1) * 100)), 2)}${labels[label]}';
}
public static inline function fpsLerp(v1:Float, v2:Float, ratio:Float):Float {
return FlxMath.lerp(v1, v2, getFPSRatio(ratio));
}
/**
* Lerps from color1 into color2 (Shortcut to `FlxColor.interpolate`)
* @param color1 Color 1
* @param color2 Color 2
* @param ratio Ratio
* @param fpsSensitive Whenever the ratio should be case sentivive (adapted when game is running at 120 instead of 60)
*/
public static inline function lerpColor(color1:FlxColor, color2:FlxColor, ratio:Float, fpsSensitive:Bool = false) {
if (!fpsSensitive)
ratio = getFPSRatio(ratio);
return FlxColor.interpolate(color1, color2, ratio);
}
/**
* Modifies a lerp ratio based on current FPS to keep a stable speed on higher framerate.
* @param ratio Ratio
* @return FPS-Modified Ratio
*/
public static inline function getFPSRatio(ratio:Float):Float {
return FlxMath.bound(ratio * 60 * FlxG.elapsed, 0, 1);
}
/**
* Tries to get a color from a `Dynamic` variable.
* @param c `Dynamic` color.
* @return The result color, or `null` if invalid.
*/
public static function getColorFromDynamic(c:Dynamic):Null<FlxColor> {
// -1
if (c is Int) return c;
// -1.0
if (c is Float) return Std.int(c);
// "#FFFFFF"
if (c is String) return FlxColor.fromString(c);
// [255, 255, 255]
if (c is Array) {
var r:Int = 0;
var g:Int = 0;
var b:Int = 0;
var a:Int = 255;
var array:Array<Dynamic> = cast c;
for(k=>e in array) {
if (e is Int || e is Float) {
switch(k) {
case 0: r = Std.int(e);
case 1: g = Std.int(e);
case 2: b = Std.int(e);
case 3: a = Std.int(e);
}
}
}
return FlxColor.fromRGB(r, g, b, a);
}
return null;
}
public static function playMenuSong(fadeIn:Bool = false) {
if (FlxG.sound.music == null || !FlxG.sound.music.playing)
{
playMusic(Paths.music('freakyMenu'), 102, fadeIn ? 0 : 1);
if (fadeIn)
FlxG.sound.music.fadeIn(4, 0, 0.7);
}
}
public static function preloadCharacter(name:String, ?spriteName:String) {
if (name == null) return;
if (spriteName == null)
spriteName = name;
Assets.getText(Paths.xml('characters/$name'));
Paths.getFrames('characters/$spriteName');
}
public static function playMusic(path:String, DefaultBPM:Int = 102, Volume:Int = 1, Looped:Bool = true, ?Group:FlxSoundGroup) {
Conductor.reset();
FlxG.sound.playMusic(path, Volume, Looped, Group);
var infoPath = '${Path.withoutExtension(path)}.ini';
if (Assets.exists(infoPath)) {
var musicInfo = IniUtil.parseAsset(infoPath, [
"BPM" => null
]);
var parsedBPM:Null<Float> = Std.parseFloat(musicInfo["BPM"]);
Conductor.changeBPM(parsedBPM == null ? DefaultBPM : parsedBPM);
} else
Conductor.changeBPM(DefaultBPM);
}
/**
* Plays a specified Menu SFX.
* @param menuSFX Menu SFX to play
* @param volume At which volume it should play
*/
public static function playMenuSFX(menuSFX:Int = 0, volume:Float = 1) {
FlxG.sound.play(Paths.sound(switch(menuSFX) {
case 1: 'menu/confirm';
case 2: 'menu/cancel';
default: 'menu/scroll';
}), volume);
}
public static function coolTextFile(path:String):Array<String>
{
return [for(e in Assets.getText(path).trim().split('\n')) e.trim()];
}
public static inline function numberArray(max:Int, ?min = 0):Array<Int>
{
return [for (i in min...max) i];
}
public static function switchAnimFrames(anim1:FlxAnimation, anim2:FlxAnimation) {
if (anim1 == null || anim2 == null) return;
var old = anim1.frames;
anim1.frames = anim2.frames;
anim2.frames = old;
}
public static function setUnstretchedGraphicSize(sprite:FlxSprite, width:Int, height:Int, fill:Bool = true, maxScale:Float = 0) {
sprite.setGraphicSize(width, height);
sprite.updateHitbox();
var nScale = (fill ? Math.max : Math.min)(sprite.scale.x, sprite.scale.y);
if (maxScale > 0 && nScale > maxScale) nScale = maxScale;
sprite.scale.set(nScale, nScale);
}
public static inline function keyToString(key:Null<FlxKey>):String {
return switch(key) {
case null | 0 | NONE: "---";
case LEFT: "←";
case DOWN: "↓";
case UP: "↑";
case RIGHT: "→";
case ESCAPE: "ESC";
case BACKSPACE: "[←]";
case NUMPADZERO: "#0";
case NUMPADONE: "#1";
case NUMPADTWO: "#2";
case NUMPADTHREE: "#3";
case NUMPADFOUR: "#4";
case NUMPADFIVE: "#5";
case NUMPADSIX: "#6";
case NUMPADSEVEN: "#7";
case NUMPADEIGHT: "#8";
case NUMPADNINE: "#9";
case NUMPADPLUS: "#+";
case NUMPADMINUS: "#-";
case NUMPADPERIOD: "#.";
case ZERO: "0";
case ONE: "1";
case TWO: "2";
case THREE: "3";
case FOUR: "4";
case FIVE: "5";
case SIX: "6";
case SEVEN: "7";
case EIGHT: "8";
case NINE: "9";
case PERIOD: ".";
default: key.toString();
}
}
public static function cameraCenter(obj:FlxObject, cam:FlxCamera, axes:FlxAxes = XY) {
switch(axes) {
case XY:
obj.setPosition((cam.width - obj.width) / 2, (cam.height - obj.height) / 2);
case X:
obj.x = (cam.width - obj.width) / 2;
case Y:
obj.y = (cam.height - obj.height) / 2;
case NONE:
}
}
public static function loadWeek(weekData:WeekData, difficulty:String = "normal") {
PlayState.storyWeek = weekData;
PlayState.storyPlaylist = [for(e in weekData.songs) e.name];
PlayState.isStoryMode = true;
PlayState.campaignScore = 0;
__loadSong(PlayState.storyPlaylist[0], difficulty);
}
public static function loadSong(name:String, difficulty:String = "normal") {
PlayState.campaignScore = 0;
PlayState.isStoryMode = false;
__loadSong(name, difficulty);
}
public static function __loadSong(name:String, difficulty:String) {
PlayState.difficulty = difficulty;
PlayState.SONG = Song.loadFromJson(name, difficulty);
}
public static function setSpriteSize(sprite:FlxSprite, width:Float, height:Float) {
sprite.scale.set(width / sprite.frameWidth, height / sprite.frameHeight);
sprite.updateHitbox();
}
public static inline function getAtt(xml:Access, name:String) {
if (!xml.has.resolve(name)) return null;
return xml.att.resolve(name);
}
public static function loadFrames(path:String, Unique:Bool = false, Key:String = null, SkipAtlasCheck:Bool = false):FlxFramesCollection {
var noExt = Path.withoutExtension(path);
if (Assets.exists('$noExt/1.png')) {
// MULTIPLE SPRITESHEETS!!
var graphic = FlxG.bitmap.add("flixel/images/logo/default.png", false, '$noExt/mult');
var frames = FlxAtlasFrames.findFrame(graphic);
if (frames != null)
return frames;
trace("no frames yet for multiple atlases!!");
var spritesheets = [];
var cur = 1;
var finalFrames = new FlxFramesCollection(graphic, ATLAS);
while(Assets.exists('$noExt/$cur.png')) {
spritesheets.push(loadFrames('$noExt/$cur.png'));
cur++;
}
for(frames in spritesheets)
if (frames != null && frames.frames != null)
for(f in frames.frames)
if (f != null) {
finalFrames.frames.push(f);
f.parent = frames.parent;
}
return finalFrames;
} else if (!SkipAtlasCheck && Assets.exists('$noExt/Animation.json')
&& Assets.exists('$noExt/spritemap.json')
&& Assets.exists('$noExt/spritemap.png')) {
return AtlasFrameMaker.construct(noExt);
} else if (Assets.exists('$noExt.xml')) {
return Paths.getSparrowAtlasAlt(noExt);
} else if (Assets.exists('$noExt.txt')) {
return Paths.getPackerAtlasAlt(noExt);
}
var graph:FlxGraphic = FlxG.bitmap.add(path, Unique, Key);
if (graph == null)
return null;
return graph.imageFrame;
}
public static function loadAnimatedGraphic(spr:FlxSprite, path:String) {
spr.frames = loadFrames(path);
if (spr.frames != null && spr.frames.frames != null) {
spr.animation.add("idle", [for(i in 0...spr.frames.frames.length) i], 24, true);
spr.animation.play("idle");
}
return spr;
}
public static inline function copyColorTransform(color1:ColorTransform, color2:ColorTransform) {
color1.alphaMultiplier = color2.alphaMultiplier;
color1.alphaOffset = color2.alphaOffset;
color1.blueMultiplier = color2.blueMultiplier;
color1.blueOffset = color2.blueOffset;
color1.greenMultiplier = color2.greenMultiplier;
color1.greenOffset = color2.greenOffset;
color1.redMultiplier = color2.redMultiplier;
color1.redOffset = color2.redOffset;
}
public static function resetSprite(spr:FlxSprite, x:Float, y:Float) {
spr.reset(x, y);
spr.alpha = 1;
spr.visible = true;
spr.active = true;
spr.antialiasing = FlxSprite.defaultAntialiasing;
spr.rotOffset.set();
}
/**
* Gets the macro class created by hscript-improved for an abstract / enum
*/
public static function getMacroAbstractClass(className:String) {
return Type.resolveClass('${className}_HSC');
}
/**
* Clears the content of an array
*/
public static function clear<T>(array:Array<T>):Array<T> {
while(array.length > 0)
array.shift();
return array;
}
public static function pushGroup<T>(array:Array<T>, ...args:T):Array<T> {
for(a in args)
array.push(a);
return array;
}
}
+41
View File
@@ -0,0 +1,41 @@
package funkin.utils;
import openfl.utils.Assets;
/**
* DOESNT SUPPORT CATEGORIES YET!!
*/
class IniUtil {
public static inline function parseAsset(assetPath:String, ?defaultVariables:Map<String, String>)
return parseString(Assets.getText(assetPath), defaultVariables);
public static function parseString(data:String, ?defaultVariables:Map<String, String>) {
var trimmed:String;
var splitContent = [for(e in data.split("\n")) if ((trimmed = e.trim()) != "") trimmed];
var finalMap:Map<String, String> = [];
if (defaultVariables != null)
for(k=>e in defaultVariables)
finalMap[k] = e;
for(line in splitContent) {
// comment
if (line.startsWith(";")) continue;
// categories; not supported yet
if (line.startsWith("[") && line.endsWith("]")) continue;
var index = line.indexOf("=");
var name = line.substr(0, index).trim();
var value = line.substr(index+1).trim();
if (value.startsWith("\"") && value.endsWith("\""))
value = value.substr(1, value.length - 2);
if (value.length == 0 || name.length == 0)
continue;
finalMap[name] = value;
}
return finalMap;
}
}
+87
View File
@@ -0,0 +1,87 @@
package funkin.utils;
import flixel.util.FlxColor;
import flixel.text.FlxText;
class MarkdownUtil {
public static function applyMarkdownText(text:FlxText, str:String) {
text.textField.htmlText = Markdown.markdownToHtml(prepareMarkdown(str));
var changes:Array<FlxTextFormatMarkerPair> = [
createAdvancedMarkerPair("{title}", 0xFFFFFFFF, text.size * 2, false, true, true),
createAdvancedMarkerPair("{subtitle}", 0xFFFFFFFF, text.size * 1.5, false, true, true),
createAdvancedMarkerPair("{subsubtitle}", 0xFFFFFFFF, text.size * 1.25, false, true, true),
createAdvancedMarkerPair("{ident1}", 0xFFFFFFFF, null, false, false, false, text.size * 2, true),
createAdvancedMarkerPair("{ident2}", 0xFFFFFFFF, null, false, false, false, text.size * 4, true),
createAdvancedMarkerPair("{ident3}", 0xFFFFFFFF, null, false, false, false, text.size * 6, true),
createAdvancedMarkerPair("{ident4}", 0xFFFFFFFF, null, false, false, false, text.size * 8, true),
new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFFFFF00, false, true), "{o}"),
new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFFF0000, false, true), "{r}"),
new FlxTextFormatMarkerPair(new FlxTextFormat(0xFF00FF08, false, true), "{g}"),
new FlxTextFormatMarkerPair(new FlxTextFormat(0xFF7D7D7D, false, true), "{gray}"),
new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFFFFFFF, false, true, 0xFF222222), "{italic}"),
new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFFFFFFF, false, true, 0xFF666666), "{bold}"),
new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFFFFFFF, false, true, 0xFF888888), "{bold-italic}"),
];
text.applyMarkup(
parseWarnings(text.textField.text),
changes
);
@:privateAccess {
// hacky fix for the text cutting when bigger text formats are used
text._regen = true;
text.regenGraphic();
text._regen = true;
text.regenGraphic();
}
}
public static function createAdvancedMarkerPair(marker:String, color:FlxColor, size:Null<Float>, bold:Bool = false, italic:Bool = false, ?underline:Bool = false, ?blockIndent:Int, ?bullet:Bool) {
@:privateAccess {
var format = new FlxTextFormat(color, bold, italic);
format.format.size = size == null ? null : Std.int(size);
format.format.underline = underline;
format.format.blockIndent = blockIndent;
format.format.bullet = bullet;
return new FlxTextFormatMarkerPair(format, marker);
}
}
public static function parseWarnings(text:String) {
text = text.replace("&nbsp;", " ");
text = parseEmote(text, "⚠", "{o}(!)", "{o}");
text = parseEmote(text, "❌", "{r}(X)", "{r}");
text = parseEmote(text, "✔", "{g}(+)", "{g}");
return text;
}
public static function prepareMarkdown(text:String) {
text = "\n" + text;
text = parseEmote(text, "\n- ", "\n{ident1}&nbsp;&nbsp;• ", "{ident1}");
text = parseEmote(text, "\n - ", "\n{ident2}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;• ", "{ident2}");
text = parseEmote(text, "\n - ", "\n{ident3}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;• ", "{ident3}");
text = parseEmote(text, "\n - ", "\n{ident4}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;• ", "{ident4}");
text = parseEmote(text, "\n> ", "\n{gray}", "{gray}");
text = parseEmote(text, "\n### ", "\n{subsubtitle}", "{subsubtitle}");
text = parseEmote(text, "\n## ", "\n{subtitle}", "{subtitle}");
text = parseEmote(text, "\n# ", "\n{title}", "{title}");
text = parseEmote(text, "***", "{bold-italic}", "{bold-italic}", "***");
text = parseEmote(text, "**", "{bold}", "{bold}", "**");
text = parseEmote(text, "*", "{italic}", "{italic}", "*");
text = parseEmote(text, "_", "{italic}", "{italic}", "*");
text = parseEmote(text, "__", "{bold}", "{bold}", "*");
text.substr("\n".length);
return text;
}
public static function parseEmote(text:String, emote:String, beginning:String, end:String, emoteEnd:String = "\n") {
var index:Int;
while((index = text.indexOf(emote)) >= 0) {
var nextIndex = text.indexOf(emoteEnd, index + emote.length);
if (nextIndex < 0)
nextIndex = text.length;
text = text.substr(0, index) + beginning + text.substring(index + emote.length, nextIndex) + end + (emote == emoteEnd ? "" : emoteEnd) + text.substring(nextIndex + emoteEnd.length);
}
return text;
}
}
+32
View File
@@ -0,0 +1,32 @@
package funkin.utils;
#if ALLOW_MULTITHREADING
class ThreadUtil {
/**
* Creates a new Thread with an error handler.
* @param func Function to execute
* @param autoRestart Whenever the thread should auto restart itself after crashing.
*/
public static function createSafe(func:Void->Void, autoRestart:Bool = false) {
if (autoRestart) {
return sys.thread.Thread.create(function() {
while(true) {
try {
func();
} catch(e) {
trace(e.details());
}
}
});
} else {
return sys.thread.Thread.create(function() {
try {
func();
} catch(e) {
trace(e.details());
}
});
}
}
}
#end
+184
View File
@@ -0,0 +1,184 @@
package funkin.utils;
import funkin.system.FunkinSprite;
import funkin.system.ErrorCode;
import funkin.system.FunkinSprite.XMLAnimType;
import flixel.math.FlxMath;
import funkin.interfaces.IBeatReceiver;
import haxe.xml.Access;
import funkin.interfaces.IOffsetCompatible;
import flixel.FlxSprite;
using StringTools;
/**
* Class made to make XML parsing easier.
*/
class XMLUtil {
/**
* Applies a property XML node to an object.
* @param object Object to which the xml property will be applied
* @param property `property` node.
*/
public static function applyXMLProperty(object:Dynamic, property:Access):ErrorCode {
if (!property.has.name || !property.has.type || !property.has.value) {
Logs.trace('Failed to apply XML property: XML Element misses name, type, or value attributes.', WARNING);
return MISSING_PROPERTY;
}
var value:Dynamic = switch(property.att.type.toLowerCase()) {
case "float" | "number": Std.parseFloat(property.att.value);
case "int" | "integer" | "color": Std.parseInt(property.att.value);
case "string" | "str" | "text": property.att.value;
case "bool" | "boolean": property.att.value.toLowerCase() == "true";
default: return TYPE_INCORRECT;
}
if (value == null) return VALUE_NULL;
try {
Reflect.setProperty(object, property.att.name, value);
} catch(e) {
Logs.trace('Failed to apply XML property: $e on ${Type.getClass(object)}', WARNING);
return REFLECT_ERROR;
}
return OK;
}
/**
* Creates a new sprite based on a XML node.
*/
public static function createSpriteFromXML(node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, ?cl:Class<FunkinSprite>):FunkinSprite {
if (parentFolder == null) parentFolder = "";
var spr:FunkinSprite = cl != null ? Type.createInstance(cl, []) : new FunkinSprite();
spr.name = node.getAtt("name");
spr.antialiasing = true;
spr.loadAnimatedGraphic(Paths.image('$parentFolder${node.getAtt("sprite")}', null, true));
spr.spriteAnimType = defaultAnimType;
if (node.has.type)
spr.spriteAnimType = XMLAnimType.fromString(node.att.type, spr.spriteAnimType);
var x:Null<Float> = node.has.x ? Std.parseFloat(node.att.x) : null;
var y:Null<Float> = node.has.y ? Std.parseFloat(node.att.y) : null;
if (x != null) spr.x = x;
if (y != null) spr.y = y;
if (node.has.scroll) {
var scroll:Null<Float> = Std.parseFloat(node.att.scroll);
if (scroll != null) spr.scrollFactor.set(scroll, scroll);
} else {
if (node.has.scrollx) {
var scroll:Null<Float> = Std.parseFloat(node.att.scrollx);
if (scroll != null) spr.scrollFactor.x = scroll;
}
if (node.has.scrolly) {
var scroll:Null<Float> = Std.parseFloat(node.att.scrolly);
if (scroll != null) spr.scrollFactor.y = scroll;
}
}
if (node.has.antialiasing) spr.antialiasing = node.att.antialiasing == "true";
if (node.has.scale) {
var scale:Null<Float> = Std.parseFloat(node.att.scale);
if (scale != null) spr.scale.set(scale, scale);
}
if (node.has.updateHitbox && node.att.updateHitbox == "true") spr.updateHitbox();
spr.zoomFactor = Std.parseFloat(node.getAtt("zoomfactor")).getDefault(spr.zoomFactor);
for(anim in node.nodes.anim)
addXMLAnimation(spr, anim);
return spr;
}
public static function extractAnimFromXML(anim:Access, animType:XMLAnimType = NONE, loop:Bool = false):AnimData {
var animData:AnimData = {
name: null,
anim: null,
fps: 24,
loop: loop,
animType: animType,
x: 0,
y: 0,
indices: []
};
if (anim.has.name) animData.name = anim.att.name;
if (anim.has.type) animData.animType = XMLAnimType.fromString(anim.att.type, animData.animType);
if (anim.has.anim) animData.anim = anim.att.anim;
if (anim.has.fps) animData.fps = Std.parseInt(anim.att.fps);
if (anim.has.x) animData.x = Std.parseFloat(anim.att.x);
if (anim.has.y) animData.y = Std.parseFloat(anim.att.y);
if (anim.has.loop) animData.loop = anim.att.loop == "true";
if (anim.has.indices) {
var indicesSplit = anim.att.indices.split(",");
for(indice in indicesSplit) {
var i = Std.parseInt(indice.trim());
if (i != null)
animData.indices.push(i);
}
}
return animData;
}
/**
* Adds an XML animation to `sprite`.
* @param sprite Destination sprite
* @param anim Animation (Must be a `anim` XML node)
*/
public static function addXMLAnimation(sprite:FlxSprite, anim:Access, loop:Bool = false):ErrorCode {
var animType:XMLAnimType = NONE;
if (sprite is FunkinSprite)
animType = cast(sprite, FunkinSprite).spriteAnimType;
return addAnimToSprite(sprite, extractAnimFromXML(anim, animType, loop));
}
public static function addAnimToSprite(sprite:FlxSprite, animData:AnimData):ErrorCode {
if (animData.name != null && animData.anim != null) {
if (animData.fps <= 0 #if web || animData.fps == null #end) animData.fps = 24;
if (sprite is FunkinSprite && cast(sprite, FunkinSprite).animateAtlas != null) {
var animateAnim = cast(sprite, FunkinSprite).animateAtlas.anim;
if (animData.indices.length > 0)
animateAnim.addBySymbolIndices(animData.name, animData.anim, animData.indices, animData.fps, animData.loop);
else
animateAnim.addBySymbol(animData.name, animData.anim, animData.fps, animData.loop);
} else {
if (animData.indices.length > 0)
sprite.animation.addByIndices(animData.name, animData.anim, animData.indices, "", animData.fps, animData.loop);
else
sprite.animation.addByPrefix(animData.name, animData.anim, animData.fps, animData.loop);
}
if (sprite is IOffsetCompatible)
cast(sprite, IOffsetCompatible).addOffset(animData.name, animData.x, animData.y);
if (sprite is FunkinSprite) {
var xmlSpr = cast(sprite, FunkinSprite);
switch(animData.animType) {
case BEAT:
xmlSpr.beatAnims.push(animData.name);
case LOOP:
xmlSpr.animation.play(animData.name);
default:
// nothing
}
}
return OK;
}
return MISSING_PROPERTY;
}
}
typedef AnimData = {
var name:String;
var anim:String;
var fps:Int;
var loop:Bool;
var x:Float;
var y:Float;
var indices:Array<Int>;
var animType:XMLAnimType;
}
+266
View File
@@ -0,0 +1,266 @@
package funkin.utils;
#if (!macro && sys)
import openfl.display.BitmapData;
#end
import haxe.Exception;
import haxe.Json;
import haxe.crypto.Crc32;
import haxe.zip.Writer;
import haxe.zip.Tools;
import haxe.zip.Entry;
import haxe.zip.Uncompress;
import haxe.zip.Reader;
import haxe.zip.Compress;
import sys.FileSystem;
import sys.io.File;
import sys.thread.Thread;
using StringTools;
// import ZipUtils; ZipUtils.uncompressZip(ZipUtils.openZip("E:\\Desktop\\test\\termination lua.ycemod"), "E:\\Desktop\\test\\uncompressed\\");
// import ZipUtils; var e = ZipUtils.createZipFile("gjnsdghs.ycemod"); ZipUtils.writeFolderToZip(e, "./mods/Friday Night Funkin'/", "Friday Night Funkin'/"); e.flush(); e.close();
class ZipUtil {
public static var bannedNames:Array<String> = [".git", ".gitignore", ".github", ".vscode", ".gitattributes", "readme.txt"];
/**
* [Description] Uncompresses `zip` into the `destFolder` folder
* @param zip
* @param destFolder
*/
public static function uncompressZip(zip:Reader, destFolder:String, ?prefix:String, ?prog:ZipProgress):ZipProgress {
// we never know
FileSystem.createDirectory(destFolder);
var fields = zip.read();
try {
if (prefix != null) {
var f = fields;
fields = new List<Entry>();
for(field in f) {
if (field.fileName.startsWith(prefix)) {
fields.push(field);
}
}
}
if (prog == null)
prog = new ZipProgress();
prog.fileCount = fields.length;
for(k=>field in fields) {
prog.curFile = k;
var isFolder = field.fileName.endsWith("/") && field.fileSize == 0;
if (isFolder) {
FileSystem.createDirectory('${destFolder}/${field.fileName}');
} else {
var split = [for(e in field.fileName.split("/")) e.trim()];
split.pop();
FileSystem.createDirectory('${destFolder}/${split.join("/")}');
var data = unzip(field);
File.saveBytes('${destFolder}/${field.fileName}', data);
}
}
prog.curFile = fields.length;
prog.done = true;
} catch(e) {
prog.done = true;
prog.error = e;
}
return prog;
}
#if (!macro && sys)
public static function uncompressZipAsync(zip:Reader, destFolder:String, ?prog:ZipProgress, ?prefix:String):ZipProgress {
if (prog == null)
prog = new ZipProgress();
Thread.create(function() {
uncompressZip(zip, destFolder, prefix, prog);
});
return prog;
}
#end
/**
* [Description] Returns a `zip.Reader` instance from path.
* @param zipPath
* @return Reader
*/
public static function openZip(zipPath:String):Reader {
return new ZipReader(File.read(zipPath));
}
/**
* [Description] Copy of haxe's Zip unzip function cause lime replaced it.
* @param f Zip entry
*/
public static function unzip(f:Entry) {
if (!f.compressed)
return f.data;
var c = new haxe.zip.Uncompress(-15);
var s = haxe.io.Bytes.alloc(f.fileSize);
var r = c.execute(f.data, 0, s, 0);
c.close();
if (!r.done || r.read != f.data.length || r.write != f.fileSize)
throw "Invalid compressed data for " + f.fileName;
f.compressed = false;
f.dataSize = f.fileSize;
f.data = s;
return f.data;
}
/**
* [Description] Creates a ZIP file at the specified location and returns the Writer.
* @param path
* @return Writer
*/
public static function createZipFile(path:String):ZipWriter {
var output = File.write(path);
return new ZipWriter(output);
}
/**
[Description] Writes the entirety of a folder to a zip file.
@param zip ZIP file to write to
@param path Folder path
@param prefix (Additional) allows you to set a prefix in the zip itself.
**/
public static function writeFolderToZip(zip:ZipWriter, path:String, ?prefix:String, ?prog:ZipProgress, ?whitelist:Array<String>):ZipProgress {
if (prefix == null) prefix = "";
if (whitelist == null) whitelist = [];
if (prog == null) prog = new ZipProgress();
try {
var curPath:Array<String> = ['$path'];
var destPath:Array<String> = [];
if (prefix != "") {
prefix = prefix.replace("\\", "/");
while(prefix.charAt(0) == "/") prefix = prefix.substr(1);
while(prefix.charAt(prefix.length-1) == "/") prefix = prefix.substr(0, prefix.length-1);
destPath.push(prefix);
}
var files:Array<StrNameLabel> = [];
var doFolder:Void->Void = null;
(doFolder = function() {
var path = curPath.join("/");
var zipPath = destPath.join("/");
for(e in FileSystem.readDirectory(path)) {
if (bannedNames.contains(e.toLowerCase()) && !whitelist.contains(e.toLowerCase())) continue;
if (FileSystem.isDirectory('$path/$e')) {
// is directory, so loop into that function again
for(p in [curPath, destPath]) p.push(e);
doFolder();
for(p in [curPath, destPath]) p.pop();
} else {
// is file, put it in the list
var zipPath = '$zipPath/$e';
while(zipPath.charAt(0) == "/") zipPath = zipPath.substr(1);
files.push(new StrNameLabel('$path/$e', zipPath));
}
}
})();
prog.fileCount = files.length;
for(k=>file in files) {
prog.curFile = k;
var fileContent = File.getBytes(file.name);
var fileInfo = FileSystem.stat(file.name);
var entry:Entry = {
fileName: file.label,
fileSize: fileInfo.size,
fileTime: Date.now(),
dataSize: 0,
data: fileContent,
crc32: Crc32.make(fileContent),
compressed: false
};
Tools.compress(entry, 1);
zip.writeFile(entry);
}
zip.writeCDR();
} catch(e) {
prog.error = e;
}
prog.done = true;
return prog;
}
public static function writeFolderToZipAsync(zip:ZipWriter, path:String, ?prefix:String):ZipProgress {
var zipProg = new ZipProgress();
Thread.create(function() {
writeFolderToZip(zip, path, prefix, zipProg);
});
return zipProg;
}
/**
* [Description] Converts an `Array<Entry>` to a `List<Entry>`.
* @param array
* @return List<Entry>
*/
public static function arrayToList(array:Array<Entry>):List<Entry> {
var list = new List<Entry>();
for(e in array) list.push(e);
return list;
}
}
class ZipProgress {
public var error:Exception = null;
public var curFile:Int = 0;
public var fileCount:Int = 0;
public var done:Bool = false;
public var percentage(get, null):Float;
private function get_percentage() {
return fileCount <= 0 ? 0 : curFile / fileCount;
}
public function new() {}
}
class ZipReader extends Reader {
public var files:List<Entry>;
public override function read() {
if (files != null) return files;
try {
var files = super.read();
return this.files = files;
} catch(e) {
}
return new List<Entry>();
}
}
class ZipWriter extends Writer {
public function flush() {
o.flush();
}
public function writeFile(entry:Entry) {
writeEntryHeader(entry);
o.writeFullBytes(entry.data, 0, entry.data.length);
}
public function close() {
o.close();
}
}
class StrNameLabel {
public var name:String;
public var label:String;
public function new(name:String, label:String) {
this.name = name;
this.label = label;
}
}