From 7a187c4e4fab9711cf9dbb426f56106c6ceeac38 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Wed, 16 Jul 2014 16:01:36 -0400 Subject: [PATCH] Game mod refactoring. - Created "GameMod" enum to handle all mod-related actions and store all related data. Signed-off-by: Jeffrey Han --- src/itdelatrisu/opsu/GameImage.java | 2 + src/itdelatrisu/opsu/GameMod.java | 172 +++++++++++++++++++ src/itdelatrisu/opsu/GameScore.java | 24 ++- src/itdelatrisu/opsu/Utils.java | 12 +- src/itdelatrisu/opsu/objects/Circle.java | 4 +- src/itdelatrisu/opsu/objects/Slider.java | 4 +- src/itdelatrisu/opsu/objects/Spinner.java | 9 +- src/itdelatrisu/opsu/states/Game.java | 15 +- src/itdelatrisu/opsu/states/GameRanking.java | 9 +- src/itdelatrisu/opsu/states/Options.java | 106 +----------- 10 files changed, 223 insertions(+), 134 deletions(-) create mode 100644 src/itdelatrisu/opsu/GameMod.java diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index 1523cf7d..0b38c7ff 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -15,6 +15,7 @@ * 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; @@ -150,6 +151,7 @@ public enum GameImage { /** * Constructor. + * @param filename the image file name */ GameImage(String filename) { this.filename = filename; diff --git a/src/itdelatrisu/opsu/GameMod.java b/src/itdelatrisu/opsu/GameMod.java new file mode 100644 index 00000000..3505b7a8 --- /dev/null +++ b/src/itdelatrisu/opsu/GameMod.java @@ -0,0 +1,172 @@ +/* + * 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 java.util.Arrays; +import java.util.Collections; + +import org.newdawn.slick.Image; +import org.newdawn.slick.SlickException; +import org.newdawn.slick.util.Log; + +/** + * Game mods. + */ +public enum GameMod { + NO_FAIL (0, "selection-mod-nofail.png"), + HARD_ROCK (1, "selection-mod-hardrock.png"), + SUDDEN_DEATH (2, "selection-mod-suddendeath.png"), + SPUN_OUT (3, "selection-mod-spunout.png"), + AUTO (4, "selection-mod-autoplay.png"); + + /** + * The ID of the mod (used for positioning). + */ + private int id; + + /** + * The file name of the mod image. + */ + private String filename; + + /** + * Whether or not this mod is active. + */ + private boolean active = false; + + /** + * The button containing the mod image (displayed in Options screen). + */ + private GUIMenuButton button; + + /** + * Total number of mods. + */ + private static final int size = GameMod.values().length; + + /** + * Returns the total number of game mods. + * @return the number of mods + */ + public static int size() { return size; } + + /** + * Returns an array of GameMod objects in reverse order. + * @return all game mods in reverse order + */ + public static GameMod[] valuesReversed() { + GameMod[] mods = GameMod.values(); + Collections.reverse(Arrays.asList(mods)); + return mods; + } + + /** + * Constructor. + * @param id the ID of the mod (for positioning). + * @param filename the image file name + */ + GameMod(int id, String filename) { + this.id = id; + this.filename = filename; + } + + /** + * Initializes the game mod. + * @param width the container width + * @param height the container height + */ + public void init(int width, int height) { + try { + // create and scale image + Image img = new Image(filename); + float scale = (height * 0.12f) / img.getHeight(); + img = img.getScaledCopy(scale); + + // find coordinates + float offsetX = img.getWidth() * 1.5f; + float x = (width / 2f) - (offsetX * size / 2.75f); + float y = (height * 0.8f) + (img.getHeight() / 2); + + // create button + img.setAlpha(0.5f); + this.button = new GUIMenuButton(img, x + (offsetX * id), y); + } catch (SlickException e) { + Log.error(String.format("Failed to initialize game mod '%s'.", this), e); + } + } + + /** + * Toggles the active status of the mod. + * @param checkInverse if true, perform checks for mutual exclusivity + */ + public void toggle(boolean checkInverse) { + button.getImage().setAlpha(active ? 0.5f : 1.0f); + active = !active; + + if (checkInverse) { + if (AUTO.isActive()) { + if (this == AUTO) { + if (SPUN_OUT.isActive()) + SPUN_OUT.toggle(false); + if (SUDDEN_DEATH.isActive()) + SUDDEN_DEATH.toggle(false); + } else if (this == SPUN_OUT || this == SUDDEN_DEATH) { + if (active) + toggle(false); + } + } else if (SUDDEN_DEATH.isActive() && NO_FAIL.isActive()) { + if (this == SUDDEN_DEATH) + NO_FAIL.toggle(false); + else + SUDDEN_DEATH.toggle(false); + } + } + } + + /** + * Returns whether or not the mod is active. + * @return true if active + */ + public boolean isActive() { return active; } + + /** + * Returns the image associated with the mod. + * @return the associated image + */ + public Image getImage() { return button.getImage(); } + + /** + * Returns the mod ID. + * @return the mod ID + */ + public int getID() { return id; } + + /** + * Draws the game mod. + */ + public void draw() { button.draw(); } + + /** + * Checks if the coordinates are within the image bounds. + * @param x the x coordinate + * @param y the y coordinate + * @return true if within bounds + */ + public boolean contains(float x, float y) { return button.contains(x, y); } +} diff --git a/src/itdelatrisu/opsu/GameScore.java b/src/itdelatrisu/opsu/GameScore.java index f771013c..a9c5149a 100644 --- a/src/itdelatrisu/opsu/GameScore.java +++ b/src/itdelatrisu/opsu/GameScore.java @@ -503,13 +503,13 @@ public class GameScore { } // mod icons - if ((firstObject && trackPosition < firstObjectTime) || Options.isModActive(Options.MOD_AUTO)) { - int modWidth = Options.getModImage(0).getWidth(); + if ((firstObject && trackPosition < firstObjectTime) || GameMod.AUTO.isActive()) { + int modWidth = GameMod.AUTO.getImage().getWidth(); float modX = (width * 0.98f) - modWidth; - for (int i = Options.MOD_MAX - 1, modCount = 0; i >= 0; i--) { - if (Options.isModActive(i)) { - Image modImage = Options.getModImage(i); - modImage.draw( + int modCount = 0; + for (GameMod mod : GameMod.valuesReversed()) { + if (mod.isActive()) { + mod.getImage().draw( modX - (modCount * (modWidth / 2f)), symbolHeight + circleDiameter + 10 ); @@ -702,9 +702,7 @@ public class GameScore { * If "No Fail" or "Auto" mods are active, this will always return true. */ public boolean isAlive() { - return (health > 0f || - Options.isModActive(Options.MOD_NO_FAIL) || - Options.isModActive(Options.MOD_AUTO)); + return (health > 0f || GameMod.NO_FAIL.isActive() || GameMod.AUTO.isActive()); } /** @@ -816,7 +814,7 @@ public class GameScore { if (combo >= 20) SoundController.playSound(SoundController.SOUND_COMBOBREAK); combo = 0; - if (Options.isModActive(Options.MOD_SUDDEN_DEATH)) + if (GameMod.SUDDEN_DEATH.isActive()) health = 0f; } @@ -906,11 +904,11 @@ public class GameScore { // game mod score multipliers float modMultiplier = 1f; - if (Options.isModActive(Options.MOD_NO_FAIL)) + if (GameMod.NO_FAIL.isActive()) modMultiplier *= 0.5f; - if (Options.isModActive(Options.MOD_HARD_ROCK)) + if (GameMod.HARD_ROCK.isActive()) modMultiplier *= 1.06f; - if (Options.isModActive(Options.MOD_SPUN_OUT)) + if (GameMod.SPUN_OUT.isActive()) modMultiplier *= 0.9f; // not implemented: // EASY (0.5x), HALF_TIME (0.3x), diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index a40bdf28..a5744378 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -130,6 +130,9 @@ public class Utils { container.getInput().enableKeyRepeat(); container.setAlwaysRender(true); + int width = container.getWidth(); + int height = container.getHeight(); + // set the cursor try { // hide the native cursor @@ -141,7 +144,6 @@ public class Utils { loadCursor(); // create fonts - int height = container.getHeight(); float fontBase; if (height <= 600) fontBase = 9f; @@ -161,8 +163,12 @@ public class Utils { FONT_SMALL = new TrueTypeFont(font.deriveFont(fontBase), false); // set default game images - for (GameImage o : GameImage.values()) - o.setDefaultImage(); + for (GameImage img : GameImage.values()) + img.setDefaultImage(); + + // initialize game mods + for (GameMod mod : GameMod.values()) + mod.init(width, height); // tab image tab = new Image("selection-tab.png"); diff --git a/src/itdelatrisu/opsu/objects/Circle.java b/src/itdelatrisu/opsu/objects/Circle.java index 5cc9203f..0277a9d1 100644 --- a/src/itdelatrisu/opsu/objects/Circle.java +++ b/src/itdelatrisu/opsu/objects/Circle.java @@ -19,12 +19,12 @@ package itdelatrisu.opsu.objects; import itdelatrisu.opsu.GameImage; +import itdelatrisu.opsu.GameMod; import itdelatrisu.opsu.GameScore; import itdelatrisu.opsu.MusicController; import itdelatrisu.opsu.OsuHitObject; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.states.Game; -import itdelatrisu.opsu.states.Options; import org.newdawn.slick.Color; import org.newdawn.slick.GameContainer; @@ -161,7 +161,7 @@ public class Circle { public boolean update(boolean overlap) { int trackPosition = MusicController.getPosition(); int[] hitResultOffset = game.getHitResultOffsets(); - boolean isAutoMod = Options.isModActive(Options.MOD_AUTO); + boolean isAutoMod = GameMod.AUTO.isActive(); if (overlap || trackPosition > hitObject.time + hitResultOffset[GameScore.HIT_50]) { if (isAutoMod) // "auto" mod: catch any missed notes due to lag diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index bc79ccd3..f7b0f39c 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -19,13 +19,13 @@ package itdelatrisu.opsu.objects; import itdelatrisu.opsu.GameImage; +import itdelatrisu.opsu.GameMod; import itdelatrisu.opsu.GameScore; import itdelatrisu.opsu.MusicController; import itdelatrisu.opsu.OsuFile; import itdelatrisu.opsu.OsuHitObject; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.states.Game; -import itdelatrisu.opsu.states.Options; import java.io.File; @@ -497,7 +497,7 @@ public class Slider { int trackPosition = MusicController.getPosition(); int[] hitResultOffset = game.getHitResultOffsets(); int lastIndex = hitObject.sliderX.length - 1; - boolean isAutoMod = Options.isModActive(Options.MOD_AUTO); + boolean isAutoMod = GameMod.AUTO.isActive(); if (!sliderClicked) { // start circle time passed diff --git a/src/itdelatrisu/opsu/objects/Spinner.java b/src/itdelatrisu/opsu/objects/Spinner.java index c950884a..1442d153 100644 --- a/src/itdelatrisu/opsu/objects/Spinner.java +++ b/src/itdelatrisu/opsu/objects/Spinner.java @@ -19,13 +19,13 @@ package itdelatrisu.opsu.objects; import itdelatrisu.opsu.GameImage; +import itdelatrisu.opsu.GameMod; import itdelatrisu.opsu.GameScore; import itdelatrisu.opsu.MusicController; import itdelatrisu.opsu.OsuHitObject; import itdelatrisu.opsu.SoundController; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.states.Game; -import itdelatrisu.opsu.states.Options; import org.newdawn.slick.Color; import org.newdawn.slick.GameContainer; @@ -154,8 +154,7 @@ public class Spinner { int result; float ratio = rotations / rotationsNeeded; if (ratio >= 1.0f || - Options.isModActive(Options.MOD_AUTO) || - Options.isModActive(Options.MOD_SPUN_OUT)) { + GameMod.AUTO.isActive() || GameMod.SPUN_OUT.isActive()) { result = GameScore.HIT_300; SoundController.playSound(SoundController.SOUND_SPINNEROSU); } else if (ratio >= 0.8f) @@ -190,12 +189,12 @@ public class Spinner { } // spin automatically (TODO: correct rotation angles) - if (Options.isModActive(Options.MOD_AUTO)) { + if (GameMod.AUTO.isActive()) { // "auto" mod (fast) score.changeHealth(delta / 200f); // maintain health (TODO) rotate(delta / 20f); return false; - } else if (Options.isModActive(Options.MOD_SPUN_OUT)) { + } else if (GameMod.SPUN_OUT.isActive()) { // "spun out" mod (slow) score.changeHealth(delta / 200f); // maintain health (TODO) rotate(delta / 32f); diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 65e2f51e..f9d03ed2 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -20,6 +20,7 @@ package itdelatrisu.opsu.states; import itdelatrisu.opsu.GUIMenuButton; import itdelatrisu.opsu.GameImage; +import itdelatrisu.opsu.GameMod; import itdelatrisu.opsu.GameScore; import itdelatrisu.opsu.MusicController; import itdelatrisu.opsu.Opsu; @@ -295,7 +296,7 @@ public class Game extends BasicGameState { } } - if (Options.isModActive(Options.MOD_AUTO)) + if (GameMod.AUTO.isActive()) GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f); Utils.drawFPS(); Utils.drawCursor(); @@ -382,7 +383,7 @@ public class Game extends BasicGameState { // draw OsuHitObjectResult objects score.drawHitResults(trackPosition); - if (Options.isModActive(Options.MOD_AUTO)) + if (GameMod.AUTO.isActive()) GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f); // returning from pause screen @@ -494,7 +495,7 @@ public class Game extends BasicGameState { } // pause game if focus lost - if (!container.hasFocus() && !Options.isModActive(Options.MOD_AUTO)) { + if (!container.hasFocus() && !GameMod.AUTO.isActive()) { if (pauseTime < 0) { pausedMouseX = input.getMouseX(); pausedMouseY = input.getMouseY(); @@ -551,7 +552,7 @@ public class Game extends BasicGameState { int trackPosition = MusicController.getPosition(); if (pauseTime < 0 && breakTime <= 0 && trackPosition >= osu.objects[0].time && - !Options.isModActive(Options.MOD_AUTO)) { + !GameMod.AUTO.isActive()) { pausedMouseX = input.getMouseX(); pausedMouseY = input.getMouseY(); pausePulse = 0f; @@ -662,7 +663,7 @@ public class Game extends BasicGameState { } // "auto" mod: ignore user actions - if (Options.isModActive(Options.MOD_AUTO)) + if (GameMod.AUTO.isActive()) return; // circles @@ -883,8 +884,8 @@ public class Game extends BasicGameState { if (Options.getFixedHP() > 0f) HPDrainRate = Options.getFixedHP(); - // hard rock modifiers - if (Options.isModActive(Options.MOD_HARD_ROCK)) { + // "Hard Rock" modifiers + if (GameMod.HARD_ROCK.isActive()) { circleSize = Math.min(circleSize * 1.4f, 10); approachRate = Math.min(approachRate * 1.4f, 10); overallDifficulty = Math.min(overallDifficulty * 1.4f, 10); diff --git a/src/itdelatrisu/opsu/states/GameRanking.java b/src/itdelatrisu/opsu/states/GameRanking.java index 8c0e6843..31c6ae0c 100644 --- a/src/itdelatrisu/opsu/states/GameRanking.java +++ b/src/itdelatrisu/opsu/states/GameRanking.java @@ -19,6 +19,7 @@ package itdelatrisu.opsu.states; import itdelatrisu.opsu.GUIMenuButton; +import itdelatrisu.opsu.GameMod; import itdelatrisu.opsu.GameScore; import itdelatrisu.opsu.MusicController; import itdelatrisu.opsu.Opsu; @@ -107,11 +108,11 @@ public class GameRanking extends BasicGameState { score.drawRankingElements(g, width, height); // game mods - for (int i = Options.MOD_MAX - 1; i >= 0; i--) { - if (Options.isModActive(i)) { - Image modImage = Options.getModImage(i); + for (GameMod mod : GameMod.valuesReversed()) { + if (mod.isActive()) { + Image modImage = mod.getImage(); modImage.draw( - (width * 0.75f) + ((i - (Options.MOD_MAX / 2)) * modImage.getWidth() / 3f), + (width * 0.75f) + ((mod.getID() - (GameMod.size() / 2)) * modImage.getWidth() / 3f), height / 2f ); } diff --git a/src/itdelatrisu/opsu/states/Options.java b/src/itdelatrisu/opsu/states/Options.java index 1fa83dce..3a7da4ad 100644 --- a/src/itdelatrisu/opsu/states/Options.java +++ b/src/itdelatrisu/opsu/states/Options.java @@ -19,6 +19,7 @@ package itdelatrisu.opsu.states; import itdelatrisu.opsu.GUIMenuButton; +import itdelatrisu.opsu.GameMod; import itdelatrisu.opsu.Opsu; import itdelatrisu.opsu.SoundController; import itdelatrisu.opsu.Utils; @@ -95,27 +96,6 @@ public class Options extends BasicGameState { */ private static File skinDir; - /** - * Game mods. - */ - public static final int - MOD_NO_FAIL = 0, - MOD_HARD_ROCK = 1, - MOD_SUDDEN_DEATH = 2, - MOD_SPUN_OUT = 3, - MOD_AUTO = 4, - MOD_MAX = 5; // not a mod - - /** - * Whether a mod is active (indexed by MOD_* constants). - */ - private static boolean[] modsActive; - - /** - * Mod buttons. - */ - private static GUIMenuButton[] modButtons; - /** * Game options. */ @@ -417,37 +397,6 @@ public class Options extends BasicGameState { ((width - subtextWidth - tab.getWidth()) / 2) / TAB_MAX); for (int i = 0; i < optionTabs.length; i++) optionTabs[i] = new GUIMenuButton(tab, tabX + (i * tabOffset), tabY); - - // game mods - modsActive = new boolean[MOD_MAX]; - modButtons = new GUIMenuButton[MOD_MAX]; - Image noFailImage = new Image("selection-mod-nofail.png"); - float modScale = (height * 0.12f) / noFailImage.getHeight(); - noFailImage = noFailImage.getScaledCopy(modScale); - float modButtonOffsetX = noFailImage.getWidth() * 1.5f; - float modButtonX = (width / 2f) - (modButtonOffsetX * modButtons.length / 2.75f); - float modButtonY = (height * 0.8f) + (noFailImage.getHeight() / 2); - modButtons[MOD_NO_FAIL] = new GUIMenuButton( - noFailImage, modButtonX, modButtonY - ); - modButtons[MOD_HARD_ROCK] = new GUIMenuButton( - new Image("selection-mod-hardrock.png").getScaledCopy(modScale), - modButtonX + modButtonOffsetX, modButtonY - ); - modButtons[MOD_SUDDEN_DEATH] = new GUIMenuButton( - new Image("selection-mod-suddendeath.png").getScaledCopy(modScale), - modButtonX + (modButtonOffsetX * 2), modButtonY - ); - modButtons[MOD_SPUN_OUT] = new GUIMenuButton( - new Image("selection-mod-spunout.png").getScaledCopy(modScale), - modButtonX + (modButtonOffsetX * 3), modButtonY - ); - modButtons[MOD_AUTO] = new GUIMenuButton( - new Image("selection-mod-autoplay.png").getScaledCopy(modScale), - modButtonX + (modButtonOffsetX * 4), modButtonY - ); - for (int i = 0; i < modButtons.length; i++) - modButtons[i].getImage().setAlpha(0.5f); } @Override @@ -508,8 +457,8 @@ public class Options extends BasicGameState { // game mods Utils.FONT_LARGE.drawString(width / 30, height * 0.8f, "Game Mods:", Color.white); - for (int i = 0; i < modButtons.length; i++) - modButtons[i].draw(); + for (GameMod mod : GameMod.values()) + mod.draw(); Utils.getBackButton().draw(); @@ -551,26 +500,11 @@ public class Options extends BasicGameState { } // game mods - for (int i = 0; i < modButtons.length; i++) { - if (modButtons[i].contains(x, y)) { - boolean prev = modsActive[i]; - toggleMod(i); - - // mutually exclusive mods - if (modsActive[MOD_AUTO]) { - if (i == MOD_AUTO) { - if (modsActive[MOD_SPUN_OUT]) - toggleMod(MOD_SPUN_OUT); - if (modsActive[MOD_SUDDEN_DEATH]) - toggleMod(MOD_SUDDEN_DEATH); - } else if (i == MOD_SPUN_OUT || i == MOD_SUDDEN_DEATH) { - if (modsActive[i]) - toggleMod(i); - } - } else if (modsActive[MOD_SUDDEN_DEATH] && modsActive[MOD_NO_FAIL]) - toggleMod((i == MOD_SUDDEN_DEATH) ? MOD_NO_FAIL : MOD_SUDDEN_DEATH); - - if (modsActive[i] != prev) + for (GameMod mod : GameMod.values()) { + if (mod.contains(x, y)) { + boolean prevState = mod.isActive(); + mod.toggle(true); + if (mod.isActive() != prevState) SoundController.playSound(SoundController.SOUND_MENUCLICK); return; } @@ -961,30 +895,6 @@ public class Options extends BasicGameState { return option; } - /** - * Toggles the active status of a game mod. - * Note that this does not perform checks for mutual exclusivity. - * @param mod the game mod (MOD_* constants) - */ - private static void toggleMod(int mod) { - modButtons[mod].getImage().setAlpha(modsActive[mod] ? 0.5f : 1.0f); - modsActive[mod] = !modsActive[mod]; - } - - /** - * Returns whether or not a game mod is active. - * @param mod the game mod (MOD_* constants) - * @return true if the mod is active - */ - public static boolean isModActive(int mod) { return modsActive[mod]; } - - /** - * Returns the image associated with a game mod. - * @param mod the game mod (MOD_* constants) - * @return the associated image - */ - public static Image getModImage(int mod) { return modButtons[mod].getImage(); } - /** * Returns the target frame rate. * @return the target FPS