diff --git a/src/LocalLevelLoader.hx b/src/LocalLevelLoader.hx new file mode 100644 index 00000000..b9c93e72 --- /dev/null +++ b/src/LocalLevelLoader.hx @@ -0,0 +1,95 @@ +package src; + +import haxe.io.Bytes; +import haxe.io.Path; +import mis.MisParser; +import src.Mission; +import src.ResourceLoader; +import src.MarbleGame; +import src.Console; + +class LocalLevelLoader { + public static var currentLocalPath:String = null; + public static var currentMission:Mission = null; + + public static function init() { + #if js + setupWebPostMessage(); + notifyReady(); + #end + } + + public static function notifyReady() { + #if js + if (js.Browser.window.opener != null) { + try { + Console.log("Notifying level editor that MBHaxe is ready..."); + js.Browser.window.opener.postMessage({ type: "mbhaxe-ready" }, "*"); + } catch (e:Dynamic) {} + } + try { + var channel = new js.html.BroadcastChannel("mbhaxe-level-editor"); + channel.postMessage({ type: "mbhaxe-ready" }); + } catch (e:Dynamic) {} + #end + } + + static function setupWebPostMessage() { + #if js + function handleMessage(data:Dynamic) { + if (data == null || data.type != "loadLevel" || data.files == null) return; + Console.log("Received level load message via postMessage/BroadcastChannel"); + + var mission:Mission = null; + + var filesAccess:haxe.DynamicAccess = data.files; + for (k in filesAccess.keys()) { + var fileData:Dynamic = filesAccess.get(k); + var bytes:Bytes = null; + if (Std.isOfType(fileData, String)) { + bytes = Bytes.ofString(fileData); + } else { + bytes = Bytes.ofData(fileData); + } + ResourceLoader.registerLocalFile(k, bytes); + + if (StringTools.endsWith(k.toLowerCase(), ".mis")) { + mission = createLocalMission(k, bytes.toString()); + } + } + + if (mission == null) { + Console.error("postMessage loadLevel missing .mis file in files payload"); + return; + } + + MarbleGame.instance.playMission(mission); + } + + js.Browser.window.addEventListener("message", (e:js.html.MessageEvent) -> { + handleMessage(e.data); + }); + + try { + var channel = new js.html.BroadcastChannel("mbhaxe-level-editor"); + channel.onmessage = (e:js.html.MessageEvent) -> { + handleMessage(e.data); + }; + } catch (e:Dynamic) {} + #end + } + + public static function createLocalMission(misRelPath:String, misText:String):Mission { + var misParser = new MisParser(misText); + var parsed = misParser.parse(); + var mInfo = misParser.parseMissionInfo(); + + var mission = Mission.fromMissionInfo(misRelPath, mInfo); + mission.isCustom = true; + mission.isLocal = true; + mission.game = mInfo.game?.toLowerCase(); + + currentMission = mission; + return mission; + } +} \ No newline at end of file diff --git a/src/Main.hx b/src/Main.hx index 72c8b48d..6ab75267 100644 --- a/src/Main.hx +++ b/src/Main.hx @@ -7,6 +7,7 @@ import src.Console; import hxd.Key; import src.Util; import src.ResourceLoader; +import src.LocalLevelLoader; #if js import fs.ManifestFileSystem; import fs.ManifestBuilder; @@ -108,6 +109,7 @@ class Main extends hxd.App { new ProfilerUI(s2d); + LocalLevelLoader.init(); loaded = true; }); } catch (e) { diff --git a/src/Mission.hx b/src/Mission.hx index 62b7b6d1..45e4e138 100644 --- a/src/Mission.hx +++ b/src/Mission.hx @@ -41,6 +41,7 @@ class Mission { public var game:String; public var hasEgg:Bool; public var isCustom:Bool; + public var isLocal:Bool = false; public var gameMode:String; #if hl public var addedAt:Int64; @@ -237,6 +238,10 @@ class Mission { #end if (!StringTools.endsWith(path, ".dif")) path += ".dif"; + if (ResourceLoader.existsInZip(path)) + return path; + if (ResourceLoader.existsInZip(fname)) + return fname; if (ResourceLoader.exists(path)) return path; if (StringTools.contains(path, 'interiors_mbg/')) @@ -254,6 +259,8 @@ class Mission { path = StringTools.replace(path, "lbinteriors", "interiors"); // This shit ew if (ResourceLoader.exists(path)) return path; + if (ResourceLoader.exists(fname)) + return fname; Console.error("Interior resource not found: " + rawElementPath); return ""; } diff --git a/src/ResourceLoader.hx b/src/ResourceLoader.hx index 7cae151d..9e8be8aa 100644 --- a/src/ResourceLoader.hx +++ b/src/ResourceLoader.hx @@ -283,10 +283,16 @@ class ResourceLoader { #if (js || android) path = StringTools.replace(path, "data/", ""); #end + if (ResourceLoader.existsInZip(path)) + return path; + if (ResourceLoader.existsInZip(fname)) + return fname; if (ResourceLoader.exists(path)) return path; if (ResourceLoader.exists(dirpath + fname)) return dirpath + fname; + if (ResourceLoader.exists(fname)) + return fname; return ""; } @@ -553,6 +559,13 @@ class ResourceLoader { return fileSystem.exists(path); } + public static function existsInZip(path:String):Bool { + #if (js || android) + path = StringTools.replace(path, "data/", ""); + #end + return zipFilesystem.exists(path.toLowerCase()); + } + public static function clearInteriorResources() { interiorResources = new Map(); } @@ -610,4 +623,13 @@ class ResourceLoader { zipFilesystem.set(fname, zfe); } } + + public static function registerLocalFile(path:String, bytes:haxe.io.Bytes) { + path = StringTools.replace(path, "\\", "/").toLowerCase(); + #if (js || android) + path = StringTools.replace(path, "data/", ""); + #end + var zfe = new BytesFileEntry(path, bytes); + zipFilesystem.set(path, zfe); + } } diff --git a/src/gui/EndGameGui.hx b/src/gui/EndGameGui.hx index cc293be8..c56ee2d7 100644 --- a/src/gui/EndGameGui.hx +++ b/src/gui/EndGameGui.hx @@ -396,7 +396,7 @@ class EndGameGui extends GuiControl { var rewindUsed = MarbleGame.instance.world.rewindUsed; var cheatsUsed = MarbleGame.instance.world.cheatsUsed; - if (idx <= 4) { + if (idx <= 4 && !mission.isLocal) { setButtonStates(false); var end = new EnterNameDlg(idx, (name) -> { setButtonStates(true); @@ -440,7 +440,7 @@ class EndGameGui extends GuiControl { scoreSubmitted = true; }); this.addChild(end); - } else { + } else if (!mission.isLocal) { // Check if we can submit LB scores var lbPath = mission.path; if (mission.isClaMission)