From 16afcaf3e643e9af2fee3bce222f4b56fa43c414 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Fri, 4 Jul 2014 16:41:52 -0400 Subject: [PATCH] Added initial support for loading beatmap skins. - Added a GameImage enum for more organized loading of image resources. - Game image loading now takes place directly before each beatmap is loaded. - Added option 'IGNORE_BEATMAP_SKINS' to disable this feature. Other changes: - Slight correction in readme file: apparently the JAR will not run in the osu! program folder. Signed-off-by: Jeffrey Han --- README.md | 7 +- src/itdelatrisu/opsu/GameImage.java | 178 +++++++++++ src/itdelatrisu/opsu/GameScore.java | 291 ++++++++++-------- src/itdelatrisu/opsu/Utils.java | 2 - src/itdelatrisu/opsu/objects/Circle.java | 42 +-- src/itdelatrisu/opsu/objects/Slider.java | 78 +++-- src/itdelatrisu/opsu/objects/Spinner.java | 31 +- src/itdelatrisu/opsu/states/Game.java | 155 +++++----- .../opsu/states/GamePauseMenu.java | 69 ++--- src/itdelatrisu/opsu/states/Options.java | 29 +- 10 files changed, 536 insertions(+), 346 deletions(-) create mode 100644 src/itdelatrisu/opsu/GameImage.java diff --git a/README.md b/README.md index 02e884d4..b6029f61 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,9 @@ opsu! also requires beatmaps to run, which are available for download on the [osu!Mirror](https://osu.yas-online.net/) or [Bloodcat](http://bloodcat.com/osu/). If osu! is already installed, this application will attempt to load songs -directly from the osu! program folder. Otherwise, run this application from -one directory above the root song directory, or place songs in the generated -`songs` folder. This path can be changed at any time by editing the -`BeatmapDirectory` value in the generated configuration file. +directly from the osu! program folder. Otherwise, place songs in the generated +`Songs` folder or set the `BeatmapDirectory` value in the generated +configuration file to the path of the root song directory. ### First Run The `Music Offset` value will likely need to be adjusted when playing for the diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java new file mode 100644 index 00000000..111cf20b --- /dev/null +++ b/src/itdelatrisu/opsu/GameImage.java @@ -0,0 +1,178 @@ +/* + * opsu! - an open-source osu! client + * Copyright (C) 2014 Jeffrey Han + * + * opsu! is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu! is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!. If not, see . + */ +package itdelatrisu.opsu; + +import itdelatrisu.opsu.states.Options; + +import java.io.File; + +import org.newdawn.slick.Image; +import org.newdawn.slick.SlickException; +import org.newdawn.slick.util.Log; + +/** + * Game images. + */ +public enum GameImage { + // Game + SECTION_PASS ("section-pass.png"), + SECTION_FAIL ("section-fail.png"), + WARNINGARROW ("play-warningarrow.png"), + SKIP ("play-skip.png"), + COUNTDOWN_READY ("ready.png"), + COUNTDOWN_3 ("count3.png"), + COUNTDOWN_2 ("count2.png"), + COUNTDOWN_1 ("count1.png"), + COUNTDOWN_GO ("go.png"), + HITCIRCLE_SELECT ("hitcircleselect.png"), + UNRANKED ("play-unranked.png"), + + // Game Pause/Fail + PAUSE_CONTINUE ("pause-continue.png"), + PAUSE_RETRY ("pause-retry.png"), + PAUSE_BACK ("pause-back.png"), + PAUSE_OVERLAY ("pause-overlay.png"), + FAIL_BACKGROUND ("fail-background.png"), + + // Circle + HITCIRCLE ("hitcircle.png"), + HITCIRCLE_OVERLAY ("hitcircleoverlay.png"), + APPROACHCIRCLE ("approachcircle.png"), + + // Slider + SLIDER_FOLLOWCIRCLE ("sliderfollowcircle.png"), + REVERSEARROW ("reversearrow.png"), + SLIDER_TICK ("sliderscorepoint.png"), + + // Spinner + SPINNER_CIRCLE ("spinner-circle.png"), + SPINNER_APPROACHCIRCLE ("spinner-approachcircle.png"), + SPINNER_METRE ("spinner-metre.png"), + SPINNER_SPIN ("spinner-spin.png"), + SPINNER_CLEAR ("spinner-clear.png"), + SPINNER_OSU ("spinner-osu.png"), + + // Game Score + SCOREBAR_BG ("scorebar-bg.png"), + SCOREBAR_COLOUR ("scorebar-colour.png"), + SCOREBAR_KI ("scorebar-ki.png"), + SCOREBAR_KI_DANGER ("scorebar-kidanger.png"), + SCOREBAR_KI_DANGER2 ("scorebar-kidanger2.png"), + HIT_MISS ("hit0.png"), + HIT_50 ("hit50.png"), + HIT_100 ("hit100.png"), + HIT_300 ("hit300.png"), + HIT_100K ("hit100k.png"), + HIT_300K ("hit300k.png"), + HIT_300G ("hit300g.png"), + HIT_SLIDER10 ("sliderpoint10.png"), + HIT_SLIDER30 ("sliderpoint30.png"), + RANKING_SS ("ranking-X.png"), + RANKING_SS_SMALL ("ranking-X-small.png"), + RANKING_SSH ("ranking-XH.png"), + RANKING_SSH_SMALL ("ranking-XH-small.png"), + RANKING_S ("ranking-S.png"), + RANKING_S_SMALL ("ranking-S-small.png"), + RANKING_SH ("ranking-SH.png"), + RANKING_SH_SMALL ("ranking-SH-small.png"), + RANKING_A ("ranking-A.png"), + RANKING_A_SMALL ("ranking-A-small.png"), + RANKING_B ("ranking-B.png"), + RANKING_B_SMALL ("ranking-B-small.png"), + RANKING_C ("ranking-C.png"), + RANKING_C_SMALL ("ranking-C-small.png"), + RANKING_D ("ranking-D.png"), + RANKING_D_SMALL ("ranking-D-small.png"), + RANKING_PANEL ("ranking-panel.png"), + RANKING_PERFECT ("ranking-perfect.png"), + RANKING_TITLE ("ranking-title.png"), + RANKING_MAXCOMBO ("ranking-maxcombo.png"), + RANKING_ACCURACY ("ranking-accuracy.png"), + DEFAULT_0 ("default-0.png"), + DEFAULT_1 ("default-1.png"), + DEFAULT_2 ("default-2.png"), + DEFAULT_3 ("default-3.png"), + DEFAULT_4 ("default-4.png"), + DEFAULT_5 ("default-5.png"), + DEFAULT_6 ("default-6.png"), + DEFAULT_7 ("default-7.png"), + DEFAULT_8 ("default-8.png"), + DEFAULT_9 ("default-9.png"), + SCORE_0 ("score-0.png"), + SCORE_1 ("score-1.png"), + SCORE_2 ("score-2.png"), + SCORE_3 ("score-3.png"), + SCORE_4 ("score-4.png"), + SCORE_5 ("score-5.png"), + SCORE_6 ("score-6.png"), + SCORE_7 ("score-7.png"), + SCORE_8 ("score-8.png"), + SCORE_9 ("score-9.png"), + SCORE_COMMA ("score-comma.png"), + SCORE_DOT ("score-dot.png"), + SCORE_PERCENT ("score-percent.png"), + SCORE_X ("score-x.png"); + + /** + * The file name. + */ + private String filename; + + /** + * The associated image. + */ + private Image img; + + /** + * Constructor. + */ + GameImage(String filename) { + this.filename = filename; + } + + /** + * Returns the associated image. + */ + public Image getImage() { return img; } + + /** + * Sets an image. + */ + public void setImage(Image img) { this.img = img; } + + /** + * Sets an image. + * Scans the path for the image first, then uses the default image. + */ + public void setImage(File dir) { + try { + // destroy the existing image, if any + if (img != null && !img.isDestroyed()) + img.destroy(); + + // set a new image + File file = new File(dir, filename); + if (file.isFile() && !Options.isBeatmapSkinIgnored()) + img = new Image(file.getAbsolutePath()); + else + img = new Image(filename); + } catch (SlickException e) { + Log.error(String.format("Failed to set image '%s'.", filename), e); + } + } +} \ No newline at end of file diff --git a/src/itdelatrisu/opsu/GameScore.java b/src/itdelatrisu/opsu/GameScore.java index 530031ba..386bc5b7 100644 --- a/src/itdelatrisu/opsu/GameScore.java +++ b/src/itdelatrisu/opsu/GameScore.java @@ -20,6 +20,7 @@ package itdelatrisu.opsu; import itdelatrisu.opsu.states.Options; +import java.io.File; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; @@ -28,7 +29,6 @@ import org.newdawn.slick.Color; import org.newdawn.slick.Graphics; import org.newdawn.slick.Image; import org.newdawn.slick.SlickException; -import org.newdawn.slick.util.Log; /** * Holds score data and renders all score-related elements. @@ -179,26 +179,6 @@ public class GameScore { */ private float difficulty = 5f; - /** - * Scorebar-related images. - */ - private Image - bgImage, // background (always rendered) - colourImage, // health bar (cropped) - kiImage, // end image (50~100% health) - kiDangerImage, // end image (25~50% health) - kiDanger2Image; // end image (0~25% health) - - /** - * Ranking screen images. - */ - private Image - rankingPanel, // panel to display text in - perfectImage, // display if full combo - rankingImage, // styled text "Ranking" - comboImage, // styled text "Combo" - accuracyImage; // styled text "Accuracy" - /** * Default text symbol images. */ @@ -233,20 +213,7 @@ public class GameScore { this.width = width; this.height = height; - hitResults = new Image[HIT_MAX]; - defaultSymbols = new Image[10]; - scoreSymbols = new HashMap(14); - gradesLarge = new Image[GRADE_MAX]; - gradesSmall = new Image[GRADE_MAX]; - comboBurstImages = new Image[4]; - clear(); - - try { - initializeImages(); - } catch (Exception e) { - Log.error("Failed to initialize images.", e); - } } /** @@ -267,100 +234,150 @@ public class GameScore { } /** - * Initialize all images tied to this object. + * Loads all game score images. * @throws SlickException */ - private void initializeImages() throws SlickException { - // scorebar - setScorebarImage( - new Image("scorebar-bg.png"), - new Image("scorebar-colour.png"), - new Image("scorebar-ki.png"), - new Image("scorebar-kidanger.png"), - new Image("scorebar-kidanger2.png") - ); - - // text symbol images - for (int i = 0; i <= 9; i++) { - defaultSymbols[i] = new Image(String.format("default-%d.png", i)); - scoreSymbols.put(Character.forDigit(i, 10), new Image(String.format("score-%d.png", i))); - } - scoreSymbols.put(',', new Image("score-comma.png")); - scoreSymbols.put('.', new Image("score-dot.png")); - scoreSymbols.put('%', new Image("score-percent.png")); - scoreSymbols.put('x', new Image("score-x.png")); - - // hit result images - hitResults[HIT_MISS] = new Image("hit0.png"); - hitResults[HIT_50] = new Image("hit50.png"); - hitResults[HIT_100] = new Image("hit100.png"); - hitResults[HIT_300] = new Image("hit300.png"); - hitResults[HIT_100K] = new Image("hit100k.png"); - hitResults[HIT_300K] = new Image("hit300k.png"); - hitResults[HIT_300G] = new Image("hit300g.png"); - hitResults[HIT_SLIDER10] = new Image("sliderpoint10.png"); - hitResults[HIT_SLIDER30] = new Image("sliderpoint30.png"); + public void loadImages() throws SlickException { + File dir = MusicController.getOsuFile().getFile().getParentFile(); // combo burst images - for (int i = 0; i <= 3; i++) - comboBurstImages[i] = new Image(String.format("comboburst-%d.png", i)); + if (comboBurstImages != null) { + for (int i = 0; i < comboBurstImages.length; i++) { + if (!comboBurstImages[i].isDestroyed()) + comboBurstImages[i].destroy(); + } + } + LinkedList comboBurst = new LinkedList(); + String comboFormat = "comboburst-%d.png"; + int comboIndex = 0; + File comboFile = new File(dir, "comboburst.png"); + File comboFileN = new File(dir, String.format(comboFormat, comboIndex)); + if (comboFileN.isFile()) { // beatmap provides images + do { + comboBurst.add(new Image(comboFileN.getAbsolutePath())); + comboFileN = new File(dir, String.format(comboFormat, ++comboIndex)); + } while (comboFileN.isFile()); + } else if (comboFile.isFile()) // beatmap provides single image + comboBurst.add(new Image(comboFile.getAbsolutePath())); + else { // load default images + while (true) { + try { + Image comboImage = new Image(String.format(comboFormat, comboIndex++)); + comboBurst.add(comboImage); + } catch (Exception e) { + break; + } + } + } + comboBurstImages = comboBurst.toArray(new Image[comboBurst.size()]); // lighting image - try { - lighting = new Image("lighting.png"); - lighting1 = new Image("lighting1.png"); - } catch (Exception e) { - // optional + if (lighting != null && !lighting.isDestroyed()) { + lighting.destroy(); + lighting = null; } + if (lighting1 != null && !lighting1.isDestroyed()) { + lighting1.destroy(); + lighting1 = null; + } + File lightingFile = new File(dir, "lighting.png"); + File lighting1File = new File(dir, "lighting1.png"); + if (lightingFile.isFile()) { // beatmap provides images + try { + lighting = new Image(lightingFile.getAbsolutePath()); + lighting1 = new Image(lighting1File.getAbsolutePath()); + } catch (Exception e) { + // optional + } + } else { // load default image + try { + lighting = new Image("lighting.png"); + lighting1 = new Image("lighting1.png"); + } catch (Exception e) { + // optional + } + } + + // scorebar + Image bg = GameImage.SCOREBAR_BG.getImage(); + Image colour = GameImage.SCOREBAR_COLOUR.getImage(); + int bgWidth = width / 2; + GameImage.SCOREBAR_BG.setImage(bg.getScaledCopy(bgWidth, bg.getHeight())); + GameImage.SCOREBAR_COLOUR.setImage(colour.getScaledCopy(bgWidth, colour.getHeight())); + + // default symbol images + defaultSymbols = new Image[10]; + defaultSymbols[0] = GameImage.DEFAULT_0.getImage(); + defaultSymbols[1] = GameImage.DEFAULT_1.getImage(); + defaultSymbols[2] = GameImage.DEFAULT_2.getImage(); + defaultSymbols[3] = GameImage.DEFAULT_3.getImage(); + defaultSymbols[4] = GameImage.DEFAULT_4.getImage(); + defaultSymbols[5] = GameImage.DEFAULT_5.getImage(); + defaultSymbols[6] = GameImage.DEFAULT_6.getImage(); + defaultSymbols[7] = GameImage.DEFAULT_7.getImage(); + defaultSymbols[8] = GameImage.DEFAULT_8.getImage(); + defaultSymbols[9] = GameImage.DEFAULT_9.getImage(); + + // score symbol images + scoreSymbols = new HashMap(14); + scoreSymbols.put('0', GameImage.SCORE_0.getImage()); + scoreSymbols.put('1', GameImage.SCORE_1.getImage()); + scoreSymbols.put('2', GameImage.SCORE_2.getImage()); + scoreSymbols.put('3', GameImage.SCORE_3.getImage()); + scoreSymbols.put('4', GameImage.SCORE_4.getImage()); + scoreSymbols.put('5', GameImage.SCORE_5.getImage()); + scoreSymbols.put('6', GameImage.SCORE_6.getImage()); + scoreSymbols.put('7', GameImage.SCORE_7.getImage()); + scoreSymbols.put('8', GameImage.SCORE_8.getImage()); + scoreSymbols.put('9', GameImage.SCORE_9.getImage()); + scoreSymbols.put(',', GameImage.SCORE_COMMA.getImage()); + scoreSymbols.put('.', GameImage.SCORE_DOT.getImage()); + scoreSymbols.put('%', GameImage.SCORE_PERCENT.getImage()); + scoreSymbols.put('x', GameImage.SCORE_X.getImage()); + + // hit result images + hitResults = new Image[HIT_MAX]; + hitResults[HIT_MISS] = GameImage.HIT_MISS.getImage(); + hitResults[HIT_50] = GameImage.HIT_50.getImage(); + hitResults[HIT_100] = GameImage.HIT_100.getImage(); + hitResults[HIT_300] = GameImage.HIT_300.getImage(); + hitResults[HIT_100K] = GameImage.HIT_100K.getImage(); + hitResults[HIT_300K] = GameImage.HIT_300K.getImage(); + hitResults[HIT_300G] = GameImage.HIT_300G.getImage(); + hitResults[HIT_SLIDER10] = GameImage.HIT_SLIDER10.getImage(); + hitResults[HIT_SLIDER30] = GameImage.HIT_SLIDER30.getImage(); // letter grade images - String[] grades = { "X", "XH", "S", "SH", "A", "B", "C", "D" }; - for (int i = 0; i < grades.length; i++) { - gradesLarge[i] = new Image(String.format("ranking-%s.png", grades[i])); - gradesSmall[i] = new Image(String.format("ranking-%s-small.png", grades[i])); - } + gradesLarge = new Image[GRADE_MAX]; + gradesSmall = new Image[GRADE_MAX]; + gradesLarge[GRADE_SS] = GameImage.RANKING_SS.getImage(); + gradesSmall[GRADE_SS] = GameImage.RANKING_SS_SMALL.getImage(); + gradesLarge[GRADE_SSH] = GameImage.RANKING_SSH.getImage(); + gradesSmall[GRADE_SSH] = GameImage.RANKING_SSH_SMALL.getImage(); + gradesLarge[GRADE_S] = GameImage.RANKING_S.getImage(); + gradesSmall[GRADE_S] = GameImage.RANKING_S_SMALL.getImage(); + gradesLarge[GRADE_SH] = GameImage.RANKING_SH.getImage(); + gradesSmall[GRADE_SH] = GameImage.RANKING_SH_SMALL.getImage(); + gradesLarge[GRADE_A] = GameImage.RANKING_A.getImage(); + gradesSmall[GRADE_A] = GameImage.RANKING_A_SMALL.getImage(); + gradesLarge[GRADE_B] = GameImage.RANKING_B.getImage(); + gradesSmall[GRADE_B] = GameImage.RANKING_B_SMALL.getImage(); + gradesLarge[GRADE_C] = GameImage.RANKING_C.getImage(); + gradesSmall[GRADE_C] = GameImage.RANKING_C_SMALL.getImage(); + gradesLarge[GRADE_D] = GameImage.RANKING_D.getImage(); + gradesSmall[GRADE_D] = GameImage.RANKING_D_SMALL.getImage(); // ranking screen elements - setRankingImage( - new Image("ranking-panel.png"), - new Image("ranking-perfect.png"), - new Image("ranking-title.png"), - new Image("ranking-maxcombo.png"), - new Image("ranking-accuracy.png") - ); - } - - /** - * Sets a background, health bar, and end image. - * @param bgImage background image - * @param colourImage health bar image - * @param kiImage end image - */ - public void setScorebarImage(Image bg, Image colour, - Image ki, Image kiDanger, Image kiDanger2) { - int bgWidth = width / 2; - this.bgImage = bg.getScaledCopy(bgWidth, bg.getHeight()); - this.colourImage = colour.getScaledCopy(bgWidth, colour.getHeight()); - this.kiImage = ki; - this.kiDangerImage = kiDanger; - this.kiDanger2Image = kiDanger2; - } - - /** - * Sets a ranking panel, full combo, and ranking/combo/accuracy text image. - * @param rankingPanel ranking panel image - * @param perfectImage full combo image - * @param rankingImage styled text "Ranking" - * @param comboImage styled text "Combo" - * @param accuracyImage styled text "Accuracy" - */ - public void setRankingImage(Image rankingPanel, Image perfectImage, - Image rankingImage, Image comboImage, Image accuracyImage) { - this.rankingPanel = rankingPanel.getScaledCopy((height * 0.63f) / rankingPanel.getHeight()); - this.perfectImage = perfectImage.getScaledCopy((height * 0.16f) / perfectImage.getHeight()); - this.rankingImage = rankingImage.getScaledCopy((height * 0.15f) / rankingImage.getHeight()); - this.comboImage = comboImage.getScaledCopy((height * 0.05f) / comboImage.getHeight()); - this.accuracyImage = accuracyImage.getScaledCopy((height * 0.05f) / accuracyImage.getHeight()); + Image rankingPanel = GameImage.RANKING_PANEL.getImage(); + Image rankingPerfect = GameImage.RANKING_PERFECT.getImage(); + Image rankingTitle = GameImage.RANKING_TITLE.getImage(); + Image rankingMaxCombo = GameImage.RANKING_MAXCOMBO.getImage(); + Image rankingAccuracy = GameImage.RANKING_ACCURACY.getImage(); + GameImage.RANKING_PANEL.setImage(rankingPanel.getScaledCopy((height * 0.63f) / rankingPanel.getHeight())); + GameImage.RANKING_PERFECT.setImage(rankingPerfect.getScaledCopy((height * 0.16f) / rankingPerfect.getHeight())); + GameImage.RANKING_TITLE.setImage(rankingTitle.getScaledCopy((height * 0.15f) / rankingTitle.getHeight())); + GameImage.RANKING_MAXCOMBO.setImage(rankingMaxCombo.getScaledCopy((height * 0.05f) / rankingMaxCombo.getHeight())); + GameImage.RANKING_ACCURACY.setImage(rankingAccuracy.getScaledCopy((height * 0.05f) / rankingAccuracy.getHeight())); } /** @@ -469,15 +486,19 @@ public class GameScore { if (firstObjectTime >= 1500 && trackPosition < firstObjectTime - 500) healthRatio = (float) trackPosition / (firstObjectTime - 500); } - bgImage.draw(0, 0); - Image colourCropped = colourImage.getSubImage(0, 0, (int) (colourImage.getWidth() * healthRatio), colourImage.getHeight()); - colourCropped.draw(0, bgImage.getHeight() / 4f); + GameImage.SCOREBAR_BG.getImage().draw(0, 0); + Image colour = GameImage.SCOREBAR_COLOUR.getImage(); + Image colourCropped = colour.getSubImage(0, 0, (int) (colour.getWidth() * healthRatio), colour.getHeight()); + colourCropped.draw(0, GameImage.SCOREBAR_BG.getImage().getHeight() / 4f); if (health >= 50f) - kiImage.drawCentered(colourCropped.getWidth(), kiImage.getHeight() / 2f); + GameImage.SCOREBAR_KI.getImage().drawCentered( + colourCropped.getWidth(), GameImage.SCOREBAR_KI.getImage().getHeight() / 2f); else if (health >= 25f) - kiDangerImage.drawCentered(colourCropped.getWidth(), kiDangerImage.getHeight() / 2f); + GameImage.SCOREBAR_KI_DANGER.getImage().drawCentered( + colourCropped.getWidth(), GameImage.SCOREBAR_KI_DANGER.getImage().getHeight() / 2f); else - kiDanger2Image.drawCentered(colourCropped.getWidth(), kiDanger2Image.getHeight() / 2f); + GameImage.SCOREBAR_KI_DANGER2.getImage().drawCentered( + colourCropped.getWidth(), GameImage.SCOREBAR_KI_DANGER2.getImage().getHeight() / 2f); // combo burst if (comboBurstIndex != -1 && comboBurstAlpha > 0f) { @@ -513,12 +534,14 @@ public class GameScore { grade.draw(width - grade.getWidth(), height * 0.09f); // header & "Ranking" text - float rankingHeight = (rankingImage.getHeight() * 0.75f) + 3; + Image rankingTitle = GameImage.RANKING_TITLE.getImage(); + float rankingHeight = (rankingTitle.getHeight() * 0.75f) + 3; g.setColor(Utils.COLOR_BLACK_ALPHA); g.fillRect(0, 0, width, rankingHeight); - rankingImage.draw((width * 0.97f) - rankingImage.getWidth(), 0); + rankingTitle.draw((width * 0.97f) - rankingTitle.getWidth(), 0); // ranking panel + Image rankingPanel = GameImage.RANKING_PANEL.getImage(); int rankingPanelWidth = rankingPanel.getWidth(); int rankingPanelHeight = rankingPanel.getHeight(); rankingPanel.draw(0, rankingHeight - (rankingHeight / 10f)); @@ -557,19 +580,25 @@ public class GameScore { } // combo and accuracy + Image rankingMaxCombo = GameImage.RANKING_MAXCOMBO.getImage(); + Image rankingAccuracy = GameImage.RANKING_ACCURACY.getImage(); float textY = rankingHeight + (rankingPanelHeight * 0.87f) - (rankingHeight / 10f); - float numbersX = comboImage.getWidth() * .07f; - float numbersY = textY + comboImage.getHeight() * 0.7f; - comboImage.draw(width * 0.01f, textY); - accuracyImage.draw(rankingPanelWidth / 2f, textY); + float numbersX = rankingMaxCombo.getWidth() * .07f; + float numbersY = textY + rankingMaxCombo.getHeight() * 0.7f; + rankingMaxCombo.draw(width * 0.01f, textY); + rankingAccuracy.draw(rankingPanelWidth / 2f, textY); drawSymbolString(String.format("%dx", comboMax), (int) (width * 0.01f + numbersX), (int) numbersY, symbolTextScale, false); drawSymbolString(String.format("%02.2f%%", getScorePercent()), (int) (rankingPanelWidth / 2f + numbersX), (int) numbersY, symbolTextScale, false); // full combo - if (combo == fullObjectCount) - perfectImage.draw(width * 0.08f, (height * 0.99f) - perfectImage.getHeight()); + if (combo == fullObjectCount) { + GameImage.RANKING_PERFECT.getImage().draw( + width * 0.08f, + (height * 0.99f) - GameImage.RANKING_PERFECT.getImage().getHeight() + ); + } } /** diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index da6d4290..137a9e3d 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -104,7 +104,6 @@ public class Utils { // game-related variables private static GameContainer container; -// private static StateBasedGame game; private static Input input; // This class should not be instantiated. @@ -119,7 +118,6 @@ public class Utils { public static void init(GameContainer container, StateBasedGame game) throws SlickException { Utils.container = container; -// Utils.game = game; Utils.input = container.getInput(); // game settings diff --git a/src/itdelatrisu/opsu/objects/Circle.java b/src/itdelatrisu/opsu/objects/Circle.java index 167426e0..5cc9203f 100644 --- a/src/itdelatrisu/opsu/objects/Circle.java +++ b/src/itdelatrisu/opsu/objects/Circle.java @@ -18,6 +18,7 @@ package itdelatrisu.opsu.objects; +import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.GameScore; import itdelatrisu.opsu.MusicController; import itdelatrisu.opsu.OsuHitObject; @@ -27,21 +28,12 @@ import itdelatrisu.opsu.states.Options; import org.newdawn.slick.Color; import org.newdawn.slick.GameContainer; -import org.newdawn.slick.Image; import org.newdawn.slick.SlickException; /** * Data type representing a circle object. */ public class Circle { - /** - * Images related to hit circles. - */ - private static Image - hitCircle, // hit circle - hitCircleOverlay, // hit circle overlay - approachCircle; // approach circle - /** * The associated OsuHitObject. */ @@ -76,26 +68,11 @@ public class Circle { public static void init(GameContainer container, float circleSize) throws SlickException { int diameter = (int) (96 - (circleSize * 8)); diameter = diameter * container.getWidth() / 640; // convert from Osupixels (640x480) - hitCircle = new Image("hitcircle.png").getScaledCopy(diameter, diameter); - hitCircleOverlay = new Image("hitcircleoverlay.png").getScaledCopy(diameter, diameter); - approachCircle = new Image("approachcircle.png").getScaledCopy(diameter, diameter); + GameImage.HITCIRCLE.setImage(GameImage.HITCIRCLE.getImage().getScaledCopy(diameter, diameter)); + GameImage.HITCIRCLE_OVERLAY.setImage(GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(diameter, diameter)); + GameImage.APPROACHCIRCLE.setImage(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(diameter, diameter)); } - /** - * Returns the hit circle image. - */ - public static Image getHitCircle() { return hitCircle; } - - /** - * Returns the hit circle overlay image. - */ - public static Image getHitCircleOverlay() { return hitCircleOverlay; } - - /** - * Returns the approach circle image. - */ - public static Image getApproachCircle() { return approachCircle; } - /** * Constructor. * @param hitObject the associated OsuHitObject @@ -121,11 +98,12 @@ public class Circle { if (timeDiff >= 0) { float approachScale = 1 + (timeDiff * 2f / game.getApproachTime()); - Utils.drawCentered(approachCircle.getScaledCopy(approachScale), hitObject.x, hitObject.y, color); - Utils.drawCentered(hitCircleOverlay, hitObject.x, hitObject.y, Color.white); - Utils.drawCentered(hitCircle, hitObject.x, hitObject.y, color); + Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), + hitObject.x, hitObject.y, color); + Utils.drawCentered(GameImage.HITCIRCLE_OVERLAY.getImage(), hitObject.x, hitObject.y, Color.white); + Utils.drawCentered(GameImage.HITCIRCLE.getImage(), hitObject.x, hitObject.y, color); score.drawSymbolNumber(hitObject.comboNumber, hitObject.x, hitObject.y, - hitCircle.getWidth() * 0.40f / score.getDefaultSymbolImage(0).getHeight()); + GameImage.HITCIRCLE.getImage().getWidth() * 0.40f / score.getDefaultSymbolImage(0).getHeight()); } } @@ -162,7 +140,7 @@ public class Circle { */ public boolean mousePressed(int x, int y) { double distance = Math.hypot(hitObject.x - x, hitObject.y - y); - int circleRadius = hitCircle.getWidth() / 2; + int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2; if (distance < circleRadius) { int result = hitResult(hitObject.time); if (result > -1) { diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index 5ad9cc2b..c4dd9ea2 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -18,6 +18,7 @@ package itdelatrisu.opsu.objects; +import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.GameScore; import itdelatrisu.opsu.MusicController; import itdelatrisu.opsu.OsuFile; @@ -26,6 +27,8 @@ import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.states.Game; import itdelatrisu.opsu.states.Options; +import java.io.File; + import org.newdawn.slick.Animation; import org.newdawn.slick.Color; import org.newdawn.slick.GameContainer; @@ -36,14 +39,6 @@ import org.newdawn.slick.SlickException; * Data type representing a slider object. */ public class Slider { - /** - * Images related to sliders. - */ - private static Image - sliderFollowCircle, // slider follow circle - reverseArrow, // reverse arrow (for repeats) - sliderTick; // slider tick - /** * Slider ball animation. */ @@ -261,8 +256,8 @@ public class Slider { * Draws the full Bezier curve to the graphics context. */ public void draw() { - Image hitCircle = Circle.getHitCircle(); - Image hitCircleOverlay = Circle.getHitCircleOverlay(); + Image hitCircle = GameImage.HITCIRCLE.getImage(); + Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage(); // draw overlay and hit circle for (int i = curveX.length - 1; i >= 0; i--) @@ -283,12 +278,38 @@ public class Slider { int diameter = (int) (96 - (circleSize * 8)); diameter = diameter * container.getWidth() / 640; // convert from Osupixels (640x480) + // slider ball + if (sliderBall != null) { + for (int i = 0; i < sliderBall.getFrameCount(); i++) { + Image img = sliderBall.getImage(i); + if (!img.isDestroyed()) + img.destroy(); + } + } sliderBall = new Animation(); - for (int i = 0; i <= 9; i++) - sliderBall.addFrame(new Image(String.format("sliderb%d.png", i)).getScaledCopy(diameter * 118 / 128, diameter * 118 / 128), 60); - sliderFollowCircle = new Image("sliderfollowcircle.png").getScaledCopy(diameter * 259 / 128, diameter * 259 / 128); - reverseArrow = new Image("reversearrow.png").getScaledCopy(diameter, diameter); - sliderTick = new Image("sliderscorepoint.png").getScaledCopy(diameter / 4, diameter / 4); + String sliderFormat = "sliderb%d.png"; + int sliderIndex = 0; + File dir = MusicController.getOsuFile().getFile().getParentFile(); + File slider = new File(dir, String.format(sliderFormat, sliderIndex)); + if (slider.isFile()) { + do { + sliderBall.addFrame(new Image(slider.getAbsolutePath()).getScaledCopy(diameter * 118 / 128, diameter * 118 / 128), 60); + slider = new File(dir, String.format(sliderFormat, ++sliderIndex)); + } while (slider.isFile()); + } else { + while (true) { + try { + Image sliderFrame = new Image(String.format(sliderFormat, sliderIndex++)); + sliderBall.addFrame(sliderFrame.getScaledCopy(diameter * 118 / 128, diameter * 118 / 128), 60); + } catch (Exception e) { + break; + } + } + } + + GameImage.SLIDER_FOLLOWCIRCLE.setImage(GameImage.SLIDER_FOLLOWCIRCLE.getImage().getScaledCopy(diameter * 259 / 128, diameter * 259 / 128)); + GameImage.REVERSEARROW.setImage(GameImage.REVERSEARROW.getImage().getScaledCopy(diameter, diameter)); + GameImage.SLIDER_TICK.setImage(GameImage.SLIDER_TICK.getImage().getScaledCopy(diameter / 4, diameter / 4)); sliderMultiplier = osu.sliderMultiplier; sliderTickRate = osu.sliderTickRate; @@ -310,8 +331,6 @@ public class Slider { this.comboEnd = comboEnd; this.bezier = new Bezier(); - - // calculate slider time and ticks upon first update call } /** @@ -322,8 +341,8 @@ public class Slider { public void draw(int trackPosition, boolean currentObject) { int timeDiff = hitObject.time - trackPosition; - Image hitCircleOverlay = Circle.getHitCircleOverlay(); - Image hitCircle = Circle.getHitCircle(); + Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage(); + Image hitCircle = GameImage.HITCIRCLE.getImage(); // bezier bezier.draw(); @@ -332,7 +351,7 @@ public class Slider { if (currentObject && ticksT != null) { for (int i = 0; i < ticksT.length; i++) { float[] c = bezier.pointAt(ticksT[i]); - sliderTick.drawCentered(c[0], c[1]); + GameImage.SLIDER_TICK.getImage().drawCentered(c[0], c[1]); } } @@ -352,19 +371,20 @@ public class Slider { // repeats if (hitObject.repeat - 1 > currentRepeats) { + Image arrow = GameImage.REVERSEARROW.getImage(); if (currentRepeats % 2 == 0) { // last circle - reverseArrow.setRotation(bezier.getEndAngle()); - reverseArrow.drawCentered(hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex]); + arrow.setRotation(bezier.getEndAngle()); + arrow.drawCentered(hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex]); } else { // first circle - reverseArrow.setRotation(bezier.getStartAngle()); - reverseArrow.drawCentered(hitObject.x, hitObject.y); + arrow.setRotation(bezier.getStartAngle()); + arrow.drawCentered(hitObject.x, hitObject.y); } } if (timeDiff >= 0) { // approach circle float approachScale = 1 + (timeDiff * 2f / game.getApproachTime()); - Utils.drawCentered(Circle.getApproachCircle().getScaledCopy(approachScale), + Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), hitObject.x, hitObject.y, color); } else { float[] c = bezier.pointAt(getT(trackPosition, false)); @@ -374,7 +394,7 @@ public class Slider { // follow circle if (followCircleActive) - sliderFollowCircle.drawCentered(c[0], c[1]); + GameImage.SLIDER_FOLLOWCIRCLE.getImage().drawCentered(c[0], c[1]); } } @@ -421,7 +441,7 @@ public class Slider { return false; double distance = Math.hypot(hitObject.x - x, hitObject.y - y); - int circleRadius = Circle.getHitCircle().getWidth() / 2; + int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2; if (distance < circleRadius) { int trackPosition = MusicController.getPosition(); int timeDiff = Math.abs(trackPosition - hitObject.time); @@ -513,7 +533,7 @@ public class Slider { // check if cursor pressed and within end circle else if (game.isInputKeyPressed()) { double distance = Math.hypot(hitObject.sliderX[lastIndex] - mouseX, hitObject.sliderY[lastIndex] - mouseY); - int followCircleRadius = sliderFollowCircle.getWidth() / 2; + int followCircleRadius = GameImage.SLIDER_FOLLOWCIRCLE.getImage().getWidth() / 2; if (distance < followCircleRadius) ticksHit++; } @@ -550,7 +570,7 @@ public class Slider { // holding slider... float[] c = bezier.pointAt(getT(trackPosition, false)); double distance = Math.hypot(c[0] - mouseX, c[1] - mouseY); - int followCircleRadius = sliderFollowCircle.getWidth() / 2; + int followCircleRadius = GameImage.SLIDER_FOLLOWCIRCLE.getImage().getWidth() / 2; if ((game.isInputKeyPressed() && distance < followCircleRadius) || isAutoMod) { // mouse pressed and within follow circle followCircleActive = true; diff --git a/src/itdelatrisu/opsu/objects/Spinner.java b/src/itdelatrisu/opsu/objects/Spinner.java index f982ea49..c950884a 100644 --- a/src/itdelatrisu/opsu/objects/Spinner.java +++ b/src/itdelatrisu/opsu/objects/Spinner.java @@ -18,6 +18,7 @@ package itdelatrisu.opsu.objects; +import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.GameScore; import itdelatrisu.opsu.MusicController; import itdelatrisu.opsu.OsuHitObject; @@ -36,17 +37,6 @@ import org.newdawn.slick.SlickException; * Data type representing a spinner object. */ public class Spinner { - /** - * Images related to spinners. - */ - private static Image - spinnerCircle, // spinner - spinnerApproachCircle, // spinner approach circle (for end time) - spinnerMetre, // spinner meter (subimage based on completion ratio) -// spinnerOsuImage, // spinner "OSU!" text (complete) - spinnerSpinImage, // spinner "SPIN!" text (start) - spinnerClearImage; // spinner "CLEAR" text (passed) - /** * Container dimensions. */ @@ -91,12 +81,10 @@ public class Spinner { width = container.getWidth(); height = container.getHeight(); - spinnerCircle = new Image("spinner-circle.png").getScaledCopy(height * 9 / 10, height * 9 / 10); - spinnerApproachCircle = new Image("spinner-approachcircle.png").getScaledCopy(spinnerCircle.getWidth(), spinnerCircle.getHeight()); - spinnerMetre = new Image("spinner-metre.png").getScaledCopy(width, height); - spinnerSpinImage = new Image("spinner-spin.png"); - spinnerClearImage = new Image("spinner-clear.png"); -// spinnerOsuImage = new Image("spinner-osu.png"); + Image spinnerCircle = GameImage.SPINNER_CIRCLE.getImage(); + GameImage.SPINNER_CIRCLE.setImage(spinnerCircle.getScaledCopy(height * 9 / 10, height * 9 / 10)); + GameImage.SPINNER_APPROACHCIRCLE.setImage(GameImage.SPINNER_APPROACHCIRCLE.getImage().getScaledCopy(spinnerCircle.getWidth(), spinnerCircle.getHeight())); + GameImage.SPINNER_METRE.setImage(GameImage.SPINNER_METRE.getImage().getScaledCopy(width, height)); } /** @@ -135,6 +123,7 @@ public class Spinner { return; // spinner meter (subimage) + Image spinnerMetre = GameImage.SPINNER_METRE.getImage(); int spinnerMetreY = (spinnerComplete) ? 0 : (int) (spinnerMetre.getHeight() * (1 - (rotations / rotationsNeeded))); Image spinnerMetreSub = spinnerMetre.getSubImage( 0, spinnerMetreY, @@ -143,12 +132,12 @@ public class Spinner { spinnerMetreSub.draw(0, height - spinnerMetreSub.getHeight()); // main spinner elements - spinnerCircle.drawCentered(width / 2, height / 2); - spinnerApproachCircle.getScaledCopy(1 - ((float) timeDiff / (hitObject.time - hitObject.endTime))).drawCentered(width / 2, height / 2); - spinnerSpinImage.drawCentered(width / 2, height * 3 / 4); + GameImage.SPINNER_CIRCLE.getImage().drawCentered(width / 2, height / 2); + GameImage.SPINNER_APPROACHCIRCLE.getImage().getScaledCopy(1 - ((float) timeDiff / (hitObject.time - hitObject.endTime))).drawCentered(width / 2, height / 2); + GameImage.SPINNER_SPIN.getImage().drawCentered(width / 2, height * 3 / 4); if (spinnerComplete) { - spinnerClearImage.drawCentered(width / 2, height / 4); + GameImage.SPINNER_CLEAR.getImage().drawCentered(width / 2, height / 4); int extraRotations = (int) (rotations - rotationsNeeded); if (extraRotations > 0) score.drawSymbolNumber(extraRotations * 1000, width / 2, height * 2 / 3, 1.0f); diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 1b691079..2459c6b5 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -19,6 +19,7 @@ package itdelatrisu.opsu.states; import itdelatrisu.opsu.GUIMenuButton; +import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.GameScore; import itdelatrisu.opsu.MusicController; import itdelatrisu.opsu.Opsu; @@ -31,6 +32,7 @@ import itdelatrisu.opsu.objects.Circle; import itdelatrisu.opsu.objects.Slider; import itdelatrisu.opsu.objects.Spinner; +import java.io.File; import java.util.HashMap; import java.util.Stack; @@ -121,16 +123,6 @@ public class Game extends BasicGameState { */ private int breakIndex; - /** - * Warning arrows, pointing right and left. - */ - private Image warningArrowR, warningArrowL; - - /** - * Section pass and fail images (displayed at start of break, when necessary). - */ - private Image breakStartPass, breakStartFail; - /** * Break start time (0 if not in break). */ @@ -161,16 +153,6 @@ public class Game extends BasicGameState { */ private float beatLengthBase, beatLength; - /** - * Countdown-related images. - */ - private Image - countdownReady, // "READY?" text - countdown3, // "3" text - countdown1, // "2" text - countdown2, // "1" text - countdownGo; // "GO!" text - /** * Whether the countdown sound has been played. */ @@ -178,11 +160,6 @@ public class Game extends BasicGameState { countdownReadySound, countdown3Sound, countdown1Sound, countdown2Sound, countdownGoSound; - /** - * Glowing hit circle outline which must be clicked when returning from pause menu. - */ - private Image hitCircleSelect; - /** * Mouse coordinates before game paused. */ @@ -204,11 +181,6 @@ public class Game extends BasicGameState { */ private Image playfield; - /** - * Image displayed during unranked plays. - */ - private Image unrankedImage; - // game-related variables private GameContainer container; private StateBasedGame game; @@ -229,41 +201,8 @@ public class Game extends BasicGameState { int width = container.getWidth(); int height = container.getHeight(); - // spinners have fixed properties, and only need to be initialized once - Spinner.init(container); - - // breaks - breakStartPass = new Image("section-pass.png"); - breakStartFail = new Image("section-fail.png"); - warningArrowR = new Image("play-warningarrow.png"); - warningArrowL = warningArrowR.getFlippedCopy(true, false); - - // skip button - Image skip = new Image("play-skip.png"); - float skipScale = (height * 0.1f) / skip.getHeight(); - skip = skip.getScaledCopy(skipScale); - skipButton = new GUIMenuButton(skip, - width - (skip.getWidth() / 2f), - height - (skip.getHeight() / 2f)); - - // countdown - float countdownHeight = height / 3f; - countdownReady = new Image("ready.png"); - countdownReady = countdownReady.getScaledCopy(countdownHeight / countdownReady.getHeight()); - countdown3 = new Image("count3.png"); - countdown3 = countdown3.getScaledCopy(countdownHeight / countdown3.getHeight()); - countdown2 = new Image("count2.png"); - countdown2 = countdown2.getScaledCopy(countdownHeight / countdown2.getHeight()); - countdown1 = new Image("count1.png"); - countdown1 = countdown1.getScaledCopy(countdownHeight / countdown1.getHeight()); - countdownGo = new Image("go.png"); - countdownGo = countdownGo.getScaledCopy(countdownHeight / countdownGo.getHeight()); - - // hit circle select - hitCircleSelect = new Image("hitcircleselect.png"); - - // "unranked" image - unrankedImage = new Image("play-unranked.png"); + // create the associated GameScore object + score = new GameScore(width, height); // playfield background try { @@ -271,9 +210,6 @@ public class Game extends BasicGameState { } catch (Exception e) { // optional } - - // create the associated GameScore object - score = new GameScore(width, height); } @Override @@ -317,13 +253,13 @@ public class Game extends BasicGameState { trackPosition - breakTime < 5000) { // show break start if (score.getHealth() >= 50) { - breakStartPass.drawCentered(width / 2f, height / 2f); + GameImage.SECTION_PASS.getImage().drawCentered(width / 2f, height / 2f); if (!breakSound) { SoundController.playSound(SoundController.SOUND_SECTIONPASS); breakSound = true; } } else { - breakStartFail.drawCentered(width / 2f, height / 2f); + GameImage.SECTION_FAIL.getImage().drawCentered(width / 2f, height / 2f); if (!breakSound) { SoundController.playSound(SoundController.SOUND_SECTIONFAIL); breakSound = true; @@ -334,15 +270,18 @@ public class Game extends BasicGameState { int endTimeDiff = endTime - trackPosition; if ((endTimeDiff > 1500 && endTimeDiff < 2000) || (endTimeDiff > 500 && endTimeDiff < 1000)) { - warningArrowR.draw(width * 0.15f, height * 0.15f); - warningArrowR.draw(width * 0.15f, height * 0.75f); - warningArrowL.draw(width * 0.75f, height * 0.15f); - warningArrowL.draw(width * 0.75f, height * 0.75f); + Image arrow = GameImage.WARNINGARROW.getImage(); + arrow.setRotation(0); + arrow.draw(width * 0.15f, height * 0.15f); + arrow.draw(width * 0.15f, height * 0.75f); + arrow.setRotation(180); + arrow.draw(width * 0.75f, height * 0.15f); + arrow.draw(width * 0.75f, height * 0.75f); } } if (Options.isModActive(Options.MOD_AUTO)) - unrankedImage.drawCentered(width / 2, height * 0.077f); + GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f); Utils.drawFPS(); Utils.drawCursor(); return; @@ -380,36 +319,37 @@ public class Game extends BasicGameState { int timeDiff = osu.objects[0].time - trackPosition; if (timeDiff >= 500 && timeDiff < 3000) { if (timeDiff >= 1500) { - countdownReady.drawCentered(width / 2, height / 2); + GameImage.COUNTDOWN_READY.getImage().drawCentered(width / 2, height / 2); if (!countdownReadySound) { SoundController.playSound(SoundController.SOUND_READY); countdownReadySound = true; } } if (timeDiff < 2000) { - countdown3.draw(0, 0); + GameImage.COUNTDOWN_3.getImage().draw(0, 0); if (!countdown3Sound) { SoundController.playSound(SoundController.SOUND_COUNT3); countdown3Sound = true; } } if (timeDiff < 1500) { - countdown2.draw(width - countdown2.getWidth(), 0); + GameImage.COUNTDOWN_2.getImage().draw(width - GameImage.COUNTDOWN_2.getImage().getWidth(), 0); if (!countdown2Sound) { SoundController.playSound(SoundController.SOUND_COUNT2); countdown2Sound = true; } } if (timeDiff < 1000) { - countdown1.drawCentered(width / 2, height / 2); + GameImage.COUNTDOWN_1.getImage().drawCentered(width / 2, height / 2); if (!countdown1Sound) { SoundController.playSound(SoundController.SOUND_COUNT1); countdown1Sound = true; } } } else if (timeDiff >= -500 && timeDiff < 500) { - countdownGo.setAlpha((timeDiff < 0) ? 1 - (timeDiff / -1000f) : 1); - countdownGo.drawCentered(width / 2, height / 2); + Image go = GameImage.COUNTDOWN_GO.getImage(); + go.setAlpha((timeDiff < 0) ? 1 - (timeDiff / -1000f) : 1); + go.drawCentered(width / 2, height / 2); if (!countdownGoSound) { SoundController.playSound(SoundController.SOUND_GO); countdownGoSound = true; @@ -442,7 +382,7 @@ public class Game extends BasicGameState { score.drawHitResults(trackPosition); if (Options.isModActive(Options.MOD_AUTO)) - unrankedImage.drawCentered(width / 2, height * 0.077f); + GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f); // returning from pause screen if (pauseTime > -1 && pausedMouseX > -1 && pausedMouseY > -1) { @@ -451,8 +391,8 @@ public class Game extends BasicGameState { g.fillRect(0, 0, width, height); // draw glowing hit select circle and pulse effect - int circleRadius = Circle.getHitCircle().getWidth(); - Image cursorCircle = hitCircleSelect.getScaledCopy(circleRadius, circleRadius); + int circleRadius = GameImage.HITCIRCLE.getImage().getWidth(); + Image cursorCircle = GameImage.HITCIRCLE_SELECT.getImage().getScaledCopy(circleRadius, circleRadius); cursorCircle.setAlpha(1.0f); cursorCircle.drawCentered(pausedMouseX, pausedMouseY); Image cursorCirclePulse = cursorCircle.getScaledCopy(1f + pausePulse); @@ -643,7 +583,7 @@ public class Game extends BasicGameState { // returning from pause screen if (pauseTime > -1) { double distance = Math.hypot(pausedMouseX - x, pausedMouseY - y); - int circleRadius = Circle.getHitCircle().getWidth() / 2; + int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2; if (distance < circleRadius) { // unpause the game pauseTime = -1; @@ -698,6 +638,7 @@ public class Game extends BasicGameState { if (restart != RESTART_FALSE) { // new game if (restart == RESTART_NEW) { + loadImages(); setMapModifiers(); // calculate map length (TODO: end on slider?) @@ -802,6 +743,49 @@ public class Game extends BasicGameState { input.isKeyDown(Input.KEY_Z) || input.isKeyDown(Input.KEY_X)); } + /** + * Loads all game images. + * @throws SlickException + */ + private void loadImages() throws SlickException { + int width = container.getWidth(); + int height = container.getHeight(); + + // set images + File parent = osu.getFile().getParentFile(); + for (GameImage o : GameImage.values()) + o.setImage(parent); + + // skip button + Image skip = GameImage.SKIP.getImage(); + float skipScale = (height * 0.1f) / skip.getHeight(); + skip = skip.getScaledCopy(skipScale); + skipButton = new GUIMenuButton(skip, + width - (skip.getWidth() / 2f), + height - (skip.getHeight() / 2f)); + + // countdown + Image countdownReady = GameImage.COUNTDOWN_READY.getImage(); + Image countdown3 = GameImage.COUNTDOWN_3.getImage(); + Image countdown2 = GameImage.COUNTDOWN_2.getImage(); + Image countdown1 = GameImage.COUNTDOWN_1.getImage(); + Image countdownGo = GameImage.COUNTDOWN_GO.getImage(); + float countdownHeight = height / 3f; + GameImage.COUNTDOWN_READY.setImage( + countdownReady.getScaledCopy(countdownHeight / countdownReady.getHeight())); + GameImage.COUNTDOWN_3.setImage( + countdown3.getScaledCopy(countdownHeight / countdown3.getHeight())); + GameImage.COUNTDOWN_2.setImage( + countdown2.getScaledCopy(countdownHeight / countdown2.getHeight())); + GameImage.COUNTDOWN_1.setImage( + countdown1.getScaledCopy(countdownHeight / countdown1.getHeight())); + GameImage.COUNTDOWN_GO.setImage( + countdownGo.getScaledCopy(countdownHeight / countdownGo.getHeight())); + + ((GamePauseMenu) game.getState(Opsu.STATE_GAMEPAUSEMENU)).loadImages(); + score.loadImages(); + } + /** * Set map modifiers. */ @@ -821,6 +805,7 @@ public class Game extends BasicGameState { Circle.init(container, circleSize); Slider.init(container, circleSize, osu); + Spinner.init(container); // approachRate (hit object approach time) if (approachRate < 5) diff --git a/src/itdelatrisu/opsu/states/GamePauseMenu.java b/src/itdelatrisu/opsu/states/GamePauseMenu.java index 0fdc6c6d..e14f343e 100644 --- a/src/itdelatrisu/opsu/states/GamePauseMenu.java +++ b/src/itdelatrisu/opsu/states/GamePauseMenu.java @@ -19,6 +19,7 @@ package itdelatrisu.opsu.states; import itdelatrisu.opsu.GUIMenuButton; +import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.MusicController; import itdelatrisu.opsu.Opsu; import itdelatrisu.opsu.SoundController; @@ -27,7 +28,6 @@ import itdelatrisu.opsu.Utils; import org.newdawn.slick.Color; import org.newdawn.slick.GameContainer; import org.newdawn.slick.Graphics; -import org.newdawn.slick.Image; import org.newdawn.slick.Input; import org.newdawn.slick.SlickException; import org.newdawn.slick.state.BasicGameState; @@ -36,7 +36,7 @@ import org.newdawn.slick.state.transition.FadeInTransition; import org.newdawn.slick.state.transition.FadeOutTransition; /** - * "Game Paused" state. + * "Game Pause/Fail" state. *
    *
  • [Continue] - unpause game (return to game state) *
  • [Retry] - restart game (return to game state) @@ -59,18 +59,9 @@ public class GamePauseMenu extends BasicGameState { */ private GUIMenuButton continueButton, retryButton, backButton; - /** - * Background image for pause menu (optional). - */ - private Image backgroundImage; - - /** - * Background image for fail menu (optional). - */ - private Image failImage; - // game-related variables private StateBasedGame game; + private GameContainer container; private int state; public GamePauseMenu(int state) { @@ -80,43 +71,18 @@ public class GamePauseMenu extends BasicGameState { @Override public void init(GameContainer container, StateBasedGame game) throws SlickException { + this.container = container; this.game = game; - - int width = container.getWidth(); - int height = container.getHeight(); - - // initialize buttons - continueButton = new GUIMenuButton(new Image("pause-continue.png"), width / 2f, height * 0.25f); - retryButton = new GUIMenuButton(new Image("pause-retry.png"), width / 2f, height * 0.5f); - backButton = new GUIMenuButton(new Image("pause-back.png"), width / 2f, height * 0.75f); - - // pause background image - try { - backgroundImage = new Image("pause-overlay.png").getScaledCopy(width, height); - backgroundImage.setAlpha(0.7f); - } catch (Exception e) { - // optional - } - - // fail image - try { - failImage = new Image("fail-background.png").getScaledCopy(width, height); - failImage.setAlpha(0.7f); - } catch (Exception e) { - // optional - } } @Override public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException { // background - if (backgroundImage != null && Game.getRestart() != Game.RESTART_LOSE) - backgroundImage.draw(); - else if (failImage != null && Game.getRestart() == Game.RESTART_LOSE) - failImage.draw(); + if (Game.getRestart() != Game.RESTART_LOSE) + GameImage.PAUSE_OVERLAY.getImage().draw(); else - g.setBackground(Color.black); + GameImage.FAIL_BACKGROUND.getImage().draw(); // draw buttons if (Game.getRestart() != Game.RESTART_LOSE) @@ -202,4 +168,25 @@ public class GamePauseMenu extends BasicGameState { Game.setRestart(restart); game.enterState(Opsu.STATE_GAME); } + + /** + * Loads all game pause/fail menu images. + */ + public void loadImages() { + int width = container.getWidth(); + int height = container.getHeight(); + + // initialize buttons + continueButton = new GUIMenuButton(GameImage.PAUSE_CONTINUE.getImage(), width / 2f, height * 0.25f); + retryButton = new GUIMenuButton(GameImage.PAUSE_RETRY.getImage(), width / 2f, height * 0.5f); + backButton = new GUIMenuButton(GameImage.PAUSE_BACK.getImage(), width / 2f, height * 0.75f); + + // pause background image + GameImage.PAUSE_OVERLAY.setImage(GameImage.PAUSE_OVERLAY.getImage().getScaledCopy(width, height)); + GameImage.PAUSE_OVERLAY.getImage().setAlpha(0.7f); + + // fail image + GameImage.FAIL_BACKGROUND.setImage(GameImage.FAIL_BACKGROUND.getImage().getScaledCopy(width, height)); + GameImage.FAIL_BACKGROUND.getImage().setAlpha(0.7f); + } } diff --git a/src/itdelatrisu/opsu/states/Options.java b/src/itdelatrisu/opsu/states/Options.java index e06dc341..85efff71 100644 --- a/src/itdelatrisu/opsu/states/Options.java +++ b/src/itdelatrisu/opsu/states/Options.java @@ -129,7 +129,8 @@ public class Options extends BasicGameState { DYNAMIC_BACKGROUND, SHOW_PERFECT_HIT, BACKGROUND_DIM, - FORCE_DEFAULT_PLAYFIELD; + FORCE_DEFAULT_PLAYFIELD, + IGNORE_BEATMAP_SKINS; }; /** @@ -189,6 +190,7 @@ public class Options extends BasicGameState { private static final GameOption[] gameplayOptions = { GameOption.BACKGROUND_DIM, GameOption.FORCE_DEFAULT_PLAYFIELD, + GameOption.IGNORE_BEATMAP_SKINS, GameOption.SHOW_HIT_LIGHTING, GameOption.SHOW_COMBO_BURSTS, GameOption.SHOW_PERFECT_HIT @@ -311,6 +313,11 @@ public class Options extends BasicGameState { */ private static boolean forceDefaultPlayfield = false; + /** + * Whether or not to ignore resources in the beatmap folders. + */ + private static boolean ignoreBeatmapSkins = false; + /** * Game option coordinate modifiers (for drawing). */ @@ -545,6 +552,9 @@ public class Options extends BasicGameState { case FORCE_DEFAULT_PLAYFIELD: forceDefaultPlayfield = !forceDefaultPlayfield; break; + case IGNORE_BEATMAP_SKINS: + ignoreBeatmapSkins = !ignoreBeatmapSkins; + break; default: break; } @@ -721,6 +731,12 @@ public class Options extends BasicGameState { "Override the song background with the default playfield background." ); break; + case IGNORE_BEATMAP_SKINS: + drawOption(pos, "Ignore All Beatmap Skins", + ignoreBeatmapSkins ? "Yes" : "No", + "Never use skin element overrides provided by beatmaps." + ); + break; case SHOW_HIT_LIGHTING: drawOption(pos, "Show Hit Lighting", showHitLighting ? "Yes" : "No", @@ -920,6 +936,12 @@ public class Options extends BasicGameState { */ public static boolean isDefaultPlayfieldForced() { return forceDefaultPlayfield; } + /** + * Returns whether or not beatmap skins are ignored. + * @return true if ignored + */ + public static boolean isBeatmapSkinIgnored() { return ignoreBeatmapSkins; } + /** * Returns the current beatmap directory. * If invalid, this will attempt to search for the directory, @@ -1044,6 +1066,9 @@ public class Options extends BasicGameState { case "ForceDefaultPlayfield": forceDefaultPlayfield = Boolean.parseBoolean(value); break; + case "IgnoreBeatmapSkins": + ignoreBeatmapSkins = Boolean.parseBoolean(value); + break; case "HitLighting": showHitLighting = Boolean.parseBoolean(value); break; @@ -1112,6 +1137,8 @@ public class Options extends BasicGameState { writer.newLine(); writer.write(String.format("ForceDefaultPlayfield = %b", forceDefaultPlayfield)); writer.newLine(); + writer.write(String.format("IgnoreBeatmapSkins = %b", ignoreBeatmapSkins)); + writer.newLine(); writer.write(String.format("HitLighting = %b", showHitLighting)); writer.newLine(); writer.write(String.format("ComboBurst = %b", showComboBursts));