diff --git a/source/funkin/backend/system/macros/ControlsMacro.macro.hx b/source/funkin/backend/system/macros/ControlsMacro.macro.hx new file mode 100644 index 00000000..7c363f6f --- /dev/null +++ b/source/funkin/backend/system/macros/ControlsMacro.macro.hx @@ -0,0 +1,312 @@ +package funkin.backend.system.macros; + + +import haxe.macro.Context; +import haxe.macro.Expr; + +using StringTools; + +using haxe.macro.Tools; + + +// Macro made by Ne_Eo +class ControlsMacro +{ + static var _allControls: Array = null; + static var _allInternalControls: Array = null; + static var _currentControls: Map> = null; + static var _currentGamepadControls: Map = null; + static var _keySet: Map = null; + + public static macro function build(): Array + { + var fields = Context.getBuildFields(); + var clRef = Context.getLocalClass(); + if (clRef == null) + return fields; + var cl = clRef.get(); + + _allControls = []; + _allInternalControls = []; + _currentControls = []; + _currentGamepadControls = []; + _keySet = []; + var fields = Context.getBuildFields(); + for (field in fields.copy()) + { + switch (field.kind) + { + case FProp(g, s, t, e): + var controlFields = handleControl(field, g, s, t, e); + for (field in controlFields) + if (field != null) + fields.push(field); + default: + } + } + + // Public fields + fields.push({ + name: "addDefaultGamepad", + access: [APublic], + kind: FFun({ + ret: macro : Void, + params: [], + expr: generateGamepadCode(), + args: [{name: "id", type: macro : Int}] + }), + pos: Context.currentPos(), + doc: null, + meta: [] + }); + + // Private fields, are prefixed with macro_, for readability + fields.push({ + name: "macro_addKeysToActions", + access: [APublic, AInline], + kind: FFun({ + ret: macro : Void, + params: [], + expr: macro $b{_allInternalControls.map((s) -> macro add($i{s}))}, + args: [] + }), + pos: Context.currentPos(), + doc: null, + meta: [] + }); + + fields.push({ + name: "macro_bindControls", + access: [], + kind: FFun({ + ret: macro : Void, + params: [], + expr: macro $b{_allControls.map((s) -> macro addKeys(Control.$s, $v{_keySet.get(s)}))}, + args: [] + }), + pos: Context.currentPos(), + doc: null, + meta: [] + }); + + fields.push({ + name: "macro_forEachBound", + access: [], + kind: FFun({ + ret: macro : Void, + params: [], + expr: generateForEachBoundCode(), + args: [ + { + name: "control", + type: macro : Control + }, + { + name: "func", + type: macro : FlxActionDigital -> FlxInputState -> Void + } + ] + }), + pos: Context.currentPos(), + doc: null, + meta: [] + }); + + // for (field in fields) + // trace(new haxe.macro.Printer().printField(field)); + + _allControls = null; + _allInternalControls = null; + _currentControls = null; + _currentGamepadControls = null; + _keySet = null; + + return fields; + } + + static function generateForEachBoundCode(): Expr + { + var cases: Array = []; + + for (field => code in _currentControls) + { + cases.push({ + values: [macro Control.$field], + expr: macro $b{code} + }); + } + + return { + expr: ESwitch(macro control, cases, macro {}), + pos: Context.currentPos() + } + } + + static function generateGamepadCode(): Expr + { + var map: Array = []; + for (name in _allControls) + { + var expr = _currentGamepadControls.get(name); + map.push(macro @:pos(expr.pos) Control.$name => ${expr}); + } + + return macro addGamepad(id, $a{map}); + } + + static function extractString(e: Expr): String + { + switch (e.expr) + { + case EConst(CString(s)): + return s; + default: + Context.error("Expected a string", e.pos); + throw "Expected a String"; + } + } + + static function camelCase(s: String): String + { + s = s.split("_").map((s) -> + { + return s.charAt(0).toUpperCase() + s.substr(1).toLowerCase(); + }).join(""); + + return s.charAt(0).toLowerCase() + s.substr(1); + } + + static function swapAB(e: Expr): Expr + { + return switch (e.expr) + { + case EConst(CIdent('A')): macro B; + case EConst(CIdent('B')): macro A; + default: e.map(swapAB); + } + } + + static function handleControl(field: Field, get: String, set: String, _: ComplexType, e: Expr): Array + { + var name = field.name; + var shortName = name; + var type = ""; + if (shortName.endsWith(type = "_P")) + shortName = shortName.substr(0, shortName.length - 2); + else if (shortName.endsWith(type = "_R")) + shortName = shortName.substr(0, shortName.length - 2); + else if (shortName.endsWith(type = "_H")) + shortName = shortName.substr(0, shortName.length - 2); + else + type = ""; + + var internalName = "_" + camelCase(shortName); + if (type != "") + internalName += type.substr(1); + + var keyset: Null = null; + var expr: Expr = null; + var metasToRemove = []; + for (meta in field.meta) + { + var shouldRemove = true; + switch (meta.name) + { + case ":gamepad": + if (!_currentGamepadControls.exists(shortName)) + { + var expr = meta.params[0]; + if (Context.defined("switch")) + { + expr = swapAB(expr); + } + _currentGamepadControls.set(shortName, expr); + } + case ":pressed": + keyset = extractString(meta.params[0]); + expr = macro func($i{internalName}, PRESSED); + case ":justPressed": + keyset = extractString(meta.params[0]); + expr = macro func($i{internalName}, JUST_PRESSED); + case ":released": + keyset = extractString(meta.params[0]); + expr = macro func($i{internalName}, RELEASED); + case ":justReleased": + keyset = extractString(meta.params[0]); + expr = macro func($i{internalName}, JUST_RELEASED); + default: + shouldRemove = false; + } + + if (shouldRemove) + metasToRemove.push(meta); + } + + for (meta in metasToRemove) + field.meta.remove(meta); + + if (_currentControls.exists(shortName)) + _currentControls.get(shortName).push(expr); + else + _currentControls.set(shortName, [expr]); + + if (!_allInternalControls.contains(internalName)) + _allInternalControls.push(internalName); + if (!_allControls.contains(shortName)) + _allControls.push(shortName); + + _keySet.set(shortName, keyset); + + // Generated Code: var _uiUp = new FlxActionDigital("_uiUp"); + var internalField: Field = { + name: internalName, + access: [APrivate], + kind: FVar(macro : FlxActionDigital, macro new FlxActionDigital($v{internalName})), + pos: field.pos, + doc: null, + meta: [] + }; + + // Generated Code: + // inline function get_UI_UP(): Bool + // return _uiUp.check(); + var getField: Field = { + name: "get_" + name, + access: [APrivate, AInline], + kind: FFun({ + ret: macro : Bool, + params: [], + expr: macro + { + return $i{internalName}.check(); + }, + args: [] + }), + pos: field.pos, + doc: field.doc, + meta: [] + }; + + // Generated Code: + // inline function set_UI_UP(val: Bool): Bool + // return @:privateAccess _uiUp._checked = val; + var setField: Field = { + name: "set_" + name, + access: [APrivate, AInline], + kind: FFun({ + ret: macro : Bool, + params: [], + expr: macro + { + return @:privateAccess $i{internalName}._checked = val; + }, + args: [{name: "val", type: macro : Bool}] + }), + pos: field.pos, + doc: field.doc, + meta: [] + }; + + return [internalField, getField, set == "set" ? setField : null]; + } +}