Merge branch 'main' into translations-system

This commit is contained in:
Furo
2025-07-21 12:58:53 +02:00
592 changed files with 20908 additions and 4426 deletions
+169 -23
View File
@@ -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
}
+50 -3
View File
@@ -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);
}
}
+166
View File
@@ -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]);
}
}
}
}
+524 -52
View File
@@ -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);
}
}
}
/**
+5 -1
View File
@@ -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
}
+105
View File
@@ -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;
}
}
}
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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)
+24 -33
View File
@@ -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);
}
}
+1 -1
View File
@@ -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> = [
+202
View File
@@ -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
}
+223
View File
@@ -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;
}
}
+15 -11
View File
@@ -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: (.+)/;
+112
View File
@@ -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;
}
}
}
+1 -1
View File
@@ -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
+120
View File
@@ -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 -1
View File
@@ -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);
}
+1 -1
View File
@@ -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;
+11 -5
View File
@@ -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);
}
/**
+23 -22
View File
@@ -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 -1
View File
@@ -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");
+43 -4
View File
@@ -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");