Merge branch 'main' into translations-system

This commit is contained in:
Ne_Eo
2025-01-09 20:26:29 +01:00
119 changed files with 1207 additions and 416 deletions
+11 -6
View File
@@ -3,15 +3,20 @@ package funkin.backend;
import flixel.graphics.frames.FlxFrame;
import flixel.math.FlxAngle;
import flixel.math.FlxMatrix;
import openfl.display.BlendMode;
import openfl.geom.ColorTransform;
class FlxAnimate extends flxanimate.FlxAnimate {
static var rMatrix = new FlxMatrix();
override function drawLimb(limb:FlxFrame, _rMatrix:FlxMatrix, ?colorTransform:ColorTransform)
override function drawLimb(limb:FlxFrame, _rMatrix:FlxMatrix, ?colorTransform:ColorTransform, ?blendMode:BlendMode)
{
if (alpha == 0 || colorTransform != null && (colorTransform.alphaMultiplier == 0 || colorTransform.alphaOffset == -255) || limb == null || limb.type == EMPTY)
return;
if (blendMode == null)
blendMode = BlendMode.NORMAL;
for (camera in cameras)
{
rMatrix.identity();
@@ -28,7 +33,7 @@ class FlxAnimate extends flxanimate.FlxAnimate {
var angleOff = (frameOffsetAngle - angle) * FlxAngle.TO_RAD;
var cos = Math.cos(angleOff);
var sin = Math.sin(angleOff);
// cos doesnt need to be negated
// cos doesn't need to be negated
rMatrix.rotateWithTrig(cos, -sin);
rMatrix.translate(-frameOffset.x, -frameOffset.y);
rMatrix.rotateWithTrig(cos, sin);
@@ -66,13 +71,13 @@ class FlxAnimate extends flxanimate.FlxAnimate {
}
rMatrix.translate(_point.x, _point.y);
camera.drawPixels(limb, null, rMatrix, colorTransform, blend, antialiasing, shaderEnabled ? shader : null);
camera.drawPixels(limb, null, rMatrix, colorTransform, blendMode, antialiasing, shaderEnabled ? shader : null);
#if FLX_DEBUG
FlxBasic.visibleCount++;
#end
}
// doesnt work, needs to be remade
// doesn't work, needs to be remade
// #if FLX_DEBUG
// if (FlxG.debugger.drawDebug)
// drawDebug();
@@ -81,7 +86,7 @@ class FlxAnimate extends flxanimate.FlxAnimate {
override function limbOnScreen(limb:FlxFrame, m:FlxMatrix, ?Camera:FlxCamera)
{
// TODO: ACTUAL OPTIMISATION
// TODO: ACTUAL OPTIMIZATION
return true;
}
}
}
+35 -11
View File
@@ -49,7 +49,8 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
public var globalCurFrame(get, set):Int;
/**
* ODD interval -> asynced; EVEN interval -> synced
* ODD interval -> not aligned to beats
* EVEN interval -> aligned to beats
*/
public var beatInterval(default, set):Int = 2;
public var beatOffset:Int = 0;
@@ -330,7 +331,31 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
lastAnimContext = Context;
}
public function getAnim(name:String):OneOfTwo<FlxAnimation, FlxSymbolAnimation> {
public inline function addAnim(name:String, prefix:String, frameRate:Float = 24, ?looped:Bool, ?forced:Bool, ?indices:Array<Int>, x:Float = 0, y:Float = 0, animType:XMLAnimType = NONE)
{
return XMLUtil.addAnimToSprite(this, {
name: name,
anim: prefix,
fps: frameRate,
loop: looped == null ? animType == LOOP : looped,
animType: animType,
x: x,
y: y,
indices: indices,
forced: forced
});
}
public inline function removeAnim(name:String)
{
if (animateAtlas != null)
@:privateAccess animateAtlas.anim.animsMap.remove(name);
else
animation.remove(name);
}
public function getAnim(name:String):OneOfTwo<FlxAnimation, FlxSymbolAnimation>
{
if(animateAtlas != null)
return animateAtlas.anim.getByName(name);
return animation.getByName(name);
@@ -343,7 +368,7 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
return FlxPoint.weak(0, 0);
}
public inline function hasAnimation(AnimName:String):Bool @:privateAccess
public inline function hasAnim(AnimName:String):Bool @:privateAccess
return animateAtlas != null ? (animateAtlas.anim.animsMap.exists(AnimName)
|| animateAtlas.anim.symbolDictionary.exists(AnimName)) : animation.exists(AnimName);
@@ -355,13 +380,6 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
return animateAtlas != null ? animateAtlas.anim.reversed : animation.curAnim != null ? animation.curAnim.reversed : false;
}
public inline function removeAnimation(name:String) {
if (animateAtlas != null)
@:privateAccess animateAtlas.anim.animsMap.remove(name);
else
animation.remove(name);
}
public inline function getNameList():Array<String> {
if (animateAtlas != null)
return [for (name in @:privateAccess animateAtlas.anim.animsMap.keys()) name];
@@ -369,7 +387,8 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
return animation.getNameList();
}
public inline function stopAnimation() {
public inline function stopAnim()
{
if (animateAtlas != null)
animateAtlas.anim.pause();
else
@@ -383,6 +402,11 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
public inline function isAnimAtEnd() {
return animateAtlas != null ? animateAtlas.anim.isAtEnd : (animation.curAnim != null ? animation.curAnim.isAtEnd : false);
}
// Backwards compat (the names used to be all different and it sucked, please lets use the same format in the future) - Nex
public inline function hasAnimation(AnimName:String) return hasAnim(AnimName);
public inline function removeAnimation(name:String) return removeAnim(name);
public inline function stopAnimation() return stopAnim();
#end
// Getter / Setters
+3
View File
@@ -2,6 +2,9 @@ package funkin.backend;
import funkin.backend.system.interfaces.IBeatReceiver;
/**
* Group of FlxSprites, if the sprite implements `IBeatReceiver`, then beatHit, stepHit and measureHit will be called.
**/
class MusicBeatGroup extends FlxTypedSpriteGroup<FlxSprite> implements IBeatReceiver {
public function beatHit(curBeat:Int) {
for(e in members) if (e is IBeatReceiver) ({var _:IBeatReceiver = cast e;_;}).beatHit(curBeat);
+13 -1
View File
@@ -13,6 +13,10 @@ import funkin.backend.system.framerate.Framerate;
import funkin.backend.system.interfaces.IBeatReceiver;
import funkin.options.PlayerSettings;
/**
* Base class for all the states.
* Handles the scripts, the transitions, and the beat and step events.
**/
class MusicBeatState extends FlxState implements IBeatReceiver
{
private var lastBeat:Float = 0;
@@ -93,6 +97,9 @@ class MusicBeatState extends FlxState implements IBeatReceiver
public var scriptsAllowed:Bool = true;
public static var lastScriptName:String = null;
public static var lastStateName:String = null;
public var scriptName:String = null;
public static var skipTransOut:Bool = false;
@@ -108,7 +115,12 @@ class MusicBeatState extends FlxState implements IBeatReceiver
public function new(scriptsAllowed:Bool = true, ?scriptName:String) {
super();
this.scriptsAllowed = #if SOFTCODED_STATES scriptsAllowed #else false #end;
this.scriptName = scriptName;
if(lastStateName != (lastStateName = Type.getClassName(Type.getClass(this)))) {
lastScriptName = null;
}
this.scriptName = scriptName != null ? scriptName : lastScriptName;
lastScriptName = this.scriptName;
}
function loadScript() {
+5 -1
View File
@@ -11,13 +11,17 @@ import funkin.backend.system.Controls;
import funkin.backend.system.interfaces.IBeatReceiver;
import funkin.options.PlayerSettings;
/**
* Base class for all the sub states.
* Handles the scripts, the transitions, and the beat and step events.
**/
class MusicBeatSubstate extends FlxSubState implements IBeatReceiver
{
private var lastBeat:Float = 0;
private var lastStep:Float = 0;
/**
* Whether this specifc substate can open custom transitions
* Whether this specific substate can open custom transitions
*/
public var canOpenCustomTransition:Bool = false;
/**
+55 -14
View File
@@ -2,14 +2,23 @@ package funkin.backend;
import flixel.FlxState;
import flixel.tweens.FlxTween;
import funkin.backend.scripting.Script;
import funkin.backend.scripting.events.CancellableEvent;
import funkin.backend.scripting.events.TransitionCreationEvent;
import funkin.backend.utils.FunkinParentDisabler;
class MusicBeatTransition extends MusicBeatSubstate {
public static var script:String = Flags.DEFAULT_TRANSITION_SCRIPT;
public var transitionScript:Script;
var nextFrameSkip:Bool = false;
public var transitionTween:FlxTween = null;
public var transitionCamera:FlxCamera;
public var newState:FlxState;
public var transOut:Bool = false;
public var allowSkip:Bool = true;
public var blackSpr:FlxSprite;
public var transitionSprite:FunkinSprite;
@@ -20,16 +29,30 @@ class MusicBeatTransition extends MusicBeatSubstate {
public override function create() {
if (newState != null)
add(new FunkinParentDisabler(true));
add(new FunkinParentDisabler(true, false));
transitionCamera = new FlxCamera();
transitionCamera.bgColor = 0;
FlxG.cameras.add(transitionCamera, false);
cameras = [transitionCamera];
var out = newState != null;
blackSpr = new FlxSprite(0, out ? -transitionCamera.height : transitionCamera.height).makeGraphic(1, 1, -1);
transitionScript = Script.create(Paths.script(script));
transitionScript.setParent(this);
transitionScript.load();
var event = EventManager.get(TransitionCreationEvent).recycle(newState != null, newState);
transitionScript.call('create', [event]);
transOut = event.transOut;
newState = event.newState;
if (event.cancelled) {
super.create();
return;
}
blackSpr = new FlxSprite(0, transOut ? -transitionCamera.height : transitionCamera.height).makeGraphic(1, 1, -1);
blackSpr.scale.set(transitionCamera.width, transitionCamera.height);
blackSpr.color = 0xFF000000;
blackSpr.updateHitbox();
@@ -43,7 +66,7 @@ class MusicBeatTransition extends MusicBeatSubstate {
} else {
transitionSprite.screenCenter();
}
transitionCamera.flipY = !out;
transitionCamera.flipY = !transOut;
add(transitionSprite);
transitionCamera.scroll.y = transitionCamera.height;
@@ -55,41 +78,59 @@ class MusicBeatTransition extends MusicBeatSubstate {
});
super.create();
transitionScript.call('postCreate', [event]);
}
public override function update(elapsed:Float) {
transitionScript.call('update', [elapsed]);
super.update(elapsed);
if (nextFrameSkip) {
finish();
return;
var event = new CancellableEvent();
transitionScript.call('onSkip', [event]);
if (!event.cancelled) {
finish();
return;
}
}
if (!parent.persistentUpdate && FlxG.keys.pressed.SHIFT) {
if (allowSkip && !parent.persistentUpdate && FlxG.keys.pressed.SHIFT) {
// skip
if (newState != null) {
nextFrameSkip = true;
parent.persistentDraw = false;
} else {
finish();
var event = new CancellableEvent();
transitionScript.call('onSkip', [event]);
if (!event.cancelled) {
finish();
}
}
}
transitionScript.call('postUpdate', [elapsed]);
}
public function finish() {
var event = new CancellableEvent();
transitionScript.call('onFinish', [event]);
if (event.cancelled) return;
if (newState != null)
FlxG.switchState(newState);
close();
transitionScript.call('onPostFinish');
}
public override function destroy() {
if (transitionTween != null)
transitionTween.cancel();
transitionScript.call('destroy');
if (transitionTween != null) transitionTween.cancel();
transitionTween = FlxDestroyUtil.destroy(transitionTween);
if (newState == null && FlxG.cameras.list.contains(transitionCamera))
FlxG.cameras.remove(transitionCamera);
else
transitionCamera.bgColor = 0xFF000000;
if (newState == null && FlxG.cameras.list.contains(transitionCamera)) FlxG.cameras.remove(transitionCamera);
else transitionCamera.bgColor = 0xFF000000;
transitionScript.destroy();
super.destroy();
}
}
+9 -3
View File
@@ -42,8 +42,9 @@ class ModsFolder {
* Whenever its the first time mods has been reloaded.
*/
private static var __firstTime:Bool = true;
/**
* Initialises `mods` folder.
* Initializes `mods` folder.
*/
public static function init() {
if(!getModsList().contains(Options.lastLoadedMod)) {
@@ -95,7 +96,12 @@ class ModsFolder {
public static function getModsList():Array<String> {
var mods:Array<String> = [];
#if MOD_SUPPORT
for(modFolder in FileSystem.readDirectory(modsPath)) {
final modsList:Array<String> = FileSystem.readDirectory(modsPath);
if (modsList == null || modsList.length <= 0)
return mods;
for (modFolder in modsList) {
if (FileSystem.isDirectory('${modsPath}${modFolder}')) {
mods.push(modFolder);
} else {
@@ -163,4 +169,4 @@ class ModsFolder {
return prepareModLibrary(libName, new ZipFolderLibrary(zipPath, libName, modName), force, tag);
}
#end
}
}
@@ -15,9 +15,9 @@ class BaseGameParser {
result.customValues = {};
result.customValues.timeChanges = data.timeChanges;
var firstTimeChng:SwagTimeChange = data.timeChanges[0];
result.bpm = firstTimeChng.bpm;
result.beatsPerMeasure = firstTimeChng.b != null ? firstTimeChng.b : Flags.DEFAULT_BEATS_PER_MEASURE;
var firstTimeChange:SwagTimeChange = data.timeChanges[0];
result.bpm = firstTimeChange.bpm;
result.beatsPerMeasure = firstTimeChange.b != null ? firstTimeChange.b : Flags.DEFAULT_BEATS_PER_MEASURE;
result.difficulties = data.playData.difficulties.concat(data.playData.songVariations);
Reflect.deleteField(data.playData, "difficulties");
+79 -26
View File
@@ -1,5 +1,6 @@
package funkin.backend.chart;
import funkin.backend.chart.ChartData;
import flixel.util.FlxColor;
import funkin.backend.chart.ChartData;
import haxe.Json;
@@ -11,7 +12,63 @@ import sys.FileSystem;
using StringTools;
enum abstract ChartFormat(Int) {
var CODENAME = 0;
var LEGACY = 1; // also used by many other engines (old Psych, Kade and more) - Nex
var VSLICE = 2;
var PSYCH_NEW = 3;
@:to public function toString():String {
return switch(cast (this, ChartFormat)) {
case CODENAME: "CODENAME";
case LEGACY: "LEGACY";
case VSLICE: "VSLICE";
case PSYCH_NEW: "PSYCH_NEW";
}
}
public static function fromString(str:String, def:ChartFormat = ChartFormat.LEGACY) {
str = str.toLowerCase();
str = StringTools.replace(str, " ", "");
str = StringTools.replace(str, "_", "");
str = StringTools.replace(str, ".", "");
if(StringTools.startsWith(str, "psychv1") || StringTools.startsWith(str, "psych1"))
return PSYCH_NEW;
return switch(str) {
case "codename" | "codenameengine": CODENAME;
case "newpsych" | "psychnew": PSYCH_NEW;
default: def;
}
}
}
class Chart {
public static function cleanSongData(data:Dynamic):Dynamic {
if (Reflect.hasField(data, "song")) {
var field:Dynamic = Reflect.field(data, "song");
if (field != null && Type.typeof(field) == TObject) // Cant use Reflect.isObject, because it detects strings for some reason
return field;
}
return data;
}
public static function detectChartFormat(data:Dynamic):ChartFormat {
var __temp:Dynamic; // imma reuse this var so the program doesn't have to get values multiple times - Nex
if ((__temp = data.codenameChart) == true || __temp == "true")
return CODENAME;
if (Reflect.hasField(data, "version") && Reflect.hasField(data, "scrollSpeed"))
return VSLICE;
if ((__temp = cleanSongData(data).format) != null && __temp is String && StringTools.startsWith(__temp, "psych_v1"))
return PSYCH_NEW;
return LEGACY;
}
public static function loadEventsJson(songName:String) {
var path = Paths.file('songs/${songName.toLowerCase()}/events.json');
var data:Array<ChartEvent> = null;
@@ -28,8 +85,9 @@ class Chart {
public static function loadChartMeta(songName:String, ?difficulty:String, fromMods:Bool = true) {
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
var metaPath = Paths.file('songs/${songName.toLowerCase()}/meta.json');
var metaDiffPath = Paths.file('songs/${songName.toLowerCase()}/meta-${difficulty.toLowerCase()}.json');
var songNameLower = songName.toLowerCase();
var metaPath = Paths.file('songs/${songNameLower}/meta.json');
var metaDiffPath = Paths.file('songs/${songNameLower}/meta-${difficulty.toLowerCase()}.json');
var data:ChartMetaData = null;
var fromMods:Bool = fromMods;
@@ -62,7 +120,7 @@ class Chart {
data.setFieldDefault("parsedColor", data.color.getColorFromDynamic().getDefault(Flags.DEFAULT_COLOR));
if (data.difficulties.length <= 0) {
data.difficulties = [for(f in Paths.getFolderContent('songs/${songName.toLowerCase()}/charts/', false, fromMods ? MODS : SOURCE)) if (Path.extension(f = f.toUpperCase()) == "JSON") Path.withoutExtension(f)];
data.difficulties = [for(f in Paths.getFolderContent('songs/${songNameLower}/charts/', false, fromMods ? MODS : SOURCE)) if (Path.extension(f = f.toUpperCase()) == "JSON") Path.withoutExtension(f)];
if (data.difficulties.length == 3) {
var hasHard = false, hasNormal = false, hasEasy = false;
for(d in data.difficulties) {
@@ -113,12 +171,12 @@ class Chart {
Logs.error('Could not parse chart for song ${songName} ($difficulty): ${Std.string(e)}');
}
if (data != null) {
/**
* CHART CONVERSION
*/
#if REGION
if (Reflect.hasField(data, "codenameChart") && Reflect.field(data, "codenameChart") == true) {
/**
* CHART CONVERSION
*/
#if REGION
if (data != null) switch (detectChartFormat(data)) {
case CODENAME:
// backward compat on events since its caused problems
var eventTypesToString:Map<Int, String> = [
-1 => "HScript Call",
@@ -129,28 +187,23 @@ class Chart {
];
if (data.events == null) data.events = [];
for (event in cast(data.events, Array<Dynamic>)) {
if (Reflect.hasField(event, "type")) {
if(event.type != null)
event.name = eventTypesToString[event.type];
Reflect.deleteField(event, "type");
}
for (event in cast(data.events, Array<Dynamic>)) if (Reflect.hasField(event, "type")) {
if (event.type != null)
event.name = eventTypesToString[event.type];
Reflect.deleteField(event, "type");
}
// codename chart
base = data;
} else {
// base game chart
FNFLegacyParser.parse(data, base);
}
#end
case PSYCH_NEW: PsychParser.parse(data, base);
case VSLICE: // TODO
case LEGACY: FNFLegacyParser.parse(data, base);
}
#end
if (base.meta == null)
base.meta = loadChartMeta(songName, difficulty, base.fromMods);
var loadedMeta = loadChartMeta(songName, difficulty, base.fromMods);
if (base.meta == null) base.meta = loadedMeta;
else {
var loadedMeta = loadChartMeta(songName, difficulty, base.fromMods);
for(field in Reflect.fields(base.meta)) {
for (field in Reflect.fields(base.meta)) {
var f = Reflect.field(base.meta, field);
if (f != null)
Reflect.setField(loadedMeta, field, f);
@@ -247,4 +300,4 @@ typedef ChartSaveSettings = {
var ?saveEventsInChart:Bool;
var ?prettyPrint:Bool;
var ?folder:String;
}
}
+1 -1
View File
@@ -44,7 +44,7 @@ typedef ChartStrumLine = {
var ?scrollSpeed:Float;
var ?vocalsSuffix:String;
var ?strumLinePos:Float; // Backwards compatability
var ?strumLinePos:Float; // Backwards compatibility
}
typedef ChartNote = {
@@ -3,15 +3,14 @@ package funkin.backend.chart;
import funkin.backend.chart.ChartData.ChartEvent;
import funkin.backend.system.Conductor;
/**
* Legacy FNF chart parser.
* This is for charts that that were made before v0.5.0.
**/
class FNFLegacyParser {
public static function parse(data:Dynamic, result:ChartData) {
// base fnf chart parsing
var data:SwagSong = data;
if (Reflect.hasField(data, "song")) {
var field:Dynamic = Reflect.field(data, "song");
if (!(field is String))
data = field;
}
var data:SwagSong = Chart.cleanSongData(data);
result.scrollSpeed = data.speed;
result.stage = data.stage;
@@ -29,7 +28,7 @@ class FNFLegacyParser {
position: "boyfriend",
notes: []
});
var gfName = data.gf != null ? data.gf : (data.gfVersion != null ? data.gfVersion : "gf");
var gfName = data.gf != null ? data.gf : (data.gfVersion != null ? data.gfVersion : (data.player3 != null ? data.player3 : "gf"));
if (!p2isGF && gfName != "none") {
result.strumLines.push({
characters: [gfName],
@@ -214,6 +213,7 @@ typedef SwagSong =
var player1:String;
var player2:String;
var ?player3:String; // alt for gf but idr if it was just used in psych or what but eh whatever maybe its better here anyways - Nex
var ?gf:String;
var ?gfVersion:String;
var validScore:Bool;
+25 -3
View File
@@ -5,16 +5,38 @@ import funkin.backend.chart.FNFLegacyParser.SwagSection;
import funkin.backend.chart.FNFLegacyParser.SwagSong;
import funkin.backend.system.Conductor;
/**
* Parser for Psych Charts.
* Both for pre-v1.0.0 and v1.0.0 charts.
**/
class PsychParser {
// Alreadly parsed in sections
// Already parsed in sections
public static var ignoreEvents:Array<String> = [
"Camera Movement",
"Alt Animation Toggle",
"BPM Change"
];
public static function parse(data:Dynamic, result:ChartData)
/**
* Converts 1.0 Psych charts to older ones
*/
public static function standardize(data:Dynamic, result:ChartData) {
var sectionsData:Array<Dynamic> = Chart.cleanSongData(data).notes;
if (sectionsData == null) return;
for (section in sectionsData) for (note in cast(section.sectionNotes, Array<Dynamic>)) {
var gottaHitNote:Bool = section.mustHitSection == (note[1] >= 4);
note[1] = (note[1] % 4) + (gottaHitNote ? 4 : 0);
if (note[3] != null && Std.isOfType(note[3], String))
note[3] = Chart.addNoteType(result, note[3]);
}
}
public static function parse(data:Dynamic, result:ChartData) {
if (Chart.detectChartFormat(data) == PSYCH_NEW) standardize(data, result);
FNFLegacyParser.parse(data, result);
}
public static function encode(chart:ChartData):Dynamic {
var base:SwagSong = FNFLegacyParser.__convertToSwagSong(chart);
@@ -88,7 +110,7 @@ class PsychParser {
var eventStep:Float = Conductor.getStepForTime(event.time);
psychEvents.push([
"Change Scroll Speed",
FlxMath.roundDecimal(event.params[1]/chart.scrollSpeed, 2), // SCROLL SPEED MULTIPLER
FlxMath.roundDecimal(event.params[1]/chart.scrollSpeed, 2), // SCROLL SPEED MULTIPLIER
FlxMath.roundDecimal( // TIME
event.params[0] ? // IS TWEENED?
(Conductor.getTimeForStep(eventStep+event.params[2]) - Conductor.getTimeForStep(eventStep))/1000
@@ -4,7 +4,7 @@ import flixel.FlxState;
import funkin.backend.scripting.events.*;
class EventManager {
// map doesnt work for that
// map doesn't work for that
public static var eventValues:Array<CancellableEvent> = [];
public static var eventKeys:Array<Class<CancellableEvent>> = [];
@@ -11,11 +11,11 @@ import funkin.backend.system.Conductor;
class GlobalScript {
public static var scripts:ScriptPack;
private static var initalized:Bool = false;
private static var initialized:Bool = false;
public static function init() {
if(initalized) return;
initalized = true;
if(initialized) return;
initialized = true;
#if MOD_SUPPORT
ModsFolder.onModSwitch.add(onModSwitch);
#end
+1 -1
View File
@@ -16,7 +16,7 @@ class HScript extends Script {
public static function initParser() {
var parser = new Parser();
parser.allowJSON = parser.allowMetadata = parser.allowTypes = true;
parser.preprocesorValues = Script.getDefaultPreprocessors();
parser.preprocessorValues = Script.getDefaultPreprocessors();
return parser;
}
+20 -2
View File
@@ -8,6 +8,7 @@ import lime.app.Application;
@:allow(funkin.backend.scripting.ScriptPack)
/**
* Class used for scripting.
* Use `Script.create` to create a script.
*/
class Script extends FlxBasic implements IFlxDestroyable {
/**
@@ -16,6 +17,9 @@ class Script extends FlxBasic implements IFlxDestroyable {
*/
public static var staticVariables:Map<String, Dynamic> = [];
/**
* Gets the default variables for a script.
*/
public static function getDefaultVariables(?script:Script):Map<String, Dynamic> {
return [
// Haxe related stuff
@@ -109,7 +113,6 @@ class Script extends FlxBasic implements IFlxDestroyable {
**/
public static function getDefaultImportRedirects():Map<String, String> {
var redirects:Map<String, String> = [
"funkin.savedata.FunkinSave" => "funkin.backend.utils.FunkinSave"
];
final events = "funkin.backend.scripting.events.";
@@ -146,6 +149,10 @@ class Script extends FlxBasic implements IFlxDestroyable {
return redirects;
}
/**
* Gets the default defines for a script.
* Includes all of the defines that the build was compiled with.
*/
public static function getDefaultPreprocessors():Map<String, Dynamic> {
var defines = funkin.backend.system.macros.DefinesMacro.defines;
defines.set("CODENAME_ENGINE", true);
@@ -187,6 +194,10 @@ class Script extends FlxBasic implements IFlxDestroyable {
private var didLoad:Bool = false;
/**
* Remapped filenames.
* Used for trace messages, to show what mod the script is from.
*/
public var remappedNames:Map<String, String> = [];
/**
@@ -312,7 +323,7 @@ class Script extends FlxBasic implements IFlxDestroyable {
}
/**
* Loads the code from a string, doesnt really work after the script has been loaded
* Loads the code from a string, doesn't really work after the script has been loaded
* @param code The code.
*/
public function loadFromString(code:String) {
@@ -371,7 +382,14 @@ class Script extends FlxBasic implements IFlxDestroyable {
private function onCall(func:String, parameters:Array<Dynamic>):Dynamic {
return null;
}
/**
* Called when the script is created.
* @param path Path to the script
*/
public function onCreate(path:String) {}
/**
* Called when the script is loaded.
*/
public function onLoad() {}
}
+61 -1
View File
@@ -3,6 +3,9 @@ package funkin.backend.scripting;
import flixel.util.FlxStringUtil;
import funkin.backend.scripting.events.CancellableEvent;
/**
* Used to group multiple scripts together, and easily be able to call them.
**/
@:access(CancellableEvent)
class ScriptPack extends Script {
public var scripts:Array<Script> = [];
@@ -10,12 +13,19 @@ class ScriptPack extends Script {
public var publicVariables:Map<String, Dynamic> = [];
public var parent:Dynamic = null;
/**
* Loads all scripts in the pack.
**/
public override function load() {
for(e in scripts) {
e.load();
}
}
/**
* Checks if the script pack contains a script with a specific path.
* @param path Path to check
*/
public function contains(path:String) {
for(e in scripts)
if (e.path == path)
@@ -27,6 +37,10 @@ class ScriptPack extends Script {
super(name);
}
/**
* Gets a script by path.
* @param name Path to the script
**/
public function getByPath(name:String) {
for(s in scripts)
if (s.path == name)
@@ -34,12 +48,22 @@ class ScriptPack extends Script {
return null;
}
/**
* Gets a script by name.
* @param name Name of the script
**/
public function getByName(name:String) {
for(s in scripts)
if (s.fileName == name)
return s;
return null;
}
/**
* Imports a script by path.
* @param path Path to the script
* @throws Error if the script does not exist
**/
public function importScript(path:String):Script {
var script = Script.create(Paths.script(path));
if (script is DummyScript) {
@@ -51,6 +75,12 @@ class ScriptPack extends Script {
return script;
}
/**
* Calls a function on every single script.
* Only calls on scripts that are active.
* @param func Function to call
* @param parameters Parameters to pass to the function
**/
public override function call(func:String, ?parameters:Array<Dynamic>):Dynamic {
for(e in scripts)
if(e.active)
@@ -74,6 +104,10 @@ class ScriptPack extends Script {
return event;
}
/**
* Gets the first script that has a variable with a specific name.
* @param val Name of the variable
**/
public override function get(val:String):Dynamic {
for(e in scripts) {
var v = e.get(val);
@@ -82,40 +116,66 @@ class ScriptPack extends Script {
return null;
}
/**
* Reloads all scripts in the pack.
**/
public override function reload() {
for(e in scripts) e.reload();
}
/**
* Sets a variable in every script.
**/
public override function set(val:String, value:Dynamic) {
for(e in scripts) e.set(val, value);
}
/**
* Sets the parent/this of every script in the pack.
*/
public override function setParent(parent:Dynamic) {
this.parent = parent;
for(e in scripts) e.setParent(parent);
}
/**
* Destroys all scripts in the pack.
**/
public override function destroy() {
super.destroy();
for(e in scripts) e.destroy();
}
public override function onCreate(path:String) {}
@:dox(hide) public override function onCreate(path:String) {}
/**
* Adds a script to the pack, and sets the parent/this of the script.
**/
public function add(script:Script) {
scripts.push(script);
__configureNewScript(script);
}
/**
* Removes a script from the pack.
* Does not reset the parent/this.
**/
public function remove(script:Script) {
scripts.remove(script);
}
/**
* Inserts a script into the pack, and sets the parent/this of the script.
**/
public function insert(pos:Int, script:Script) {
scripts.insert(pos, script);
__configureNewScript(script);
}
/**
* Configures a new script.
* @param script Script to configure
**/
private function __configureNewScript(script:Script) {
if (parent != null) script.setParent(parent);
script.setPublicMap(publicVariables);
@@ -0,0 +1,18 @@
package funkin.backend.scripting.events;
import flixel.FlxState;
/**
* CANCEL this event to prevent default behaviour!
*/
final class TransitionCreationEvent extends CancellableEvent {
/**
* If the transition is going out into another state
*/
public var transOut:Bool;
/**
* The state that is about to be loaded (only on trans out)
*/
public var newState:FlxState;
}
@@ -2,11 +2,11 @@ package funkin.backend.scripting.events.menu.freeplay;
final class FreeplayAlphaUpdateEvent extends CancellableEvent {
/**
* The alpha when nothing is playing and isnt selected
* The alpha when nothing is playing and isn't selected
*/
public var idleAlpha:Float;
/**
* The alpha when something is playing and isnt selected
* The alpha when something is playing and isn't selected
*/
public var idlePlayingAlpha:Float;
/**
@@ -86,7 +86,7 @@ final class NoteHitEvent extends CancellableEvent {
*/
public var healthGain:Float;
/**
* Rating name. Defaults to "sick", "good", "bad" and "shit". Customisable.
* Rating name. Defaults to "sick", "good", "bad" and "shit". Customizable.
*/
public var rating:String = "sick";
/**
@@ -3,11 +3,13 @@ package funkin.backend.shaders;
import flixel.util.FlxColor;
import openfl.display.ShaderParameter;
@:dox(hide)
typedef BlendModeShader =
{
var uBlendColor:ShaderParameter<Float>;
}
@:dox(hide)
class BlendModeEffect
{
public var shader(default, null):BlendModeShader;
+46 -3
View File
@@ -215,8 +215,8 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
if(value == null)
value = ShaderTemplates.defaultFragmentSource;
glRawFragmentSource = value;
value = value.replace("#pragma header", ShaderTemplates.fragHeader).replace("#pragma body", ShaderTemplates.fragBody);
value = processImports(value, FRAGMENT_SHADER);
value = value.replace("#pragma header", ShaderTemplates.fragHeader).replace("#pragma body", ShaderTemplates.fragBody);
if (value != __glFragmentSource)
{
__glSourceDirty = true;
@@ -230,8 +230,19 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
if(value == null)
value = ShaderTemplates.defaultVertexSource;
glRawVertexSource = value;
value = value.replace("#pragma header", ShaderTemplates.vertHeader).replace("#pragma body", ShaderTemplates.vertBody);
value = processImports(value, VERTEX_SHADER);
var useBackCompat:Bool = true;
for (regex in ShaderTemplates.vertBackCompatVarList) if (!regex.match(value)) {
useBackCompat = false;
break;
}
var header = useBackCompat ? ShaderTemplates.vertHeaderBackCompat : ShaderTemplates.vertHeader;
var body = useBackCompat ? ShaderTemplates.vertBodyBackCompat : ShaderTemplates.vertBody;
value = value.replace("#pragma header", header).replace("#pragma body", body);
if (value != __glVertexSource)
{
__glSourceDirty = true;
@@ -521,7 +532,7 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
// cant do "field is ShaderInput" because ShaderInput has the @:generic metadata
// aka instead of ShaderInput<Float> it gets built as ShaderInput_Float
// this should be fine tho because we check the class, and the fields dont vary based on the type
// this should be fine tho because we check the class, and the fields don't vary based on the type
// thanks for looking in the code cne fans :D!! -lunar
@@ -673,6 +684,38 @@ if(hasColorTransform) {
gl_Position = openfl_Matrix * openfl_Position;";
// TODO: make this ignore comments
public static final vertBackCompatVarList:Array<EReg> = [
~/attribute float alpha/,
~/attribute vec4 colorMultiplier/,
~/attribute vec4 colorOffset/,
~/uniform bool hasColorTransform/
];
public static final vertHeaderBackCompat:String = "attribute float openfl_Alpha;
attribute vec4 openfl_ColorMultiplier;
attribute vec4 openfl_ColorOffset;
attribute vec4 openfl_Position;
attribute vec2 openfl_TextureCoord;
varying float openfl_Alphav;
varying vec4 openfl_ColorMultiplierv;
varying vec4 openfl_ColorOffsetv;
varying vec2 openfl_TextureCoordv;
uniform mat4 openfl_Matrix;
uniform bool openfl_HasColorTransform;
uniform vec2 openfl_TextureSize;";
public static final vertBodyBackCompat:String = "openfl_Alphav = openfl_Alpha;
openfl_TextureCoordv = openfl_TextureCoord;
if(openfl_HasColorTransform) {
openfl_ColorMultiplierv = openfl_ColorMultiplier;
openfl_ColorOffsetv = openfl_ColorOffset / 255.0;
}
gl_Position = openfl_Matrix * openfl_Position;";
public static final defaultVertexSource:String = "#pragma header
@@ -2,6 +2,7 @@ package funkin.backend.shaders;
import flixel.system.FlxAssets.FlxShader;
@:dox(hide)
class OverlayShader extends FlxShader
{
@:glFragmentSource('
@@ -3,6 +3,7 @@ package funkin.backend.shaders;
// STOLEN FROM HAXEFLIXEL DEMO LOL
import flixel.system.FlxAssets.FlxShader;
@:dox(hide)
enum WiggleEffectType
{
DREAMY;
@@ -12,6 +13,7 @@ enum WiggleEffectType
FLAG;
}
@:dox(hide)
class WiggleEffect
{
public var shader(default, null):WiggleShader = new WiggleShader();
@@ -59,6 +61,7 @@ class WiggleEffect
}
}
@:dox(hide)
class WiggleShader extends FlxShader
{
@:glFragmentSource('
+2 -2
View File
@@ -38,7 +38,7 @@ class Conductor
public static var stepCrochet:Float = crochet / 4; // steps in milliseconds
/**
* Number of beats per mesure (top number in time signature). Defaults to 4.
* Number of beats per measure (top number in time signature). Defaults to 4.
*/
public static var beatsPerMeasure:Float = 4;
@@ -190,7 +190,7 @@ class Conductor
__updateSongPos(FlxG.elapsed);
if (bpm > 0) {
// updates curbeat and stuff
// updates curBeat and stuff
__lastChange = {
stepTime: 0,
songTime: 0,
-10
View File
@@ -1,10 +0,0 @@
package funkin.backend.system;
enum abstract ErrorCode(Int) {
var OK = 0;
var FAILED = 1;
var MISSING_PROPERTY = 2;
var TYPE_INCORRECT = 3;
var VALUE_NULL = 4;
var REFLECT_ERROR = 5;
}
+9 -2
View File
@@ -21,7 +21,7 @@ class Flags {
@:lazy public static var VERSION_MESSAGE:String = 'Codename Engine v${Application.current.meta.get('version')}';
public static var REPO_NAME:String = "CodenameEngine";
public static var REPO_OWNER:String = "FNF-CNE-Devs";
public static var REPO_OWNER:String = "CodenameCrew";
public static var REPO_URL:String = 'https://github.com/$REPO_OWNER/$REPO_NAME';
// make this empty once you guys are done with the project.
@@ -91,7 +91,7 @@ class Flags {
public static var DEFAULT_OPPONENT_MODE_ALLOWED:Bool = false;
@:also(funkin.game.PlayState.coopMode)
public static var DEFAULT_COOP_MODE:Bool = false; // used in playstate if it doesnt find it
public static var DEFAULT_COOP_MODE:Bool = false; // used in playstate if it doesn't find it
@:also(funkin.game.PlayState.opponentMode)
public static var DEFAULT_OPPONENT_MODE:Bool = false;
@@ -150,6 +150,13 @@ class Flags {
public static var DISABLE_EDITORS:Bool = false;
public static var DISABLE_BETA_WARNING_SCREEN:Bool = false;
public static var DISABLE_TRANSITIONS:Bool = false;
@:also(funkin.backend.MusicBeatTransition.script)
public static var DEFAULT_TRANSITION_SCRIPT:String = "";
@:also(funkin.menus.PauseSubState.script)
public static var DEFAULT_PAUSE_SCRIPT:String = "";
@:also(funkin.game.GameOverSubstate.script)
public static var DEFAULT_GAMEOVER_SCRIPT:String = "";
// -- End of Codename's Default Flags --
/**
@@ -3,7 +3,7 @@ package funkin.backend.system;
import flixel.graphics.FlxGraphic;
/**
* Dummy FlxSprite that allows you to cache FlxGraphics, and immediatly send them to GPU memory.
* Dummy FlxSprite that allows you to cache FlxGraphics, and immediately send them to GPU memory.
*/
class GraphicCacheSprite extends FlxSprite {
/**
+1 -1
View File
@@ -31,7 +31,7 @@ class Logs {
var prefix = "[FLIXEL]";
var color:ConsoleColor = LIGHTGRAY;
var level:Level = INFO;
if (style == LogStyle.CONSOLE) // cant place a switch here as these arent inline values - Nex
if (style == LogStyle.CONSOLE) // cant place a switch here as these aren't inline values - Nex
{
prefix = "> ";
color = WHITE;
+17 -3
View File
@@ -47,6 +47,10 @@ class Main extends Sprite
public static var game:FunkinGame;
/**
* The time since the game was focused last time in seconds.
*/
public static var timeSinceFocus(get, never):Float;
public static var time:Int = 0;
// You can pretty much ignore everything from here on - your code should go in your states.
@@ -148,7 +152,7 @@ class Main extends Sprite
Assets.registerLibrary('default', lib);
funkin.options.PlayerSettings.init();
funkin.backend.utils.FunkinSave.init();
funkin.savedata.FunkinSave.init();
Options.load();
FlxG.fixedTimestep = false;
@@ -158,6 +162,7 @@ class Main extends Sprite
Conductor.init();
AudioSwitchFix.init();
EventManager.init();
FlxG.signals.focusGained.add(onFocus);
FlxG.signals.preStateSwitch.add(onStateSwitch);
FlxG.signals.postStateSwitch.add(onStateSwitchPost);
@@ -203,13 +208,17 @@ class Main extends Sprite
{asset: diamond, width: 32, height: 32}, new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
}
public static function onFocus() {
_tickFocused = FlxG.game.ticks;
}
private static function onStateSwitch() {
scaleMode.resetSize();
}
private static function onStateSwitchPost() {
// manual asset clearing since base openfl one doesnt clear lime one
// doesnt clear bitmaps since flixel fork does it auto
// manual asset clearing since base openfl one does'nt clear lime one
// does'nt clear bitmaps since flixel fork does it auto
@:privateAccess {
// clear uint8 pools
@@ -236,4 +245,9 @@ class Main extends Sprite
Sys.setCwd(Path.addTrailingSlash(openfl.filesystem.File.applicationStorageDirectory.nativePath));
#end
}
private static var _tickFocused:Float = 0;
public static function get_timeSinceFocus():Float {
return (FlxG.game.ticks - _tickFocused) / 1000;
}
}
@@ -60,6 +60,7 @@ class MainState extends FlxState {
TranslationUtil.findAllLanguages();
TranslationUtil.setLanguage();
ModsFolder.onModSwitch.dispatch(ModsFolder.currentModFolder); // Loads global.hx
MusicBeatTransition.script = Flags.DEFAULT_TRANSITION_SCRIPT;
Main.refreshAssets();
DiscordUtil.init();
EventsData.reloadEvents();
@@ -126,7 +126,7 @@ class Framerate extends Sprite {
bgSprite.scaleY = height;
var selectable = debugMode == 2;
{ // idk i tried to make it more lookable:sob: - Nex
{ // idk i tried to make it more readable:sob: - Nex
memoryCounter.memoryText.selectable = memoryCounter.memoryPeakText.selectable =
fpsCounter.fpsNum.selectable = fpsCounter.fpsLabel.selectable =
#if SHOW_BUILD_ON_FPS codenameBuildField.selectable = #end selectable;
@@ -63,7 +63,7 @@ class SystemInfo extends FramerateCategory {
cpuName = process.stdout.readAll().toString().trim().split("\n")[1].trim();
#elseif mac
var process = new HiddenProcess("sysctl -a | grep brand_string"); // Somehow this isnt able to use the args but it still works
var process = new HiddenProcess("sysctl -a | grep brand_string"); // Somehow this isn't able to use the args but it still works
if (process.exitCode() != 0) throw 'Could not fetch CPU information';
cpuName = process.stdout.readAll().toString().trim().split(":")[1].trim();
@@ -7,6 +7,11 @@ import haxe.Json;
import haxe.Exception;
// TODO: Document further and perhaps make this a Haxelib.
/**
* Set of functions to interact with GitHub.
* Requires the `GITHUB_API` macro to be defined.
* This has no authentication, so it's limited to unauthenticated requests, and rate limits.
**/
class GitHub {
/**
* Gets all the releases from a specific GitHub repository using the GitHub API.
@@ -21,4 +21,12 @@ typedef GitHubContributor = {
var type:String;
var site_admin:Bool;
var contributions:Int;
}
typedef CreditsGitHubContributor = {
var login:String;
var avatar_url:String;
var html_url:String;
var ?id:Int; // Not available in the avatar part
var ?contributions:Int; // Not available in the avatar part
}
@@ -78,9 +78,9 @@ class ControlsMacro
});
function buildBindControl(s:String, prefix:String) {
var prefixxed = prefix + s;
var prefixed = prefix + s;
return macro bindKeys(Control.$s, Options.$prefixxed);
return macro bindKeys(Control.$s, Options.$prefixed);
}
fields.push({
@@ -9,7 +9,7 @@ class DefinesMacro {
/**
* Returns the defined values
*/
public static var defines(get, null):Map<String, Dynamic>;
public static var defines(get, never):Map<String, Dynamic>;
// GETTERS
#if REGION
@@ -184,7 +184,7 @@ class FlagMacro {
}
case TPath({name: "Array", pack: []}):
Context.error("Flag " + field.name + " cannot be an Array that isnt a String or Bool or TrimmedString or Int or Float", field.pos);
Context.error("Flag " + field.name + " cannot be an Array that isn't a String or Bool or TrimmedString or Int or Float", field.pos);
case TPath({name: "Map", pack: []}):
Context.error("Flag " + field.name + " cannot be a Map<K, V>", field.pos);
default:
@@ -42,20 +42,9 @@ class FunkinSaveMacro {
/**
* SAVE FUNCTION
*/
var saveFuncBlocks:Array<Expr> = [for(f in fieldNames)
macro ${{
pos: Context.currentPos(),
expr: EConst(CIdent(saveFieldName))
}}.data.$f = ${{
pos: Context.currentPos(),
expr: EConst(CIdent(f))
}}
];
var saveFuncBlocks:Array<Expr> = [for(f in fieldNames) macro $i{saveFieldName}.data.$f = $i{f}];
saveFuncBlocks.push(macro ${{
pos: Context.currentPos(),
expr: EConst(CIdent(saveFieldName))
}}.flush());
saveFuncBlocks.push(macro $i{saveFieldName}.flush());
fields.push({
pos: Context.currentPos(),
@@ -81,16 +70,8 @@ class FunkinSaveMacro {
expr: {
pos: Context.currentPos(),
expr: EBlock([for(f in fieldNames)
macro if (${{
pos: Context.currentPos(),
expr: EConst(CIdent(saveFieldName))
}}.data.$f != null) ${{
pos: Context.currentPos(),
expr: EConst(CIdent(f))
}} = ${{
pos: Context.currentPos(),
expr: EConst(CIdent(saveFieldName))
}}.data.$f
macro if ($i{saveFieldName}.data.$f != null)
$i{f} = $i{saveFieldName}.data.$f
])
}
}),
@@ -8,11 +8,27 @@ class GitCommitMacro {
/**
* Returns the current commit number
*/
public static var commitNumber(get, null):Int;
public static var commitNumber(get, never):Int;
/**
* Returns the current commit hash
*/
public static var commitHash(get, null):String;
public static var commitHash(get, never):String;
/**
* Returns the current commit hash in long format
*/
public static var commitHashLong(get, never):String;
/**
* Returns the current commit message
*/
public static var commitMessage(get, never):String;
/**
* Returns the current commit author
*/
public static var commitAuthor(get, never):String;
/**
* Returns the current commit date + time
**/
public static var commitDate(get, never):Date;
// GETTERS
#if REGION
@@ -21,6 +37,15 @@ class GitCommitMacro {
private static inline function get_commitHash()
return __getCommitHash();
private static inline function get_commitHashLong()
return __getCommitHashLong();
private static inline function get_commitMessage()
return __getCommitMessage();
private static inline function get_commitAuthor()
return __getCommitAuthor();
private static inline function get_commitDate()
return __getCommitDate();
#end
// INTERNAL MACROS
@@ -52,5 +77,77 @@ class GitCommitMacro {
return macro $v{0}
#end
}
private static macro function __getCommitHashLong() {
#if display
return macro $v{"-"};
#else
try {
var proc = new Process('git', ['rev-parse', 'HEAD'], false);
proc.exitCode(true);
return macro $v{proc.stdout.readLine()};
} catch(e) {}
return macro $v{"-"}
#end
}
private static macro function __getCommitMessage() {
#if display
return macro $v{"-"};
#else
try {
var proc = new Process('git', ['log', '-1', '--pretty=%B'], false);
proc.exitCode(true);
var all = proc.stdout.readAll().toString();
while(StringTools.endsWith(all, "\r\n")) all = all.substr(0, all.length - 2);
while(StringTools.endsWith(all, "\n")) all = all.substr(0, all.length - 1);
while(StringTools.endsWith(all, "\r")) all = all.substr(0, all.length - 1);
return macro $v{all};
} catch(e) {}
return macro $v{"-"}
#end
}
private static macro function __getCommitAuthor() {
#if display
return macro $v{"-"};
#else
try {
var proc = new Process('git', ['log', '-1', '--pretty=%an'], false);
proc.exitCode(true);
return macro $v{StringTools.trim(proc.stdout.readLine())};
} catch(e) {}
return macro $v{"-"}
#end
}
private static macro function __getCommitDate() {
#if display
return macro $v{Date.now()};
#else
try {
// get the time in UTC format
var oldTz = Sys.getEnv("TZ");
Sys.putEnv("TZ", "UTC0");
var proc = new Process('git', ['log', "--date='format-local:%Y-%m-%dT%H:%M:%SZ'", '-1', '--pretty=%cd'], false);
proc.exitCode(true);
Sys.putEnv("TZ", oldTz);
var rawDate = StringTools.trim(proc.stdout.readLine());
try {
Date.fromString(rawDate);
} catch(e) {
throw 'Invalid date format: $rawDate';
}
return macro Date.fromString($v{rawDate});
} catch(e) {}
return macro Date.fromString("1970-01-01T00:00:00"); // failed to parse date
#end
}
#end
}
@@ -24,14 +24,8 @@ class OptionsMacro {
var ptwoname = 'P2_$name';
var newFuncExpr:Expr = macro {
var a:Array<FlxKey> = [];
for(e in ${{
pos: Context.currentPos(),
expr: EConst(CIdent(ponename))
}}) a.push(e);
for(e in ${{
pos: Context.currentPos(),
expr: EConst(CIdent(ptwoname))
}}) a.push(e);
for(e in $i{ponename}) a.push(e);
for(e in $i{ptwoname}) a.push(e);
return a;
};
@@ -6,7 +6,7 @@ import funkin.backend.utils.NativeAPI;
import lime.media.AudioManager;
/**
* if youre stealing this keep this comment at least please lol
* if you are stealing this keep this comment at least please lol
*
* hi gray itsa me yoshicrafter29 i fixed it hehe
*/
+1 -1
View File
@@ -4,7 +4,7 @@ import flixel.sound.FlxSound;
import lime.media.AudioBuffer;
// Thank you yosh :DDD -lunar
// (https://github.com/FNF-CNE-Devs/YoshiCrafterEngine/blob/main/source/WaveformSprite.hx)
// (https://github.com/CodenameCrew/YoshiCrafterEngine/blob/main/source/WaveformSprite.hx)
class AudioAnalyzer {
public var buffer:AudioBuffer;
var __peakByte:Float = 0;
+58 -16
View File
@@ -40,9 +40,15 @@ import openfl.geom.ColorTransform;
using StringTools;
/**
* Various utilities, that have no specific Util class.
**/
@:allow(funkin.game.PlayState)
class CoolUtil
{
/**
* Gets the last exception stack. Useful for debugging.
*/
public static function getLastExceptionStack():String {
return CallStack.toString(CallStack.exceptionStack());
}
@@ -149,14 +155,14 @@ class CoolUtil
* Gets file attributes from a file or a folder adding eventual missing folders in the path
* (WARNING: Only works on `windows` for now. On other platforms the attributes' value it's always going to be `0` -thanks to the wrapper you can also use `isNothing` for checking- but still creates eventual missing folders if the platforms allows it to).
* @param path Path to the file or folder
* @param useAbsol If it should use the absolute path (By default it's `true` but if it's `false` you can use files outside from this program's directory for example)
* @param useAbsolute If it should use the absolute path (By default it's `true` but if it's `false` you can use files outside from this program's directory for example)
* @return The attributes through the `FileAttributeWrapper`
*/
@:noUsing public static inline function safeGetAttributes(path:String, useAbsol:Bool = true):FileAttributeWrapper {
@:noUsing public static inline function safeGetAttributes(path:String, useAbsolute:Bool = true):FileAttributeWrapper {
#if sys
addMissingFolders(Path.directory(path));
var result = NativeAPI.getFileAttributes(path, useAbsol);
var result = NativeAPI.getFileAttributes(path, useAbsolute);
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
@@ -169,15 +175,15 @@ class CoolUtil
* (WARNING: Only works on `windows` for now. On other platforms the return code it's always going to be `0` but still creates eventual missing folders if the platforms allows it to).
* @param path Path to the file or folder
* @param attrib The attribute(s) to set (WARNING: There are some non settable attributes, such as the `COMPRESSED` one)
* @param useAbsol If it should use the absolute path (By default it's `true` but if it's `false` you can use files outside from this program's directory for example)
* @param useAbsolute If it should use the absolute path (By default it's `true` but if it's `false` you can use files outside from this program's directory for example)
* @return The result code: `0` means that it failed setting
*/
@:noUsing public static inline function safeSetAttributes(path:String, attrib:OneOfThree<NativeAPI.FileAttribute, FileAttributeWrapper, Int>, useAbsol:Bool = true):Int {
@:noUsing public static inline function safeSetAttributes(path:String, attrib:OneOfThree<NativeAPI.FileAttribute, FileAttributeWrapper, Int>, useAbsolute:Bool = true):Int {
// yes, i'm aware that FileAttribute is also an Int so need to include it too, but at least like this we don't have to make cast sometimes while passing the arguments - Nex
#if sys
addMissingFolders(Path.directory(path));
var result = NativeAPI.setFileAttributes(path, attrib, useAbsol);
var result = NativeAPI.setFileAttributes(path, attrib, useAbsolute);
if(result == 0) Logs.trace('Failed to set attributes to $path with a code of: $result', WARNING);
return result;
#else
@@ -190,14 +196,14 @@ class CoolUtil
* (WARNING: Only works on `windows` for now. On other platforms the return code it's always going to be `0` but still creates eventual missing folders if the platforms allows it to).
* @param path Path to the file or folder
* @param attrib The attribute(s) to add (WARNING: There are some non settable attributes, such as the `COMPRESSED` one)
* @param useAbsol If it should use the absolute path (By default it's `true` but if it's `false` you can use files outside from this program's directory for example)
* @param useAbsolute If it should use the absolute path (By default it's `true` but if it's `false` you can use files outside from this program's directory for example)
* @return The result code: `0` means that it failed setting
*/
@:noUsing public static inline function safeAddAttributes(path:String, attrib:OneOfTwo<NativeAPI.FileAttribute, Int>, useAbsol:Bool = true):Int {
@:noUsing public static inline function safeAddAttributes(path:String, attrib:OneOfTwo<NativeAPI.FileAttribute, Int>, useAbsolute:Bool = true):Int {
#if sys
addMissingFolders(Path.directory(path));
var result = NativeAPI.addFileAttributes(path, attrib, useAbsol);
var result = NativeAPI.addFileAttributes(path, attrib, useAbsolute);
if(result == 0) Logs.trace('Failed to add attributes to $path with a code of: $result', WARNING);
return result;
#else
@@ -210,14 +216,14 @@ class CoolUtil
* (WARNING: Only works on `windows` for now. On other platforms the return code it's always going to be `0` but still creates eventual missing folders if the platforms allows it to).
* @param path Path to the file or folder
* @param attrib The attribute(s) to remove (WARNING: There are some non settable attributes, such as the `COMPRESSED` one)
* @param useAbsol If it should use the absolute path (By default it's `true` but if it's `false` you can use files outside from this program's directory for example)
* @param useAbsolute If it should use the absolute path (By default it's `true` but if it's `false` you can use files outside from this program's directory for example)
* @return The result code: `0` means that it failed setting
*/
@:noUsing public static inline function safeRemoveAttributes(path:String, attrib:OneOfTwo<NativeAPI.FileAttribute, Int>, useAbsol:Bool = true):Int {
@:noUsing public static inline function safeRemoveAttributes(path:String, attrib:OneOfTwo<NativeAPI.FileAttribute, Int>, useAbsolute:Bool = true):Int {
#if sys
addMissingFolders(Path.directory(path));
var result = NativeAPI.removeFileAttributes(path, attrib, useAbsol);
var result = NativeAPI.removeFileAttributes(path, attrib, useAbsolute);
if(result == 0) Logs.trace('Failed to remove attributes to $path with a code of: $result', WARNING);
return result;
#else
@@ -467,7 +473,7 @@ class CoolUtil
var beatsPerMeasure:Float = Flags.DEFAULT_BEATS_PER_MEASURE;
var stepsPerBeat:Int = Flags.DEFAULT_STEPS_PER_BEAT;
// Check later, i dont think timeSignParsed can contain null, only nan
// 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];
@@ -823,6 +829,15 @@ class CoolUtil
return r;
}
/**
* Quantizes a value to a certain amount.
* Example: `quantize(2.5543, 1)` will return `2.0`
* Example: `quantize(2.5543, 10)` will return `2.5`
* Example: `quantize(2.5543, 100)` will return `2.55`
*
* @param Value Value to quantize
* @param Quant Quantization amount
*/
@:noUsing public static inline function quantize(Value:Float, Quant:Float) {
return Math.fround(Value * Quant) / Quant;
}
@@ -840,9 +855,19 @@ class CoolUtil
frontEnd.music = music;
}
/**
* Gets the FlxEase from a string.
* @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));
/**
* Sorts an array alphabetically.
* @param array Array to sort
* @param lowercase Whenever the array should be sorted in lowercase
*/
public static function sortAlphabetically(array:Array<String>, ?lowercase:Bool=false) {
array.sort(function(a1, a2):Int {
if(lowercase) {
@@ -856,6 +881,11 @@ class CoolUtil
return array;
}
/**
* Pushes an element to an array, but only if it doesn't already exist.
* @param array Array to push to
* @param element Element to push
*/
public static inline function pushOnce<T>(array:Array<T>, element:T) {
#if (haxe >= "4.0.0")
if (!array.contains(element))
@@ -867,6 +897,11 @@ class CoolUtil
}
#if !(haxe >= "4.0.0")
/**
* Checks if an array contains an element.
* @param array Array to check
* @param element Element to check
*/
public static inline function contains<T>(array:Array<T>, element:T) {
return array.indexOf(element) != -1;
}
@@ -932,9 +967,10 @@ class CoolUtil
* Converts an array of numbers into a string of ranges.
* Example: [1,2,3,5,7,8,9,8,7,6,5] -> "1..3,5,7..9,8..5"
* @param numbers Array of numbers
* @param separator Separator between ranges
* @return String representing the ranges
*/
public static function formatNumberRange(numbers:Array<Int>, seperator:String = ","):String {
public static function formatNumberRange(numbers:Array<Int>, separator:String = ","):String {
if (numbers.length == 0) return "";
var result:Array<String> = [];
@@ -971,9 +1007,15 @@ class CoolUtil
i++;
}
return result.join(seperator);
return result.join(separator);
}
/**
* Deep flattens an array.
* Example: `deepFlatten([1, [2, 3], 4])` will return `[1, 2, 3, 4]`
* @param arr Array to flatten
* @param result Result array
*/
public static function deepFlatten(arr:Array<Dynamic>, ?result:Array<Dynamic>):Array<Dynamic> {
if(arr == null) return [];
if(result == null) result = [];
@@ -989,7 +1031,7 @@ class CoolUtil
}
/**
* SFXs to play using `playMenuSFX`.
* SFXs to play using `CoolUtil.playMenuSFX`.
*/
enum abstract CoolSfx(Int) from Int {
var SCROLL = 0;
@@ -19,9 +19,11 @@ class FunkinParentDisabler extends FlxBasic {
var __timers:Array<FlxTimer>;
var __sounds:Array<FlxSound>;
var __replaceUponDestroy:Bool;
public function new(replaceUponDestroy:Bool = false) {
var __restoreUponDestroy:Bool;
public function new(replaceUponDestroy:Bool = false, restoreUponDestroy:Bool = true) {
super();
__replaceUponDestroy = replaceUponDestroy;
__restoreUponDestroy = restoreUponDestroy;
@:privateAccess {
// tweens
__tweens = FlxTween.globalManager._tweens.copy();
@@ -53,6 +55,11 @@ class FunkinParentDisabler extends FlxBasic {
public override function destroy() {
super.destroy();
@:privateAccess {
if (!__restoreUponDestroy) {
for(t in __tweens) { t.cancel(); t.destroy(); };
for(t in __timers) { t.cancel(); t.destroy(); };
return;
}
if (__replaceUponDestroy) {
FlxTween.globalManager._tweens = __tweens;
FlxTimer.globalManager._timers = __timers;
-118
View File
@@ -1,118 +0,0 @@
package funkin.backend.utils;
import flixel.util.FlxSave;
import lime.app.Application;
import openfl.Lib;
/**
* Class used for saves WITHOUT going through the struggle of type checks
* Just add your save variables the way you would do in the Options.hx file.
* The macro will automatically generate the `flush` and `load` functions.
*/
@:build(funkin.backend.system.macros.FunkinSaveMacro.build("save", "flush", "load"))
class FunkinSave {
public static var highscores:Map<HighscoreEntry, SongScore> = [];
/**
* ONLY OPEN IF YOU WANT TO EDIT FUNCTIONS RELATED TO SAVING, LOADING OR HIGHSCORES.
*/
#if REGION
@:dox(hide) @:doNotSave
private static var __eventAdded = false;
@:doNotSave
public static var save:FlxSave;
public static function init() {
//trace(Application.current.meta.get('save-path'));
//trace(Application.current.meta.get('save-name'));
save = new FlxSave();
save.bind('save-default', #if sys 'YoshiCrafter29/CodenameEngine' #else 'CodenameEngine' #end);
load();
if (!__eventAdded) {
Lib.application.onExit.add(function(i:Int) {
Logs.traceColored([
Logs.getPrefix("FunkinSave"),
Logs.logText("Saving "),
Logs.logText("savedata", GREEN),
Logs.logText("...")
], VERBOSE);
flush();
});
__eventAdded = true;
}
}
/**
* Returns the high-score for a song.
* @param name Song name
* @param diff Song difficulty
* @param changes Changes made to that song in freeplay.
*/
public static inline function getSongHighscore(name:String, diff:String, ?changes:Array<HighscoreChange>) {
if (changes == null) changes = [];
return safeGetHighscore(HSongEntry(name.toLowerCase(), diff.toLowerCase(), changes));
}
public static inline function setSongHighscore(name:String, diff:String, highscore:SongScore, ?changes:Array<HighscoreChange>) {
if (changes == null) changes = [];
if (safeRegisterHighscore(HSongEntry(name.toLowerCase(), diff.toLowerCase(), changes), highscore)) {
flush();
return true;
}
return false;
}
public static inline function getWeekHighscore(name:String, diff:String)
return safeGetHighscore(HWeekEntry(name.toLowerCase(), diff.toLowerCase()));
public static inline function setWeekHighscore(name:String, diff:String, highscore:SongScore) {
if (safeRegisterHighscore(HWeekEntry(name.toLowerCase(), diff.toLowerCase()), highscore)) {
flush();
return true;
}
return false;
}
private static function safeGetHighscore(entry:HighscoreEntry):SongScore {
if (!highscores.exists(entry)) {
return {
score: 0,
accuracy: 0,
misses: 0,
hits: [],
date: null
};
}
return highscores.get(entry);
}
private static function safeRegisterHighscore(entry:HighscoreEntry, highscore:SongScore) {
var oldHigh = safeGetHighscore(entry);
if (oldHigh.date == null || oldHigh.score < highscore.score) {
highscores.set(entry, highscore);
return true;
}
return false;
}
#end
}
enum HighscoreEntry {
HWeekEntry(weekName:String, difficulty:String);
HSongEntry(songName:String, difficulty:String, changes:Array<HighscoreChange>);
}
enum HighscoreChange {
CCoopMode;
COpponentMode;
}
typedef SongScore = {
var score:Int;
var accuracy:Float;
var misses:Int;
var hits:Map<String, Int>;
var date:String;
}
+31
View File
@@ -14,6 +14,12 @@ import openfl.system.System;
using StringTools;
/**
* Tools that are related to memory.
* Including garbage collection, and memory usage, and hardware info.
*
* DISCLAIMER: Hardware info is only available on Native platforms.
**/
class MemoryUtil {
public static var disableCount:Int = 0;
@@ -34,12 +40,18 @@ class MemoryUtil {
public static function init() {}
/**
* Does a minor garbage collection.
*/
public static function clearMinor() {
#if (cpp || java || neko)
Gc.run(false);
#end
}
/**
* Does a full garbage collection.
*/
public static function clearMajor() {
#if cpp
Gc.run(true);
@@ -51,18 +63,29 @@ class MemoryUtil {
#end
}
/**
* Enables garbage collection.
*/
public static function enable() {
#if (cpp || hl)
Gc.enable(true);
#end
}
/**
* Disables garbage collection.
* Fyi: doesn't fully disable garbage collection, but prevents it from running as much.
*/
public static function disable() {
#if (cpp || hl)
Gc.enable(false);
#end
}
/**
* Gets the total memory of the system.
* Output depends on the hardware.
*/
public static function getTotalMem():Float
{
#if windows
@@ -76,6 +99,10 @@ class MemoryUtil {
#end
}
/**
* Gets the current memory usage of the app.
* DISCLAIMER: This gets the memory usage that is taken up by Haxe, not the actual memory usage of the app.
*/
public static inline function currentMemUsage() {
#if cpp
return Gc.memInfo64(Gc.MEM_INFO_USAGE);
@@ -89,6 +116,10 @@ class MemoryUtil {
}
/**
* Gets the memory type of the system.
* Output depends on the platform, and hardware.
*/
public static function getMemType():String {
#if windows
var memoryMap:Map<Int, String> = [
+10 -10
View File
@@ -35,9 +35,9 @@ class NativeAPI {
/**
* Gets the specified file's (or folder) attributes.
*/
public static function getFileAttributesRaw(path:String, useAbsol:Bool = true):Int {
public static function getFileAttributesRaw(path:String, useAbsolute:Bool = true):Int {
#if windows
if(useAbsol) path = sys.FileSystem.absolutePath(path);
if(useAbsolute) path = sys.FileSystem.absolutePath(path);
return Windows.getFileAttributes(path);
#else
return -1;
@@ -47,16 +47,16 @@ class NativeAPI {
/**
* Gets the specified file's (or folder) attributes and passes it to `FileAttributeWrapper`.
*/
public static function getFileAttributes(path:String, useAbsol:Bool = true):FileAttributeWrapper {
return new FileAttributeWrapper(getFileAttributesRaw(path, useAbsol));
public static function getFileAttributes(path:String, useAbsolute:Bool = true):FileAttributeWrapper {
return new FileAttributeWrapper(getFileAttributesRaw(path, useAbsolute));
}
/**
* Sets the specified file's (or folder) attributes. If it fails, the return value is `0`.
*/
public static function setFileAttributes(path:String, attrib:OneOfThree<NativeAPI.FileAttribute, FileAttributeWrapper, Int>, useAbsol:Bool = true):Int {
public static function setFileAttributes(path:String, attrib:OneOfThree<NativeAPI.FileAttribute, FileAttributeWrapper, Int>, useAbsolute:Bool = true):Int {
#if windows
if(useAbsol) path = sys.FileSystem.absolutePath(path);
if(useAbsolute) path = sys.FileSystem.absolutePath(path);
return Windows.setFileAttributes(path, attrib is FileAttributeWrapper ? cast(attrib, FileAttributeWrapper).getValue() : cast(attrib, Int));
#else
return 0;
@@ -66,9 +66,9 @@ class NativeAPI {
/**
* Removes from the specified file's (or folder) one (or more) specific attribute.
*/
public static function addFileAttributes(path:String, attrib:OneOfTwo<NativeAPI.FileAttribute, Int>, useAbsol:Bool = true):Int {
public static function addFileAttributes(path:String, attrib:OneOfTwo<NativeAPI.FileAttribute, Int>, useAbsolute:Bool = true):Int {
#if windows
return setFileAttributes(path, getFileAttributesRaw(path, useAbsol) | cast(attrib, Int), useAbsol);
return setFileAttributes(path, getFileAttributesRaw(path, useAbsolute) | cast(attrib, Int), useAbsolute);
#else
return 0;
#end
@@ -77,9 +77,9 @@ class NativeAPI {
/**
* Removes from the specified file's (or folder) one (or more) specific attribute.
*/
public static function removeFileAttributes(path:String, attrib:OneOfTwo<NativeAPI.FileAttribute, Int>, useAbsol:Bool = true):Int {
public static function removeFileAttributes(path:String, attrib:OneOfTwo<NativeAPI.FileAttribute, Int>, useAbsolute:Bool = true):Int {
#if windows
return setFileAttributes(path, getFileAttributesRaw(path, useAbsol) & ~cast(attrib, Int), useAbsol);
return setFileAttributes(path, getFileAttributesRaw(path, useAbsolute) & ~cast(attrib, Int), useAbsolute);
#else
return 0;
#end
+1 -1
View File
@@ -38,7 +38,7 @@ class NdllUtil {
return Reflect.makeVarArgs(function(a:Array<Dynamic>) {
// This generates horrific code
return funkin.backend.system.macros.Utils.generateReflectionLike(25, "func", "a");
//return Reflect.callMethod(null, func, a); // wouldnt work for some reason, maybe cause like c++ functions doesnt have reflection enabled
//return Reflect.callMethod(null, func, a); // wouldn't work for some reason, maybe cause like c++ functions doesn't have reflection enabled
});
#else
Logs.warn('NDLLs are not supported on this platform.');
+11 -3
View File
@@ -1,14 +1,16 @@
package funkin.backend.utils;
/**
* All types of utils only usable in a sorted array.
* Tools to work with sorted arrays, in a more efficient way.
*
* DISCLAIMER: All types of utils only usable in a sorted array.
*/
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
* @param getVal Function that returns the position value of T
* @return Index
* @return The index of the element
*/
public static function binarySearch<T>(array:Array<T>, val:Float, getVal:T->Float):Int {
if (array.length <= 0) return 0; // if the array is empty, it should be equal to zero (the beginning)
@@ -47,7 +49,13 @@ class SortedArrayUtil {
array.insert(binarySearch(array, getVal(val), getVal), val);
}
public static function removeSorted<T>(array:Array<T>, val:T, getVal:T->Float) {
/**
* Removes from a sorted array, using binary search.
* @param array Array to remove from
* @param val Value to remove
* @param getVal Function that returns the value that needs to be sorted
*/
public static inline function removeSorted<T>(array:Array<T>, val:T, getVal:T->Float) {
var index = binarySearch(array, getVal(val), getVal);
if (index != -1)
array.splice(index, 1);
+23 -6
View File
@@ -3,9 +3,9 @@ package funkin.backend.utils;
import openfl.Lib;
class WindowUtils {
public static var winTitle(default, set):String;
private static function set_winTitle(value:String):String {
winTitle = value;
public static var title(default, set):String;
private static function set_title(value:String):String {
title = value;
updateTitle();
return value;
}
@@ -28,7 +28,7 @@ class WindowUtils {
static var __triedClosing:Bool = false;
public static inline function resetClosing() __triedClosing = false;
public static inline function init() {
@:dox(hide) public static inline function init() {
resetTitle();
resetClosing();
@@ -41,18 +41,27 @@ class WindowUtils {
});
}
/**
* Resets the window title to the application name and resets the prefix and suffix.
**/
public static inline function resetTitle() {
winTitle = Lib.application.meta["name"];
title = Lib.application.meta["name"];
resetAffixes();
}
/**
* Resets the prefix and suffix.
**/
public static inline function resetAffixes() {
prefix = suffix = "";
updateTitle();
}
/**
* Updates the window title to have the current title and prefix/suffix.
**/
public static inline function updateTitle()
Lib.application.window.title = '$prefix$winTitle$suffix';
Lib.application.window.title = '$prefix$title$suffix';
// backwards compat
@:noCompletion public static var endfix(get, set):String;
@@ -62,4 +71,12 @@ class WindowUtils {
@:noCompletion private static function get_endfix():String {
return suffix;
}
@:noCompletion public static var winTitle(get, set):String;
@:noCompletion private static function get_winTitle():String {
return title;
}
@:noCompletion private static function set_winTitle(value:String):String {
return title = value;
}
}
+92 -26
View File
@@ -5,7 +5,6 @@ import flixel.util.typeLimit.OneOfTwo;
import funkin.backend.FunkinSprite.XMLAnimType;
import funkin.backend.FunkinSprite;
import funkin.game.Character;
import funkin.backend.system.ErrorCode;
import funkin.backend.FunkinSprite.XMLAnimType;
import flixel.util.FlxColor;
import haxe.xml.Access;
@@ -18,17 +17,53 @@ import haxe.xml.Access;
using StringTools;
@:dox(hide)
enum abstract ErrorCode(Int) {
var OK = 0;
var FAILED = 1;
var MISSING_PROPERTY = 2;
var TYPE_INCORRECT = 3;
var VALUE_NULL = 4;
var REFLECT_ERROR = 5;
}
@:dox(hide)
typedef TextFormat = { text:String, format:Dynamic }
/**
* Class made to make XML parsing easier.
* Used in Stage.hx, Character.hx, and more.
*/
class XMLUtil {
/**
* Applies a property XML node to an object.
* The format for the XML is as follows:
* ```xml
* <!-- float -->
* <property name="name" type="float" value="value" />
* <property name="name" type="number" value="value" />
* <property name="name" type="f" value="value" />
* <!-- string -->
* <property name="name" type="string" value="value" />
* <property name="name" type="str" value="value" />
* <property name="name" type="text" value="value" />
* <property name="name" type="s" value="value" />
* <!-- int -->
* <property name="name" type="int" value="value" />
* <property name="name" type="integer" value="value" />
* <property name="name" type="i" value="value" />
* <!-- bool -->
* <property name="name" type="bool" value="value" />
* <property name="name" type="boolean" value="value" />
* <property name="name" type="b" value="value" />
* <!-- color -->
* <property name="name" type="color" value="value" />
* <property name="name" type="c" value="value" />
* ```
*
* @param object Object to which the xml property will be applied
* @param property `property` node.
* @return Error code (0 = OK, 1 = FAILED, 2 = MISSING_PROPERTY, 3 = TYPE_INCORRECT, 4 = VALUE_NULL, 5 = REFLECT_ERROR)
*/
public static function applyXMLProperty(object:Dynamic, property:Access):ErrorCode {
if (!property.has.name || !property.has.type || !property.has.value) {
@@ -47,7 +82,8 @@ class XMLUtil {
var value:Dynamic = switch(property.att.type.toLowerCase()) {
case "f" | "float" | "number": Std.parseFloat(property.att.value);
case "i" | "int" | "integer" | "color": Std.parseInt(property.att.value);
case "i" | "int" | "integer": Std.parseInt(property.att.value);
case "c" | "color": FlxColor.fromString(property.att.value);
case "s" | "string" | "str" | "text": property.att.value;
case "b" | "bool" | "boolean": property.att.value.toLowerCase() == "true";
default: return TYPE_INCORRECT;
@@ -72,7 +108,11 @@ class XMLUtil {
}
/**
* Overrides a sprite based on a XML node.
* Sets the properties of a sprite based on a XML node.
* @param spr The sprite
* @param node The XML node
* @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 {
if (parentFolder == null) parentFolder = "";
@@ -147,8 +187,8 @@ class XMLUtil {
var graphicSizey:Null<Int> = Std.parseInt(node.att.graphicSizey);
if (graphicSizey.isNotNull()) spr.setGraphicSize(0, graphicSizey);
}
if (node.has.flipX && node.att.flipX == "true") spr.flipX = true;
if (node.has.flipX) spr.flipX = node.att.flipX == "true";
if (node.has.flipY) spr.flipY = node.att.flipY == "true";
if (node.has.updateHitbox && node.att.updateHitbox == "true") spr.updateHitbox();
if (node.has.zoomfactor)
@@ -170,19 +210,17 @@ class XMLUtil {
if(node.hasNode.anim) {
for(anim in node.nodes.anim)
addXMLAnimation(spr, anim);
} else {
if (spr.frames != null && spr.frames.frames != null) {
addAnimToSprite(spr, {
name: "idle",
anim: null,
fps: 24,
loop: spr.spriteAnimType == LOOP,
animType: spr.spriteAnimType,
x: 0,
y: 0,
indices: [for(i in 0...spr.frames.frames.length) i]
});
}
} else if (spr.frames != null && spr.frames.frames != null) {
addAnimToSprite(spr, {
name: "idle",
anim: null,
fps: 24,
loop: spr.spriteAnimType == LOOP,
animType: spr.spriteAnimType,
x: 0,
y: 0,
indices: [for(i in 0...spr.frames.frames.length) i]
});
}
return spr;
@@ -190,6 +228,11 @@ class XMLUtil {
/**
* Creates a new sprite based on a XML node.
* @param node The XML node
* @param parentFolder The parent folder
* @param defaultAnimType The default animation type
* @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 {
if(cl == null) cl = FunkinSprite;
@@ -197,6 +240,12 @@ class XMLUtil {
return loadSpriteFromXML(Type.createInstance(cl, args), node, parentFolder, defaultAnimType);
}
/**
* Extracts an animation from an XML node.
* @param anim The animation node
* @param animType The animation type
* @param loop Whether the animation should loop
**/
public static function extractAnimFromXML(anim:Access, animType:XMLAnimType = NONE, loop:Bool = false):AnimData {
var animData:AnimData = {
name: null,
@@ -235,6 +284,11 @@ class XMLUtil {
return addAnimToSprite(sprite, extractAnimFromXML(anim, animType, loop));
}
/**
* Adds an animation to a sprite.
* @param sprite The sprite
* @param animData The animation data (gotten from `extractAnimFromXML`)
**/
public static function addAnimToSprite(sprite:FlxSprite, animData:AnimData):ErrorCode {
if (animData.name != null) {
if (animData.fps <= 0 #if web || animData.fps == null #end) animData.fps = 24;
@@ -244,16 +298,17 @@ class XMLUtil {
if(animData.anim == null)
return MISSING_PROPERTY;
if (animData.indices.length > 0)
if (animData.indices != null && animData.indices.length > 0)
animateAnim.addBySymbolIndices(animData.name, animData.anim, animData.indices, animData.fps, animData.loop);
else
animateAnim.addBySymbol(animData.name, animData.anim, animData.fps, animData.loop);
} else {
if (animData.anim == null && animData.indices.length > 0)
sprite.animation.add(animData.name, animData.indices, animData.fps, animData.loop);
else if (animData.indices.length > 0)
sprite.animation.addByIndices(animData.name, animData.anim, animData.indices, "", animData.fps, animData.loop);
else
if (animData.indices != null && animData.indices.length > 0) {
if (animData.anim == null)
sprite.animation.add(animData.name, animData.indices, animData.fps, animData.loop);
else
sprite.animation.addByIndices(animData.name, animData.anim, animData.indices, "", animData.fps, animData.loop);
} else
sprite.animation.addByPrefix(animData.name, animData.anim, animData.fps, animData.loop);
}
@@ -309,6 +364,17 @@ class XMLUtil {
return node;
}
/**
* Gets the text formats from an XML node.
* The format for the XML is as follows:
* ```xml
* <text>Hello <format color="#FF0000">World</format></text>
* ```
*
* @param _node The XML node
* @param currentFormat The current format
* @param parsedSegments The parsed segments
**/
public static function getTextFormats(_node:OneOfTwo<Xml, Access>, currentFormat:Dynamic = null, parsedSegments:Array<TextFormat> = null):Array<TextFormat> {
var node:Xml = cast _node;
if (currentFormat == null)
@@ -401,4 +467,4 @@ typedef BeatAnim = {
interface IXMLEvents {
public function onPropertySet(property:String, value:Dynamic):Void;
}
}
+3 -3
View File
@@ -21,13 +21,13 @@ import sys.thread.Thread;
using StringTools;
// import ZipUtils; ZipUtils.uncompressZip(ZipUtils.openZip("E:\\Desktop\\test\\termination lua.ycemod"), "E:\\Desktop\\test\\uncompressed\\");
// import ZipUtils; var e = ZipUtils.createZipFile("gjnsdghs.ycemod"); ZipUtils.writeFolderToZip(e, "./mods/Friday Night Funkin'/", "Friday Night Funkin'/"); e.flush(); e.close();
// import ZipUtils; var e = ZipUtils.createZipFile("file.ycemod"); ZipUtils.writeFolderToZip(e, "./mods/Friday Night Funkin'/", "Friday Night Funkin'/"); e.flush(); e.close();
class ZipUtil {
public static var bannedNames:Array<String> = [".git", ".gitignore", ".github", ".vscode", ".gitattributes", "readme.txt"];
/**
* [Description] Uncompresses `zip` into the `destFolder` folder
* [Description] Decompresses a `zip` into the `destFolder` folder
* @param zip
* @param destFolder
*/
@@ -218,7 +218,7 @@ class ZipProgress {
public var curFile:Int = 0;
public var fileCount:Int = 0;
public var done:Bool = false;
public var percentage(get, null):Float;
public var percentage(get, never):Float;
private function get_percentage() {
return fileCount <= 0 ? 0 : curFile / fileCount;
@@ -13,7 +13,7 @@ import funkin.backend.utils.NativeAPI.MessageBoxIcon;
</target>
')
// majority is taken from microsofts doc
// majority is taken from Microsoft's doc
@:cppFileCode('
#include "mmdeviceapi.h"
#include "combaseapi.h"
@@ -132,7 +132,7 @@ class Windows {
int darkMode = enable ? 1 : 0;
HWND window = FindWindowA(NULL, title.c_str());
// Look for child windows if top level aint found
// Look for child windows if top level is not found
if (window == NULL) window = FindWindowExA(GetActiveWindow(), NULL, NULL, title.c_str());
// If still not found, try to get the active window
if (window == NULL) window = GetActiveWindow();