diff --git a/modSource/scripts/import.hx b/modSource/scripts/import.hx new file mode 100644 index 0000000000..464b67d17c --- /dev/null +++ b/modSource/scripts/import.hx @@ -0,0 +1,84 @@ +#if !macro +package scripts; +import funkin.backend.scripting.HardcodedScript; +import funkin.backend.scripting.events.*; +import funkin.backend.scripting.events.character.*; +import funkin.backend.scripting.events.dialogue.*; +import funkin.backend.scripting.events.discord.*; +import funkin.backend.scripting.events.gameover.*; +import funkin.backend.scripting.events.gameplay.*; +import funkin.backend.scripting.events.healthicon.*; +import funkin.backend.scripting.events.menu.*; +import funkin.backend.scripting.events.note.*; +import funkin.backend.scripting.events.shader.*; +import funkin.backend.scripting.events.soundtray.*; +import funkin.backend.scripting.events.splash.*; +import funkin.backend.scripting.events.sprite.*; +import funkin.backend.scripting.events.stage.*; + +import StringTools; +import haxe.Json; +import Xml; + +import openfl.utils.Assets; +import lime.app.Application; +import funkin.backend.system.Main; + +import flixel.FlxG; +import flixel.FlxSprite; +import flixel.FlxBasic; +import flixel.FlxCamera; +import flixel.tweens.FlxEase; +import flixel.tweens.FlxTween; +import flixel.sound.FlxSound; +import flixel.system.FlxAssets; +import flixel.math.FlxMath; +import flixel.group.FlxGroup; +import flixel.group.FlxGroup.FlxTypedGroup; +import flixel.group.FlxSpriteGroup; +import flixel.addons.text.FlxTypeText; +import flixel.text.FlxText; +import flixel.util.FlxTimer; +import flixel.math.FlxPoint; +import flixel.util.FlxAxes; +import flixel.util.FlxColor; +import funkin.backend.scripting.ModState; +import funkin.backend.scripting.ModSubState; +import funkin.game.PlayState; +import funkin.game.GameOverSubstate; +import funkin.game.HealthIcon; +import funkin.game.HudCamera; +import funkin.game.Note; +import funkin.game.Strum; +import funkin.game.StrumLine; +import funkin.game.Character; +import funkin.menus.PauseSubState; +import funkin.menus.FreeplayState; +import funkin.menus.MainMenuState; +import funkin.menus.PauseSubState; +import funkin.menus.StoryMenuState; +import funkin.menus.TitleState; +import funkin.options.Options; +import funkin.backend.assets.Paths; +import funkin.backend.system.Conductor; +import funkin.backend.shaders.FunkinShader; +import funkin.backend.shaders.CustomShader; +import funkin.backend.FunkinText; +import animate.FlxAnimate; +import funkin.backend.FunkinSprite; +import funkin.menus.ui.Alphabet; +import funkin.backend.system.Flags; + +import funkin.backend.utils.CoolUtil; +import funkin.backend.utils.IniUtil; +import funkin.backend.utils.XMLUtil; +#if sys +import funkin.backend.utils.ZipUtil; +#end +import funkin.backend.utils.MarkdownUtil; +import funkin.backend.utils.EngineUtil; +import funkin.backend.utils.ThreadUtil; +import funkin.backend.utils.MemoryUtil; +import funkin.backend.utils.BitmapUtil; +import funkin.backend.utils.TranslationUtil; +#end \ No newline at end of file diff --git a/project.xml b/project.xml index b129dbb377..9afdad34de 100644 --- a/project.xml +++ b/project.xml @@ -118,6 +118,9 @@ + + + @@ -150,6 +153,7 @@ + diff --git a/source/funkin/backend/scripting/HScript.hx b/source/funkin/backend/scripting/HScript.hx index c3656478c6..4ad2e52a27 100644 --- a/source/funkin/backend/scripting/HScript.hx +++ b/source/funkin/backend/scripting/HScript.hx @@ -13,6 +13,10 @@ class HScript extends Script { //public var folderlessPath:String; var __importedPaths:Array; + #if HARDCODED_SCRIPTS + public var trackedSets:Array = []; + #end + public static function initParser() { var parser = new Parser(); parser.allowJSON = parser.allowMetadata = parser.allowTypes = true; @@ -146,6 +150,10 @@ class HScript extends Script { if (expr != null) { interp.execute(expr); call("new", []); + + #if HARDCODED_SCRIPTS + if (Flags.CONVERT_HSCRIPT_TO_SOURCE) HardcodedScriptPrinter.convertHscript(this); + #end } #if GLOBAL_SCRIPT @@ -195,6 +203,15 @@ class HScript extends Script { public override function set(val:String, value:Dynamic) { interp.variables.set(val, value); + #if HARDCODED_SCRIPTS + if (Flags.CONVERT_HSCRIPT_TO_SOURCE) { + if (!trackedSets.contains(val)) { + if (!(value is Class)) { + trackedSets.push(val); + } + } + } + #end } public override function trace(v:Dynamic) { diff --git a/source/funkin/backend/scripting/HardcodedScript.hx b/source/funkin/backend/scripting/HardcodedScript.hx new file mode 100644 index 0000000000..79404cfef2 --- /dev/null +++ b/source/funkin/backend/scripting/HardcodedScript.hx @@ -0,0 +1,99 @@ +package funkin.backend.scripting; + +#if HARDCODED_SCRIPTS +class HardcodedScript extends Script { + private var parent:T = null; + private var handleErrors:Bool = true; + private var disableAfterError:Bool = false; + private var publicVariables:Map = []; + private var dynamicVariables:Map = []; + + public override function get(v:String) { + if (Reflect.getProperty(this, v) != null) return Reflect.getProperty(this, v); + if (Reflect.getProperty(parent, v) != null) return Reflect.getProperty(parent, v); + return dynamicVariables.get(v); + } + public override function set(v:String, v2:Dynamic) { + if (v2 is Class) return; + + if (Reflect.getProperty(this, v) != null) { + Reflect.setProperty(this, v, v2); + return; + } + if (Reflect.getProperty(parent, v) != null) { + Reflect.setProperty(parent, v, v2); + return; + } + dynamicVariables.set(v, v2); + } + + public function getDynamic(v:String) { + return dynamicVariables.get(v); + } + public function setDynamic(v:String, v2:Dynamic) { + dynamicVariables.set(v, v2); + } + public function getPublic(v:String) { + return publicVariables.get(v); + } + public function setPublic(v:String, v2:Dynamic) { + publicVariables.set(v, v2); + } + public function getStatic(v:String) { + return Script.staticVariables.get(v); + } + public function setStatic(v:String, v2:Dynamic) { + Script.staticVariables.set(v, v2); + } + /*public function importScript(path:String) { + //TODO + }*/ + + public override function setParent(variable:Dynamic) { parent = variable; } + public override function setPublicMap(map:Map) { this.publicVariables = map; } + + public override function onCall(method:String, parameters:Array):Dynamic { + var func = Reflect.getProperty(this, method); + if (Reflect.isFunction(func)) { + if (handleErrors) { + var ret = null; + try { + ret = (parameters != null && parameters.length > 0) ? Reflect.callMethod(null, func, parameters) : func(); + } catch(e) { + _errorHandler(e); + } + return ret; + } + return (parameters != null && parameters.length > 0) ? Reflect.callMethod(null, func, parameters) : func(); + } + return null; + } + private function _errorHandler(error:haxe.Exception) { + var fileName = this.fileName; + for (item in error.stack) { + switch(item) { + case FilePos(s, file, line, col): + fileName = file + ":" + line; + default: + } + break; + } + + Logs.traceColored([ + Logs.logText(fileName + ': ', GREEN), + Logs.logText(error.toString(), RED) + ], ERROR); + + if (disableAfterError) { + active = false; + } + } + + public override function load() { + if (!didLoad) { + onCall("initVars", []); //initVars generated from HardcodedScriptVarMacro.hx + } + super.load(); + } +} +#end \ No newline at end of file diff --git a/source/funkin/backend/scripting/HardcodedScriptPrinter.hx b/source/funkin/backend/scripting/HardcodedScriptPrinter.hx new file mode 100644 index 0000000000..6d1ec73dc0 --- /dev/null +++ b/source/funkin/backend/scripting/HardcodedScriptPrinter.hx @@ -0,0 +1,747 @@ +/* + * Copyright (C)2008-2017 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package funkin.backend.scripting; +#if HARDCODED_SCRIPTS + +import hscript.Tools; +import hscript.Expr; +import hscript.Printer; + +class HardcodedScriptPrinter { + + var script:HScript; + var parentInstanceFields:Array = []; + var currentFuncParams:Array> = []; + var currentForParams:Array> = []; + + var staticFields:Array = []; + var publicFields:Array = []; + + var usedPublic:Array = []; + var usedPublicTypes:Array = []; + var usedStatic:Array = []; + var usedStaticTypes:Array = []; + var usedDynamic:Array = []; + var usedDynamicTypes:Array = []; + + var imports:Array = []; + var usings:Array = []; + var funcStack:Int = 0; + + var funcsToForcePublicOverride = [ + "update", "draw", "destroy" + ]; + + var defaultFuncArgTypes:Map> = [ + "preUpdate" => ["Float"], + "update" => ["Float"], + "postUpdate" => ["Float"], + "stepHit" => ["Int"], + "beatHit" => ["Int"], + "measureHit" => ["Int"], + "draw" => ["DrawEvent"], + "postDraw" => ["DrawEvent"], + "onResize" => ["ResizeEvent"], + "gameResized" => ["Int", "Int"], + "preStateCreate" => ["FlxState"], + "onDance" => ["DanceEvent"], + "onTryDance" => ["CancellableEvent"], + "onPlaySingAnim" => ["DirectionAnimEvent"], + "playSingAnimUnsafe" => ["DirectionAnimEvent"], + "onPlayAnim" => ["PlayAnimEvent"], + "onGetCamPos" => ["PointEvent"], + "onPreGenerateStrums" => ["AmountEvent"], + "onPostGenerateStrums" => ["AmountEvent"], + "onRatingUpdate" => ["RatingUpdateEvent"], + "onStartCountdown" => ["CancellableEvent"], + "onCountdown" => ["CountdownEvent"], + "onPostCountdown" => ["CountdownEvent"], + "onSubstateOpen" => ["StateEvent"], + "onSubstateClose" => ["StateEvent"], + "onStateSwitch" => ["StateEvent"], + "onGamePause" => ["CancellableEvent"], + "onCameraMove" => ["CamMoveEvent"], + "onEvent" => ["EventGameEvent"], + "onPostEvent" => ["EventGameEvent"], + "onGameOver" => ["GameOverEvent"], + "onPostGameOver" => ["GameOverEvent"], + "onSongEnd" => ["CancellableEvent"], + "onPlayerMiss" => ["NoteMissEvent"], + "onPostPlayerMiss" => ["NoteMissEvent"], + "onNoteHit" => ["NoteHitEvent"], + "onPlayerHit" => ["NoteHitEvent"], + "onDadHit" => ["NoteHitEvent"], + "onHealthIconAnimChange" => ["HealthIconChangeEvent"], + "onNoteCreation" => ["NoteCreationEvent"], + "onPostNoteCreation" => ["NoteCreationEvent"], + "onSplashShown" => ["SplashShowEvent"], + "onStageXMLParsed" => ["StageXMLEvent"], + "onStageNodeParsed" => ["StageNodeEvent"], + "onPostStageCreation" => ["StageXMLEvent"], + "onStageDestroy" => ["Stage"], + "onInputUpdate" => ["InputSystemEvent"], + "onStrumCreation" => ["StrumCreationEvent"], + "onPostStrumCreation" => ["StrumCreationEvent"] + ]; + + var buf : StringBuf; + var tabs : String; + + public function new(script:HScript) { + this.script = script; + if (script != null && script.interp.scriptObject != null) { + parentInstanceFields = Type.getInstanceFields(Type.getClass(script.interp.scriptObject)); + } + } + + public function exprToString( e : Expr ):String { + buf = new StringBuf(); + tabs = ""; + expr(e); + return buf.toString(); + } + + public function typeToString( t : CType ):String { + buf = new StringBuf(); + tabs = ""; + type(t); + return buf.toString(); + } + + inline function add(s:T):Void buf.add(s); + + function type( t : CType ):Void { + switch( t ) { + case CTOpt(t): + add('?'); + type(t); + case CTPath(path, params): + add(path.join(".")); + if( params != null ) { + add("<"); + var first = true; + for( p in params ) { + if( first ) first = false else add(", "); + type(p); + } + add(">"); + } + case CTNamed(name, t): + add(name); + add(':'); + type(t); + case CTFun(args, ret) if (Lambda.exists(args, function (a) return a.match(CTNamed(_, _)))): + add('('); + for (a in args) + switch a { + case CTNamed(_, _): type(a); + default: type(CTNamed('_', a)); + } + add(')->'); + type(ret); + case CTFun(args, ret): + if( args.length == 0 ) + add("Void -> "); + else { + for( a in args ) { + type(a); + add(" -> "); + } + } + type(ret); + case CTAnon(fields): + add("{"); + var first = true; + for( f in fields ) { + if( first ) { first = false; add(" "); } else add(", "); + add(f.name + " : "); + type(f.t); + } + add(first ? "}" : " }"); + case CTParent(t): + add("("); + type(t); + add(")"); + case CTExpr(e): + expr(e); + } + } + + function addType( t : CType ):Void { + if( t != null ) { + add(" : "); + type(t); + } + } + + function expr( e : Expr ):Void { + if( e == null ) { + add("??NULL??"); + return; + } + switch(Tools.expr(e)) { + case EPackage(n): + /* + add('package'); + if(n != null) + add(' $n'); + add(';\n'); + */ + case EImport(c, n, u): + //var str = '${u ? 'using' : 'import'} $c'; + //if(n != null) + //str += ' as $n'; + + if (u) { + usings.push(c + (n != null ? ' as $n' : '')); + } else { + imports.push(c); + } + + //add('${u ? 'using' : 'import'} $c'); + //if(n != null) + //add(' as $n'); + case EClass(name, fields, extend, interfaces, fnal): + /* + var isFinal = fnal != null && fnal; + if(isFinal) + add('final '); + add('class $name'); + if (extend != null) + add(' extends $extend'); + for(_interface in interfaces) { + add(' implements $_interface'); + } + tabs += "\t"; + add(" {\n"); + for( e in fields ) { + add(tabs); + expr(e); + //add(";\n"); + } + //for(field in fields) { + // expr(field); + //} + + tabs = tabs.substr(1); + add("}"); + */ + case EEnum(en, _): + /* + add('enum ${en.name}'); + if(en.fields.length == 0) { + add(' {}'); + return; + } + tabs += "\t"; + add(" {\n"); + + for(e in en.fields) { + add(tabs); + add(e.name); + if(e.args.length > 0) { + add("("); + var first = true; + for( a in e.args ) { + if( first ) first = false else add(", "); + if( a.opt ) add("?"); + add(a.name); + addType(a.t); + } + add(')'); + } + add(";\n"); + } + + tabs = tabs.substr(1); + add("}"); + */ + case ECast(e, t): + var safe = t != null; + add("cast "); + if(safe) add("("); + expr(e); + if(safe) { + add(", "); + addType(t); + add(")"); + } + case ERegex(e, f): + add('~/$e/$f'); + add(';\n'); + case EConst(c): + switch( c ) { + case CInt(i): add(i); + case CFloat(f): add(f); + case CString(s): add('"'); add(s.split('"').join('\\"').split("\n").join("\\n").split("\r").join("\\r").split("\t").join("\\t")); add('"'); + } + case EIdent(v): + + var isLocal:Bool = false; + for (f in currentFuncParams) { + if (f.contains(v)) {isLocal = true; break;} + } + for (f in currentForParams) { + if (f.contains(v)) {isLocal = true; break;} + } + + if (v == "this") v = "parent"; + + if (isLocal) { + add(v); + } else if (parentInstanceFields.contains(v) || parentInstanceFields.contains("get_" + v)) { + add("parent."); + add(v); + } else if (script.interp.publicVariables.exists(v)) { + add(v); + if (!usedPublic.contains(v)) { + usedPublic.push(v); + usedPublicTypes.push(getVarTypeName(script.interp.publicVariables.get(v))); + } + } else if (Script.staticVariables.exists(v)) { + add(v); + if (!usedStatic.contains(v)) { + usedStatic.push(v); + usedStaticTypes.push(getVarTypeName(Script.staticVariables.get(v))); + } + } else if (script.trackedSets.contains(v)) { + add(v); + if (!usedDynamic.contains(v)) { + usedDynamic.push(v); + usedDynamicTypes.push(getVarTypeName(script.get(v))); + } + } else { + add(v); + } + case EVar(n, t, e, p, s, pr, isFinal, isInline, get, set, _): + var name = n; + if (funcStack == 0) { + if (s) { + add("@:staticVar "); + staticFields.push(n); + } else if (p) { + add("@:publicVar "); + publicFields.push(n); + } + + if(p) add(""); //disable + else if(pr) add("private "); + if(s) add(""); //disable + if(isInline) add("inline "); + } + if(isFinal) add("final " + name); + else add("var " + name); + + if(get != null || set != null) { + add("("); + switch(get) { + case ADefault: add("default, "); + case ANull: add("null, "); + case AGet: add("get, "); + case ADynamic: add("dynamic, "); + case ANever: add("never, "); + default: + } + switch(set) { + case ADefault: add("default"); + case ANull: add("null"); + case ASet: add("set"); + case ADynamic: add("dynamic"); + case ANever: add("never"); + default: + } + add(")"); + } + + addType(t); + if( e != null ) { + add(" = "); + expr(e); + } + case EParent(e): + add("("); expr(e); add(")"); + case EBlock(el): + if( el.length == 0 ) { + add("{}"); + } else { + tabs += "\t"; + add("{\n"); + for( e in el ) { + add(tabs); + expr(e); + add(";\n"); + } + tabs = tabs.substr(1); + add(tabs); + add("}"); + } + case EField(e, f, s): + expr(e); + add((s == true ? "?." : ".") + f); + case EBinop(op, e1, e2): + expr(e1); + add(" " + op + " "); + expr(e2); + case EUnop(op, pre, e): + if( pre ) { + add(op); + expr(e); + } else { + expr(e); + add(op); + } + case ECall(e, args): + if( e == null ) + expr(e); + else switch( Tools.expr(e)) { + case EField(_), EIdent(_), EConst(_): + expr(e); + default: + add("("); + expr(e); + add(")"); + } + add("("); + var first = true; + for( a in args ) { + if( first ) first = false else add(", "); + expr(a); + } + add(")"); + case EIf(cond,e1,e2): + add("if( "); + expr(cond); + add(" ) "); + expr(e1); + if( e2 != null ) { + add(" else "); + expr(e2); + } + case EWhile(cond,e): + add("while( "); + expr(cond); + add(" ) "); + expr(e); + case EDoWhile(cond,e): + add("do "); + expr(e); + add(" while ( "); + expr(cond); + add(" )"); + case EFor(v, it, e, ithv): + + if(ithv != null) { + add("for( "+ithv+" => "+v+" in "); + currentForParams.push([ithv, v]); + } else { + add("for( "+v+" in "); + currentForParams.push([v]); + } + expr(it); + add(" ) "); + expr(e); + currentForParams.pop(); + case EBreak: + add("break"); + case EContinue: + add("continue"); + case EFunction(params, e, n, ret, p, s, o, pr, isFinal, isInline): + var name = n; + if (funcStack == 0) { + if (s) { + add("@:staticVar "); + staticFields.push(n); + } else if (p) { + add("@:publicVar "); + publicFields.push(n); + } + + if (funcsToForcePublicOverride.contains(n)) { + o = true; + p = true; + } + + if(o) add("override "); + if (p) add("public "); + else if(pr) add("private "); + if(s) add(""); //disable + if(isInline) add("inline "); + if(isFinal) add("final "); + } + add("function"); + if( name != null ) + add(" " + name); + add("("); + var first = true; + var funcParamNames:Array = []; + for( i => a in params ) { + if( first ) first = false else add(", "); + if( a.opt ) add("?"); + add(a.name); + if (a.t == null) { + if (defaultFuncArgTypes.exists(name)) { + var arr = defaultFuncArgTypes.get(name); + if (i < arr.length) add(" : " + arr[i]); + } + } else { + addType(a.t); + } + + funcParamNames.push(a.name); + } + currentFuncParams.push(funcParamNames); + add(")"); + addType(ret); + add(" "); + funcStack++; + expr(e); + funcStack--; + currentFuncParams.pop(); + case EReturn(e): + add("return"); + if( e != null ) { + add(" "); + expr(e); + } + case EArray(e,index): + expr(e); + add("["); + expr(index); + add("]"); + case EArrayDecl(el, _): + add("["); + var first = true; + for( e in el ) { + if( first ) first = false else add(", "); + expr(e); + } + add("]"); + case ENew(cl, args, params): + add("new " + cl); + if(params != null) { + add("<"); + var first = true; + for( p in params ) { + if( first ) first = false else add(", "); + type(p); + } + add(">"); + } + add("("); + var first = true; + for( e in args ) { + if( first ) first = false else add(", "); + expr(e); + } + add(")"); + case EThrow(e): + add("throw "); + expr(e); + case ETry(e, v, t, ecatch): + add("try "); + expr(e); + add(" catch( " + v); + addType(t); + add(") "); + expr(ecatch); + case EObject(fl): + if( fl.length == 0 ) { + add("{}"); + } else { + tabs += "\t"; + add("{\n"); + for( f in fl ) { + add(tabs); + add(f.name+" : "); + expr(f.e); + add(",\n"); + } + tabs = tabs.substr(1); + add(tabs); + add("}"); + } + case ETernary(c,e1,e2): + expr(c); + add(" ? "); + expr(e1); + add(" : "); + expr(e2); + case ESwitch(e, cases, def): + add("switch( "); + expr(e); + add(") {\n"); + tabs += "\t"; + for( c in cases ) { + add(tabs); + add("case "); + var first = true; + for( v in c.values ) { + if( first ) first = false else add(", "); + expr(v); + } + add(": "); + expr(c.expr); + add(";\n"); + } + if( def != null ) { + add(tabs); + add("default: "); + expr(def); + add(";\n"); + } + tabs = tabs.substr(1); + add(tabs); + add("}"); + case EMeta(name, args, e): + add("@"); + add(name); + if( args != null && args.length > 0 ) { + add("("); + var first = true; + for( a in args ) { + if( first ) first = false else add(", "); + expr(e); + } + add(")"); + } + add(" "); + expr(e); + case ECheckType(e, t): + add("("); + expr(e); + add(" : "); + addType(t); + add(")"); + } + } + + public static function errorToString( e : Error ):String { + var message = switch( #if hscriptPos e.e #else e #end ) { + case EInvalidChar(c): "Invalid character: '"+(StringTools.isEof(c) ? "EOF (End Of File)" : String.fromCharCode(c))+"' ("+c+")"; + case EUnexpected(s): "Unexpected token: \""+s+"\""; + case EUnterminatedString: "Unterminated string"; + case EUnterminatedComment: "Unterminated comment"; + case EInvalidPreprocessor(str): "Invalid preprocessor (" + str + ")"; + case EUnknownVariable(v): "Unknown variable: "+v; + case EInvalidIterator(v): "Invalid iterator: "+v; + case EInvalidOp(op): "Invalid operator: "+op; + case EInvalidAccess(f): "Invalid access to field " + f; + case ECustom(msg): msg; + case EInvalidClass(cla): "Invalid class: " + cla + " was not found."; + case EAlreadyExistingClass(cla): 'Custom Class named $cla already exists.'; + }; + #if hscriptPos + return e.origin + ":" + e.line + ": " + message; + #else + return message; + #end + } + + private function getVarTypeName(obj:Dynamic) { + switch(Type.typeof(obj)) { + case TBool: + return "Bool"; + case TInt: + return "Int"; + case TFloat: + return "Float"; + case TFunction: + return "Dynamic"; //use dynamic for now + case TObject: + return "Dynamic"; + case TEnum(c): + return Type.getEnumName(c); + case TClass(c): + return Type.getClassName(c); + default: + return Type.getClassName(Type.getClass(obj)); + } + return "Dynamic"; + } + + public static function convertHscript(script:HScript) { + var printer = new HardcodedScriptPrinter(script); + var savePath = "scriptOutput/" + script.path.replace("assets/", ""); + var str = printer.exprToString(script.expr); + + var scriptClassName = "Script"; // + script.path.replace("assets/", "").replace("/", "_").replace(" ", "_").replace(".hx", "").replace(".", ""); + var parentClassName:String = Type.getClassName(Type.getClass(script.interp.scriptObject)); + if (parentClassName != null && parentClassName != "null" ) { + if (!printer.imports.contains(parentClassName)) printer.imports.push(parentClassName); + var splitPackage = parentClassName.split("."); + parentClassName = splitPackage[splitPackage.length-1]; + } else { + parentClassName = "Dynamic"; + } + + var scriptPackageName = script.path.replace("assets/", "").replace(".hx", ""); + scriptPackageName = scriptPackageName.substring(0, scriptPackageName.lastIndexOf("/")); + scriptPackageName = scriptPackageName.replace("/", ".").replace(" ", "_"); + + var redirects = Script.getDefaultImportRedirects(); + var importBlacklist:Array = ["Int", "String", "Float"]; + + var headerStr = "package scripts." + scriptPackageName + ";\n\n"; + for (imp in printer.imports) { + var i = imp; + if (importBlacklist.contains(imp)) continue; + if (redirects.exists(imp)) i = redirects.get(imp); + headerStr += "import " + i + ";\n"; + } + for (imp in printer.usings) { + headerStr += "using " + imp + ";\n"; + } + headerStr += "import funkin.backend.scripting.HardcodedScript;\n"; + headerStr += "\n"; + headerStr += '@:registerScript("' + script.path.replace("assets/", "") + '")\n'; + headerStr += "class " + scriptClassName + " extends HardcodedScript<" + parentClassName + ">\n"; + + var extraClassCode:String = ""; + + for (i => field in printer.usedPublic) { + if (!printer.publicFields.contains(field)) { + if (extraClassCode == "") extraClassCode = "\n"; + extraClassCode += "\t@:publicVarRef var " + field + ":" + printer.usedPublicTypes[i] + ";\n"; + } + } + for (i => field in printer.usedStatic) { + if (!printer.staticFields.contains(field)) { + if (extraClassCode == "") extraClassCode = "\n"; + extraClassCode += "\t@:staticVarRef var " + field + ":" + printer.usedStaticTypes[i] + ";\n"; + } + } + for (i => field in printer.usedDynamic) { + if (extraClassCode == "") extraClassCode = "\n"; + extraClassCode += "\t@:dynamicVarRef var " + field + ":" + printer.usedDynamicTypes[i] + ";\n"; + } + + str = str.replace("\t;\n", ""); + if (str.charAt(0) == "{") { + str = "{" + extraClassCode + str.substring(1); + } else { + str = "{\n\t" + extraClassCode + str + "\n}"; + } + + CoolUtil.safeSaveFile(savePath, headerStr + str); + } +} +#end \ No newline at end of file diff --git a/source/funkin/backend/scripting/HardcodedScriptRegistry.hx b/source/funkin/backend/scripting/HardcodedScriptRegistry.hx new file mode 100644 index 0000000000..56734ddab9 --- /dev/null +++ b/source/funkin/backend/scripting/HardcodedScriptRegistry.hx @@ -0,0 +1,54 @@ +package funkin.backend.scripting; + +import haxe.io.Path; + +#if HARDCODED_SCRIPTS +@:rtti +@:build(funkin.backend.system.macros.HardcodedScriptRegistryMacro.build()) +class HardcodedScriptRegistry { + + private static var data:Map = null; + + public static function getRegisteredScripts():Map { + if (data == null) { + data = []; + + var rtti = haxe.rtti.Rtti.getRtti(HardcodedScriptRegistry); + var registeredScripts:Array = []; + var registeredClasses:Array = []; + for (metadata in rtti.meta) { + if (metadata.name == "registeredScripts") { + registeredScripts = metadata.params; + } else if (metadata.name == "registeredClasses") { + registeredClasses = metadata.params; + } + } + for (i in 0...registeredScripts.length) { + var scriptPath = registeredScripts[i].replace("\"", "").trim(); + var classPath = registeredClasses[i].replace("\"", "").trim(); + if (scriptPath.endsWith(".hx")) scriptPath = Path.withoutExtension(scriptPath); + data.set(scriptPath, classPath); + } + } + return data; + } + + public static function getFolderScripts(path:String):Array { + var registeredScripts = getRegisteredScripts(); + var scripts:Array = []; + path = Path.addTrailingSlash(path); + for (key => val in registeredScripts) { + if (Path.addTrailingSlash(Path.directory(key)) == path) scripts.push(key); + } + return scripts; + } + + public static function exists(path:String) { + var registeredScripts = getRegisteredScripts(); + if (path.startsWith("assets/")) path = path.replace("assets/", ""); + return registeredScripts.exists(path); + } +} +#end + + diff --git a/source/funkin/backend/scripting/Script.hx b/source/funkin/backend/scripting/Script.hx index 4a03275f2a..efdb50be98 100644 --- a/source/funkin/backend/scripting/Script.hx +++ b/source/funkin/backend/scripting/Script.hx @@ -215,6 +215,12 @@ class Script extends FlxBasic implements IFlxDestroyable { * @param path Path in assets */ public static function create(path:String):Script { + #if HARDCODED_SCRIPTS + if (Flags.PREFER_HARDCODED_SCRIPTS) { + var script = tryHardcodedScript(path); + if (script != null) return script; + } + #end if (Assets.exists(path)) { return switch(Path.extension(path).toLowerCase()) { case "hx" | "hscript" | "hsc" | "hxs": @@ -229,9 +235,33 @@ class Script extends FlxBasic implements IFlxDestroyable { new DummyScript(path); } } + #if HARDCODED_SCRIPTS + if (!Flags.PREFER_HARDCODED_SCRIPTS) { + var script = tryHardcodedScript(path); + if (script != null) return script; + } + #end return new DummyScript(path); } + #if HARDCODED_SCRIPTS + private static function tryHardcodedScript(path:String):Script { + var hardcodedScripts = HardcodedScriptRegistry.getRegisteredScripts(); + var hardcodedPath = Path.withoutExtension(path).replace("assets/", ""); + for (p in [hardcodedPath, hardcodedPath.replace("/LIB_assets", "")]) { + if (hardcodedScripts.exists(p)) { + var cl = Type.resolveClass(hardcodedScripts.get(p)); + if (cl != null) { + return Type.createInstance(cl, [path]); + } else { + trace("hardcoded script class is null?", p, hardcodedScripts.get(p)); + } + } + } + return null; + } + #end + /** * Creates a script from the string. The language is determined based on the path. * @param code code diff --git a/source/funkin/backend/system/Flags.hx b/source/funkin/backend/system/Flags.hx index 61c0e2e869..73eb2362f0 100644 --- a/source/funkin/backend/system/Flags.hx +++ b/source/funkin/backend/system/Flags.hx @@ -285,6 +285,11 @@ class Flags { public static var DEFAULT_GLSL_VERSION:String = "120"; @:also(funkin.backend.utils.HttpUtil.userAgent) public static var USER_AGENT:String = 'request'; + + #if HARDCODED_SCRIPTS + public static var CONVERT_HSCRIPT_TO_SOURCE:Bool = true; + public static var PREFER_HARDCODED_SCRIPTS:Bool = false; + #end // -- End of Codename's Default Flags -- /** diff --git a/source/funkin/backend/system/macros/HardcodedScriptRegistryMacro.hx b/source/funkin/backend/system/macros/HardcodedScriptRegistryMacro.hx new file mode 100644 index 0000000000..8c62809420 --- /dev/null +++ b/source/funkin/backend/system/macros/HardcodedScriptRegistryMacro.hx @@ -0,0 +1,54 @@ +package funkin.backend.system.macros; + +#if HARDCODED_SCRIPTS +#if macro +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.ExprTools; +#end + +class HardcodedScriptRegistryMacro { + #if macro + public static function build() { + var fields = Context.getBuildFields(); + Context.onGenerate(findRegisteredScripts); + return fields; + } + + static function findRegisteredScripts(allTypes:Array) { + var registeredScripts:Array = []; + var registeredClasses:Array = []; + for (type in allTypes) { + switch (type) { + case TInst(cl,_): + var c = cl.get(); + var meta = c.meta.get(); + for (m in meta) { + if (m.name == ":registerScript") { + var classPath:String = c.module; + if (classPath.indexOf(".") != -1) { + classPath = classPath.substring(0, classPath.lastIndexOf(".")+1) + c.name; + } + registeredClasses.push(macro $v{classPath}); + registeredScripts.push(m.params[0]); + } + } + default: + } + } + + //store within metadata, which we can get at runtime + //based on how this lib does it: https://github.com/jasononeil/compiletime/blob/master/src/CompileTime.hx#L268 + switch (Context.getType("funkin.backend.scripting.HardcodedScriptRegistry")) { + case TInst(cl, _): + var c = cl.get(); + if (c.meta.has('registeredScripts')) c.meta.remove('registeredScripts'); + c.meta.add('registeredScripts', registeredScripts, Context.currentPos()); + if (c.meta.has('registeredClasses')) c.meta.remove('registeredClasses'); + c.meta.add('registeredClasses', registeredClasses, Context.currentPos()); + default: + } + } + #end +} +#end \ No newline at end of file diff --git a/source/funkin/backend/system/macros/HardcodedScriptVarMacro.hx b/source/funkin/backend/system/macros/HardcodedScriptVarMacro.hx new file mode 100644 index 0000000000..613e734559 --- /dev/null +++ b/source/funkin/backend/system/macros/HardcodedScriptVarMacro.hx @@ -0,0 +1,137 @@ +package funkin.backend.system.macros; + +import haxe.macro.TypeTools; +#if HARDCODED_SCRIPTS +#if macro +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.ExprTools; +using haxe.macro.Tools; +#end + +class HardcodedScriptVarMacro { + + #if macro + + public static function build() + { + var fields = Context.getBuildFields(); + + var newFields:Array = []; + + var varInits:Array = []; + + for (field in fields) { + for (meta in field.meta) { + + if (meta.name == ":publicVar" || meta.name == ":publicVarRef" || meta.name == ":staticVar" || meta.name == ":staticVarRef" || meta.name == ":dynamicVar" || meta.name == ":dynamicVarRef") { + var name:String = field.name; + var ret:Null; + switch(field.kind) { + case FVar(t, e): + if (t != null) { + ret = t; + field.kind = FieldType.FProp("get", "set", t); + } + if (meta.name == ":publicVar" && e != null) { + varInits.push(macro {setPublic($v{name}, ${e});}); + } else if (meta.name == ":staticVar" && e != null) { + varInits.push(macro {setStatic($v{name}, ${e});}); + } else if (meta.name == ":dynamicVar" && e != null) { + varInits.push(macro {setDynamic($v{name}, ${e});}); + } + case FFun(f): + field.name = "__" + field.name; + var argTypes:Array = []; + for (a in f.args) { + argTypes.push(a.type); + } + ret = macro : Dynamic; //just do dynamic for now, should still work + //ret = TFunction(argTypes, f.ret); + //ret = TypeTools.toComplexType() + newFields.push({ + name: name, + access: [Access.APrivate], + kind: FieldType.FProp("get", "set", ret), + pos: Context.currentPos(), + }); + + if (meta.name == ":publicVar") { + varInits.push(macro {setPublic($v{name}, $i{field.name});}); + } else if (meta.name == ":staticVar") { + varInits.push(macro {setStatic($v{name}, $i{field.name});}); + } else if (meta.name == ":dynamicVar") { + varInits.push(macro {setDynamic($v{name}, $i{field.name});}); + } + default: + } + + var getFuncExpr:Expr = null; + var setFuncExpr:Expr = null; + + if (meta.name == ":publicVar" || meta.name == ":publicVarRef") { + getFuncExpr = macro return getPublic($v{name}); + setFuncExpr = macro { + setPublic($v{name}, value); + return value; + }; + } else if (meta.name == ":staticVar" || meta.name == ":staticVarRef") { + getFuncExpr = macro return getStatic($v{name}); + setFuncExpr = macro { + setStatic($v{name}, value); + return value; + }; + } else if (meta.name == ":dynamicVar" || meta.name == ":dynamicVarRef") { + getFuncExpr = macro return getDynamic($v{name}); + setFuncExpr = macro { + setDynamic($v{name}, value); + return value; + }; + } + + var getterField:Field = { + name: "get_" + name, + access: [Access.APrivate, Access.AInline], + kind: FieldType.FFun({ + expr: getFuncExpr, + ret: ret, + args: [], + }), + pos: Context.currentPos(), + }; + var setterField:Field = { + name: "set_" + name, + access: [Access.APrivate, Access.AInline], + kind: FieldType.FFun({ + expr: setFuncExpr, + ret: ret, + args: [{name: "value", type: ret}] + }), + pos: Context.currentPos(), + }; + newFields.push(getterField); + newFields.push(setterField); + } + } + } + for (field in newFields) fields.push(field); + + fields.push({ + pos: Context.currentPos(), + name: "initVars", + kind: FFun({ + args: [], + expr: { + pos: Context.currentPos(), + expr: EBlock(varInits) + } + }), + access: [Access.APrivate] + }); + + return fields; + } + + #end +} +#end \ No newline at end of file diff --git a/source/funkin/backend/system/macros/Macros.hx b/source/funkin/backend/system/macros/Macros.hx index 801eda0c17..823f77190c 100644 --- a/source/funkin/backend/system/macros/Macros.hx +++ b/source/funkin/backend/system/macros/Macros.hx @@ -22,6 +22,7 @@ class Macros { #if THREE_D_SUPPORT "away3d", "flx3d", #end #if VIDEO_CUTSCENES "hxvlc.flixel", "hxvlc.openfl", #end #if NAPE_ENABLED "nape", "flixel.addons.nape", #end + #if HARDCODED_SCRIPTS "scripts", #end // BASE HAXE "DateTools", "EReg", "Lambda", "StringBuf", "haxe.crypto", "haxe.display", "haxe.exceptions", "haxe.extern", "scripting", "animate" ]) @@ -63,6 +64,10 @@ class Macros { if (Context.defined("hscript_improved") && !Context.defined("hscript")) { Compiler.define('hscript'); } + + #if HARDCODED_SCRIPTS + Compiler.addGlobalMetadata('scripts', '@:build(funkin.backend.system.macros.HardcodedScriptVarMacro.build())'); + #end } public static function buildLimeAssetLibrary():Array { diff --git a/source/funkin/editors/charter/CharterNoteTypesList.hx b/source/funkin/editors/charter/CharterNoteTypesList.hx index d9a2b66f30..75e1181ae3 100644 --- a/source/funkin/editors/charter/CharterNoteTypesList.hx +++ b/source/funkin/editors/charter/CharterNoteTypesList.hx @@ -55,6 +55,12 @@ class CharterNoteTypesList extends UISubstateWindow { var file:String = Path.withoutDirectory(path); list.pushOnce(file); } + #if HARDCODED_SCRIPTS + for (path in funkin.backend.scripting.HardcodedScriptRegistry.getFolderScripts(pathString)) { + var file:String = Path.withoutDirectory(path); + list.pushOnce(file); + } + #end return list; } diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 5ac54c0e73..d73225ceac 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -750,6 +750,14 @@ class PlayState extends MusicBeatState addScript(file); } + + #if HARDCODED_SCRIPTS + for (file in funkin.backend.scripting.HardcodedScriptRegistry.getFolderScripts(folder)) { + if (!scripts.contains("assets/" + file)) { + scripts.add(Script.create("assets/" + file)); + } + } + #end } var songEvents:Array = []; @@ -761,6 +769,15 @@ class PlayState extends MusicBeatState addScript(file); } } + + #if HARDCODED_SCRIPTS + for (file in funkin.backend.scripting.HardcodedScriptRegistry.getFolderScripts('data/events/')) { + var fileName:String = CoolUtil.getFilename(file); + if (EventsData.eventsList.contains(fileName) && songEvents.contains(fileName) && !scripts.contains("assets/" + file)) { + scripts.add(Script.create("assets/" + file)); + } + } + #end } } @@ -786,11 +803,13 @@ class PlayState extends MusicBeatState for(noteType in SONG.noteTypes) { var scriptPath = Paths.script('data/notes/${noteType}'); - if (Assets.exists(scriptPath) && !scripts.contains(scriptPath)) { - var script = Script.create(scriptPath); - if (!(script is DummyScript)) { - scripts.add(script); - script.load(); + if (!scripts.contains(scriptPath)) { + if (Assets.exists(scriptPath) #if HARDCODED_SCRIPTS || funkin.backend.scripting.HardcodedScriptRegistry.exists(scriptPath) #end) { + var script = Script.create(scriptPath); + if (!(script is DummyScript)) { + scripts.add(script); + script.load(); + } } } }