Merge branch 'main' into translations-system
This commit is contained in:
@@ -3,38 +3,184 @@ package funkin.backend.utils;
|
||||
import flixel.sound.FlxSound;
|
||||
import lime.media.AudioBuffer;
|
||||
|
||||
// Thank you yosh :DDD -lunar
|
||||
// (https://github.com/CodenameCrew/YoshiCrafterEngine/blob/main/source/WaveformSprite.hx)
|
||||
#if lime_vorbis
|
||||
import lime.media.vorbis.Vorbis;
|
||||
import lime.media.vorbis.VorbisFile;
|
||||
import lime.utils.ArrayBuffer;
|
||||
#end
|
||||
|
||||
// ORIGINAL CODES FROM YOSH & LUNAR https://github.com/CodenameCrew/YoshiCrafterEngine/blob/main/source/WaveformSprite.hx
|
||||
// REWRITTEN BY RALTYRO
|
||||
class AudioAnalyzer {
|
||||
public var sound:FlxSound;
|
||||
public var buffer:AudioBuffer;
|
||||
var __peakByte:Float = 0;
|
||||
var __timeMulti:Float = 0;
|
||||
var __toBits:Float;
|
||||
var __wordSize:Int;
|
||||
var __bitUnsignedSize:Int;
|
||||
var __bitSize:Int;
|
||||
|
||||
#if lime_vorbis
|
||||
var __vorbis:VorbisFile;
|
||||
#end
|
||||
|
||||
public function new(sound:FlxSound) {
|
||||
@:privateAccess buffer = sound._sound.__buffer;
|
||||
|
||||
__peakByte = Math.pow(2, buffer.bitsPerSample-1)-1;
|
||||
|
||||
__timeMulti = 1 / (1 / buffer.sampleRate);
|
||||
__timeMulti *= buffer.bitsPerSample;
|
||||
__timeMulti -= __timeMulti % buffer.bitsPerSample;
|
||||
this.sound = sound;
|
||||
__check();
|
||||
}
|
||||
|
||||
public function analyze(startPos:Float, endPos:Float):Float {
|
||||
var bytesStartPos:Int = Math.floor(startPos * __timeMulti / 4000 / buffer.bitsPerSample) * buffer.bitsPerSample;
|
||||
var bytesEndPos:Int = Math.floor(endPos * __timeMulti / 4000 / buffer.bitsPerSample) * buffer.bitsPerSample;
|
||||
inline function __check() if (sound.buffer != buffer) {
|
||||
__bitSize = 1 << ((buffer = sound.buffer).bitsPerSample - 1);
|
||||
__bitUnsignedSize = 1 << buffer.bitsPerSample;
|
||||
__toBits = buffer.sampleRate / 1000 * buffer.channels * (__wordSize = buffer.bitsPerSample >> 3);
|
||||
__vorbis = null;
|
||||
}
|
||||
|
||||
bytesStartPos -= bytesStartPos % buffer.bitsPerSample;
|
||||
bytesEndPos -= bytesEndPos % buffer.bitsPerSample;
|
||||
public function analyze(startPos:Float, endPos:Float, ?outSeperate:Array<Float>):Float {
|
||||
__check();
|
||||
if (buffer.data != null) return __analyze(startPos, endPos, outSeperate);
|
||||
#if lime_vorbis
|
||||
else if (__prepareVorbis()) return __analyzeVorbis(startPos, endPos, outSeperate);
|
||||
#end
|
||||
return 0;
|
||||
}
|
||||
|
||||
var maxByte:Int = 0;
|
||||
for(i in 0...Math.floor((bytesEndPos - bytesStartPos) / buffer.bitsPerSample)) {
|
||||
var byte:Int = buffer.data.buffer.get(bytesStartPos + (i * buffer.bitsPerSample))
|
||||
| (buffer.data.buffer.get(bytesStartPos + (i * buffer.bitsPerSample) + 1) << 8);
|
||||
if (byte > 256 * 128) byte -= 256 * 256;
|
||||
if (maxByte < byte) maxByte = byte;
|
||||
inline function __analyze(startPos:Float, endPos:Float, ?outSeperate:Array<Float>):Float {
|
||||
var pos = Math.floor(startPos * __toBits), end = Math.floor(endPos * __toBits), buf = buffer.data #if !js .buffer #end;
|
||||
var max = 0, b = 0, w = 0, c = 0, useSeperate = outSeperate != null;
|
||||
if (useSeperate) {
|
||||
while (c < outSeperate.length) outSeperate[c++] *= __bitSize;
|
||||
while (c++ < buffer.channels) outSeperate.push(0);
|
||||
c = Math.floor((pos % (__wordSize * buffer.channels)) / __wordSize);
|
||||
}
|
||||
|
||||
return maxByte/__peakByte;
|
||||
pos -= pos % __wordSize;
|
||||
end -= end % __wordSize;
|
||||
|
||||
while (pos < end) {
|
||||
// 8-bit audio data is unsigned (0 is 128, 128 is 256, -128 is 0)
|
||||
if (__wordSize == 1) b = #if js buf[pos++] #else buf.get(pos++) #end - __bitSize;
|
||||
else {
|
||||
while (w < buffer.bitsPerSample) {
|
||||
b |= #if js buf[pos] #else buf.get(pos) #end << w;
|
||||
w += 8;
|
||||
pos++;
|
||||
}
|
||||
if (b > __bitSize) b -= __bitUnsignedSize;
|
||||
}
|
||||
if (max < b) max = b;
|
||||
if (useSeperate) {
|
||||
if (outSeperate[c] < b) outSeperate[c] = b;
|
||||
if (++c > buffer.channels) c = 0;
|
||||
}
|
||||
w = b = 0;
|
||||
}
|
||||
|
||||
return max / __bitSize;
|
||||
}
|
||||
|
||||
#if lime_vorbis // As far i know, only native supports vorbis
|
||||
var __buffer:ArrayBuffer;
|
||||
var __bufferSize:Int;
|
||||
function __prepareVorbis():Bool @:privateAccess {
|
||||
if (buffer.__srcVorbisFile == null) return __vorbis != null;
|
||||
if (__vorbis != null) return true;
|
||||
if ((__vorbis = buffer.__srcVorbisFile.clone()) != null) { // IM HOPING IT HAVE A GC CLOSURE.
|
||||
__buffer = new ArrayBuffer(__bufferSize = 0x400 * buffer.channels * (buffer.bitsPerSample >> 3));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline function __analyzeVorbis(startPos:Float, endPos:Float, ?outSeperate:Array<Float>):Float @:privateAccess {
|
||||
var n = Math.floor((endPos - startPos) * __toBits);
|
||||
if ((n -= n % __wordSize) < __wordSize) return 0;
|
||||
|
||||
var pos = 0, max = 0, b = 0, w = 0, c = 0, useSeperate = outSeperate != null;
|
||||
if (useSeperate) {
|
||||
while (c < outSeperate.length) outSeperate[c++] *= __bitSize;
|
||||
while (c++ < buffer.channels) outSeperate.push(0);
|
||||
}
|
||||
|
||||
// IT SHOULD BE ALWAYS BACKEND.STREAMED IF THIS GETS CALLED.
|
||||
var backend = sound._source != null ? sound._source.__backend : null;
|
||||
if (backend != null && backend.streamTimer != null) {
|
||||
var i = backend.bufferSizes.length - backend.queuedBuffers;
|
||||
var time = backend.bufferTimes[i] * 1000;
|
||||
|
||||
if (startPos >= time && startPos < backend.bufferTimes[backend.bufferSizes.length - 1] * 1000) {
|
||||
var buf = backend.bufferDatas[i].buffer, size = backend.bufferSizes[i];
|
||||
pos = Math.floor((startPos - time) * __toBits);
|
||||
c = Math.floor((pos % (__wordSize * buffer.channels)) / __wordSize);
|
||||
pos -= pos % __wordSize;
|
||||
|
||||
while (n > 0) {
|
||||
if (__wordSize == 1) {
|
||||
b = buf.get(pos++) - __bitSize;
|
||||
n--;
|
||||
}
|
||||
else {
|
||||
while (w < buffer.bitsPerSample) {
|
||||
b |= buf.get(pos) << w;
|
||||
w += 8;
|
||||
pos++;
|
||||
n--;
|
||||
}
|
||||
if (b > __bitSize) b -= __bitUnsignedSize;
|
||||
}
|
||||
if (max < b) max = b;
|
||||
if (useSeperate) {
|
||||
if (outSeperate[c] < b) outSeperate[c] = b;
|
||||
if (++c > buffer.channels) c = 0;
|
||||
}
|
||||
if (pos >= size) {
|
||||
if (++i >= backend.bufferDatas.length) break;
|
||||
size = backend.bufferSizes[i];
|
||||
buf = backend.bufferDatas[i].buffer;
|
||||
pos = 0;
|
||||
}
|
||||
w = b = 0;
|
||||
}
|
||||
|
||||
startPos = pos / __toBits + time;
|
||||
// Cannot inline a not final return
|
||||
}
|
||||
}
|
||||
if (n == 0) return max / __bitSize;
|
||||
|
||||
if (Math.abs(startPos - __vorbis.timeTell() * 1000) > 4) {
|
||||
if (startPos < 1) __vorbis.rawSeek(0);
|
||||
else __vorbis.timeSeek(startPos / 1000);
|
||||
}
|
||||
|
||||
pos = c = 0;
|
||||
var isBigEndian = lime.system.System.endianness == lime.system.Endian.BIG_ENDIAN, result;
|
||||
while (n > 0) {
|
||||
n -= (result = __vorbis.read(__buffer, 0, n < __bufferSize ? n : __bufferSize, isBigEndian, __wordSize, true));
|
||||
|
||||
if (result == Vorbis.HOLE) continue;
|
||||
else if (result <= 0) break;
|
||||
|
||||
while (pos < result) {
|
||||
if (__wordSize == 1) b = __buffer.get(pos++) - __bitSize;
|
||||
else {
|
||||
while (w < buffer.bitsPerSample) {
|
||||
b |= __buffer.get(pos) << w;
|
||||
w += 8;
|
||||
pos++;
|
||||
}
|
||||
if (b > __bitSize) b -= __bitUnsignedSize;
|
||||
}
|
||||
if (max < b) max = b;
|
||||
if (useSeperate) {
|
||||
if (outSeperate[c] < b) outSeperate[c] = b;
|
||||
if (++c > buffer.channels) c = 0;
|
||||
}
|
||||
w = b = 0;
|
||||
}
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
return max / __bitSize;
|
||||
}
|
||||
#end
|
||||
}
|
||||
@@ -2,8 +2,10 @@ package funkin.backend.utils;
|
||||
|
||||
import flixel.util.FlxColor;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.geom.Point;
|
||||
import openfl.geom.Rectangle;
|
||||
|
||||
class BitmapUtil {
|
||||
final class BitmapUtil {
|
||||
/**
|
||||
* Returns the most present color in a Bitmap.
|
||||
* @param bmap Bitmap
|
||||
@@ -18,7 +20,7 @@ class BitmapUtil {
|
||||
for(y in 0...bmap.height) {
|
||||
for(x in 0...bmap.width) {
|
||||
color = bmap.getPixel32(x, y);
|
||||
fixedColor = 0xFF000000 + (color % 0x1000000);
|
||||
fixedColor = 0xFF000000 | (color & 0xFFFFFF);
|
||||
if (!colorMap.exists(fixedColor))
|
||||
colorMap[fixedColor] = 0;
|
||||
colorMap[fixedColor] += color.alphaFloat;
|
||||
@@ -49,7 +51,7 @@ class BitmapUtil {
|
||||
for(y in 0...bmap.height) {
|
||||
for(x in 0...bmap.width) {
|
||||
color = bmap.getPixel32(x, y);
|
||||
fixedColor = 0xFF000000 + (color % 0x1000000);
|
||||
fixedColor = 0xFF000000 | (color & 0xFFFFFF);
|
||||
if (!colorMap.exists(fixedColor))
|
||||
colorMap[fixedColor] = 0;
|
||||
colorMap[fixedColor] += color.alphaFloat * 0.33 + (0.67 * (color.saturation * (2 * (color.lightness > 0.5 ? 0.5 - (color.lightness) : color.lightness))));
|
||||
@@ -66,4 +68,49 @@ class BitmapUtil {
|
||||
}
|
||||
return mostPresentColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new bitmap without any empty transperent space on the edges
|
||||
* @param bitmap The bitmap to be cropped
|
||||
*/
|
||||
public static function crop(bitmap:BitmapData) {
|
||||
var bitmapBounds:Rectangle = BitmapUtil.bounds(bitmap);
|
||||
|
||||
var croppedBitmap:BitmapData = new BitmapData(Std.int(bitmapBounds.width), Std.int(bitmapBounds.height), true, 0x00000000);
|
||||
croppedBitmap.copyPixels(bitmap, bitmapBounds, new Point(0,0));
|
||||
return croppedBitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bounds of non empty pixels in the bitmap
|
||||
* @param bitmap
|
||||
* @return
|
||||
*/
|
||||
public static function bounds(bitmap:BitmapData, ?limit:Rectangle = null):Rectangle {
|
||||
var hasLimit:Bool = limit != null;
|
||||
// Searching bounds
|
||||
var startX:Int = hasLimit ? Std.int(limit.x) : 0;
|
||||
var startY:Int = hasLimit ? Std.int(limit.y) : 0;
|
||||
var endX:Int = hasLimit ? Std.int(limit.width) : bitmap.width;
|
||||
var endY:Int = hasLimit ? Std.int(limit.height) : bitmap.height;
|
||||
|
||||
// Detected bounds
|
||||
var minX:Int = endX;
|
||||
var minY:Int = endY;
|
||||
var maxX:Int = startX;
|
||||
var maxY:Int = startY;
|
||||
|
||||
for (y in startY...endY) {
|
||||
for (x in startX...endX) {
|
||||
if (bitmap.getPixel32(x, y) != 0x00000000) {
|
||||
if (x < minX) minX = x;
|
||||
if (y < minY) minY = y;
|
||||
if (x > maxX) maxX = x;
|
||||
if (y > maxY) maxY = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Rectangle(minX, minY, maxX-minX+1, maxY-minY+1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package funkin.backend.utils;
|
||||
|
||||
import funkin.options.PlayerSettings;
|
||||
import flixel.input.FlxInput;
|
||||
import flixel.input.actions.FlxAction;
|
||||
import funkin.backend.system.Controls;
|
||||
import flixel.input.actions.FlxActionInput;
|
||||
import flixel.input.actions.FlxActionManager;
|
||||
import flixel.input.actions.FlxActionSet;
|
||||
import flixel.input.gamepad.FlxGamepadInputID;
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
import haxe.xml.Access;
|
||||
|
||||
class ControlsUtil {
|
||||
|
||||
private static var storedCustomControls:Array<String> = [];
|
||||
|
||||
public static function initCustomControl(controls:Controls, name:String) {
|
||||
if (controls.byName.exists(name)) return;
|
||||
|
||||
controls.byName.set(name, new FlxActionDigital(name));
|
||||
controls.byName.set(name+"_P", new FlxActionDigital(name+"_P"));
|
||||
controls.byName.set(name+"_R", new FlxActionDigital(name+"_R"));
|
||||
storedCustomControls.push(name);
|
||||
}
|
||||
|
||||
public static function addKeysToCustomControl(controls:Controls, name:String, keys:Array<FlxKey>) {
|
||||
if (!controls.byName.exists(name)) return;
|
||||
|
||||
var pressed:FlxActionDigital = controls.byName.get(name);
|
||||
if (pressed != null) Controls.addKeys(pressed, keys, PRESSED);
|
||||
|
||||
var justPressed:FlxActionDigital = controls.byName.get(name+"_P");
|
||||
if (justPressed != null) Controls.addKeys(justPressed, keys, JUST_PRESSED);
|
||||
|
||||
var justReleased:FlxActionDigital = controls.byName.get(name+"_R");
|
||||
if (justReleased != null) Controls.addKeys(justReleased, keys, JUST_RELEASED);
|
||||
}
|
||||
|
||||
public static function removeKeysFromCustomControl(controls:Controls, name:String, keys:Array<FlxKey>) {
|
||||
if (!controls.byName.exists(name)) return;
|
||||
|
||||
var pressed:FlxActionDigital = controls.byName.get(name);
|
||||
if (pressed != null) Controls.removeKeys(pressed, keys);
|
||||
|
||||
var justPressed:FlxActionDigital = controls.byName.get(name+"_P");
|
||||
if (justPressed != null) Controls.removeKeys(justPressed, keys);
|
||||
|
||||
var justReleased:FlxActionDigital = controls.byName.get(name+"_R");
|
||||
if (justReleased != null) Controls.removeKeys(justReleased, keys);
|
||||
}
|
||||
|
||||
public static function resetCustomControls() {
|
||||
removeCustomControls(PlayerSettings.solo.controls);
|
||||
removeCustomControls(PlayerSettings.player1.controls);
|
||||
removeCustomControls(PlayerSettings.player2.controls);
|
||||
storedCustomControls = [];
|
||||
}
|
||||
|
||||
private static inline function removeCustomControls(controls:Controls) {
|
||||
for (name in storedCustomControls) {
|
||||
var pressed:FlxActionDigital = controls.byName.get(name);
|
||||
if (pressed != null) pressed.destroy();
|
||||
|
||||
var justPressed:FlxActionDigital = controls.byName.get(name+"_P");
|
||||
if (justPressed != null) justPressed.destroy();
|
||||
|
||||
var justReleased:FlxActionDigital = controls.byName.get(name+"_R");
|
||||
if (justReleased != null) justReleased.destroy();
|
||||
|
||||
controls.byName.remove(name);
|
||||
controls.byName.remove(name+"_P");
|
||||
controls.byName.remove(name+"_R");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static inline function checkControl(controls:Controls, name:String) {
|
||||
var control = getControl(controls, name);
|
||||
if (control != null) return control.check();
|
||||
return false;
|
||||
}
|
||||
|
||||
public static inline function getControl(controls:Controls, name:String) : FlxActionDigital {
|
||||
return controls.byName.get(name);
|
||||
}
|
||||
|
||||
public static inline function getJustPressed(controls:Controls, name:String) {
|
||||
return checkControl(controls, name + "_P");
|
||||
}
|
||||
public static inline function getJustReleased(controls:Controls, name:String) {
|
||||
return checkControl(controls, name + "_R");
|
||||
}
|
||||
public static inline function getPressed(controls:Controls, name:String) {
|
||||
return checkControl(controls, name);
|
||||
}
|
||||
|
||||
public static function loadCustomControls() {
|
||||
var xmlPath = Paths.xml("config/controls");
|
||||
for(source in [funkin.backend.assets.AssetSource.SOURCE, funkin.backend.assets.AssetSource.MODS]) {
|
||||
if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT", source)) {
|
||||
var access:Access = null;
|
||||
try {
|
||||
access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT", source)).firstElement());
|
||||
} catch(e) {
|
||||
Logs.trace('Error while parsing controls.xml: ${Std.string(e)}', ERROR);
|
||||
}
|
||||
|
||||
if (access != null) {
|
||||
parseControlsXml(access);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static inline function parseControlsXml(xml:Access) {
|
||||
for (category in xml.elements) {
|
||||
for (control in category.elements) {
|
||||
if (!control.has.name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = control.getAtt("name");
|
||||
var saveName = control.getAtt("saveName").getDefault("");
|
||||
var defaultKeyP1 = control.getAtt("keyP1").getDefault("");
|
||||
var defaultKeyP2 = control.getAtt("keyP2").getDefault("");
|
||||
|
||||
var keyP1:Null<FlxKey> = 0;
|
||||
var keyP2:Null<FlxKey> = 0;
|
||||
|
||||
ControlsUtil.initCustomControl(PlayerSettings.solo.controls, name);
|
||||
ControlsUtil.initCustomControl(PlayerSettings.player1.controls, name);
|
||||
ControlsUtil.initCustomControl(PlayerSettings.player2.controls, name);
|
||||
|
||||
if (saveName != "") {
|
||||
var p1:Null<FlxKey> = Reflect.getProperty(FlxG.save.data, "P1_"+saveName);
|
||||
var p2:Null<FlxKey> = Reflect.getProperty(FlxG.save.data, "P2_"+saveName);
|
||||
|
||||
//check for save data (and load default if it doesnt exist)
|
||||
if (p1 != null) {
|
||||
keyP1 = p1;
|
||||
} else {
|
||||
if (defaultKeyP1 != "") keyP1 = FlxKey.fromString(defaultKeyP1);
|
||||
Reflect.setProperty(FlxG.save.data, "P1_"+saveName, keyP1);
|
||||
}
|
||||
|
||||
if (p2 != null) {
|
||||
keyP2 = p2;
|
||||
} else {
|
||||
if (defaultKeyP2 != "") keyP2 = FlxKey.fromString(defaultKeyP2);
|
||||
Reflect.setProperty(FlxG.save.data, "P2_"+saveName, keyP2);
|
||||
}
|
||||
|
||||
} else {
|
||||
//if no save data just load the default
|
||||
if (defaultKeyP1 != "") keyP1 = FlxKey.fromString(defaultKeyP1);
|
||||
if (defaultKeyP2 != "") keyP2 = FlxKey.fromString(defaultKeyP2);
|
||||
}
|
||||
|
||||
ControlsUtil.addKeysToCustomControl(PlayerSettings.solo.controls, name, [keyP1, keyP2]);
|
||||
ControlsUtil.addKeysToCustomControl(PlayerSettings.player1.controls, name, [keyP1, 0]);
|
||||
ControlsUtil.addKeysToCustomControl(PlayerSettings.player2.controls, name, [0, keyP2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,13 @@
|
||||
package funkin.backend.utils;
|
||||
|
||||
#if cpp
|
||||
import cpp.Float64;
|
||||
#end
|
||||
import flxanimate.data.AnimationData.AnimAtlas;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import openfl.display.BitmapData;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
#if sys
|
||||
import sys.FileSystem;
|
||||
#end
|
||||
@@ -37,6 +45,9 @@ import haxe.io.Path;
|
||||
import haxe.xml.Access;
|
||||
import lime.utils.Assets;
|
||||
import openfl.geom.ColorTransform;
|
||||
import flixel.math.FlxPoint;
|
||||
import haxe.Constraints.IMap;
|
||||
import haxe.EnumTools.EnumValueTools;
|
||||
|
||||
using StringTools;
|
||||
|
||||
@@ -44,7 +55,7 @@ using StringTools;
|
||||
* Various utilities, that have no specific Util class.
|
||||
**/
|
||||
@:allow(funkin.game.PlayState)
|
||||
class CoolUtil
|
||||
final class CoolUtil
|
||||
{
|
||||
/**
|
||||
* Gets the last exception stack. Useful for debugging.
|
||||
@@ -166,7 +177,7 @@ class CoolUtil
|
||||
if(result.isNothing) Logs.trace('The file where it has been tried to get the attributes from, might be corrupted or inexistent (code: ${result.getValue()})', WARNING);
|
||||
return result;
|
||||
#else
|
||||
return 0;
|
||||
return new FileAttributeWrapper(0);
|
||||
#end
|
||||
}
|
||||
|
||||
@@ -204,7 +215,7 @@ class CoolUtil
|
||||
addMissingFolders(Path.directory(path));
|
||||
|
||||
var result = NativeAPI.addFileAttributes(path, attrib, useAbsolute);
|
||||
if(result == 0) Logs.trace('Failed to add attributes to $path with a code of: $result', WARNING);
|
||||
if (result == 0) Logs.trace('Failed to add attributes to $path with a code of: $result', WARNING);
|
||||
return result;
|
||||
#else
|
||||
return 0;
|
||||
@@ -265,10 +276,8 @@ class CoolUtil
|
||||
* Whenever a value is NaN or not.
|
||||
* @param v Value
|
||||
*/
|
||||
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 isNaN(v:Dynamic):Bool {
|
||||
return (v is Float) ? Math.isNaN(cast(v, Float)) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,22 +335,41 @@ class CoolUtil
|
||||
return str;
|
||||
}
|
||||
|
||||
private static var sizeLabels:Array<String> = ["B", "KB", "MB", "GB", "TB"];
|
||||
|
||||
/**
|
||||
* Returns a string representation of a size, following this format: `1.02 GB`, `134.00 MB`
|
||||
* @param size Size to convert to string
|
||||
* @return String Result string representation
|
||||
*/
|
||||
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) {
|
||||
var len = sizeLabels.length;
|
||||
while(rSize >= 1024 && label < len-1) {
|
||||
label++;
|
||||
rSize /= 1024;
|
||||
}
|
||||
return '${Std.int(rSize) + "." + addZeros(Std.string(Std.int((rSize % 1) * 100)), 2)}${labels[label]}';
|
||||
return Std.int(rSize) + ((label <= 1) ? "" : "." + addZeros(Std.string(Std.int((rSize % 1) * 100)), 2)) + sizeLabels[label];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of a size, following this format: `1.02 GB`, `134.00 MB`, using Float64 on cpp targets
|
||||
* @param size Size to convert to string
|
||||
* @return String Result string representation
|
||||
*/
|
||||
public static function getSizeString64(size: #if cpp Float64 #else Float #end):String {
|
||||
var rSize: #if cpp Float64 #else Float #end = size;
|
||||
var label:Int = 0;
|
||||
var len = sizeLabels.length;
|
||||
while(rSize >= 1024 && label < len-1) {
|
||||
label++;
|
||||
rSize /= 1024;
|
||||
}
|
||||
return Std.int(rSize) + ((label <= 1) ? "" : "." + addZeros(Std.string(Std.int((rSize % 1) * 100)), 2)) + sizeLabels[label];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces in a string any kind of IP with `[Your IP]` making the string safer to trace.
|
||||
* @param msg String to check and edit
|
||||
@@ -380,7 +408,7 @@ class CoolUtil
|
||||
* @return FPS-Modified Ratio
|
||||
*/
|
||||
@:noUsing public static inline function getFPSRatio(ratio:Float):Float {
|
||||
return FlxMath.bound(ratio * 60 * FlxG.elapsed, 0, 1);
|
||||
return CoolUtil.bound(ratio * 60 * FlxG.elapsed, 0.0, 1.0);
|
||||
}
|
||||
/**
|
||||
* Tries to get a color from a `Dynamic` variable.
|
||||
@@ -427,7 +455,6 @@ class CoolUtil
|
||||
if (FlxG.sound.music == null || !FlxG.sound.music.playing)
|
||||
{
|
||||
playMusic(Paths.music('freakyMenu'), true, fadeIn ? 0 : 1, true, 102);
|
||||
FlxG.sound.music.persist = true;
|
||||
if (fadeIn)
|
||||
FlxG.sound.music.fadeIn(4, 0, 0.7);
|
||||
}
|
||||
@@ -457,32 +484,34 @@ class CoolUtil
|
||||
*/
|
||||
@:noUsing public static function playMusic(path:String, Persist:Bool = false, Volume:Float = 1, Looped:Bool = true, DefaultBPM:Float = 102, ?Group:FlxSoundGroup) {
|
||||
Conductor.reset();
|
||||
FlxG.sound.playMusic(path, Volume, Looped, Group);
|
||||
if (FlxG.sound.music != null) {
|
||||
FlxG.sound.music.persist = Persist;
|
||||
}
|
||||
if (FlxG.sound.music == null) FlxG.sound.music = new FlxSound();
|
||||
else if (FlxG.sound.music.active) FlxG.sound.music.stop();
|
||||
FlxG.sound.music.loadEmbedded(path, Looped);
|
||||
FlxG.sound.music.volume = Volume;
|
||||
FlxG.sound.music.persist = Persist;
|
||||
FlxG.sound.defaultMusicGroup.add(FlxG.sound.music);
|
||||
|
||||
var infoPath = '${Path.withoutExtension(path)}.ini';
|
||||
if (Assets.exists(infoPath)) {
|
||||
var musicInfo = IniUtil.parseAsset(infoPath, [
|
||||
"BPM" => null,
|
||||
"TimeSignature" => Flags.DEFAULT_BEATS_PER_MEASURE + "/" + Flags.DEFAULT_STEPS_PER_BEAT
|
||||
]);
|
||||
var iniPath = '${Path.withoutExtension(path)}.ini';
|
||||
var musicData = Assets.exists(iniPath) ? IniUtil.parseAsset(iniPath)["Global"] : null;
|
||||
if (musicData != null) {
|
||||
if (musicData["LoopTime"] != null) FlxG.sound.music.loopTime = Std.parseFloat(musicData["LoopTime"]) * 1000;
|
||||
if (musicData["EndTime"] != null) FlxG.sound.music.endTime = Std.parseFloat(musicData["EndTime"]) * 1000;
|
||||
if (musicData["Offset"] != null) FlxG.sound.music.offset = Std.parseFloat(musicData["Offset"]) * 1000;
|
||||
|
||||
var timeSignParsed:Array<Null<Float>> = musicInfo["TimeSignature"] == null ? [] : [for(s in musicInfo["TimeSignature"].split("/")) Std.parseFloat(s)];
|
||||
var beatsPerMeasure:Float = Flags.DEFAULT_BEATS_PER_MEASURE;
|
||||
var stepsPerBeat:Int = Flags.DEFAULT_STEPS_PER_BEAT;
|
||||
|
||||
// Check later, i don't think timeSignParsed can contain null, only nan
|
||||
if (timeSignParsed.length == 2 && !timeSignParsed.contains(null)) {
|
||||
beatsPerMeasure = timeSignParsed[0] == null || timeSignParsed[0] <= 0 ? Flags.DEFAULT_BEATS_PER_MEASURE : cast timeSignParsed[0];
|
||||
stepsPerBeat = timeSignParsed[1] == null || timeSignParsed[1] <= 0 ? Flags.DEFAULT_STEPS_PER_BEAT : cast timeSignParsed[1];
|
||||
var timeSignParsed:Array<Null<Float>> = musicData["TimeSignature"] == null ? [] : [for(s in musicData["TimeSignature"].split("/")) Std.parseFloat(s)];
|
||||
var beatsPerMeasure:Float = Flags.DEFAULT_BEATS_PER_MEASURE, stepsPerBeat:Float = Flags.DEFAULT_STEPS_PER_BEAT;
|
||||
if (timeSignParsed.length == 2) {
|
||||
beatsPerMeasure = Math.isNaN(timeSignParsed[0]) ? Flags.DEFAULT_BEATS_PER_MEASURE : timeSignParsed[0];
|
||||
stepsPerBeat = Math.isNaN(timeSignParsed[1]) ? Flags.DEFAULT_STEPS_PER_BEAT : (16 / timeSignParsed[1]); // from denominator
|
||||
}
|
||||
|
||||
var bpm:Null<Float> = Std.parseFloat(musicInfo["BPM"]).getDefaultFloat(DefaultBPM);
|
||||
Conductor.changeBPM(bpm, beatsPerMeasure, stepsPerBeat);
|
||||
} else
|
||||
var bpm:Float = Std.parseFloat(musicData["BPM"]).getDefault(DefaultBPM);
|
||||
Conductor.changeBPM(bpm, beatsPerMeasure, Math.floor(stepsPerBeat));
|
||||
}
|
||||
else
|
||||
Conductor.changeBPM(DefaultBPM);
|
||||
|
||||
FlxG.sound.music.play();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -525,6 +554,23 @@ class CoolUtil
|
||||
* @return Array<Int> Final array
|
||||
*/
|
||||
@:noUsing public static inline function numberArray(max:Int, ?min:Int = 0):Array<Int>
|
||||
{
|
||||
/*return [for (i in min...max) i];*/
|
||||
// Reason for change:
|
||||
// Old one uses push instead of creating an array with the wanted size.
|
||||
var len:Int = CoolUtil.maxInt(max - min, 0);
|
||||
#if cpp
|
||||
var arr:Array<Int> = untyped __cpp__("::Array_obj< int >::__new({0})", len);
|
||||
#else
|
||||
var arr:Array<Int> = [];
|
||||
arr.resize(len);
|
||||
#end
|
||||
for(i in 0...len)
|
||||
arr[i] = i + min;
|
||||
return arr;
|
||||
}
|
||||
|
||||
@:noUsing public static inline function numberArrayOld(max:Int, ?min:Int = 0):Array<Int>
|
||||
{
|
||||
return [for (i in min...max) i];
|
||||
}
|
||||
@@ -557,6 +603,21 @@ class CoolUtil
|
||||
sprite.scale.set(nScale, nScale);
|
||||
}
|
||||
|
||||
public static function setGraphicSizeFloat(sprite:FlxSprite, Width:Float = 0, Height:Float = 0):Void
|
||||
{
|
||||
if (Width <= 0 && Height <= 0)
|
||||
return;
|
||||
|
||||
var newScaleX:Float = Width / sprite.frameWidth;
|
||||
var newScaleY:Float = Height / sprite.frameHeight;
|
||||
sprite.scale.set(newScaleX, newScaleY);
|
||||
|
||||
if (Width <= 0)
|
||||
sprite.scale.x = newScaleY;
|
||||
else if (Height <= 0)
|
||||
sprite.scale.y = newScaleX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a simple string representation of a FlxKey. Used in Controls options.
|
||||
* @param key Key
|
||||
@@ -614,7 +675,6 @@ class CoolUtil
|
||||
case Y:
|
||||
obj.y = (cam.height - obj.height) / 2;
|
||||
case NONE:
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,8 +696,16 @@ class CoolUtil
|
||||
* @param name Name of the attribute
|
||||
*/
|
||||
public static inline function getAtt(xml:Access, name:String) {
|
||||
if (!xml.has.resolve(name)) return null;
|
||||
return xml.att.resolve(name);
|
||||
/*if (!xml.has.resolve(name)) return null;
|
||||
return xml.att.resolve(name);*/
|
||||
// Reason for change:
|
||||
// Old one has error checking 4 times, this one has it once.
|
||||
var xml:Xml = xml.x;
|
||||
if (xml.nodeType != Element) {
|
||||
throw 'Bad node type, expected Element but found ${xml.nodeType}';
|
||||
}
|
||||
@:privateAccess
|
||||
return xml.attributeMap.exists(name) ? xml.attributeMap.get(name) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -686,11 +754,11 @@ class CoolUtil
|
||||
* @param spr Sprite to load the graphic for
|
||||
* @param path Path to the graphic
|
||||
*/
|
||||
public static function loadAnimatedGraphic(spr:FlxSprite, path:String) {
|
||||
public static function loadAnimatedGraphic(spr:FlxSprite, path:String, fps:Float = 24.0) {
|
||||
spr.frames = Paths.getFrames(path, true);
|
||||
|
||||
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.add("idle", [for(i in 0...spr.frames.frames.length) i], fps, true);
|
||||
spr.animation.play("idle");
|
||||
}
|
||||
|
||||
@@ -736,7 +804,7 @@ class CoolUtil
|
||||
* Gets the macro class created by hscript-improved for an abstract / enum
|
||||
*/
|
||||
@:noUsing public static inline function getMacroAbstractClass(className:String) {
|
||||
return Type.resolveClass('${className}_HSC');
|
||||
return Type.resolveClass(className + '_HSC');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -753,6 +821,8 @@ class CoolUtil
|
||||
i--;
|
||||
}
|
||||
return i;*/
|
||||
// Reason for change:
|
||||
// Old one was made because at the time we didnt know the lastIndexOf function.
|
||||
return array.lastIndexOf(element);
|
||||
}
|
||||
|
||||
@@ -793,6 +863,24 @@ class CoolUtil
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Browse a path in the operating system's explorer
|
||||
* @param path
|
||||
*/
|
||||
public static inline function browsePath(path:String) {
|
||||
var formattedPath:String = Path.normalize(path);
|
||||
|
||||
#if windows
|
||||
formattedPath = formattedPath.replace("/", "\\");
|
||||
Sys.command("explorer", [formattedPath]);
|
||||
#elseif mac
|
||||
Sys.command("open", [formattedPath]);
|
||||
#elseif linux
|
||||
var cmd = Sys.command("xdg-open", [formattedPath]);
|
||||
if (cmd != 0) cmd = Sys.command("/usr/bin/xdg-open", [formattedPath]);
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a timestamp to a readable format such as `01:22` (`mm:ss`)
|
||||
*/
|
||||
@@ -818,6 +906,15 @@ class CoolUtil
|
||||
@:noUsing public static inline function maxInt(p1:Int, p2:Int)
|
||||
return p1 < p2 ? p2 : p1;
|
||||
|
||||
/**
|
||||
* Equivalent of `Math.min`, except doesn't require a Int -> Float -> Int conversion.
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @return return p1 > p2 ? p2 : p1
|
||||
*/
|
||||
@:noUsing public static inline function minInt(p1:Int, p2:Int)
|
||||
return p1 > p2 ? p2 : p1;
|
||||
|
||||
/**
|
||||
* Equivalent of `Math.floor`, except doesn't require a Int -> Float -> Int conversion.
|
||||
* @param e Value to get the floor of.
|
||||
@@ -849,10 +946,11 @@ class CoolUtil
|
||||
* @param music Music
|
||||
*/
|
||||
public static inline function setMusic(frontEnd:SoundFrontEnd, music:FlxSound) {
|
||||
if (frontEnd.music != null)
|
||||
@:privateAccess frontEnd.destroySound(frontEnd.music);
|
||||
if (frontEnd.music == music) return;
|
||||
|
||||
if (frontEnd.music != null) @:privateAccess frontEnd.destroySound(frontEnd.music);
|
||||
frontEnd.list.remove(music);
|
||||
frontEnd.music = music;
|
||||
frontEnd.defaultMusicGroup.add(frontEnd.music = music);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -860,8 +958,67 @@ class CoolUtil
|
||||
* @param mainEase Main ease
|
||||
* @param suffix Suffix (Ignored if `mainEase` is `linear`)
|
||||
*/
|
||||
@:noUsing public static inline function flxeaseFromString(mainEase:String, suffix:String)
|
||||
return Reflect.field(FlxEase, mainEase + (mainEase == "linear" ? "" : suffix));
|
||||
@:noUsing public static inline function flxeaseFromString(mainEase:String, ?suffix:String)
|
||||
return Reflect.field(FlxEase, mainEase + (mainEase == "linear" || suffix == null ? "" : suffix));
|
||||
|
||||
/*
|
||||
* Returns the filename of a path, without the extension.
|
||||
* @param path Path to get the filename from
|
||||
* @return Filename
|
||||
*/
|
||||
@:noUsing public static inline function getFilename(file:String) {
|
||||
var file = new haxe.io.Path(file);
|
||||
return file.file;
|
||||
}
|
||||
|
||||
@:noUsing public static function getClosestAngle(angle:Float, targetAngle:Float):Float {
|
||||
var diff:Float = angle - targetAngle;
|
||||
if (diff < -180) diff += 360;
|
||||
else if (diff > 180) diff -= 360;
|
||||
return angle - diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the screen position of an object, while taking the camera zoom into account.
|
||||
*
|
||||
* @param object Any `FlxObject`
|
||||
* @param camera The desired "screen" coordinate space. If `null`, `FlxG.camera` is used.
|
||||
* @param result Optional arg for the returning point
|
||||
* @return The screen position of the object.
|
||||
*/
|
||||
public static function worldToScreenPosition(object:FlxObject, ?camera:FlxCamera, ?result:FlxPoint) {
|
||||
if (result == null)
|
||||
result = FlxPoint.get();
|
||||
if (camera == null)
|
||||
camera = FlxG.camera;
|
||||
|
||||
result.set(object.x, object.y);
|
||||
result.x = (((result.x - camera.scroll.x * object.scrollFactor.x) * camera.zoom) - ((camera.width * 0.5) * (camera.zoom - camera.initialZoom)));
|
||||
result.y = (((result.y - camera.scroll.y * object.scrollFactor.y) * camera.zoom) - ((camera.height * 0.5) * (camera.zoom - camera.initialZoom)));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the screen position of an point, while taking the camera zoom into account.
|
||||
*
|
||||
* @param object Any `FlxObject`
|
||||
* @param camera The desired "screen" coordinate space. If `null`, `FlxG.camera` is used.
|
||||
* @param result Optional arg for the returning point
|
||||
* @return The screen position of the object.
|
||||
*/
|
||||
public static function pointToScreenPosition(object:FlxPoint, ?camera:FlxCamera, ?result:FlxPoint) {
|
||||
if (result == null)
|
||||
result = FlxPoint.get();
|
||||
if (camera == null)
|
||||
camera = FlxG.camera;
|
||||
|
||||
result.set(object.x, object.y);
|
||||
result.x = (((result.x - camera.scroll.x) * camera.zoom) - ((camera.width * 0.5) * (camera.zoom - camera.initialZoom)));
|
||||
result.y = (((result.y - camera.scroll.y) * camera.zoom) - ((camera.height * 0.5) * (camera.zoom - camera.initialZoom)));
|
||||
|
||||
object.putWeak();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts an array alphabetically.
|
||||
@@ -913,15 +1070,35 @@ class CoolUtil
|
||||
r.add(str);
|
||||
return r.toString();
|
||||
}
|
||||
|
||||
public static inline function bound(Value:Float, Min:Float, Max:Float):Float {
|
||||
#if cpp
|
||||
var _hx_tmp1:Float = Value;
|
||||
var _hx_tmp2:Float = Min;
|
||||
var _hx_tmp3:Float = Max;
|
||||
return untyped __cpp__("((({0}) < ({1})) ? ({1}) : (({0}) > ({2})) ? ({2}) : ({0}))", _hx_tmp1, _hx_tmp2, _hx_tmp3);
|
||||
#else
|
||||
return (Value < Min) ? Min : (Value > Max) ? Max : Value;
|
||||
#end
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the filename of a path, without the extension.
|
||||
* @param path Path to get the filename from
|
||||
* @return Filename
|
||||
*/
|
||||
@:noUsing public static inline function getFilename(file:String) {
|
||||
var file = new haxe.io.Path(file);
|
||||
return file.file;
|
||||
public static inline function boundInt(Value:Int, Min:Int, Max:Int):Int {
|
||||
#if cpp
|
||||
var _hx_tmp1:Int = Value;
|
||||
var _hx_tmp2:Int = Min;
|
||||
var _hx_tmp3:Int = Max;
|
||||
return untyped __cpp__("((({0}) < ({1})) ? ({1}) : (({0}) > ({2})) ? ({2}) : ({0}))", _hx_tmp1, _hx_tmp2, _hx_tmp3);
|
||||
#else
|
||||
return (Value < Min) ? Min : (Value > Max) ? Max : Value;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function boolToInt(b:Bool):Int {
|
||||
#if cpp
|
||||
return untyped __cpp__("(({0}) ? 1 : 0)", b);
|
||||
#else
|
||||
return b ? 1 : 0;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1028,6 +1205,301 @@ class CoolUtil
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the luminance of the given color
|
||||
* @param color Color to use
|
||||
* @return Number between 0 and 1
|
||||
*/
|
||||
public static function getLuminance(color:FlxColor):Float {
|
||||
return (0.2126*color.redFloat + 0.7152*color.greenFloat + 0.0722*color.blueFloat);
|
||||
}
|
||||
|
||||
/**
|
||||
* ! REQUIRES FULL PATH!!!
|
||||
* @param path
|
||||
* @return Bool
|
||||
*/
|
||||
public static function imageHasFrameData(path:String):String {
|
||||
if (FileSystem.exists(Path.withExtension(path, "xml"))) return "xml";
|
||||
if (FileSystem.exists(Path.withExtension(path, "txt"))) return "txt";
|
||||
if (FileSystem.exists(Path.withExtension(path, "json"))) return "json";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// loads frames with no image, but with data, so you can parse it
|
||||
public static function loadFramesFromData(data:String, ext:String = null):FlxFramesCollection {
|
||||
var frames:FlxFramesCollection = null;
|
||||
var tempBitmap:BitmapData = new BitmapData(1, 1, false);
|
||||
|
||||
// Worlds hackiest work around alert???
|
||||
var graphic:FlxGraphic = FlxG.bitmap.add(tempBitmap);
|
||||
@:privateAccess {
|
||||
// prevent errors from being shown, such as Size is too small
|
||||
graphic.width = 9999999;
|
||||
graphic.height = 9999999;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (ext) {
|
||||
case "xml": frames = FlxAtlasFrames.fromSparrow(graphic, Xml.parse(data));
|
||||
case "txt": frames = FlxAtlasFrames.fromSpriteSheetPacker(graphic, data);
|
||||
case "json": frames = FlxAtlasFrames.fromAseprite(graphic, data);
|
||||
}
|
||||
} catch (e) {trace(e);}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
public static function removeBOM(str:String):String {
|
||||
return StringTools.replace(str, String.fromCharCode(0xFEFF), "");
|
||||
}
|
||||
|
||||
public static function getAnimsListFromFrames(frames:FlxFramesCollection, ext:String = null):Array<String> {
|
||||
if (frames == null) return [];
|
||||
|
||||
var animsList:Array<String> = [];
|
||||
for (frame in frames.frames) {
|
||||
var animName:String = ext == "txt" ? frame.name.split("_")[0] : frame.name.substr(0, frame.name.length-4);
|
||||
if (!animsList.contains(animName))
|
||||
animsList.push(animName);
|
||||
}
|
||||
|
||||
return animsList;
|
||||
}
|
||||
|
||||
public static function getAnimsListFromAtlas(atlas:AnimAtlas):Array<String> {
|
||||
if (atlas == null) return [];
|
||||
|
||||
var animsList:Array<String> = [];
|
||||
if (atlas.AN.SN != null) animsList.push(atlas.AN.SN);
|
||||
if (atlas.SD != null)
|
||||
for (symbol in atlas.SD.S)
|
||||
if (symbol.SN != null) animsList.push(symbol.SN);
|
||||
|
||||
return animsList;
|
||||
}
|
||||
|
||||
public static function getAnimsListFromSprite(spr:FunkinSprite):Array<String> {
|
||||
if (spr.animateAtlas != null) {
|
||||
return [for (symbol => timeline in spr.animateAtlas.anim.symbolDictionary) symbol];
|
||||
} else
|
||||
return getAnimsListFromFrames(spr.frames);
|
||||
}
|
||||
|
||||
// TODO: check this for bugs
|
||||
// Code from https://github.com/elnabo/equals/blob/master/src/equals/Equal.hx, (MIT License), but updated to work with haxe 4
|
||||
public static function deepEqual<T> (a:T, b:T) : Bool {
|
||||
if (a == b) { return true; } // if physical equality
|
||||
if (isNull(a) || isNull(b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (Type.typeof(a)) {
|
||||
case TNull, TInt, TBool, TUnknown:
|
||||
return a == b;
|
||||
case TFloat:
|
||||
return Math.isNaN(cast a) && Math.isNaN(cast b); // only valid true result remaining
|
||||
case TFunction:
|
||||
return Reflect.compareMethods(a, b); // only physical equality can be tested for function
|
||||
case TEnum(_):
|
||||
if (EnumValueTools.getIndex(cast a) != EnumValueTools.getIndex(cast b)) {
|
||||
return false;
|
||||
}
|
||||
var a_args = EnumValueTools.getParameters(cast a);
|
||||
var b_args = EnumValueTools.getParameters(cast b);
|
||||
return deepEqual(a_args, b_args);
|
||||
case TClass(_):
|
||||
if ((a is String) && (b is String)) {
|
||||
return a == b;
|
||||
}
|
||||
if ((a is Array) && (b is Array)) {
|
||||
var a = cast(a, Array<Dynamic>);
|
||||
var b = cast(b, Array<Dynamic>);
|
||||
if (a.length != b.length) { return false; }
|
||||
for (i in 0...a.length) {
|
||||
if (!deepEqual(a[i], b[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((a is IMap) && (b is IMap)) {
|
||||
var a = cast(a, IMap<Dynamic, Dynamic>);
|
||||
var b = cast(b, IMap<Dynamic, Dynamic>);
|
||||
var a_keys = [ for (key in a.keys()) key ];
|
||||
var b_keys = [ for (key in b.keys()) key ];
|
||||
a_keys.sort(Reflect.compare);
|
||||
b_keys.sort(Reflect.compare);
|
||||
if (!deepEqual(a_keys, b_keys)) { return false; }
|
||||
for (key in a_keys) {
|
||||
if (!deepEqual(a.get(key), b.get(key))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((a is Date) && (b is Date)) {
|
||||
return cast(a, Date).getTime() == cast(b, Date).getTime();
|
||||
}
|
||||
|
||||
if ((a is haxe.io.Bytes) && (b is haxe.io.Bytes)) {
|
||||
return deepEqual(cast(a, haxe.io.Bytes).getData(), cast(b, haxe.io.Bytes).getData());
|
||||
}
|
||||
|
||||
case TObject:
|
||||
}
|
||||
|
||||
for (field in Reflect.fields(a)) {
|
||||
var pa = Reflect.field(a, field);
|
||||
var pb = Reflect.field(b, field);
|
||||
if (isFunction(pa)) {
|
||||
// ignore function as only physical equality can be tested, unless null
|
||||
if (isNull(pa) != isNull(pb)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!deepEqual(pa, pb)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline function isNull(a:Dynamic):Bool {
|
||||
return Type.enumEq(Type.typeof(a), TNull);
|
||||
}
|
||||
|
||||
static inline function isFunction(a:Dynamic):Bool {
|
||||
return Type.enumEq(Type.typeof(a), TFunction);
|
||||
}
|
||||
|
||||
public static inline function isMapEmpty<K, V>(map: Map<K, V>): Bool {
|
||||
return !map.keys().hasNext();
|
||||
}
|
||||
|
||||
public inline static function parsePropertyString(fieldPath:String):Array<OneOfTwo<String, Int>> {
|
||||
return FlxTween.parseFieldString(fieldPath);
|
||||
}
|
||||
|
||||
public static function stringifyFieldsPath(fields:Array<OneOfTwo<String, Int>>):String {
|
||||
var str = new StringBuf();
|
||||
var first = true;
|
||||
for (field in fields) {
|
||||
if (Type.typeof(field) == TInt) {
|
||||
str.add('[${field}]');
|
||||
} else {
|
||||
if (!first)
|
||||
str.add('.');
|
||||
str.add(field);
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
public static function parseProperty(target:Dynamic, fields:OneOfTwo<String, Array<OneOfTwo<String, Int>>>):Dynamic {
|
||||
var fields:Array<OneOfTwo<String, Int>> = {
|
||||
if((fields is String)) CoolUtil.parsePropertyString(fields);
|
||||
else fields;
|
||||
}
|
||||
|
||||
var field = CoolUtil.last(fields);
|
||||
for (i in 0...fields.length - 1) {
|
||||
var component = fields[i];
|
||||
if (Type.typeof(component) == TInt) {
|
||||
if ((target is Array)) {
|
||||
var index:Int = cast component;
|
||||
var arr:Array<Dynamic> = cast target;
|
||||
target = arr[index];
|
||||
}
|
||||
} else { // TClass(String)
|
||||
target = Reflect.getProperty(target, component);
|
||||
}
|
||||
if (!Reflect.isObject(target) && !(target is Array))
|
||||
throw 'The object does not have the property "$component" in "${stringifyFieldsPath(fields)}"';
|
||||
}
|
||||
return new PropertyInfo(target, field);
|
||||
}
|
||||
|
||||
public static function cloneProperty(toTarget:Dynamic, fields:OneOfTwo<String, Array<OneOfTwo<String, Int>>>, fromTarget:Dynamic):Dynamic {
|
||||
var fields:Array<OneOfTwo<String, Int>> = {
|
||||
if((fields is String)) CoolUtil.parsePropertyString(fields);
|
||||
else fields;
|
||||
}
|
||||
|
||||
var toProperty = CoolUtil.parseProperty(toTarget, fields);
|
||||
var fromProperty = CoolUtil.parseProperty(fromTarget, fields);
|
||||
|
||||
return toProperty.setValue(fromProperty.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
class PropertyInfo {
|
||||
public var object:Dynamic;
|
||||
public var field:OneOfTwo<String, Int>;
|
||||
public var typeOfField:Type.ValueType;
|
||||
#if hscript_improved
|
||||
public var isCustom:Bool = false;
|
||||
public var custom:hscript.IHScriptCustomBehaviour;
|
||||
#end
|
||||
|
||||
public function new(object:Dynamic, field:OneOfTwo<String, Int>) {
|
||||
this.object = object;
|
||||
this.field = field;
|
||||
#if hscript_improved
|
||||
if (object is hscript.IHScriptCustomBehaviour)
|
||||
{
|
||||
isCustom = true;
|
||||
custom = cast object;
|
||||
}
|
||||
#end
|
||||
|
||||
typeOfField = Type.typeof(field);
|
||||
}
|
||||
|
||||
public function getValue():Dynamic
|
||||
{
|
||||
if (typeOfField == TInt)
|
||||
{
|
||||
var index:Int = cast field;
|
||||
var arr:Array<Dynamic> = cast object;
|
||||
return arr[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
#if hscript_improved
|
||||
if (isCustom)
|
||||
return custom.hget(field);
|
||||
else
|
||||
#end
|
||||
return Reflect.getProperty(object, field);
|
||||
}
|
||||
}
|
||||
|
||||
public function setValue(value:Dynamic):Void
|
||||
{
|
||||
if (typeOfField == TInt)
|
||||
{
|
||||
var index:Int = cast field;
|
||||
var arr:Array<Dynamic> = cast object;
|
||||
arr[index] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if hscript_improved
|
||||
if (isCustom)
|
||||
custom.hset(field, value);
|
||||
else
|
||||
#end
|
||||
Reflect.setProperty(object, field, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,7 @@ import lime.app.Application;
|
||||
import sys.thread.Thread;
|
||||
#end
|
||||
|
||||
class DiscordUtil
|
||||
final class DiscordUtil
|
||||
{
|
||||
public static var currentID(default, set):String = null;
|
||||
public static var discordThread:#if DISCORD_RPC Thread #else Dynamic #end = null;
|
||||
@@ -89,6 +89,10 @@ class DiscordUtil
|
||||
config.logoKey = config.logoKey.getDefault(Flags.DEFAULT_DISCORD_LOGO_KEY);
|
||||
config.logoText = config.logoText.getDefault(Flags.DEFAULT_DISCORD_LOGO_TEXT);
|
||||
config.clientID = config.clientID.getDefault(Flags.DEFAULT_DISCORD_CLIENT_ID);
|
||||
|
||||
if (Flags.MOD_DISCORD_CLIENT_ID.length > 0) config.clientID = Flags.MOD_DISCORD_CLIENT_ID;
|
||||
if (Flags.MOD_DISCORD_LOGO_KEY.length > 0) config.logoKey = Flags.MOD_DISCORD_LOGO_KEY;
|
||||
if (Flags.MOD_DISCORD_LOGO_TEXT.length > 0) config.logoText = Flags.MOD_DISCORD_LOGO_TEXT;
|
||||
currentID = config.clientID;
|
||||
#end
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package funkin.backend.utils;
|
||||
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.math.FlxAngle;
|
||||
import flixel.math.FlxPoint;
|
||||
|
||||
final class DrawUtil {
|
||||
public static var line:FlxSprite = null;
|
||||
public static var dot:FlxSprite = null;
|
||||
public static var square:FlxSprite = null;
|
||||
|
||||
public static inline function drawSquare(x:Float, y:Float, ?scale:Float = 1) {
|
||||
if (square == null) createDrawers();
|
||||
|
||||
square.setPosition(x, y);
|
||||
|
||||
square.scale.set(scale, scale);
|
||||
square.updateHitbox();
|
||||
|
||||
square.x -= square.width / 2;
|
||||
square.y -= square.height / 2;
|
||||
square.draw();
|
||||
}
|
||||
|
||||
public static inline function drawDot(x:Float, y:Float, ?scale:Float = 1) {
|
||||
if (dot == null) createDrawers();
|
||||
|
||||
dot.setPosition(x, y);
|
||||
|
||||
dot.scale.set(scale, scale);
|
||||
dot.updateHitbox();
|
||||
|
||||
dot.x -= dot.width / 2;
|
||||
dot.y -= dot.height / 2;
|
||||
dot.draw();
|
||||
}
|
||||
|
||||
public static inline function drawLine(point1:FlxPoint, point2:FlxPoint, thickness:Float = 1, ?color:Null<FlxColor>) {
|
||||
if (line == null) createDrawers();
|
||||
|
||||
var dx:Float = point2.x - point1.x;
|
||||
var dy:Float = point2.y - point1.y;
|
||||
|
||||
var angle:Float = Math.atan2(dy, dx);
|
||||
var distance:Float = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// Math.ceil to prevent flickering
|
||||
line.setPosition(point1.x, point1.y);
|
||||
line.angle = angle * FlxAngle.TO_DEG;
|
||||
line.origin.set(0, line.frameHeight / 2);
|
||||
line.scale.x = distance / line.frameWidth;
|
||||
line.scale.y = thickness;
|
||||
line.y -= line.height / 2;
|
||||
if (color != null) line.color = color;
|
||||
line.draw();
|
||||
|
||||
line.angle = 0;
|
||||
line.scale.x = line.scale.y = 1;
|
||||
line.updateHitbox();
|
||||
|
||||
point1.putWeak(); point2.putWeak();
|
||||
}
|
||||
|
||||
public static inline function drawRect(rect:FlxRect, thickness:Float = 1, ?color:Null<FlxColor>) {
|
||||
if (rect.width <= 0 || rect.height <= 0) return;
|
||||
DrawUtil.drawLine(FlxPoint.weak(rect.x, rect.y), FlxPoint.weak(rect.x + rect.width, rect.y), thickness, color);
|
||||
DrawUtil.drawLine(FlxPoint.weak(rect.x, rect.y), FlxPoint.weak(rect.x, rect.y + rect.height), thickness, color);
|
||||
DrawUtil.drawLine(FlxPoint.weak(rect.x + rect.width, rect.y), FlxPoint.weak(rect.x + rect.width, rect.y + rect.height), thickness, color);
|
||||
DrawUtil.drawLine(FlxPoint.weak(rect.x, rect.y + rect.height), FlxPoint.weak(rect.x + rect.width, rect.y + rect.height), thickness, color);
|
||||
}
|
||||
|
||||
public static inline function createDrawers() {
|
||||
dot = new FlxSprite().loadGraphic(Paths.image("editors/stage/selectionDot"), true, 32, 32);
|
||||
dot.antialiasing = true;
|
||||
dot.animation.add("default", [0], 0, false);
|
||||
dot.animation.add("hollow", [1], 0, false);
|
||||
dot.animation.play("default");
|
||||
dot.camera = FlxG.camera;
|
||||
dot.forceIsOnScreen = true;
|
||||
|
||||
line = new FlxSprite().makeGraphic(1, 1, FlxColor.WHITE);
|
||||
line.camera = FlxG.camera;
|
||||
line.forceIsOnScreen = true;
|
||||
|
||||
square = new FlxSprite().makeGraphic(32, 32, FlxColor.WHITE);
|
||||
square.camera = FlxG.camera;
|
||||
square.forceIsOnScreen = true;
|
||||
}
|
||||
|
||||
public static function destroyDrawers() {
|
||||
if(dot != null) {
|
||||
dot.destroy();
|
||||
dot = null;
|
||||
}
|
||||
if(line != null) {
|
||||
line.destroy();
|
||||
line = null;
|
||||
}
|
||||
if(square != null) {
|
||||
square.destroy();
|
||||
square = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package funkin.backend.utils;
|
||||
import funkin.backend.scripting.MultiThreadedScript;
|
||||
import funkin.backend.scripting.Script;
|
||||
|
||||
class EngineUtil {
|
||||
final class EngineUtil {
|
||||
/**
|
||||
* Starts a new multithreaded script.
|
||||
* This script will share all the variables with the current one, which means already existing callbacks will be replaced by new ones on conflict.
|
||||
|
||||
@@ -2,7 +2,7 @@ package funkin.backend.utils;
|
||||
|
||||
import haxe.Http;
|
||||
|
||||
class HttpUtil
|
||||
final class HttpUtil
|
||||
{
|
||||
public static var userAgent:String = Flags.USER_AGENT;
|
||||
public static function requestText(url:String)
|
||||
|
||||
@@ -1,42 +1,33 @@
|
||||
package funkin.backend.utils;
|
||||
|
||||
typedef IniMap = Map<String, String>;
|
||||
|
||||
/**
|
||||
* Parses an ini file and returns a map of its contents.
|
||||
* WARNING: This is not a full ini parser, it only supports the basics. So no sections
|
||||
*/
|
||||
class IniUtil {
|
||||
public static inline function parseAsset(assetPath:String, ?defaultVariables:IniMap):IniMap
|
||||
return parseString(Assets.getText(assetPath), defaultVariables);
|
||||
public static inline function parseAsset(assetPath:String)
|
||||
return parseString(Assets.getText(assetPath));
|
||||
|
||||
public static function parseString(data:String, ?defaultVariables:IniMap):IniMap {
|
||||
var trimmed:String;
|
||||
var splitContent = [for(e in data.split("\n")) if ((trimmed = e.trim()) != "") trimmed];
|
||||
public static function parseString(data:String):Map<String, Map<String, String>> {
|
||||
var res:Map<String, Map<String, String>> = [];
|
||||
parseStringToMap(res, data);
|
||||
return res;
|
||||
}
|
||||
|
||||
var finalMap:IniMap = [];
|
||||
if (defaultVariables != null)
|
||||
for(k=>e in defaultVariables)
|
||||
finalMap[k] = e;
|
||||
public static function parseStringToMap(map:Map<String, Map<String, String>>, data:String) {
|
||||
var section:Map<String, String> = null;
|
||||
|
||||
for(line in splitContent) {
|
||||
// comment
|
||||
if (line.startsWith(";")) continue;
|
||||
// sections; not supported yet
|
||||
if (line.startsWith("[") && line.endsWith("]")) continue;
|
||||
var regexSec = ~/\\[(.+)\\]/g, regexVal = ~/(.+)=(.+)/g, quote = ~/[\\'"](.+)[\\'"]/g, iln = 0;
|
||||
do {
|
||||
var line = data.substring(iln, ((iln = data.indexOf('\n', iln) + 1) == 0 ? data.length : iln - 1));
|
||||
if (regexSec.match(line)) {
|
||||
if ((section = map.get(regexSec.matched(1))) == null) map.set(regexSec.matched(1), section = []);
|
||||
}
|
||||
else if (regexVal.match(line)) {
|
||||
var s = regexVal.matched(2).trim();
|
||||
if (quote.match(s)) s = quote.matched(1);
|
||||
|
||||
var index = line.indexOf("=");
|
||||
var name = line.substr(0, index).trim();
|
||||
var value = line.substr(index+1).trim();
|
||||
|
||||
if (value.length > 1 && value.charCodeAt(0) == '"'.code && value.charCodeAt(value.length-1) == '"'.code)
|
||||
value = value.substr(1, value.length - 2);
|
||||
|
||||
if (value.length == 0 || name.length == 0)
|
||||
continue;
|
||||
|
||||
finalMap[name] = value;
|
||||
}
|
||||
return finalMap;
|
||||
if (section == null) {
|
||||
if ((section = map.get("Global")) == null) map.set("Global", section = []);
|
||||
}
|
||||
section.set(regexVal.matched(1).trim(), s);
|
||||
}
|
||||
} while (iln != 0);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package funkin.backend.utils;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
class MarkdownUtil {
|
||||
final class MarkdownUtil {
|
||||
public static function applyMarkdownText(text:FlxText, str:String) {
|
||||
text.textField.htmlText = Markdown.markdownToHtml(prepareMarkdown(str));
|
||||
var changes:Array<FlxTextFormatMarkerPair> = [
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
package funkin.backend.utils;
|
||||
|
||||
import haxe.macro.Expr;
|
||||
|
||||
final class MathUtil {
|
||||
/**
|
||||
* Returns the maximum value in the arguments.
|
||||
* @param args Array of values
|
||||
*
|
||||
* @return The maximum value
|
||||
**/
|
||||
public static function maxInt(...args:Int):Int {
|
||||
var max = args[0];
|
||||
for(i in 1...args.length) {
|
||||
var arg = args[i];
|
||||
if(arg > max)
|
||||
max = arg;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum value in the arguments.
|
||||
* @param args Array of values
|
||||
*
|
||||
* @return The minimum value
|
||||
**/
|
||||
public static function minInt(...args:Int):Int {
|
||||
var min = args[0];
|
||||
for(i in 1...args.length) {
|
||||
var arg = args[i];
|
||||
if(arg < min)
|
||||
min = arg;
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum value in the arguments.
|
||||
*
|
||||
* NOTE: If you are using this in compile time, you should use `MathUtil.maxSmart` instead of this for better performance.
|
||||
*
|
||||
* @param args Array of values
|
||||
*
|
||||
* @return The maximum value
|
||||
**/
|
||||
public static function max(...args:Float):Float {
|
||||
var max = args[0];
|
||||
for(i in 1...args.length) {
|
||||
var arg = args[i];
|
||||
if(arg > max)
|
||||
max = arg;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum value in the arguments.
|
||||
*
|
||||
* NOTE: If you are using this in compile time, you should use `MathUtil.minSmart` instead of this for better performance.
|
||||
*
|
||||
* @param args Array of values
|
||||
*
|
||||
* @return The minimum value
|
||||
**/
|
||||
public static function min(...args:Float):Float {
|
||||
var min = args[0];
|
||||
for(i in 1...args.length) {
|
||||
var arg = args[i];
|
||||
if(arg < min)
|
||||
min = arg;
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a is less than b with considering a margin of error.
|
||||
*
|
||||
* @param a Float
|
||||
* @param b Float
|
||||
* @param margin Float (Default: EPSILON)
|
||||
*
|
||||
* @return Bool
|
||||
**/
|
||||
public static function lessThan(a:Float, b:Float, margin:Float = 0.0000001):Bool {
|
||||
return a < b - margin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a is less than or equally b with considering a margin of error.
|
||||
*
|
||||
* @param a Float
|
||||
* @param b Float
|
||||
* @param margin Float (Default: EPSILON)
|
||||
*
|
||||
* @return Bool
|
||||
**/
|
||||
public static function lessThanEqual(a:Float, b:Float, margin:Float = 0.0000001):Bool {
|
||||
return a <= b - margin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a is greater than b with considering a margin of error.
|
||||
*
|
||||
* @param a Float
|
||||
* @param b Float
|
||||
* @param margin Float (Default: EPSILON)
|
||||
*
|
||||
* @return Bool
|
||||
**/
|
||||
public static function greaterThan(a:Float, b:Float, margin:Float = 0.0000001):Bool {
|
||||
return a > b + margin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a is greater than or equally b with considering a margin of error.
|
||||
*
|
||||
* @param a Float
|
||||
* @param b Float
|
||||
* @param margin Float (Default: EPSILON)
|
||||
*
|
||||
* @return Bool
|
||||
**/
|
||||
public static function greaterThanEqual(a:Float, b:Float, margin:Float = 0.0000001):Bool {
|
||||
return a >= b + margin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a is approximately equal to b.
|
||||
*
|
||||
* @param a Float
|
||||
* @param b Float
|
||||
* @param margin Float (Default: EPSILON)
|
||||
*
|
||||
* @return Bool
|
||||
**/
|
||||
public static function equal(a:Float, b:Float, margin:Float = 0.0000001):Bool {
|
||||
return Math.abs(a - b) <= margin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a are not approximately equal to b.
|
||||
*
|
||||
* @param a Float
|
||||
* @param b Float
|
||||
* @param margin Float (Default: EPSILON)
|
||||
*
|
||||
* @return Bool
|
||||
**/
|
||||
public static function notEqual(a:Float, b:Float, margin:Float = 0.0000001):Bool {
|
||||
return Math.abs(a - b) > margin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to `Math.max` but with infinite amount of arguments
|
||||
*
|
||||
* Might not preserve the order of arguments, please test this.
|
||||
*
|
||||
* Dont use this in hscript, it doesnt work, it only works on compile time
|
||||
**/
|
||||
@:dox(hide) public static macro function maxSmart(..._args:Expr):Expr {
|
||||
return genericMinMaxSmart(_args.toArray(), "Math.max");
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to `Math.min` but with infinite amount of arguments
|
||||
*
|
||||
* Might not preserve the order of arguments, please test this.
|
||||
*
|
||||
* Dont use this in hscript, it doesnt work, it only works on compile time
|
||||
**/
|
||||
@:dox(hide) public static macro function minSmart(..._args:Expr):Expr {
|
||||
return genericMinMaxSmart(_args.toArray(), "Math.min");
|
||||
}
|
||||
|
||||
#if macro
|
||||
@:dox(hide) private static function genericMinMaxSmart(_args:Array<Expr>, funcPath:String):Expr {
|
||||
var args = _args.copy();
|
||||
if (args.length == 0) return macro 0;
|
||||
|
||||
var func = funcPath.split(".");
|
||||
|
||||
function nested(lst:Array<Expr>):Expr {
|
||||
if (lst.length == 1) {
|
||||
return macro ${lst[0]};
|
||||
} else if (lst.length == 2) {
|
||||
return macro $p{func}(${lst[0]}, ${lst[1]});
|
||||
} else {
|
||||
var mid = Std.int(lst.length / 2);
|
||||
return macro $p{func}(${nested(lst.slice(0, mid))}, ${nested(lst.slice(mid, lst.length))});
|
||||
}
|
||||
}
|
||||
|
||||
var expr = nested(args);
|
||||
|
||||
//var printer = new haxe.macro.Printer();
|
||||
//trace(printer.printExpr(expr));
|
||||
|
||||
return macro $expr;
|
||||
}
|
||||
#end
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package funkin.backend.utils;
|
||||
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.typeLimit.OneOfTwo;
|
||||
import funkin.backend.system.FakeCamera;
|
||||
import funkin.backend.system.FakeCamera.FakeCallCamera;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.graphics.tile.FlxDrawTrianglesItem;
|
||||
import flixel.math.FlxMatrix;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.BlendMode;
|
||||
import openfl.geom.ColorTransform;
|
||||
import openfl.geom.Point;
|
||||
import openfl.geom.Rectangle;
|
||||
|
||||
interface IPrePostDraw {
|
||||
public function preDraw():Void;
|
||||
public function postDraw():Void;
|
||||
}
|
||||
|
||||
@:access(flixel.FlxCamera)
|
||||
@:access(flixel.FlxSprite)
|
||||
@:access(flixel.math.FlxMatrix)
|
||||
@:access(openfl.geom.Matrix)
|
||||
final class MatrixUtil {
|
||||
public static function getMatrixPosition(sprite:FlxSprite, points:OneOfTwo<FlxPoint, Array<FlxPoint>>, ?camera:FlxCamera, _width:Float = 1, _height:Float = 1):Array<FlxPoint>
|
||||
{
|
||||
//if(_width == -1) _width = sprite.width;
|
||||
//if(_height == -1) _height = sprite.height;
|
||||
if(camera == null) camera = sprite.camera;
|
||||
if(points is FlxBasePoint) points = [points];
|
||||
var isFunkinSprite = sprite is FunkinSprite;
|
||||
var funkinSprite:FunkinSprite = null;
|
||||
if(isFunkinSprite) funkinSprite = cast sprite;
|
||||
var isAnimateAtlas = isFunkinSprite && funkinSprite.animateAtlas != null;
|
||||
|
||||
var nc:FakeCamera = isAnimateAtlas ? FakeCallCamera.instance : FakeCamera.instance;
|
||||
nc.zoom = camera.zoom;
|
||||
nc.scroll.set(camera.scroll.x, camera.scroll.y);
|
||||
nc.pixelPerfectRender = camera.pixelPerfectRender;
|
||||
|
||||
var points:Array<FlxPoint> = cast points;
|
||||
|
||||
if(isAnimateAtlas) {
|
||||
var cnc = FakeCallCamera.instance;
|
||||
var sprite = funkinSprite;
|
||||
var oldOnDraw = cnc.onDraw;
|
||||
|
||||
// TODO: fix rotation
|
||||
// TODO: fix skew
|
||||
|
||||
var boundsTopLeft = new FlxPoint(Math.POSITIVE_INFINITY, Math.POSITIVE_INFINITY);
|
||||
var boundsTopRight = new FlxPoint(Math.NEGATIVE_INFINITY, Math.POSITIVE_INFINITY);
|
||||
var boundsBottomLeft = new FlxPoint(Math.POSITIVE_INFINITY, Math.NEGATIVE_INFINITY);
|
||||
var boundsBottomRight = new FlxPoint(Math.NEGATIVE_INFINITY, Math.NEGATIVE_INFINITY);
|
||||
|
||||
cnc.onDraw = function(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) {
|
||||
// cnc is already perfect rn
|
||||
// so we need to manually process the limb
|
||||
var points = [
|
||||
// corners
|
||||
FlxPoint.get(0, 0),
|
||||
FlxPoint.get(1, 0),
|
||||
FlxPoint.get(0, 1),
|
||||
FlxPoint.get(1, 1)
|
||||
];
|
||||
|
||||
rawTransformPoints(points, matrix, frame.sourceSize.x, frame.sourceSize.y);
|
||||
|
||||
// sort the points, incase like they were transformed with a scale -1 matrix
|
||||
|
||||
var topLeft:FlxPoint = points[0];
|
||||
var topRight:FlxPoint = points[1];
|
||||
var bottomLeft:FlxPoint = points[2];
|
||||
var bottomRight:FlxPoint = points[3];
|
||||
|
||||
if (points[0].y > points[1].y) {
|
||||
topLeft = points[1];
|
||||
topRight = points[0];
|
||||
}
|
||||
if (points[2].y > points[3].y) {
|
||||
bottomLeft = points[3];
|
||||
bottomRight = points[2];
|
||||
}
|
||||
|
||||
if (topLeft.x > topRight.x) {
|
||||
var temp = topLeft;
|
||||
topLeft = topRight;
|
||||
topRight = temp;
|
||||
}
|
||||
|
||||
if (bottomLeft.x > bottomRight.x) {
|
||||
var temp = bottomLeft;
|
||||
bottomLeft = bottomRight;
|
||||
bottomRight = temp;
|
||||
}
|
||||
|
||||
// points should now contain the corners of the limb
|
||||
|
||||
// trace("Corners of limb: " + points);
|
||||
|
||||
// set the max bounds of each corner of the limb
|
||||
if(topLeft.x < boundsTopLeft.x) boundsTopLeft.x = topLeft.x;
|
||||
if(topLeft.y < boundsTopLeft.y) boundsTopLeft.y = topLeft.y;
|
||||
if(topRight.x > boundsTopRight.x) boundsTopRight.x = topRight.x;
|
||||
if(topRight.y < boundsTopRight.y) boundsTopRight.y = topRight.y;
|
||||
if(bottomLeft.x < boundsBottomLeft.x) boundsBottomLeft.x = bottomLeft.x;
|
||||
if(bottomLeft.y > boundsBottomLeft.y) boundsBottomLeft.y = bottomLeft.y;
|
||||
if(bottomRight.x > boundsBottomRight.x) boundsBottomRight.x = bottomRight.x;
|
||||
if(bottomRight.y > boundsBottomRight.y) boundsBottomRight.y = bottomRight.y;
|
||||
|
||||
// recycle the points
|
||||
for(point in points) point.put();
|
||||
}
|
||||
var cameras = sprite._cameras;
|
||||
var oldVisible = cnc.visible;
|
||||
cnc.visible = true;
|
||||
sprite._cameras = [cnc];
|
||||
sprite.draw();
|
||||
sprite._cameras = cameras;
|
||||
cnc.visible = oldVisible;
|
||||
cnc.onDraw = oldOnDraw;
|
||||
|
||||
// fake transform
|
||||
|
||||
//trace("");
|
||||
//trace("topLeft: " + boundsTopLeft);
|
||||
//trace("topRight: " + boundsTopRight);
|
||||
//trace("bottomLeft: " + boundsBottomLeft);
|
||||
//trace("bottomRight: " + boundsBottomRight);
|
||||
//trace("Before transform");
|
||||
//trace(points);
|
||||
|
||||
for(point in points) {
|
||||
//var x = matrix.__transformX(point.x * _width, point.y * _height);
|
||||
//var y = matrix.__transformY(point.x * _width, point.y * _height);
|
||||
// apply using boundsTopLeft, boundsTopRight, boundsBottomLeft, boundsBottomRight
|
||||
// matrix doesnt work for this
|
||||
var x = FlxMath.lerp(
|
||||
FlxMath.lerp(boundsTopLeft.x, boundsTopRight.x, point.x),
|
||||
FlxMath.lerp(boundsBottomLeft.x, boundsBottomRight.x, point.x),
|
||||
point.y
|
||||
);
|
||||
var y = FlxMath.lerp(
|
||||
FlxMath.lerp(boundsTopLeft.y, boundsTopRight.y, point.x),
|
||||
FlxMath.lerp(boundsBottomLeft.y, boundsBottomRight.y, point.x),
|
||||
point.y
|
||||
);
|
||||
|
||||
//trace("(" + point.x + ", " + point.y + ") -> (" + x + ", " + y + ")");
|
||||
|
||||
// reset to ingame coords
|
||||
x += camera.scroll.x;
|
||||
y += camera.scroll.y;
|
||||
|
||||
if(isFunkinSprite) {
|
||||
var ratio = 1 - FlxMath.lerp(1 / camera.zoom, 1, funkinSprite.zoomFactor);
|
||||
x += camera.width / 2 * ratio;
|
||||
y += camera.height / 2 * ratio;
|
||||
}
|
||||
point.set(x, y);
|
||||
}
|
||||
|
||||
//trace("After transform");
|
||||
//trace(points);
|
||||
} else {
|
||||
if(sprite is IPrePostDraw) {
|
||||
var postDraw = cast(sprite, IPrePostDraw);
|
||||
postDraw.preDraw();
|
||||
sprite.drawComplex(nc);
|
||||
postDraw.postDraw();
|
||||
} else {
|
||||
sprite.drawComplex(nc);
|
||||
}
|
||||
transformPoints(sprite, points, sprite._matrix, camera, _width, _height);
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning: modifies the points in the array
|
||||
**/
|
||||
public static function transformPoints(sprite:FlxSprite, points:Array<FlxPoint>, matrix:FlxMatrix, ?camera:FlxCamera, _width:Float = 1, _height:Float = 1, doCameraTransform:Bool = true):Array<FlxPoint> {
|
||||
var isFunkinSprite = sprite is FunkinSprite;
|
||||
var funkinSprite:FunkinSprite = null;
|
||||
if(isFunkinSprite) funkinSprite = cast sprite;
|
||||
|
||||
for(point in points) {
|
||||
var x = matrix.__transformX(point.x * _width, point.y * _height);
|
||||
var y = matrix.__transformY(point.x * _width, point.y * _height);
|
||||
|
||||
if(doCameraTransform) {
|
||||
// reset to ingame coords
|
||||
x += camera.scroll.x;
|
||||
y += camera.scroll.y;
|
||||
|
||||
if(isFunkinSprite) {
|
||||
var ratio = 1 - FlxMath.lerp(1 / camera.zoom, 1, funkinSprite.zoomFactor);
|
||||
x += camera.width / 2 * ratio;
|
||||
y += camera.height / 2 * ratio;
|
||||
}
|
||||
}
|
||||
point.set(x, y);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
private static function rawTransformPoints(points:Array<FlxPoint>, matrix:FlxMatrix, _width:Float = 1, _height:Float = 1):Array<FlxPoint> {
|
||||
for(point in points) {
|
||||
var x = matrix.__transformX(point.x * _width, point.y * _height);
|
||||
var y = matrix.__transformY(point.x * _width, point.y * _height);
|
||||
|
||||
point.set(x, y);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ using StringTools;
|
||||
*
|
||||
* DISCLAIMER: Hardware info is only available on Native platforms.
|
||||
**/
|
||||
class MemoryUtil {
|
||||
final class MemoryUtil {
|
||||
public static var disableCount:Int = 0;
|
||||
|
||||
public static function askDisable() {
|
||||
@@ -88,14 +88,18 @@ class MemoryUtil {
|
||||
*/
|
||||
public static function getTotalMem():Float
|
||||
{
|
||||
#if windows
|
||||
return funkin.backend.utils.native.Windows.getTotalRam();
|
||||
#elseif mac
|
||||
return funkin.backend.utils.native.Mac.getTotalRam();
|
||||
#elseif linux
|
||||
return funkin.backend.utils.native.Linux.getTotalRam();
|
||||
#if cpp
|
||||
#if windows
|
||||
return funkin.backend.utils.native.Windows.getTotalRam();
|
||||
#elseif mac
|
||||
return funkin.backend.utils.native.Mac.getTotalRam();
|
||||
#elseif linux
|
||||
return funkin.backend.utils.native.Linux.getTotalRam();
|
||||
#else
|
||||
return 0;
|
||||
#end
|
||||
#else
|
||||
return 0;
|
||||
return 0;
|
||||
#end
|
||||
}
|
||||
|
||||
@@ -123,7 +127,7 @@ class MemoryUtil {
|
||||
public static function getMemType():String {
|
||||
#if windows
|
||||
var memoryMap:Map<Int, String> = [
|
||||
0 => "Unknown",
|
||||
0 => null,
|
||||
1 => "Other",
|
||||
2 => "DRAM",
|
||||
3 => "Synchronous DRAM",
|
||||
@@ -162,9 +166,9 @@ class MemoryUtil {
|
||||
];
|
||||
var memoryOutput:Int = -1;
|
||||
|
||||
var process = new HiddenProcess("wmic", ["memorychip", "get", "SMBIOSMemoryType"]);
|
||||
var process = new HiddenProcess("powershell", ["-Command", "Get-CimInstance Win32_PhysicalMemory | Select-Object -ExpandProperty SMBIOSMemoryType" ]);
|
||||
if (process.exitCode() == 0) memoryOutput = Std.int(Std.parseFloat(process.stdout.readAll().toString().trim().split("\n")[1]));
|
||||
if (memoryOutput != -1) return memoryMap[memoryOutput];
|
||||
if (memoryOutput != -1) return memoryMap[memoryOutput] == null ? 'Unknown ($memoryOutput)' : memoryMap[memoryOutput];
|
||||
#elseif mac
|
||||
var process = new HiddenProcess("system_profiler", ["SPMemoryDataType"]);
|
||||
var reg = ~/Type: (.+)/;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package funkin.backend.utils;
|
||||
|
||||
import haxe.macro.Expr.Case;
|
||||
import openfl.ui.Mouse;
|
||||
import openfl.ui.MouseCursor;
|
||||
import funkin.backend.utils.native.*;
|
||||
import flixel.util.typeLimit.OneOfTwo;
|
||||
import flixel.util.typeLimit.OneOfThree;
|
||||
@@ -200,6 +203,17 @@ class NativeAPI {
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cursor icon.
|
||||
**/
|
||||
public static function setCursorIcon(icon:CodeCursor) {
|
||||
#if (mac && cpp)
|
||||
Mac.setMouseCursorIcon(cast icon);
|
||||
#else
|
||||
Mouse.cursor = icon.toOpenFL();
|
||||
#end
|
||||
}
|
||||
|
||||
public static function consoleColorToANSI(color:ConsoleColor) {
|
||||
return switch(color) {
|
||||
case BLACK: 30;
|
||||
@@ -289,4 +303,102 @@ enum abstract MessageBoxIcon(Int) {
|
||||
var MSG_QUESTION = 0x00000020;
|
||||
var MSG_WARNING = 0x00000030;
|
||||
var MSG_INFORMATION = 0x00000040;
|
||||
}
|
||||
|
||||
enum abstract CodeCursor(String) {
|
||||
var CUSTOM;// = "arrow";
|
||||
var ARROW;// = "arrow";
|
||||
var CLICK;// = "click";
|
||||
var CROSSHAIR;// = "crosshair";
|
||||
var HAND;// = "hand";
|
||||
var IBEAM;// = "ibeam";
|
||||
var MOVE;// = "move";
|
||||
|
||||
var RESIZE_H;// = "resize_we";
|
||||
var RESIZE_V;// = "resize_ns";
|
||||
var RESIZE_TL;// = "resize_nw";
|
||||
var RESIZE_TR;// = "resize_ne";
|
||||
var RESIZE_BL;// = "resize_sw";
|
||||
var RESIZE_BR;// = "resize_se";
|
||||
var RESIZE_T;// = "resize_n";
|
||||
var RESIZE_B;// = "resize_b";
|
||||
var RESIZE_L;// = "resize_w";
|
||||
var RESIZE_R;// = "resize_e";
|
||||
|
||||
var RESIZE_TLBR;// = "resize_nw_se";
|
||||
var RESIZE_TRBL;// = "resize_ne_sw";
|
||||
|
||||
var WAIT;// = "wait";
|
||||
var WAIT_ARROW;// = "waitarrow";
|
||||
var DISABLED;// = "disabled";
|
||||
var DRAG;// = "drag";
|
||||
var DRAG_OPEN;// = "dragopen";
|
||||
|
||||
@:to public function toOpenFL():MouseCursor {
|
||||
return @:privateAccess switch(cast this) {
|
||||
case ARROW: MouseCursor.ARROW;
|
||||
case CROSSHAIR: MouseCursor.__CROSSHAIR;
|
||||
case CLICK: MouseCursor.BUTTON;
|
||||
case IBEAM: MouseCursor.IBEAM;
|
||||
case MOVE: MouseCursor.__MOVE;
|
||||
case HAND: MouseCursor.HAND;
|
||||
case DRAG: MouseCursor.HAND;
|
||||
case DRAG_OPEN: MouseCursor.ARROW; // Could be HAND, but it might be better to use ARROW since on windows it would be weird to have the dragging cursor
|
||||
case WAIT: MouseCursor.__WAIT;
|
||||
case WAIT_ARROW: MouseCursor.__WAIT_ARROW;
|
||||
|
||||
case DISABLED: MouseCursor.ARROW;
|
||||
|
||||
case RESIZE_TR: MouseCursor.__RESIZE_NESW;
|
||||
case RESIZE_BL: MouseCursor.__RESIZE_NESW;
|
||||
case RESIZE_TL: MouseCursor.__RESIZE_NWSE;
|
||||
case RESIZE_BR: MouseCursor.__RESIZE_NWSE;
|
||||
case RESIZE_H: MouseCursor.__RESIZE_WE;
|
||||
case RESIZE_V: MouseCursor.__RESIZE_NS;
|
||||
|
||||
case RESIZE_T: MouseCursor.__RESIZE_NS;
|
||||
case RESIZE_B: MouseCursor.__RESIZE_NS;
|
||||
case RESIZE_L: MouseCursor.__RESIZE_WE;
|
||||
case RESIZE_R: MouseCursor.__RESIZE_WE;
|
||||
|
||||
case RESIZE_TLBR: MouseCursor.__RESIZE_NWSE;
|
||||
case RESIZE_TRBL: MouseCursor.__RESIZE_NESW;
|
||||
case CUSTOM: MouseCursor.__CUSTOM;
|
||||
//default: ARROW;
|
||||
}
|
||||
}
|
||||
|
||||
@:to public function toInt():Int {
|
||||
return switch(cast this) {
|
||||
case ARROW: 0;
|
||||
case CROSSHAIR: 1;
|
||||
case CLICK: 2;
|
||||
case IBEAM: 3;
|
||||
case MOVE: 4;
|
||||
case HAND: 5;
|
||||
case DRAG: 6;
|
||||
case DRAG_OPEN: 7;
|
||||
case WAIT: 8;
|
||||
case WAIT_ARROW: 9;
|
||||
|
||||
case DISABLED: 10;
|
||||
|
||||
case RESIZE_TR: 11;
|
||||
case RESIZE_BL: 12;
|
||||
case RESIZE_TL: 13;
|
||||
case RESIZE_BR: 14;
|
||||
case RESIZE_H: 15;
|
||||
case RESIZE_V: 16;
|
||||
|
||||
case RESIZE_T: 17;
|
||||
case RESIZE_B: 18;
|
||||
case RESIZE_L: 19;
|
||||
case RESIZE_R: 20;
|
||||
|
||||
case RESIZE_TLBR: 21;
|
||||
case RESIZE_TRBL: 22;
|
||||
|
||||
case CUSTOM: -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ package funkin.backend.utils;
|
||||
* - The Function cannot be found in the NDLL
|
||||
* then an empty function will be returned instead, and a message will be shown in logs.
|
||||
*/
|
||||
class NdllUtil {
|
||||
final class NdllUtil {
|
||||
#if NDLLS_SUPPORTED
|
||||
#if windows public static final os:String = "windows"; #end
|
||||
#if linux public static final os:String = "linux"; #end
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package funkin.backend.utils;
|
||||
|
||||
enum abstract RegistryHive(Int) {
|
||||
var HKEY_CLASSES_ROOT = 0x80000000;
|
||||
var HKEY_CURRENT_USER = 0x80000001;
|
||||
var HKEY_LOCAL_MACHINE = 0x80000002;
|
||||
var HKEY_USERS = 0x80000003;
|
||||
var HKEY_CURRENT_CONFIG = 0x80000005;
|
||||
}
|
||||
#if windows
|
||||
@:cppFileCode('
|
||||
#include <windows.h>
|
||||
#include <tchar.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
')
|
||||
#end
|
||||
class RegistryUtil {
|
||||
#if windows
|
||||
@:functionCode('
|
||||
HKEY hKey;
|
||||
LONG result;
|
||||
DWORD dataSize = 0;
|
||||
DWORD dataType = 0;
|
||||
|
||||
std::wstring subkey = std::wstring(key.wchar_str());
|
||||
std::wstring valname = std::wstring(string.wchar_str());
|
||||
|
||||
result = RegOpenKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, KEY_READ, &hKey);
|
||||
if (result != ERROR_SUCCESS) return null();
|
||||
|
||||
result = RegQueryValueExW(hKey, valname.c_str(), NULL, &dataType, NULL, &dataSize);
|
||||
if (result != ERROR_SUCCESS || dataSize == 0) {
|
||||
RegCloseKey(hKey);
|
||||
return null();
|
||||
}
|
||||
|
||||
std::vector<wchar_t> buffer(dataSize / sizeof(wchar_t));
|
||||
result = RegQueryValueExW(hKey, valname.c_str(), NULL, NULL, (LPBYTE)buffer.data(), &dataSize);
|
||||
RegCloseKey(hKey);
|
||||
|
||||
if (result == ERROR_SUCCESS) {
|
||||
return ::String(buffer.data());
|
||||
}
|
||||
return null();
|
||||
')
|
||||
#end
|
||||
public static function get(hive:RegistryHive, key:String, string:String):Null<String>
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
#if windows
|
||||
@:functionCode('
|
||||
HKEY hKey;
|
||||
LONG result;
|
||||
|
||||
std::wstring subkey = std::wstring(key.wchar_str());
|
||||
std::wstring valname = std::wstring(string.wchar_str());
|
||||
std::wstring data = std::wstring(value.wchar_str());
|
||||
|
||||
result = RegCreateKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL);
|
||||
if (result != ERROR_SUCCESS) return false;
|
||||
|
||||
result = RegSetValueExW(hKey, valname.c_str(), 0, REG_SZ, (const BYTE*)data.c_str(), (DWORD)((data.length() + 1) * sizeof(wchar_t)));
|
||||
RegCloseKey(hKey);
|
||||
|
||||
return result == ERROR_SUCCESS;
|
||||
')
|
||||
#end
|
||||
public static function set(hive:RegistryHive, key:String, string:String, value:String):Bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if windows
|
||||
@:functionCode('
|
||||
HKEY hKey;
|
||||
LONG result;
|
||||
|
||||
std::wstring subkey = std::wstring(key.wchar_str());
|
||||
std::wstring valname = std::wstring(string.wchar_str());
|
||||
|
||||
result = RegOpenKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, KEY_READ, &hKey);
|
||||
if (result != ERROR_SUCCESS) return false;
|
||||
|
||||
DWORD dataType = 0;
|
||||
result = RegQueryValueExW(hKey, valname.c_str(), NULL, &dataType, NULL, NULL);
|
||||
|
||||
RegCloseKey(hKey);
|
||||
|
||||
return result == ERROR_SUCCESS;
|
||||
')
|
||||
#end
|
||||
public static function exists(hive:RegistryHive, key:String, string:String):Bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if windows
|
||||
@:functionCode('
|
||||
HKEY hKey;
|
||||
LONG result;
|
||||
|
||||
std::wstring subkey = std::wstring(key.wchar_str());
|
||||
std::wstring valname = std::wstring(string.wchar_str());
|
||||
|
||||
result = RegOpenKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, KEY_SET_VALUE, &hKey);
|
||||
if (result != ERROR_SUCCESS) return false;
|
||||
|
||||
result = RegDeleteValueW(hKey, valname.c_str());
|
||||
RegCloseKey(hKey);
|
||||
|
||||
return result == ERROR_SUCCESS;
|
||||
')
|
||||
#end
|
||||
public static function delete(hive:RegistryHive, key:String, string:String):Bool {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package funkin.backend.utils;
|
||||
|
||||
import openfl.display.Sprite;
|
||||
|
||||
class ShaderResizeFix {
|
||||
final class ShaderResizeFix {
|
||||
public static var doResizeFix:Bool = true;
|
||||
|
||||
public static function init() {
|
||||
|
||||
@@ -5,7 +5,7 @@ package funkin.backend.utils;
|
||||
*
|
||||
* DISCLAIMER: All types of utils only usable in a sorted array.
|
||||
*/
|
||||
class SortedArrayUtil {
|
||||
final class SortedArrayUtil {
|
||||
/**
|
||||
* Gets the index of a possible new element of an Array of T using an efficient algorithm.
|
||||
* @param array Array of T to check in
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package funkin.backend.utils;
|
||||
|
||||
#if ALLOW_MULTITHREADING
|
||||
class ThreadUtil {
|
||||
final class ThreadUtil {
|
||||
/**
|
||||
* Creates a new Thread with an error handler.
|
||||
* @param func Function to execute
|
||||
|
||||
@@ -38,7 +38,7 @@ final class TranslationUtil
|
||||
/**
|
||||
* Returns the current language config.
|
||||
*/
|
||||
public static var config:IniMap = [];
|
||||
public static var config:Map<String, String> = [];
|
||||
/**
|
||||
* Returns the current language.
|
||||
*/
|
||||
@@ -65,15 +65,15 @@ final class TranslationUtil
|
||||
|
||||
// Private
|
||||
private static inline var LANG_FOLDER:String = "languages";
|
||||
private static var langConfigs:Map<String, IniMap> = [];
|
||||
private static var langConfigs:Map<String, Map<String, String>> = [];
|
||||
private static var nameMap:Map<String, String> = [];
|
||||
private static inline function getDefaultNameMap():Map<String, String> {
|
||||
return [Flags.DEFAULT_LANGUAGE => Flags.DEFAULT_LANGUAGE_NAME];
|
||||
}
|
||||
private static inline function getDefaultLangConfigs():Map<String, IniMap> {
|
||||
private static inline function getDefaultLangConfigs():Map<String, Map<String, String>> {
|
||||
return [Flags.DEFAULT_LANGUAGE => getDefaultConfig(Flags.DEFAULT_LANGUAGE)];
|
||||
}
|
||||
@:noUsing private static inline function getDefaultConfig(name:String):IniMap {
|
||||
@:noUsing private static inline function getDefaultConfig(name:String):Map<String, String> {
|
||||
return ["name" => getLanguageName(name), "credits" => "", "version" => "1.0.0"];
|
||||
}
|
||||
|
||||
@@ -200,7 +200,9 @@ final class TranslationUtil
|
||||
var config = getDefaultConfig(lang);
|
||||
|
||||
if(Assets.exists(path)) {
|
||||
config = IniUtil.parseAsset(path, config);
|
||||
var c = IniUtil.parseAsset(path);
|
||||
for (key => value in c)
|
||||
config[key] = value;
|
||||
} else { // if there was no config.ini, use the file name as the language name
|
||||
for(file in Paths.getFolderContent(mainPath + lang).sortAlphabetically()) {
|
||||
if(Path.extension(file) == "xml") {
|
||||
@@ -313,7 +315,7 @@ final class TranslationUtil
|
||||
return reverseMap.exists(name) ? reverseMap.get(name) : name;
|
||||
}
|
||||
|
||||
public static function getConfig(lang:String):IniMap {
|
||||
public static function getConfig(lang:String):Map<String, String> {
|
||||
return langConfigs.exists(lang) ? langConfigs.get(lang) : getDefaultConfig(lang);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package funkin.backend.utils;
|
||||
|
||||
import openfl.Lib;
|
||||
|
||||
class WindowUtils {
|
||||
final class WindowUtils {
|
||||
public static var title(default, set):String;
|
||||
private static function set_title(value:String):String {
|
||||
title = value;
|
||||
|
||||
@@ -34,7 +34,7 @@ typedef TextFormat = { text:String, format:Dynamic }
|
||||
* Class made to make XML parsing easier.
|
||||
* Used in Stage.hx, Character.hx, and more.
|
||||
*/
|
||||
class XMLUtil {
|
||||
final class XMLUtil {
|
||||
/**
|
||||
* Applies a property XML node to an object.
|
||||
* The format for the XML is as follows:
|
||||
@@ -114,12 +114,13 @@ class XMLUtil {
|
||||
* @param parentFolder The parent folder
|
||||
* @param defaultAnimType The default animation type
|
||||
*/
|
||||
public static function loadSpriteFromXML(spr:FunkinSprite, node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT):FunkinSprite {
|
||||
public static function loadSpriteFromXML(spr:FunkinSprite, node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, loadGraphic:Bool = true):FunkinSprite {
|
||||
if (parentFolder == null) parentFolder = "";
|
||||
|
||||
spr.name = node.getAtt("name");
|
||||
spr.antialiasing = true;
|
||||
spr.loadSprite(Paths.image('$parentFolder${node.getAtt("sprite").getDefault(spr.name)}', null, true));
|
||||
if (loadGraphic)
|
||||
spr.loadSprite(Paths.image('$parentFolder${node.getAtt("sprite").getDefault(spr.name)}', null, true));
|
||||
|
||||
spr.spriteAnimType = defaultAnimType;
|
||||
if (node.has.type) {
|
||||
@@ -200,10 +201,15 @@ class XMLUtil {
|
||||
if(node.has.color)
|
||||
spr.color = FlxColor.fromString(node.getAtt("color")).getDefault(0xFFFFFFFF);
|
||||
|
||||
if(node.has.angle)
|
||||
spr.angle = Std.parseFloat(node.getAtt("angle")).getDefault(spr.angle);
|
||||
|
||||
if (node.has.playOnCountdown)
|
||||
spr.skipNegativeBeats = node.att.playOnCountdown == "true";
|
||||
if (node.has.beatInterval)
|
||||
spr.beatInterval = Std.parseInt(node.att.beatInterval);
|
||||
if (node.has.interval)
|
||||
spr.beatInterval = Std.parseInt(node.att.interval);
|
||||
if (node.has.beatOffset)
|
||||
spr.beatOffset = Std.parseInt(node.att.beatOffset);
|
||||
|
||||
@@ -234,10 +240,10 @@ class XMLUtil {
|
||||
* @param cl The class to create (advanced)
|
||||
* @param args The arguments to pass to the class (advanced)
|
||||
*/
|
||||
public static inline function createSpriteFromXML(node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, ?cl:Class<FunkinSprite>, ?args:Array<Dynamic>):FunkinSprite {
|
||||
public static inline function createSpriteFromXML(node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, ?cl:Class<FunkinSprite>, ?args:Array<Dynamic>, loadGraphic:Bool = true):FunkinSprite {
|
||||
if(cl == null) cl = FunkinSprite;
|
||||
if(args == null) args = [];
|
||||
return loadSpriteFromXML(Type.createInstance(cl, args), node, parentFolder, defaultAnimType);
|
||||
return loadSpriteFromXML(Type.createInstance(cl, args), node, parentFolder, defaultAnimType, loadGraphic);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package funkin.backend.utils;
|
||||
|
||||
import haxe.zip.Entry;
|
||||
import haxe.zip.Reader;
|
||||
|
||||
#if sys
|
||||
#if (!macro && sys)
|
||||
import openfl.display.BitmapData;
|
||||
@@ -9,8 +12,6 @@ import haxe.Exception;
|
||||
import haxe.Json;
|
||||
import haxe.crypto.Crc32;
|
||||
import haxe.zip.Compress;
|
||||
import haxe.zip.Entry;
|
||||
import haxe.zip.Reader;
|
||||
import haxe.zip.Tools;
|
||||
import haxe.zip.Uncompress;
|
||||
import haxe.zip.Writer;
|
||||
@@ -23,7 +24,7 @@ using StringTools;
|
||||
// import ZipUtils; ZipUtils.uncompressZip(ZipUtils.openZip("E:\\Desktop\\test\\termination lua.ycemod"), "E:\\Desktop\\test\\uncompressed\\");
|
||||
// import ZipUtils; var e = ZipUtils.createZipFile("file.ycemod"); ZipUtils.writeFolderToZip(e, "./mods/Friday Night Funkin'/", "Friday Night Funkin'/"); e.flush(); e.close();
|
||||
|
||||
class ZipUtil {
|
||||
final class ZipUtil {
|
||||
public static var bannedNames:Array<String> = [".git", ".gitignore", ".github", ".vscode", ".gitattributes", "readme.txt"];
|
||||
|
||||
/**
|
||||
@@ -135,12 +136,12 @@ class ZipUtil {
|
||||
if (prog == null) prog = new ZipProgress();
|
||||
|
||||
try {
|
||||
var curPath:Array<String> = ['$path'];
|
||||
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);
|
||||
while(prefix.charCodeAt(0) == "/".code) prefix = prefix.substr(1);
|
||||
while(prefix.charCodeAt(prefix.length-1) == "/".code) prefix = prefix.substr(0, prefix.length-1);
|
||||
destPath.push(prefix);
|
||||
}
|
||||
|
||||
@@ -160,7 +161,7 @@ class ZipUtil {
|
||||
} else {
|
||||
// is file, put it in the list
|
||||
var zipPath = '$zipPath/$e';
|
||||
while(zipPath.charAt(0) == "/") zipPath = zipPath.substr(1);
|
||||
while(zipPath.charCodeAt(0) == "/".code) zipPath = zipPath.substr(1);
|
||||
files.push(new StrNameLabel('$path/$e', zipPath));
|
||||
}
|
||||
}
|
||||
@@ -227,20 +228,6 @@ class ZipProgress {
|
||||
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();
|
||||
@@ -265,4 +252,18 @@ class StrNameLabel {
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
#end
|
||||
#end
|
||||
|
||||
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>();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@ package funkin.backend.utils.native;
|
||||
|
||||
#if linux
|
||||
@:cppFileCode("#include <stdio.h>")
|
||||
class Linux {
|
||||
@:dox(hide)
|
||||
final class Linux {
|
||||
@:functionCode('
|
||||
FILE *meminfo = fopen("/proc/meminfo", "r");
|
||||
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package funkin.backend.utils.native;
|
||||
|
||||
#if mac
|
||||
@:cppFileCode("#include <sys/sysctl.h>")
|
||||
class Mac {
|
||||
#if (mac && cpp)
|
||||
import funkin.backend.utils.NativeAPI.CodeCursor;
|
||||
import openfl.ui.Mouse;
|
||||
|
||||
@:headerInclude('sys/sysctl.h')
|
||||
@:dox(hide)
|
||||
final class Mac
|
||||
{
|
||||
@:functionCode('
|
||||
int mib [] = { CTL_HW, HW_MEMSIZE };
|
||||
int64_t value = 0;
|
||||
@@ -17,5 +22,39 @@ class Mac {
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static function setMouseCursorIcon(icon:CodeCursor):Void
|
||||
{
|
||||
final valid:Bool = external.ExternalMac.setCursorIcon(icon.toInt(), null, 0, 0);
|
||||
|
||||
if (!valid)
|
||||
Mouse.cursor = icon.toOpenFL();
|
||||
}
|
||||
}
|
||||
#end
|
||||
#end
|
||||
|
||||
|
||||
/*
|
||||
|
||||
Cursor
|
||||
|
||||
Description
|
||||
The arrow cursor (arrow)
|
||||
The I-beam cursor for indicating insertion points (iBeam)
|
||||
The cross-hair cursor (crosshair)
|
||||
The closed-hand cursor (closedHand)
|
||||
The open-hand cursor (openHand)
|
||||
The pointing-hand cursor (pointingHand)
|
||||
The resize-left cursor (resizeLeft)
|
||||
The resize-right cursor (resizeRight)
|
||||
The resize-left-and-right cursor (resizeLeftRight)
|
||||
The resize-up cursor (resizeUp)
|
||||
The resize-down cursor (resizeDown)
|
||||
The resize-up-and-down cursor (resizeUpDown)
|
||||
The disappearing item cursor (disappearingItem)
|
||||
The I-Beam text cursor for vertical layout (iBeamCursorForVerticalLayout).
|
||||
The not allowed cursor (operationNotAllowed).
|
||||
The drag link cursor (dragLink).
|
||||
The drag copy cursor (dragCopy).
|
||||
The contextual menu cursor (contextualMenu).
|
||||
*/
|
||||
|
||||
@@ -114,7 +114,7 @@ class AudioFixClient : public IMMNotificationClient {
|
||||
AudioFixClient *curAudioFix;
|
||||
')
|
||||
@:dox(hide)
|
||||
class Windows {
|
||||
final class Windows {
|
||||
|
||||
public static var __audioChangeCallback:Void->Void = function() {
|
||||
trace("test");
|
||||
|
||||
Reference in New Issue
Block a user