diff --git a/src/DifBuilder.hx b/src/DifBuilder.hx index a88d3ea0..d7481939 100644 --- a/src/DifBuilder.hx +++ b/src/DifBuilder.hx @@ -647,18 +647,18 @@ class DifBuilder { path = StringTools.replace(path, "data/", ""); #end - if (ResourceLoader.fileSystem.exists(Path.directory(path) + "/" + tex + ".jpg")) { + if (ResourceLoader.exists(Path.directory(path) + "/" + tex + ".jpg")) { return true; } - if (ResourceLoader.fileSystem.exists(Path.directory(path) + "/" + tex + ".png")) { + if (ResourceLoader.exists(Path.directory(path) + "/" + tex + ".png")) { return true; } var prevDir = Path.directory(Path.directory(path)); - if (ResourceLoader.fileSystem.exists(prevDir + "/" + tex + ".jpg")) { + if (ResourceLoader.exists(prevDir + "/" + tex + ".jpg")) { return true; } - if (ResourceLoader.fileSystem.exists(prevDir + "/" + tex + ".png")) { + if (ResourceLoader.exists(prevDir + "/" + tex + ".png")) { return true; } @@ -669,19 +669,19 @@ class DifBuilder { tex = tex.split('/')[1]; } - if (ResourceLoader.fileSystem.exists(Path.directory(path) + "/" + tex + ".jpg")) { + if (ResourceLoader.exists(Path.directory(path) + "/" + tex + ".jpg")) { return Path.directory(path) + "/" + tex + ".jpg"; } - if (ResourceLoader.fileSystem.exists(Path.directory(path) + "/" + tex + ".png")) { + if (ResourceLoader.exists(Path.directory(path) + "/" + tex + ".png")) { return Path.directory(path) + "/" + tex + ".png"; } var prevDir = Path.directory(Path.directory(path)); - if (ResourceLoader.fileSystem.exists(prevDir + "/" + tex + ".jpg")) { + if (ResourceLoader.exists(prevDir + "/" + tex + ".jpg")) { return prevDir + "/" + tex + ".jpg"; } - if (ResourceLoader.fileSystem.exists(prevDir + "/" + tex + ".png")) { + if (ResourceLoader.exists(prevDir + "/" + tex + ".png")) { return prevDir + "/" + tex + ".png"; } @@ -729,7 +729,7 @@ class DifBuilder { var material:Material; var texture:Texture; if (canFindTex(grp)) { - texture = ResourceLoader.getFileEntry(tex(grp)).toImage().toTexture(); + texture = ResourceLoader.getTexture(tex(grp)).resource; texture.wrap = Wrap.Repeat; texture.mipMap = Nearest; var exactName = StringTools.replace(texture.name, "data/", ""); diff --git a/src/DtsObject.hx b/src/DtsObject.hx index b880ff8c..2680d107 100644 --- a/src/DtsObject.hx +++ b/src/DtsObject.hx @@ -471,7 +471,7 @@ class DtsObject extends GameObject { } function parseIfl(path:String) { - var text = ResourceLoader.fileSystem.get(path).getText(); + var text = ResourceLoader.getFileEntry(path).entry.getText(); var lines = text.split('\n'); var keyframes = []; for (line in lines) { diff --git a/src/Http.hx b/src/Http.hx new file mode 100644 index 00000000..9079a0f2 --- /dev/null +++ b/src/Http.hx @@ -0,0 +1,68 @@ +package src; + +import src.Console; +import sys.thread.FixedThreadPool; + +typedef HttpRequest = { + var url:String; + var callback:haxe.io.Bytes->Void; + var errCallback:String->Void; +}; + +class Http { + #if sys + static var threadPool:sys.thread.FixedThreadPool; + static var requests:sys.thread.Deque = new sys.thread.Deque(); + static var responses:sys.thread.Deque<() -> Void> = new sys.thread.Deque<() -> Void>(); + #end + + public static function init() { + #if sys + threadPool = new sys.thread.FixedThreadPool(4); + threadPool.run(() -> threadLoop()); + threadPool.run(() -> threadLoop()); + threadPool.run(() -> threadLoop()); + threadPool.run(() -> threadLoop()); + #end + } + + #if sys + static function threadLoop() { + while (true) { + var req = requests.pop(true); + var http = new sys.Http(req.url); + http.onError = (e) -> { + responses.add(() -> req.errCallback(e)); + }; + http.onBytes = (b) -> { + responses.add(() -> req.callback(b)); + }; + hl.Gc.blocking(true); // Wtf is this shit + http.request(false); + hl.Gc.blocking(false); + } + } + #end + + public static function get(url:String, callback:haxe.io.Bytes->Void, errCallback:String->Void):Void { + var req = { + url: url, + callback: callback, + errCallback: errCallback + }; + #if sys + requests.add(req); + #else + js.Browser.window.fetch(url).then(r -> r.arrayBuffer().then(b -> callback(haxe.io.Bytes.ofData(b))), e -> errCallback(e.toString())); + #end + } + + public static function loop() { + #if sys + var resp = responses.pop(false); + if (resp != null) { + resp(); + } + #end + } +} diff --git a/src/Main.hx b/src/Main.hx index 9ea4e95f..2995d424 100644 --- a/src/Main.hx +++ b/src/Main.hx @@ -1,5 +1,6 @@ package; +import src.Marbleland; import src.Console; import hxd.Key; import src.Util; @@ -18,6 +19,7 @@ import hxd.res.DefaultFont; import h2d.Text; import h3d.Vector; import src.ProfilerUI; +import src.Http; class Main extends hxd.App { var marbleGame:MarbleGame; @@ -66,10 +68,13 @@ class Main extends hxd.App { #end try { + Http.init(); + haxe.MainLoop.add(() -> Http.loop()); Settings.init(); ResourceLoader.init(s2d, () -> { AudioManager.init(); AudioManager.playShell(); + Marbleland.init(); marbleGame = new MarbleGame(s2d, s3d); MarbleGame.canvas.setContent(new MainMenuGui()); diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index 2ef1b552..22c44ad8 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -181,6 +181,8 @@ class MarbleWorld extends Scheduler { public var _ready:Bool = false; + var _loadBegin:Bool = false; + var _loadingLength:Int = 0; var _resourcesLoaded:Int = 0; @@ -207,7 +209,15 @@ class MarbleWorld extends Scheduler { Console.log("*** LOADING MISSION: " + mission.path); this.loadingGui = new LoadingGui(this.mission.title, this.mission.game); MarbleGame.canvas.setContent(this.loadingGui); + if (this.mission.isClaMission) { + this.mission.download(() -> loadBegin()); + } else { + loadBegin(); + } + } + function loadBegin() { + _loadBegin = true; function scanMission(simGroup:MissionElementSimGroup) { for (element in simGroup.elements) { if ([ @@ -249,8 +259,12 @@ class MarbleWorld extends Scheduler { } public function loadMusic(onFinish:Void->Void) { - var musicFileName = 'sound/music/' + this.mission.missionInfo.music; - ResourceLoader.load(musicFileName).entry.load(onFinish); + if (this.mission.missionInfo.music != null) { + var musicFileName = 'sound/music/' + this.mission.missionInfo.music; + ResourceLoader.load(musicFileName).entry.load(onFinish); + } else { + onFinish(); + } } public function postInit() { @@ -1031,7 +1045,7 @@ class MarbleWorld extends Scheduler { public function addDtsObject(obj:DtsObject, onFinish:Void->Void) { function parseIfl(path:String, onFinish:Array->Void) { ResourceLoader.load(path).entry.load(() -> { - var text = ResourceLoader.fileSystem.get(path).getText(); + var text = ResourceLoader.getFileEntry(path).entry.getText(); var lines = text.split('\n'); var keyframes = []; for (line in lines) { @@ -1294,7 +1308,7 @@ class MarbleWorld extends Scheduler { }); #end } else { - if (this._resourcesLoaded < _loadingLength) + if (this._resourcesLoaded < _loadingLength || !this._loadBegin) return; if (!_ready && !postInited) { postInited = true; @@ -2002,22 +2016,12 @@ class MarbleWorld extends Scheduler { this.replay.name = MarbleGame.instance.recordingName; #if hl sys.FileSystem.createDirectory(haxe.io.Path.join([Settings.settingsDir, "data", "replays"])); - var replayPath = haxe.io.Path.join([ - Settings.settingsDir, - "data", - "replays", - '${this.replay.name}.mbr' - ]); + var replayPath = haxe.io.Path.join([Settings.settingsDir, "data", "replays", '${this.replay.name}.mbr']); if (sys.FileSystem.exists(replayPath)) { var count = 1; var found = false; while (!found) { - replayPath = haxe.io.Path.join([ - Settings.settingsDir, - "data", - "replays", - '${this.replay.name} (${count}).mbr' - ]); + replayPath = haxe.io.Path.join([Settings.settingsDir, "data", "replays", '${this.replay.name} (${count}).mbr']); if (!sys.FileSystem.exists(replayPath)) { this.replay.name += ' (${count})'; found = true; @@ -2066,7 +2070,8 @@ class MarbleWorld extends Scheduler { } } - this.playGui.dispose(); + if (this.playGui != null) + this.playGui.dispose(); scene.removeChildren(); for (interior in this.interiors) { @@ -2091,7 +2096,8 @@ class MarbleWorld extends Scheduler { textureResource.release(); } - sky.dispose(); + if (sky != null) + sky.dispose(); this._disposed = true; AudioManager.stopAllSounds(); diff --git a/src/Marbleland.hx b/src/Marbleland.hx new file mode 100644 index 00000000..6618c5c1 --- /dev/null +++ b/src/Marbleland.hx @@ -0,0 +1,84 @@ +package src; + +import haxe.io.BytesInput; +import haxe.zip.Reader; +import hxd.res.Image; +import hxd.BitmapData; +import haxe.Json; +import src.Mission; +import src.Http; +import src.ResourceLoader; + +class Marbleland { + public static var goldMissions = []; + public static var ultraMissions = []; + public static var platinumMissions = []; + + public static function init() { + Http.get('https://raw.githubusercontent.com/Vanilagy/MarbleBlast/master/src/assets/customs_gold.json', (b) -> { + parseMissionList(b.toString(), "gold"); + }, (e) -> {}); + Http.get('https://raw.githubusercontent.com/Vanilagy/MarbleBlast/master/src/assets/customs_ultra.json', (b) -> { + parseMissionList(b.toString(), "ultra"); + }, (e) -> {}); + Http.get('https://raw.githubusercontent.com/Vanilagy/MarbleBlast/master/src/assets/customs_platinum.json', (b) -> { + parseMissionList(b.toString(), "platinum"); + }, (e) -> {}); + } + + static function parseMissionList(s:String, game:String) { + var claJson:Array = Json.parse(s); + + for (missionData in claJson) { + var mission = new Mission(); + mission.id = missionData.id; + mission.path = 'missions/' + missionData.baseName; + #if (hl && !android) + mission.path = 'data/' + mission.path; + #end + mission.path = mission.path.toLowerCase(); + mission.title = missionData.name; + mission.artist = missionData.artist != null ? missionData.artist : "Unknown Author"; + mission.description = missionData.desc != null ? missionData.desc : ""; + mission.qualifyTime = missionData.qualifyingTime != null ? missionData.qualifyingTime / 1000 : Math.POSITIVE_INFINITY; + mission.goldTime = missionData.goldTime != null ? missionData.goldTime / 1000 : 0; + mission.game = missionData.modification; + if (missionData.modification == 'platinum') + mission.goldTime = missionData.platinumTime != null ? missionData.platinumTime / 1000 : mission.goldTime; + mission.ultimateTime = missionData.ultimateTime != null ? missionData.ultimateTime / 1000 : 0; + mission.hasEgg = missionData.hasEgg; + mission.isClaMission = true; + + switch (game) { + case 'gold': + goldMissions.push(mission); + case 'ultra': + ultraMissions.push(mission); + case 'platinum': + platinumMissions.push(mission); + } + } + } + + public static function getMissionImage(id:Int, cb:Image->Void) { + Http.get('https://marbleland.vani.ga/api/level/${id}/image?width=258&height=194', (imageBytes) -> { + var res = new Image(new hxd.fs.BytesFileSystem.BytesFileEntry('${id}.png', imageBytes)); + cb(res); + }, (e) -> { + cb(null); + }); + } + + public static function download(id:Int, cb:Array->Void) { + Http.get('https://marbleblast.vani.ga/api/custom/${id}.zip', (zipData -> { + var reader = new Reader(new BytesInput(zipData)); + var entries:Array = null; + try { + entries = [for (x in reader.read()) x]; + } catch (e) {} + cb(entries); + }), (e) -> { + cb(null); + }); + } +} diff --git a/src/Mission.hx b/src/Mission.hx index 86ff1361..5d0b0b63 100644 --- a/src/Mission.hx +++ b/src/Mission.hx @@ -1,5 +1,7 @@ package src; +import gui.Canvas; +import gui.MessageBoxOkDlg; import haxe.Json; import mis.MissionElement.MissionElementItem; import haxe.io.BytesBuffer; @@ -15,6 +17,8 @@ import hxd.res.Image; import src.Resource; import src.Util; import src.Console; +import src.Marbleland; +import src.MarbleGame; class Mission { public var root:MissionElementSimGroup; @@ -45,7 +49,7 @@ class Mission { public function new() {} public function load() { - var misParser = new MisParser(ResourceLoader.fileSystem.get(this.path).getText()); + var misParser = new MisParser(ResourceLoader.getFileEntry(this.path).entry.getText()); var contents = misParser.parse(); root = contents.root; @@ -60,6 +64,8 @@ class Mission { } else if (element._type == MissionElementType.SimGroup && !this.hasEgg) { scanMission(cast element); } + if (element._name == 'MissionInfo') + missionInfo = cast element; } }; @@ -153,10 +159,17 @@ class Mission { onLoaded(Tile.fromBitmap(img)); return null; } else { - Console.error("Preview image not found for " + this.path); - var img = new BitmapData(1, 1); - img.setPixel(0, 0, 0); - onLoaded(Tile.fromBitmap(img)); + Marbleland.getMissionImage(this.id, (im) -> { + if (im != null) { + onLoaded(im.toTile()); + } else { + Console.error("Preview image not found for " + this.path); + var img = new BitmapData(1, 1); + img.setPixel(0, 0, 0); + onLoaded(Tile.fromBitmap(img)); + } + }); + return null; } } @@ -175,15 +188,20 @@ class Mission { #if (js || android) path = StringTools.replace(path, "data/", ""); #end - if (ResourceLoader.fileSystem.exists(path)) + if (ResourceLoader.exists(path)) return path; if (StringTools.contains(path, 'interiors_mbg/')) path = StringTools.replace(path, 'interiors_mbg/', 'interiors/'); var dirpath = path.substring(0, path.lastIndexOf('/') + 1); - if (ResourceLoader.fileSystem.exists(path)) + if (ResourceLoader.exists(path)) return path; - if (ResourceLoader.fileSystem.exists(dirpath + fname)) + if (ResourceLoader.exists(dirpath + fname)) return dirpath + fname; + if (game == 'gold') { + path = StringTools.replace(path, 'interiors/', 'interiors_mbg/'); + if (ResourceLoader.exists(path)) + return path; + } Console.error("Interior resource not found: " + rawElementPath); return ""; } @@ -200,4 +218,17 @@ class Mission { return alarmStart; } + + public function download(onFinish:Void->Void) { + if (this.isClaMission) { + Marbleland.download(this.id, (zipEntries) -> { + if (zipEntries != null) { + ResourceLoader.loadZip(zipEntries); + onFinish(); + } else { + MarbleGame.canvas.pushDialog(new MessageBoxOkDlg("Failed to download mission")); + } + }); + } + } } diff --git a/src/MissionList.hx b/src/MissionList.hx index 7ab46634..dccaceef 100644 --- a/src/MissionList.hx +++ b/src/MissionList.hx @@ -110,22 +110,4 @@ class MissionList { _build = true; } - - static function parseCLAList() { - var claJson:Array = Json.parse(ResourceLoader.fileSystem.get("data/cla_list.json").getText()); - - for (missionData in claJson) { - var mission = new Mission(); - mission.id = missionData.id; - mission.artist = missionData.artist; - mission.title = missionData.name; - mission.description = missionData.desc; - mission.qualifyTime = missionData.time; - mission.goldTime = missionData.goldTime; - mission.path = missionData.baseName; - mission.isClaMission = true; - - customMissions.push(mission); - } - } } diff --git a/src/ResourceLoader.hx b/src/ResourceLoader.hx index 42dc7abc..6c145d97 100644 --- a/src/ResourceLoader.hx +++ b/src/ResourceLoader.hx @@ -1,5 +1,7 @@ package src; +import hxd.res.Any; +import hxd.fs.BytesFileSystem.BytesFileEntry; #if (js || android) import fs.ManifestLoader; import fs.ManifestBuilder; @@ -43,6 +45,7 @@ class ResourceLoader { static var textureCache:Map> = new Map(); static var imageCache:Map> = new Map(); static var audioCache:Map> = new Map(); + static var zipFilesystem:Map = new Map(); // static var threadPool:FixedThreadPool = new FixedThreadPool(4); @@ -256,9 +259,9 @@ class ResourceLoader { #if (js || android) path = StringTools.replace(path, "data/", ""); #end - if (ResourceLoader.fileSystem.exists(path)) + if (ResourceLoader.exists(path)) return path; - if (ResourceLoader.fileSystem.exists(dirpath + fname)) + if (ResourceLoader.exists(dirpath + fname)) return dirpath + fname; return ""; } @@ -271,6 +274,9 @@ class ResourceLoader { #if (js || android) path = StringTools.replace(path, "data/", ""); #end + if (zipFilesystem.exists(path.toLowerCase())) { + return new hxd.res.Any(loader, zipFilesystem.get(path.toLowerCase())); + } return ResourceLoader.loader.load(path); } @@ -284,7 +290,10 @@ class ResourceLoader { var itr:Dif; // var lock = new Lock(); // threadPool.run(() -> { - itr = Dif.LoadFromBuffer(fileSystem.get(path).getBytes()); + if (zipFilesystem.exists(path.toLowerCase())) + itr = Dif.LoadFromBuffer(zipFilesystem.get(path.toLowerCase()).getBytes()); + else + itr = Dif.LoadFromBuffer(fileSystem.get(path).getBytes()); var itrresource = new Resource(itr, path, interiorResources, dif -> {}); interiorResources.set(path, itrresource); // lock.release(); @@ -296,6 +305,7 @@ class ResourceLoader { public static function loadDts(path:String) { path = getProperFilepath(path); + if (dtsResources.exists(path)) return dtsResources.get(path); else { @@ -314,6 +324,18 @@ class ResourceLoader { public static function getTexture(path:String) { path = getProperFilepath(path); + if (zipFilesystem.exists(path.toLowerCase())) { + var img = new hxd.res.Image(zipFilesystem.get(path.toLowerCase())); + Image.setupTextureFlags = (texObj) -> { + texObj.flags.set(MipMapped); + } + var tex = img.toTexture(); + tex.mipMap = Nearest; + var textureresource = new Resource(tex, path, textureCache, tex -> tex.dispose()); + textureCache.set(path, textureresource); + + return textureresource; + } if (textureCache.exists(path)) return textureCache.get(path); if (fileSystem.exists(path)) { @@ -336,6 +358,12 @@ class ResourceLoader { #if (js || android) path = StringTools.replace(path, "data/", ""); #end + if (zipFilesystem.exists(path.toLowerCase())) { + var fentry = new hxd.res.Image(zipFilesystem.get(path.toLowerCase())); + var imageresource = new Resource(fentry, path, imageCache, img -> {}); + imageCache.set(path, imageresource); + return imageresource; + } if (imageCache.exists(path)) return imageCache.get(path); if (fileSystem.exists(path)) { @@ -379,10 +407,20 @@ class ResourceLoader { #if (js || android) path = StringTools.replace(path, "data/", ""); #end + if (zipFilesystem.exists(path.toLowerCase())) { + var fentry = zipFilesystem.get(path.toLowerCase()); + return new hxd.res.Any(loader, fentry); + } var file = loader.load(path); return file; } + public static function exists(path:String) { + if (zipFilesystem.exists(path.toLowerCase())) + return true; + return fileSystem.exists(path); + } + public static function clearInteriorResources() { interiorResources = new Map(); } @@ -405,4 +443,17 @@ class ResourceLoader { } return names; } + + public static function loadZip(entries:Array) { + zipFilesystem.clear(); // We are only allowed to load one zip + for (entry in entries) { + var fname = entry.fileName.toLowerCase(); + // fname = "data/" + fname; + if (exists(fname)) + continue; + Console.log("Loaded zip entry: " + fname); + var zfe = new BytesFileEntry(fname, entry.data); + zipFilesystem.set(fname, zfe); + } + } } diff --git a/src/Sky.hx b/src/Sky.hx index df1ec1e0..6670be9a 100644 --- a/src/Sky.hx +++ b/src/Sky.hx @@ -72,8 +72,8 @@ class Sky extends Object { #if (js || android) dmlPath = StringTools.replace(dmlPath, "data/", ""); #end - if (ResourceLoader.fileSystem.exists(dmlPath)) { - var dmlFileEntry = ResourceLoader.fileSystem.get(dmlPath); + if (ResourceLoader.exists(dmlPath)) { + var dmlFileEntry = ResourceLoader.getFileEntry(dmlPath).entry; dmlFileEntry.load(() -> { var dmlFile = dmlFileEntry.getText(); var dmlDirectory = Path.directory(dmlPath); diff --git a/src/dts/DtsFile.hx b/src/dts/DtsFile.hx index 92bfac1a..044da608 100644 --- a/src/dts/DtsFile.hx +++ b/src/dts/DtsFile.hx @@ -61,7 +61,7 @@ class DtsFile { public function new() {} public function read(filepath:String) { - var f = ResourceLoader.fileSystem.get(filepath); + var f = ResourceLoader.getFileEntry(filepath).entry; var bytes = f.getBytes(); var br = new BytesReader(bytes); diff --git a/src/gui/PlayMissionGui.hx b/src/gui/PlayMissionGui.hx index 1bee2026..90a6e4a0 100644 --- a/src/gui/PlayMissionGui.hx +++ b/src/gui/PlayMissionGui.hx @@ -1,5 +1,6 @@ package gui; +import src.Marbleland; import h2d.filter.DropShadow; import src.Replay; import haxe.ds.Option; @@ -50,6 +51,9 @@ class PlayMissionGui extends GuiImage { #if js var previewTimeoutHandle:Option = None; #end + #if hl + var previewToken:Int = 0; + #end public function new() { MissionList.buildMissionList(); @@ -195,7 +199,7 @@ class PlayMissionGui extends GuiImage { pmSearch.position = new Vector(315, 325); pmSearch.extent = new Vector(43, 43); pmSearch.pressedAction = (e) -> { - MarbleGame.canvas.pushDialog(new SearchGui(currentGame)); + MarbleGame.canvas.pushDialog(new SearchGui(currentGame, currentCategory == "custom")); } pmBox.addChild(pmSearch); @@ -420,6 +424,18 @@ class PlayMissionGui extends GuiImage { pmDifficultyTopCTab2.vertSizing = Bottom; pmDifficultyTopC2.addChild(pmDifficultyTopCTab2); + var pmDifficultyUltraCustom = new GuiButtonText(loadButtonImages("data/ui/play/difficulty_highlight-120"), markerFelt24); + pmDifficultyUltraCustom.position = new Vector(277, 164); + pmDifficultyUltraCustom.ratio = -1 / 16; + pmDifficultyUltraCustom.setExtent(new Vector(120, 31)); + pmDifficultyUltraCustom.txtCtrl.text.text = " Custom"; + pmDifficultyUltraCustom.pressedAction = (e) -> { + currentList = Marbleland.goldMissions; + currentCategory = "custom"; + setCategoryFunc("ultra", "custom"); + } + pmDifficultyCtrl.addChild(pmDifficultyUltraCustom); + var pmDifficultyUltraAdvanced = new GuiButtonText(loadButtonImages("data/ui/play/difficulty_highlight-120"), markerFelt24); pmDifficultyUltraAdvanced.position = new Vector(277, 134); pmDifficultyUltraAdvanced.ratio = -1 / 16; @@ -456,6 +472,18 @@ class PlayMissionGui extends GuiImage { } pmDifficultyCtrl.addChild(pmDifficultyUltraIntermediate); + var pmDifficultyGoldCustom = new GuiButtonText(loadButtonImages("data/ui/play/difficulty_highlight-120"), markerFelt24); + pmDifficultyGoldCustom.position = new Vector(37, 164); + pmDifficultyGoldCustom.ratio = -1 / 16; + pmDifficultyGoldCustom.setExtent(new Vector(120, 31)); + pmDifficultyGoldCustom.txtCtrl.text.text = " Custom"; + pmDifficultyGoldCustom.pressedAction = (e) -> { + currentList = Marbleland.goldMissions; + currentCategory = "custom"; + setCategoryFunc("gold", "custom"); + } + pmDifficultyCtrl.addChild(pmDifficultyGoldCustom); + var pmDifficultyGoldAdvanced = new GuiButtonText(loadButtonImages("data/ui/play/difficulty_highlight-120"), markerFelt24); pmDifficultyGoldAdvanced.position = new Vector(37, 134); pmDifficultyGoldAdvanced.ratio = -1 / 16; @@ -741,7 +769,12 @@ class PlayMissionGui extends GuiImage { currentList = MissionList.missionList["platinum"]["beginner"]; setCategoryFunc = function(game:String, category:String, ?doRender:Bool = true) { - currentList = "category" == "custom" ? MissionList.customMissions : MissionList.missionList[game][category]; + currentList = category == "custom" ? (switch (game) { + case 'gold': Marbleland.goldMissions; + case 'platinum': Marbleland.platinumMissions; + case 'ultra': Marbleland.ultraMissions; + default: MissionList.customMissions; + }) : MissionList.missionList[game][category]; @:privateAccess pmDifficulty.anim.frames = loadButtonImages('data/ui/play/difficulty_${category}'); pmDifficultyMarble.bmp.tile = ResourceLoader.getResource('data/ui/play/marble_${game}.png', ResourceLoader.getImage, this.imageResources).toTile(); @@ -889,13 +922,13 @@ class PlayMissionGui extends GuiImage { var scoreTextTime = '

${Util.formatTime(score.time)}

'; rightText += scoreTextTime; - descText += '${i + 1}. ${score.name}
'; + descText += '${i + 1}. ${StringTools.htmlEscape(score.name)}
'; } pmDescriptionRight.text.text = rightText; } else { - descText += '

Author: ${currentMission.artist}

'; - descText += '${currentMission.description}'; + descText += '

Author: ${StringTools.htmlEscape(currentMission.artist)}

'; + descText += '${StringTools.htmlEscape(currentMission.description)}'; pmDescriptionRight.text.text = ''; } pmDescription.text.text = descText; @@ -947,7 +980,10 @@ class PlayMissionGui extends GuiImage { } #end #if hl + var pTok = previewToken++; var prevpath = currentMission.getPreviewImage(prevImg -> { + if (pTok + 1 != previewToken) + return; pmPreview.bmp.tile = prevImg; }); // Shit be sync if (prevpath != pmPreview.bmp.tile.getTexture().name) { diff --git a/src/gui/SearchGui.hx b/src/gui/SearchGui.hx index 884a139f..61265787 100644 --- a/src/gui/SearchGui.hx +++ b/src/gui/SearchGui.hx @@ -1,5 +1,6 @@ package gui; +import src.Marbleland; import h2d.Tile; import hxd.BitmapData; import src.MarbleGame; @@ -9,7 +10,7 @@ import src.ResourceLoader; import src.Settings; class SearchGui extends GuiImage { - public function new(game:String) { + public function new(game:String, isCustom:Bool) { var img = ResourceLoader.getImage("data/ui/search/window.png"); super(img.resource.toTile()); @@ -19,8 +20,29 @@ class SearchGui extends GuiImage { this.extent = new Vector(487, 463); var missionList = []; - for (diff in MissionList.missionList[game]) { - for (mis in diff) { + if (!isCustom) { + for (diff in MissionList.missionList[game]) { + for (mis in diff) { + missionList.push({ + mis: mis, + name: mis.title, + artist: mis.artist, + path: mis.path + }); + } + } + } else { + var customsList = switch (game) { + case 'gold': + Marbleland.goldMissions; + case 'platinum': + Marbleland.platinumMissions; + case 'ultra': + Marbleland.ultraMissions; + default: + MissionList.customMissions; + }; + for (mis in customsList) { missionList.push({ mis: mis, name: mis.title,