diff --git a/data/ui/achievement/1.png b/data/ui/achievement/1.png new file mode 100644 index 00000000..fee91c2d Binary files /dev/null and b/data/ui/achievement/1.png differ diff --git a/data/ui/achievement/10.png b/data/ui/achievement/10.png new file mode 100644 index 00000000..0227444b Binary files /dev/null and b/data/ui/achievement/10.png differ diff --git a/data/ui/achievement/11.png b/data/ui/achievement/11.png new file mode 100644 index 00000000..f5145c2c Binary files /dev/null and b/data/ui/achievement/11.png differ diff --git a/data/ui/achievement/12.png b/data/ui/achievement/12.png new file mode 100644 index 00000000..106251b3 Binary files /dev/null and b/data/ui/achievement/12.png differ diff --git a/data/ui/achievement/2.png b/data/ui/achievement/2.png new file mode 100644 index 00000000..49b657ab Binary files /dev/null and b/data/ui/achievement/2.png differ diff --git a/data/ui/achievement/3.png b/data/ui/achievement/3.png new file mode 100644 index 00000000..dce41e63 Binary files /dev/null and b/data/ui/achievement/3.png differ diff --git a/data/ui/achievement/4.png b/data/ui/achievement/4.png new file mode 100644 index 00000000..1b1b95d1 Binary files /dev/null and b/data/ui/achievement/4.png differ diff --git a/data/ui/achievement/5.png b/data/ui/achievement/5.png new file mode 100644 index 00000000..c4a296aa Binary files /dev/null and b/data/ui/achievement/5.png differ diff --git a/data/ui/achievement/6.png b/data/ui/achievement/6.png new file mode 100644 index 00000000..1a94272a Binary files /dev/null and b/data/ui/achievement/6.png differ diff --git a/data/ui/achievement/7.png b/data/ui/achievement/7.png new file mode 100644 index 00000000..d3fa5991 Binary files /dev/null and b/data/ui/achievement/7.png differ diff --git a/data/ui/achievement/8.png b/data/ui/achievement/8.png new file mode 100644 index 00000000..0557dc70 Binary files /dev/null and b/data/ui/achievement/8.png differ diff --git a/data/ui/achievement/9.png b/data/ui/achievement/9.png new file mode 100644 index 00000000..0bdf4100 Binary files /dev/null and b/data/ui/achievement/9.png differ diff --git a/data/ui/achievement/all.png b/data/ui/achievement/all.png new file mode 100644 index 00000000..18750e1b Binary files /dev/null and b/data/ui/achievement/all.png differ diff --git a/data/ui/xbox/DemoOutOfTimeIconMoved.png b/data/ui/xbox/DemoOutOfTimeIconMoved.png new file mode 100644 index 00000000..43478400 Binary files /dev/null and b/data/ui/xbox/DemoOutOfTimeIconMoved.png differ diff --git a/data/ui/xbox/achievementWindow.png b/data/ui/xbox/achievementWindow.png new file mode 100644 index 00000000..a2fc6c73 Binary files /dev/null and b/data/ui/xbox/achievementWindow.png differ diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index 5093e873..9b1aea63 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -1,5 +1,6 @@ package src; +import gui.AchievementsGui; import src.Radar; import gui.LevelSelectGui; import h3d.scene.fwd.Light; @@ -1390,8 +1391,18 @@ class MarbleWorld extends Scheduler { this.finishYaw = this.marble.camera.CameraYaw; this.finishPitch = this.marble.camera.CameraPitch; displayAlert("Congratulations! You've finished!"); - if (!this.isWatching) - this.schedule(this.timeState.currentAttemptTime + 5, () -> cast showFinishScreen()); + if (!this.isWatching) { + var notifies = AchievementsGui.check(); + var delay = 5.0; + var achDelay = 0.0; + for (i in 0...9) { + if (notifies & (1 << i) > 0) + achDelay += 3; + } + if (notifies > 0) + achDelay += 0.5; + this.schedule(this.timeState.currentAttemptTime + Math.max(delay, achDelay), () -> cast showFinishScreen()); + } // Stop the ongoing sounds if (timeTravelSound != null) { timeTravelSound.stop(); diff --git a/src/Settings.hx b/src/Settings.hx index 4aac6f1f..445008a7 100644 --- a/src/Settings.hx +++ b/src/Settings.hx @@ -193,6 +193,8 @@ class Settings { public static var levelStatistics:Map = []; + public static var achievementProgression:Int; + public static var highscoreName = ""; public static var uiScale = 1.0; @@ -263,7 +265,13 @@ class Settings { touch: touchSettings, gamepad: gamepadSettings, stats: playStatistics, - highscoreName: highscoreName + highscoreName: highscoreName, + marbleIndex: optionsSettings.marbleIndex, + marbleSkin: optionsSettings.marbleSkin, + marbleModel: optionsSettings.marbleModel, + marbleCategoryIndex: optionsSettings.marbleCategoryIndex, + marbleShader: optionsSettings.marbleShader, + achievementProgression: achievementProgression }; var scoreCount = 0; var eggCount = 0; @@ -408,6 +416,7 @@ class Settings { if (json.stats != null) { playStatistics = json.stats; } + achievementProgression = json.achievementProgression; if (json.levelStatistics != null) { var levelStatData:DynamicAccess = json.levelStatistics; for (key => value in levelStatData) { @@ -435,6 +444,11 @@ class Settings { if (optionsSettings.rewindEnabled == null) { optionsSettings.rewindEnabled = false; } + if (optionsSettings.rewindTimescale == null) { + optionsSettings.rewindTimescale = 1; + } + if (achievementProgression == null) + achievementProgression = 0; #end highscoreName = json.highscoreName; } else { diff --git a/src/gui/AchievementPopupDlg.hx b/src/gui/AchievementPopupDlg.hx new file mode 100644 index 00000000..0cc95488 --- /dev/null +++ b/src/gui/AchievementPopupDlg.hx @@ -0,0 +1,36 @@ +package gui; + +import gui.GuiControl.MouseState; +import h3d.Vector; +import src.ResourceLoader; +import src.MarbleGame; + +class AchievementPopupDlg extends GuiControl { + var lifetime:Float = 0; + var onFinish:() -> Void; + + public function new(id:Int) { + super(); + this.extent = new Vector(640, 480); + this.position = new Vector(); + this.horizSizing = Width; + this.vertSizing = Height; + + var popup = new GuiImage(ResourceLoader.getResource('data/ui/achievement/${id}.png', ResourceLoader.getImage, this.imageResources).toTile()); + popup.horizSizing = Center; + popup.vertSizing = Bottom; + popup.position = new Vector(70, 465); + popup.extent = new Vector(477, 90); + this.addChild(popup); + } + + override function update(dt:Float, mouseState:MouseState) { + super.update(dt, mouseState); + lifetime += dt; + if (lifetime > 3) { + MarbleGame.canvas.popDialog(this); + if (onFinish != null) + onFinish(); + } + } +} diff --git a/src/gui/AchievementsGui.hx b/src/gui/AchievementsGui.hx index 02d2b819..5b9677c0 100644 --- a/src/gui/AchievementsGui.hx +++ b/src/gui/AchievementsGui.hx @@ -9,184 +9,309 @@ import src.Mission; import src.MissionList; class AchievementsGui extends GuiImage { - public function new() { - var img = ResourceLoader.getImage("data/ui/achiev/window.png"); - super(img.resource.toTile()); - this.horizSizing = Center; - this.vertSizing = Center; - this.position = new Vector(73, -21); - this.extent = new Vector(493, 512); + var innerCtrl:GuiControl; + var btnList:GuiXboxList; - var achiev = new GuiImage(ResourceLoader.getResource("data/ui/achiev/achiev.png", ResourceLoader.getImage, this.imageResources).toTile()); - achiev.position = new Vector(152, 26); - achiev.extent = new Vector(176, 50); - this.addChild(achiev); + public function new(isPause:Bool = false) { + var res = ResourceLoader.getImage("data/ui/xbox/BG_fadeOutSoftEdge.png").resource.toTile(); + super(res); var arial14fontdata = ResourceLoader.getFileEntry("data/font/Arial Bold.fnt"); var arial14b = new BitmapFont(arial14fontdata.entry); @:privateAccess arial14b.loader = ResourceLoader.loader; - var arial14 = arial14b.toSdfFont(cast 12 * Settings.uiScale, MultiChannel); + var arial14 = arial14b.toSdfFont(cast 21 * Settings.uiScale, h2d.Font.SDFChannel.MultiChannel); - var domcasual32fontdata = ResourceLoader.getFileEntry("data/font/DomCasualD.fnt"); - var domcasual32b = new BitmapFont(domcasual32fontdata.entry); - @:privateAccess domcasual32b.loader = ResourceLoader.loader; - var domcasual32 = domcasual32b.toSdfFont(cast 26 * Settings.uiScale, MultiChannel); - var domcasual64 = domcasual32b.toSdfFont(cast 58 * Settings.uiScale, MultiChannel); - var domcasual24 = domcasual32b.toSdfFont(cast 20 * Settings.uiScale, MultiChannel); + this.horizSizing = Width; + this.vertSizing = Height; + this.position = new Vector(); + this.extent = new Vector(640, 480); - function mlFontLoader(text:String) { - switch (text) { - case "DomCasual24": - return domcasual24; - case "Arial14": - return arial14; - default: - return null; + var scene2d = MarbleGame.canvas.scene2d; + + var offsetX = (scene2d.width - 1280) / 2; + var offsetY = (scene2d.height - 720) / 2; + + var subX = 640 - (scene2d.width - offsetX) * 640 / scene2d.width; + var subY = 480 - (scene2d.height - offsetY) * 480 / scene2d.height; + + innerCtrl = new GuiControl(); + innerCtrl.position = new Vector(offsetX, offsetY); + innerCtrl.extent = new Vector(640 - subX, 480 - subY); + innerCtrl.horizSizing = Width; + innerCtrl.vertSizing = Height; + this.addChild(innerCtrl); + + var achievementsWnd = new GuiImage(ResourceLoader.getResource("data/ui/xbox/achievementWindow.png", ResourceLoader.getImage, this.imageResources) + .toTile()); + achievementsWnd.horizSizing = Center; + achievementsWnd.vertSizing = Center; + achievementsWnd.position = new Vector(25, 58); + achievementsWnd.extent = new Vector(600, 480); + innerCtrl.addChild(achievementsWnd); + + function imgLoader(path:String) { + switch (path) { + case "locked": + return ResourceLoader.getResource("data/ui/xbox/DemoOutOfTimeIcon.png", ResourceLoader.getImage, this.imageResources).toTile(); + case "unlocked": + return ResourceLoader.getResource("data/ui/xbox/Ready.png", ResourceLoader.getImage, this.imageResources).toTile(); } + return null; } - function loadButtonImages(path:String) { - var normal = ResourceLoader.getResource('${path}_n.png', ResourceLoader.getImage, this.imageResources).toTile(); - var hover = ResourceLoader.getResource('${path}_h.png', ResourceLoader.getImage, this.imageResources).toTile(); - var pressed = ResourceLoader.getResource('${path}_d.png', ResourceLoader.getImage, this.imageResources).toTile(); - var disabled = ResourceLoader.getResource('${path}_i.png', ResourceLoader.getImage, this.imageResources).toTile(); - return [normal, hover, pressed, disabled]; + var curSelection = -1; + + check(); // Check achievements + + var achNameDisplays = [ + "Timely Marble", + "Apprentice's Badge", + "Journeyman's Badge", + "Adept's Badge", + "Marble-fu Initiate", + "Marble-fu Master", + "Marble-fu Transcendent", + "Egg Seeker", + "Egg Basket" + ]; + if (Settings.achievementProgression & 1 == 1) + achNameDisplays[0] = "Timely Marble"; + if (Settings.achievementProgression & 2 == 2) + achNameDisplays[1] = "Apprentice's Badge"; + if (Settings.achievementProgression & 4 == 4) + achNameDisplays[2] = "Journeyman's Badge"; + if (Settings.achievementProgression & 8 == 8) + achNameDisplays[3] = "Adept's Badge"; + if (Settings.achievementProgression & 16 == 16) + achNameDisplays[4] = "Marble-fu Initiate"; + if (Settings.achievementProgression & 32 == 32) + achNameDisplays[5] = "Marble-fu Master"; + if (Settings.achievementProgression & 64 == 64) + achNameDisplays[6] = "Marble-fu Transcendent"; + if (Settings.achievementProgression & 128 == 128) + achNameDisplays[7] = "Egg Seeker"; + if (Settings.achievementProgression & 256 == 256) + achNameDisplays[8] = "Egg Basket"; + + var achievementsList = new GuiMLTextListCtrl(arial14, achNameDisplays, imgLoader); + + achievementsList.selectedColor = 0xF29515; + achievementsList.selectedFillColor = 0xEBEBEB; + achievementsList.position = new Vector(25, 22); + achievementsList.extent = new Vector(550, 480); + achievementsList.scrollable = true; + achievementsList.onSelectedFunc = (sel) -> { + curSelection = sel; } + achievementsWnd.addChild(achievementsList); - var achievText = new GuiMLText(domcasual32, mlFontLoader); - achievText.position = new Vector(156, 60); - achievText.extent = new Vector(262, 410); - achievText.text.textColor = 0; - achievText.text.text = 'Amateur Marbler
Beat all Beginner levels.
-Experienced Marbler
Beat all Intermediate levels.
-Pro Marbler
Beat all Advanced levels.
-Skilled Marbler
Beat all Expert Levels
-Marble Master
Beat all of the Platinum Times.
-Legendary Marbler
Beat all of the Ultimate Times.
-Egg Seeker
Find any Easter Egg.
-Easter Bunny
Find all of the Easter Eggs.'; + var bottomBar = new GuiControl(); + bottomBar.position = new Vector(0, 590); + bottomBar.extent = new Vector(640, 200); + bottomBar.horizSizing = Width; + bottomBar.vertSizing = Bottom; + innerCtrl.addChild(bottomBar); - this.addChild(achievText); + var backButton = new GuiXboxButton("Back", 160); + backButton.position = new Vector(400, 0); + backButton.vertSizing = Bottom; + backButton.horizSizing = Right; + backButton.gamepadAccelerator = ["B"]; + if (isPause) + backButton.pressedAction = (e) -> { + MarbleGame.canvas.popDialog(this); + MarbleGame.instance.showPauseUI(); + } + else + backButton.pressedAction = (e) -> MarbleGame.canvas.setContent(new MainMenuGui()); + bottomBar.addChild(backButton); - var bmp1 = new GuiImage(ResourceLoader.getResource("data/ui/achiev/nonachiev.png", ResourceLoader.getImage, this.imageResources).toTile()); - bmp1.position = new Vector(39, 62); - bmp1.extent = new Vector(113, 44); - this.addChild(bmp1); - - var bmp2 = new GuiImage(ResourceLoader.getResource("data/ui/achiev/nonachiev.png", ResourceLoader.getImage, this.imageResources).toTile()); - bmp2.position = new Vector(35, 115); - bmp2.extent = new Vector(117, 44); - this.addChild(bmp2); - - var bmp3 = new GuiImage(ResourceLoader.getResource("data/ui/achiev/nonachiev.png", ResourceLoader.getImage, this.imageResources).toTile()); - bmp3.position = new Vector(30, 168); - bmp3.extent = new Vector(122, 44); - this.addChild(bmp3); - - var bmp4 = new GuiImage(ResourceLoader.getResource("data/ui/achiev/nonachiev.png", ResourceLoader.getImage, this.imageResources).toTile()); - bmp4.position = new Vector(30, 221); - bmp4.extent = new Vector(122, 44); - this.addChild(bmp4); - - var bmp5 = new GuiImage(ResourceLoader.getResource("data/ui/achiev/nonachiev.png", ResourceLoader.getImage, this.imageResources).toTile()); - bmp5.position = new Vector(36, 274); - bmp5.extent = new Vector(116, 44); - this.addChild(bmp5); - - var bmp6 = new GuiImage(ResourceLoader.getResource("data/ui/achiev/nonachiev.png", ResourceLoader.getImage, this.imageResources).toTile()); - bmp6.position = new Vector(37, 327); - bmp6.extent = new Vector(115, 44); - this.addChild(bmp6); - - var bmp7 = new GuiImage(ResourceLoader.getResource("data/ui/achiev/nonachiev.png", ResourceLoader.getImage, this.imageResources).toTile()); - bmp7.position = new Vector(38, 380); - bmp7.extent = new Vector(114, 44); - this.addChild(bmp7); - - var bmp8 = new GuiImage(ResourceLoader.getResource("data/ui/achiev/nonachiev.png", ResourceLoader.getImage, this.imageResources).toTile()); - bmp8.position = new Vector(39, 433); - bmp8.extent = new Vector(113, 44); - this.addChild(bmp8); - - var closeButton = new GuiButton(loadButtonImages("data/ui/achiev/close")); - closeButton.position = new Vector(355, 426); - closeButton.extent = new Vector(95, 45); - closeButton.pressedAction = (e) -> { - MarbleGame.canvas.popDialog(this); - } - this.addChild(closeButton); + var nextButton = new GuiXboxButton("Details", 160); + nextButton.position = new Vector(960, 0); + nextButton.vertSizing = Bottom; + nextButton.horizSizing = Right; + nextButton.pressedAction = (e) -> { + var desc = "Select an achievement from the list."; + var selection = curSelection; + switch (selection) { + case 0: desc = "Finish any level under par time."; + case 1: desc = "Complete all Beginner levels."; + case 2: desc = "Complete all Intermediate levels."; + case 3: desc = "Complete all Advanced levels."; + case 4: desc = "Finish all Beginner levels under par time."; + case 5: desc = "Finish all Intermediate levels under par time."; + case 6: desc = "Finish all Advanced levels under par time."; + case 7: desc = "Find any hidden easter egg."; + case 8: desc = "Find all twenty easter eggs."; + case 9: desc = "Get first place in a multiplayer match."; + case 10: desc = "Get 75 points in a multiplayer match."; + case 11: desc = "Collect 2,000 total points in multiplayer."; + case 12: desc = "Complete all Bonus levels."; + case 13: desc = "Finish all Bonus levels under par time."; + } + MarbleGame.canvas.pushDialog(new MessageBoxOkDlg(desc)); + }; + bottomBar.addChild(nextButton); + } + public static function check() { // Now do the actual achievement check logic var completions:Map> = []; - var totalPlatinums = 0; - var totalUltimates = 0; var totalEggs = 0; + var totalPars = 0; - for (difficulty => missions in MissionList.missionList["platinum"]) { + var notifies = 0; + + for (difficulty => missions in MissionList.missionList["ultra"]) { completions.set(difficulty, missions.map(mis -> { var misScores = Settings.getScores(mis.path); if (misScores.length == 0) { return { mission: mis, beatPar: false, - beatPlatinum: false, - beatUltimate: false, beaten: false } } var bestTime = misScores[0]; var beatPar = bestTime.time < mis.qualifyTime; - var beatPlatinum = bestTime.time < mis.goldTime; - var beatUltimate = bestTime.time < mis.ultimateTime; - var beaten = beatPar || beatPlatinum || beatUltimate; + if (beatPar) + totalPars++; + var beaten = true; - if (beatPlatinum) - totalPlatinums++; - if (beatUltimate) - totalUltimates++; if (Settings.easterEggs.exists(mis.path)) totalEggs++; return { mission: mis, beatPar: beatPar, - beatPlatinum: beatPlatinum, - beatUltimate: beatUltimate, beaten: beaten }; })); } - var beginnerFinishAchiev = completions["beginner"].filter(x -> !x.beatPar).length == 0; - var intermediateFinishAchiev = completions["intermediate"].filter(x -> !x.beatPar).length == 0; - var advancedFinishAchiev = completions["advanced"].filter(x -> !x.beatPar).length == 0; - var expertFinishAchiev = completions["expert"].filter(x -> !x.beatPar).length == 0; - var beatPlatinumAchiev = totalPlatinums == 120; - var beatUltimateAchiev = totalUltimates == 120; - var easterEggAny = totalEggs != 0; - var easterEggAll = totalEggs == 96; + // Timely marble + if (Settings.achievementProgression & 1 != 1) { + if (totalPars > 0) { + Settings.achievementProgression |= 1; + notifies |= 1; + } + } - if (beginnerFinishAchiev) - bmp1.bmp.tile = ResourceLoader.getResource("data/ui/achiev/n1.png", ResourceLoader.getImage, this.imageResources).toTile(); - if (intermediateFinishAchiev) - bmp2.bmp.tile = ResourceLoader.getResource("data/ui/achiev/n2.png", ResourceLoader.getImage, this.imageResources).toTile(); - if (advancedFinishAchiev) - bmp3.bmp.tile = ResourceLoader.getResource("data/ui/achiev/n3.png", ResourceLoader.getImage, this.imageResources).toTile(); - if (expertFinishAchiev) - bmp4.bmp.tile = ResourceLoader.getResource("data/ui/achiev/n4.png", ResourceLoader.getImage, this.imageResources).toTile(); - if (beatPlatinumAchiev) - bmp5.bmp.tile = ResourceLoader.getResource("data/ui/achiev/n6.png", ResourceLoader.getImage, this.imageResources).toTile(); - if (beatUltimateAchiev) - bmp6.bmp.tile = ResourceLoader.getResource("data/ui/achiev/n5.png", ResourceLoader.getImage, this.imageResources).toTile(); - if (easterEggAny) - bmp7.bmp.tile = ResourceLoader.getResource("data/ui/achiev/1.png", ResourceLoader.getImage, this.imageResources).toTile(); - if (easterEggAll) - bmp8.bmp.tile = ResourceLoader.getResource("data/ui/achiev/2.png", ResourceLoader.getImage, this.imageResources).toTile(); + // All Beginners + if (Settings.achievementProgression & 2 != 2) { + if (completions.get("beginner") + .filter(x -> x.beaten) + .length == completions.get("beginner") + .length) { + Settings.achievementProgression |= 2; + notifies |= 2; + } + } + + // All Intermediates + if (Settings.achievementProgression & 4 != 4) { + if (completions.get("intermediate") + .filter(x -> x.beaten) + .length == completions.get("intermediate") + .length) { + Settings.achievementProgression |= 4; + notifies |= 4; + } + } + + // All Advanced + if (Settings.achievementProgression & 8 != 8) { + if (completions.get("advanced") + .filter(x -> x.beaten) + .length == completions.get("advanced") + .length) { + Settings.achievementProgression |= 8; + notifies |= 8; + } + } + + // All Beginner pars + if (Settings.achievementProgression & 16 != 16) { + if (completions.get("beginner") + .filter(x -> x.beatPar) + .length == completions.get("beginner") + .length) { + Settings.achievementProgression |= 16; + notifies |= 16; + } + } + + // All Intermediate pars + if (Settings.achievementProgression & 32 != 32) { + if (completions.get("intermediate") + .filter(x -> x.beatPar) + .length == completions.get("intermediate") + .length) { + Settings.achievementProgression |= 32; + notifies |= 32; + } + } + + // All Advanced pars + if (Settings.achievementProgression & 64 != 64) { + if (completions.get("advanced") + .filter(x -> x.beatPar) + .length == completions.get("advanced") + .length) { + Settings.achievementProgression |= 64; + notifies |= 64; + } + } + + // Egg Seeker + if (Settings.achievementProgression & 128 != 128) { + if (totalEggs >= 1) { + Settings.achievementProgression |= 128; + notifies |= 128; + } + } + + // Egg Basket + if (Settings.achievementProgression & 256 != 256) { + if (totalEggs >= 20) { + Settings.achievementProgression |= 256; + notifies |= 256; + } + } + + var showdlgs = []; + for (i in 0...9) { + if (notifies & (1 << i) > 0) { + var popup = new AchievementPopupDlg(i + 1); + showdlgs.push(popup); + } + } + if (showdlgs.length > 1) { + for (i in 0...showdlgs.length - 1) { + showdlgs[i].onFinish = () -> { + MarbleGame.canvas.pushDialog(showdlgs[i + 1]); + } + } + } + if (showdlgs.length != 0) + MarbleGame.canvas.pushDialog(showdlgs[0]); // Start off! + + return notifies; + } + + override function onResize(width:Int, height:Int) { + var offsetX = (width - 1280) / 2; + var offsetY = (height - 720) / 2; + + var subX = 640 - (width - offsetX) * 640 / width; + var subY = 480 - (height - offsetY) * 480 / height; + innerCtrl.position = new Vector(offsetX, offsetY); + innerCtrl.extent = new Vector(640 - subX, 480 - subY); + + super.onResize(width, height); } } diff --git a/src/gui/Canvas.hx b/src/gui/Canvas.hx index 8cb5401d..f598955c 100644 --- a/src/gui/Canvas.hx +++ b/src/gui/Canvas.hx @@ -36,7 +36,8 @@ class Canvas extends GuiControl { public function pushDialog(content:GuiControl) { this.addChild(content); - this.render(scene2d); + content.render(scene2d, @:privateAccess this._flow); + // this.render(scene2d); } public function popDialog(content:GuiControl, dispose:Bool = true) { diff --git a/src/gui/ExitGameDlg.hx b/src/gui/ExitGameDlg.hx index d6f6b795..897e04e8 100644 --- a/src/gui/ExitGameDlg.hx +++ b/src/gui/ExitGameDlg.hx @@ -81,7 +81,10 @@ class ExitGameDlg extends GuiImage { MarbleGame.canvas.pushDialog(new OptionsListGui(true)); }, 20); btnList.addButton(2, "Leaderboards", (evt) -> {}); - btnList.addButton(2, "Achievements", (evt) -> {}); + btnList.addButton(2, "Achievements", (evt) -> { + MarbleGame.canvas.popDialog(this); + MarbleGame.canvas.pushDialog(new AchievementsGui(true)); + }); btnList.addButton(4, "Main Menu", (evt) -> { yesFunc(btnList); MarbleGame.canvas.setContent(new MainMenuGui()); diff --git a/src/gui/GuiMLTextListCtrl.hx b/src/gui/GuiMLTextListCtrl.hx new file mode 100644 index 00000000..d4a1d6f0 --- /dev/null +++ b/src/gui/GuiMLTextListCtrl.hx @@ -0,0 +1,295 @@ +package gui; + +import h2d.HtmlText; +import h2d.Flow; +import h3d.Engine; +import h2d.Tile; +import h2d.Bitmap; +import h3d.mat.Texture; +import shaders.GuiClipFilter; +import h2d.Graphics; +import gui.GuiControl.MouseState; +import h2d.Scene; +import h2d.Text; +import h2d.Font; +import src.MarbleGame; +import src.Settings; + +class GuiMLTextListCtrl extends GuiControl { + public var texts:Array; + public var onSelectedFunc:Int->Void; + + var font:Font; + var textObjs:Array; + var g:Graphics; + var _prevSelected:Int = -1; + + public var selectedColor:Int = 0x206464; + public var selectedFillColor:Int = 0xC8C8C8; + + public var textYOffset:Int = 0; + + public var scroll:Float = 0; + + public var scrollable:Bool = false; + + var flow:Flow; + var _imageLoader:String->Tile; + + public function new(font:Font, texts:Array, imageLoader:String->Tile) { + super(); + this.font = font; + this.texts = texts; + this._manualScroll = true; + this.textObjs = []; + this._imageLoader = imageLoader; + for (text in texts) { + var tobj = new HtmlText(font); + tobj.lineHeightMode = TextOnly; + tobj.loadImage = imageLoader; + tobj.text = text; + tobj.textColor = 0; + textObjs.push(tobj); + } + this.g = new Graphics(); + } + + public function setTexts(texts:Array) { + var renderRect = this.getRenderRectangle(); + for (textObj in this.textObjs) { + textObj.remove(); + } + this.textObjs = []; + for (text in texts) { + var tobj = new HtmlText(font); + tobj.loadImage = this._imageLoader; + tobj.lineHeightMode = TextOnly; + tobj.text = text; + tobj.textColor = 0; + textObjs.push(tobj); + + if (this.scrollable) { + if (this.flow.contains(tobj)) + this.flow.removeChild(tobj); + + this.flow.addChild(tobj); + + this.flow.getProperties(tobj).isAbsolute = true; + } + } + this.texts = texts; + this._prevSelected = -1; + if (this.onSelectedFunc != null) + this.onSelectedFunc(-1); + + redrawSelectionRect(renderRect); + + for (i in 0...textObjs.length) { + var text = textObjs[i]; + text.setPosition(Math.floor((!scrollable ? renderRect.position.x : 0) + 5), + Math.floor((!scrollable ? renderRect.position.y : 0) + + (i * (text.font.size + 4 * Settings.uiScale) + (5 + textYOffset) * Settings.uiScale - this.scroll))); + + if (_prevSelected == i) { + text.textColor = selectedColor; + } + } + } + + public override function render(scene2d:Scene, ?parent:h2d.Flow) { + var renderRect = this.getRenderRectangle(); + var htr = this.getHitTestRect(false); + + if (parent != null) { + if (parent.contains(g)) + parent.removeChild(g); + parent.addChild(g); + + var off = this.getOffsetFromParent(); + parent.getProperties(g).isAbsolute = true; + + g.setPosition(off.x, off.y - this.scroll); + } + + if (scrollable) { + this.flow = new Flow(); + + this.flow.maxWidth = cast htr.extent.x; + this.flow.maxHeight = cast htr.extent.y; + this.flow.multiline = true; + this.flow.layout = Stack; + this.flow.overflow = FlowOverflow.Hidden; + + if (parent != null) { + if (parent.contains(this.flow)) { + parent.removeChild(this.flow); + } + parent.addChild(this.flow); + var off = this.getOffsetFromParent(); + var props = parent.getProperties(this.flow); + props.isAbsolute = true; + + this.flow.setPosition(off.x, off.y); + } + } + + for (i in 0...textObjs.length) { + var text = textObjs[i]; + if (!scrollable) { + if (scene2d.contains(text)) + scene2d.removeChild(text); + scene2d.addChild(text); + } else { + if (this.flow.contains(text)) + this.flow.removeChild(text); + this.flow.addChild(text); + + this.flow.getProperties(text).isAbsolute = true; + } + + text.setPosition(Math.floor((!scrollable ? renderRect.position.x : 0) + 5), + Math.floor((!scrollable ? renderRect.position.y : 0) + + (i * (text.font.size + 4 * Settings.uiScale) + (5 + textYOffset) * Settings.uiScale - this.scroll))); + + if (_prevSelected == i) { + text.textColor = selectedColor; + } + } + + redrawSelectionRect(htr); + super.render(scene2d, parent); + } + + public function calculateFullHeight() { + return (this.texts.length * (font.size + 4 * Settings.uiScale)); + } + + public override function dispose() { + super.dispose(); + for (text in textObjs) { + text.remove(); + } + this.g.remove(); + if (this.scrollable) { + this.flow.remove(); + } + } + + public override function onRemove() { + super.onRemove(); + for (text in textObjs) { + if (MarbleGame.canvas.scene2d.contains(text)) { + MarbleGame.canvas.scene2d.removeChild(text); // Refresh "layer" + } + text.remove(); + } + if (MarbleGame.canvas.scene2d.contains(g)) + MarbleGame.canvas.scene2d.removeChild(g); + g.remove(); + } + + public override function onMouseMove(mouseState:MouseState) { + var mousePos = mouseState.position; + var renderRect = this.getRenderRectangle(); + var yStart = renderRect.position.y; + var dy = mousePos.y - yStart; + var hoverIndex = Math.floor(dy / (font.size + 4 * Settings.uiScale)); + if (hoverIndex >= this.texts.length) { + hoverIndex = -1; + } + + // Update the texts + for (i in 0...textObjs.length) { + var selected = i == hoverIndex || i == this._prevSelected; + var text = textObjs[i]; + text.textColor = selected ? selectedColor : 0; + // fill color = 0xC8C8C8 + } + // obviously in renderRect + } + + public override function onMouseLeave(mouseState:MouseState) { + for (i in 0...textObjs.length) { + if (i == this._prevSelected) + continue; + var text = textObjs[i]; + text.textColor = 0; + // fill color = 0xC8C8C8 + } + } + + public override function onMousePress(mouseState:MouseState) { + super.onMousePress(mouseState); + + var mousePos = mouseState.position; + var renderRect = this.getRenderRectangle(); + var yStart = renderRect.position.y; + var dy = mousePos.y - yStart; + var selectedIndex = Math.floor((dy + this.scroll) / (font.size + 4 * Settings.uiScale)); + if (selectedIndex >= this.texts.length) { + selectedIndex = -1; + } + if (_prevSelected != selectedIndex) { + _prevSelected = selectedIndex; + + redrawSelectionRect(renderRect); + } + + if (onSelectedFunc != null) { + onSelectedFunc(selectedIndex); + } + } + + function redrawSelectionRect(renderRect:Rect) { + if (_prevSelected != -1) { + g.clear(); + g.beginFill(selectedFillColor); + + var off = this.getOffsetFromParent(); + // Check if we are between the top and bottom, render normally in that case + var topY = 2 * Settings.uiScale + (_prevSelected * (font.size + 4 * Settings.uiScale)) + g.y; + var bottomY = 2 * Settings.uiScale + (_prevSelected * (font.size + 4 * Settings.uiScale)) + g.y + font.size + 4 * Settings.uiScale; + var topRectY = off.y; + var bottomRectY = off.y + renderRect.extent.y; + + if (topY >= topRectY && bottomY <= bottomRectY) + g.drawRect(0, 5 * Settings.uiScale + + (_prevSelected * (font.size + 4 * Settings.uiScale)) + - 3 * Settings.uiScale, renderRect.extent.x, + font.size + + 4 * Settings.uiScale); + // We need to do math the draw the partially visible top selected + if (topY <= topRectY && bottomY >= topRectY) { + g.drawRect(0, this.scroll, renderRect.extent.x, topY + font.size + 4 * Settings.uiScale - off.y); + } + // Same for the bottom + if (topY <= bottomRectY && bottomY >= bottomRectY) { + g.drawRect(0, this.scroll + + renderRect.extent.y + - font.size + - 4 * Settings.uiScale + + (topY + font.size + 4 * Settings.uiScale - bottomRectY), + renderRect.extent.x, off.y + + renderRect.extent.y + - (topY)); + } + g.endFill(); + } else { + g.clear(); + } + } + + public override function onScroll(scrollX:Float, scrollY:Float) { + super.onScroll(scrollX, scrollY); + var renderRect = this.getRenderRectangle(); + + this.scroll = scrollY; + var hittestrect = this.getHitTestRect(false); + for (i in 0...textObjs.length) { + var text = textObjs[i]; + text.y = Math.floor((i * (text.font.size + 4 * Settings.uiScale) + (5 + textYOffset) * Settings.uiScale - scrollY)); + g.y = -scrollY; + } + redrawSelectionRect(hittestrect); + } +} diff --git a/src/gui/MainMenuGui.hx b/src/gui/MainMenuGui.hx index 3a3fd710..41699595 100644 --- a/src/gui/MainMenuGui.hx +++ b/src/gui/MainMenuGui.hx @@ -64,7 +64,9 @@ class MainMenuGui extends GuiImage { cast(this.parent, Canvas).setContent(new DifficultySelectGui()); }); btnList.addButton(2, "Leaderboards", (e) -> {}, 20); - btnList.addButton(2, "Achievements", (e) -> {}); + btnList.addButton(2, "Achievements", (e) -> { + cast(this.parent, Canvas).setContent(new AchievementsGui()); + }); btnList.addButton(3, "Replays", (sender) -> { #if hl MarbleGame.canvas.setContent(new ReplayCenterGui()); diff --git a/src/gui/MessageBoxOkDlg.hx b/src/gui/MessageBoxOkDlg.hx index 69217aaa..acc65da0 100644 --- a/src/gui/MessageBoxOkDlg.hx +++ b/src/gui/MessageBoxOkDlg.hx @@ -6,45 +6,37 @@ import h3d.Vector; import src.ResourceLoader; import src.Settings; -class MessageBoxOkDlg extends GuiControl { +class MessageBoxOkDlg extends GuiImage { public function new(text:String) { - super(); + var res = ResourceLoader.getImage("data/ui/xbox/roundedBG.png").resource.toTile(); + super(res); this.horizSizing = Width; this.vertSizing = Height; this.position = new Vector(); this.extent = new Vector(640, 480); - var domcasual24fontdata = ResourceLoader.getFileEntry("data/font/DomCasualD.fnt"); - var domcasual24b = new BitmapFont(domcasual24fontdata.entry); - @:privateAccess domcasual24b.loader = ResourceLoader.loader; - var domcasual24 = domcasual24b.toSdfFont(cast 20 * Settings.uiScale, MultiChannel); + var arial14fontdata = ResourceLoader.getFileEntry("data/font/Arial Bold.fnt"); + var arial14b = new BitmapFont(arial14fontdata.entry); + @:privateAccess arial14b.loader = ResourceLoader.loader; + var arial14 = arial14b.toSdfFont(cast 21 * Settings.uiScale, h2d.Font.SDFChannel.MultiChannel); - function loadButtonImages(path:String) { - var normal = ResourceLoader.getResource('${path}_n.png', ResourceLoader.getImage, this.imageResources).toTile(); - var hover = ResourceLoader.getResource('${path}_h.png', ResourceLoader.getImage, this.imageResources).toTile(); - var pressed = ResourceLoader.getResource('${path}_d.png', ResourceLoader.getImage, this.imageResources).toTile(); - return [normal, hover, pressed]; - } - - var yesNoFrame = new GuiImage(ResourceLoader.getResource("data/ui/common/dialog.png", ResourceLoader.getImage, this.imageResources).toTile()); + var yesNoFrame = new GuiImage(ResourceLoader.getResource("data/ui/xbox/popupGUI.png", ResourceLoader.getImage, this.imageResources).toTile()); yesNoFrame.horizSizing = Center; yesNoFrame.vertSizing = Center; - yesNoFrame.position = new Vector(187, 156); - yesNoFrame.extent = new Vector(300, 161); + yesNoFrame.position = new Vector(70, 30); + yesNoFrame.extent = new Vector(512, 400); this.addChild(yesNoFrame); - var yesNoText = new GuiMLText(domcasual24, null); - yesNoText.position = new Vector(33, 46); - yesNoText.horizSizing = Center; - yesNoText.extent = new Vector(198, 23); + var yesNoText = new GuiMLText(arial14, null); + yesNoText.position = new Vector(103, 85); + yesNoText.extent = new Vector(313, 186); yesNoText.text.text = text; - yesNoText.text.textColor = 0; - yesNoText.text.maxWidth = 198; + yesNoText.text.textColor = 0xEBEBEB; yesNoFrame.addChild(yesNoText); - var okButton = new GuiButton(loadButtonImages("data/ui/common/ok")); - okButton.position = new Vector(117, 85); - okButton.extent = new Vector(88, 41); + var okButton = new GuiXboxButton("Ok", 120); + okButton.position = new Vector(211, 248); + okButton.extent = new Vector(120, 94); okButton.vertSizing = Top; okButton.accelerator = hxd.Key.ENTER; okButton.gamepadAccelerator = ["A"]; diff --git a/src/shaders/UVRotAnim.hx b/src/shaders/UVRotAnim.hx index f7ef0d00..49149957 100644 --- a/src/shaders/UVRotAnim.hx +++ b/src/shaders/UVRotAnim.hx @@ -15,7 +15,7 @@ class UVRotAnim extends hxsl.Shader { var vx = v.x * c - v.y * s; var vy = v.x * s + v.y * c; - calculatedUV += vec2(offset.x + vx, offset.y + vy); + calculatedUV = vec2(offset.x + vx, offset.y + vy); } }; diff --git a/src/shapes/EasterEgg.hx b/src/shapes/EasterEgg.hx index fdcf32ad..86bae6fe 100644 --- a/src/shapes/EasterEgg.hx +++ b/src/shapes/EasterEgg.hx @@ -1,5 +1,6 @@ package shapes; +import gui.AchievementsGui; import src.Settings; import mis.MissionElement.MissionElementItem; import src.ResourceLoader; @@ -24,6 +25,7 @@ class EasterEgg extends PowerUp { Settings.easterEggs.set(this.level.mission.path, this.level.timeState.currentAttemptTime); this.pickupSound = ResourceLoader.getResource("data/sound/easter_egg.wav", ResourceLoader.getAudio, this.soundResources); this.customPickupMessage = "You found a new egg! Yeah!"; + AchievementsGui.check(); } else { this.pickupSound = ResourceLoader.getResource("data/sound/pu_easter.wav", ResourceLoader.getAudio, this.soundResources); this.customPickupMessage = "You already found this egg!"; diff --git a/src/shapes/EndPad.hx b/src/shapes/EndPad.hx index a1aed2c1..3a413f42 100644 --- a/src/shapes/EndPad.hx +++ b/src/shapes/EndPad.hx @@ -106,7 +106,7 @@ class EndPad extends DtsObject { material.mainPass.setPassName("glowPre"); material.mainPass.enableLights = false; - var rotshader = new shaders.UVRotAnim(-0.5, -0.5, 1); + var rotshader = new shaders.UVRotAnim(0.5, 0.5, 1); material.mainPass.addShader(rotshader); var thisprops:Dynamic = material.getDefaultProps(); diff --git a/src/shapes/StartPad.hx b/src/shapes/StartPad.hx index 62c2e0f8..a824d1c6 100644 --- a/src/shapes/StartPad.hx +++ b/src/shapes/StartPad.hx @@ -96,7 +96,7 @@ class StartPad extends DtsObject { material.props = thisprops; material.shadows = false; material.receiveShadows = true; - var rotshader = new shaders.UVRotAnim(-0.5, -0.5, 1); + var rotshader = new shaders.UVRotAnim(0.5, 0.5, 1); material.mainPass.addShader(rotshader); } @@ -110,7 +110,7 @@ class StartPad extends DtsObject { material.mainPass.setPassName("glowPre"); material.mainPass.enableLights = false; - var rotshader = new shaders.UVRotAnim(-0.5, -0.5, 1); + var rotshader = new shaders.UVRotAnim(0.5, 0.5, 1); material.mainPass.addShader(rotshader); var thisprops:Dynamic = material.getDefaultProps();