diff --git a/src/itdelatrisu/opsu/Container.java b/src/itdelatrisu/opsu/Container.java index 9de44c0c..e3e76a33 100644 --- a/src/itdelatrisu/opsu/Container.java +++ b/src/itdelatrisu/opsu/Container.java @@ -19,7 +19,6 @@ package itdelatrisu.opsu; import itdelatrisu.opsu.audio.MusicController; -import itdelatrisu.opsu.states.Options; import org.newdawn.slick.AppGameContainer; import org.newdawn.slick.Game; diff --git a/src/itdelatrisu/opsu/ErrorHandler.java b/src/itdelatrisu/opsu/ErrorHandler.java index bd84c905..af94d407 100644 --- a/src/itdelatrisu/opsu/ErrorHandler.java +++ b/src/itdelatrisu/opsu/ErrorHandler.java @@ -18,8 +18,6 @@ package itdelatrisu.opsu; -import itdelatrisu.opsu.states.Options; - import java.awt.Cursor; import java.awt.Desktop; import java.io.PrintWriter; diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index 25729d72..d7b56f24 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -18,8 +18,6 @@ package itdelatrisu.opsu; -import itdelatrisu.opsu.states.Options; - import java.io.File; import java.util.ArrayList; import java.util.List; diff --git a/src/itdelatrisu/opsu/GameMod.java b/src/itdelatrisu/opsu/GameMod.java index ba3b7f23..e43ae41a 100644 --- a/src/itdelatrisu/opsu/GameMod.java +++ b/src/itdelatrisu/opsu/GameMod.java @@ -65,7 +65,7 @@ public enum GameMod { private boolean active = false; /** - * The button containing the mod image (displayed in Options screen). + * The button containing the mod image (displayed in OptionsMenu screen). */ private MenuButton button; diff --git a/src/itdelatrisu/opsu/GameScore.java b/src/itdelatrisu/opsu/GameScore.java index 8991d909..ef922378 100644 --- a/src/itdelatrisu/opsu/GameScore.java +++ b/src/itdelatrisu/opsu/GameScore.java @@ -22,7 +22,6 @@ import itdelatrisu.opsu.audio.HitSound; import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.audio.SoundEffect; -import itdelatrisu.opsu.states.Options; import java.io.File; import java.util.HashMap; diff --git a/src/itdelatrisu/opsu/Opsu.java b/src/itdelatrisu/opsu/Opsu.java index a61f4e00..15542a09 100644 --- a/src/itdelatrisu/opsu/Opsu.java +++ b/src/itdelatrisu/opsu/Opsu.java @@ -23,7 +23,7 @@ import itdelatrisu.opsu.states.GamePauseMenu; import itdelatrisu.opsu.states.GameRanking; import itdelatrisu.opsu.states.MainMenu; import itdelatrisu.opsu.states.MainMenuExit; -import itdelatrisu.opsu.states.Options; +import itdelatrisu.opsu.states.OptionsMenu; import itdelatrisu.opsu.states.SongMenu; import itdelatrisu.opsu.states.Splash; @@ -83,7 +83,7 @@ public class Opsu extends StateBasedGame { addState(new Game(STATE_GAME)); addState(new GamePauseMenu(STATE_GAMEPAUSEMENU)); addState(new GameRanking(STATE_GAMERANKING)); - addState(new Options(STATE_OPTIONS)); + addState(new OptionsMenu(STATE_OPTIONS)); } /** diff --git a/src/itdelatrisu/opsu/states/Options.java b/src/itdelatrisu/opsu/Options.java similarity index 74% rename from src/itdelatrisu/opsu/states/Options.java rename to src/itdelatrisu/opsu/Options.java index 85211780..fda4225b 100644 --- a/src/itdelatrisu/opsu/states/Options.java +++ b/src/itdelatrisu/opsu/Options.java @@ -16,18 +16,7 @@ * along with opsu!. If not, see . */ -package itdelatrisu.opsu.states; - -import itdelatrisu.opsu.Container; -import itdelatrisu.opsu.ErrorHandler; -import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.GameMod; -import itdelatrisu.opsu.MenuButton; -import itdelatrisu.opsu.Opsu; -import itdelatrisu.opsu.OsuFile; -import itdelatrisu.opsu.Utils; -import itdelatrisu.opsu.audio.SoundController; -import itdelatrisu.opsu.audio.SoundEffect; +package itdelatrisu.opsu; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -44,22 +33,15 @@ import java.util.Locale; import java.util.concurrent.TimeUnit; import org.lwjgl.input.Keyboard; -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; -import org.newdawn.slick.state.StateBasedGame; -import org.newdawn.slick.state.transition.EmptyTransition; -import org.newdawn.slick.state.transition.FadeInTransition; import org.newdawn.slick.util.Log; /** - * "Game Options" state. + * Handles all user options. */ -public class Options extends BasicGameState { +public class Options { /** * Temporary folder for file conversions, auto-deleted upon successful exit. */ @@ -137,7 +119,7 @@ public class Options extends BasicGameState { /** * Game options. */ - private static enum GameOption { + public static enum GameOption { NULL (null, null), SCREEN_RESOLUTION ("Screen Resolution", "Restart (Ctrl+Shift+F5) to apply resolution changes.") { @Override @@ -346,22 +328,10 @@ public class Options extends BasicGameState { KEY_LEFT ("Left Game Key", "Select this option to input a key.") { @Override public String getValueString() { return Keyboard.getKeyName(getGameKeyLeft()); } - - @Override - public void click(GameContainer container) { - keyEntryLeft = true; - keyEntryRight = false; - } }, KEY_RIGHT ("Right Game Key", "Select this option to input a key.") { @Override public String getValueString() { return Keyboard.getKeyName(getGameKeyRight()); } - - @Override - public void click(GameContainer container) { - keyEntryLeft = false; - keyEntryRight = true; - } }, SHOW_UNICODE ("Prefer Non-English Metadata", "Where available, song titles will be shown in their native language.") { @Override @@ -441,96 +411,6 @@ public class Options extends BasicGameState { public void drag(GameContainer container, int d) {} }; - /** - * Option tab constants. - */ - private static final int - TAB_DISPLAY = 0, - TAB_MUSIC = 1, - TAB_GAMEPLAY = 2, - TAB_CUSTOM = 3, - TAB_MAX = 4; // not a tab - - /** - * Option tab names. - */ - private static final String[] TAB_NAMES = { - "Display", - "Music", - "Gameplay", - "Custom" - }; - - /** - * Option tab buttons. - */ - private static MenuButton[] optionTabs = new MenuButton[TAB_MAX]; - - /** - * Current tab. - */ - private static int currentTab; - - /** - * Display options. - */ - private static final GameOption[] displayOptions = { - GameOption.SCREEN_RESOLUTION, -// GameOption.FULLSCREEN, - GameOption.TARGET_FPS, - GameOption.SHOW_FPS, - GameOption.SHOW_UNICODE, - GameOption.SCREENSHOT_FORMAT, - GameOption.NEW_CURSOR, - GameOption.DYNAMIC_BACKGROUND, - GameOption.LOAD_VERBOSE - }; - - /** - * Music options. - */ - private static final GameOption[] musicOptions = { - GameOption.MASTER_VOLUME, - GameOption.MUSIC_VOLUME, - GameOption.EFFECT_VOLUME, - GameOption.HITSOUND_VOLUME, - GameOption.MUSIC_OFFSET, - GameOption.DISABLE_SOUNDS, - GameOption.ENABLE_THEME_SONG - }; - - /** - * Gameplay options. - */ - private static final GameOption[] gameplayOptions = { - GameOption.KEY_LEFT, - GameOption.KEY_RIGHT, - GameOption.BACKGROUND_DIM, - GameOption.FORCE_DEFAULT_PLAYFIELD, - GameOption.IGNORE_BEATMAP_SKINS, - GameOption.SHOW_HIT_LIGHTING, - GameOption.SHOW_COMBO_BURSTS, - GameOption.SHOW_PERFECT_HIT - }; - - /** - * Custom options. - */ - private static final GameOption[] customOptions = { - GameOption.FIXED_CS, - GameOption.FIXED_HP, - GameOption.FIXED_AR, - GameOption.FIXED_OD, - GameOption.CHECKPOINT - }; - - /** - * Max number of options displayed on one screen. - */ - private static int maxOptionsScreen = Math.max( - Math.max(displayOptions.length, musicOptions.length), - Math.max(gameplayOptions.length, customOptions.length)); - /** * Screen resolutions. */ @@ -736,334 +616,8 @@ public class Options extends BasicGameState { keyLeft = Keyboard.KEY_NONE, keyRight = Keyboard.KEY_NONE; - /** - * Key entry states. - */ - private static boolean keyEntryLeft = false, keyEntryRight = false; - - /** - * Game option coordinate modifiers (for drawing). - */ - private int textY, offsetY; - - // game-related variables - private GameContainer container; - private StateBasedGame game; - private Input input; - private Graphics g; - private int state; - - public Options(int state) { - this.state = state; - } - - @Override - public void init(GameContainer container, StateBasedGame game) - throws SlickException { - this.container = container; - this.game = game; - this.input = container.getInput(); - this.g = container.getGraphics(); - - int width = container.getWidth(); - int height = container.getHeight(); - - // game option coordinate modifiers - textY = 20 + (Utils.FONT_XLARGE.getLineHeight() * 3 / 2); - offsetY = (int) (((height * 0.8f) - textY) / maxOptionsScreen); - - // option tabs - Image tab = GameImage.MENU_TAB.getImage(); - int subtextWidth = Utils.FONT_DEFAULT.getWidth("Click or drag an option to change it."); - float tabX = (width / 50) + (tab.getWidth() / 2f); - float tabY = 15 + Utils.FONT_XLARGE.getLineHeight() + (tab.getHeight() / 2f); - int tabOffset = Math.min(tab.getWidth(), - ((width - subtextWidth - tab.getWidth()) / 2) / TAB_MAX); - for (int i = 0; i < optionTabs.length; i++) - optionTabs[i] = new MenuButton(tab, tabX + (i * tabOffset), tabY); - } - - @Override - public void render(GameContainer container, StateBasedGame game, Graphics g) - throws SlickException { - g.setBackground(Utils.COLOR_BLACK_ALPHA); - - int width = container.getWidth(); - int height = container.getHeight(); - int mouseX = input.getMouseX(), mouseY = input.getMouseY(); - - // title - Utils.FONT_XLARGE.drawString( - (width / 2) - (Utils.FONT_XLARGE.getWidth("GAME OPTIONS") / 2), 10, - "GAME OPTIONS", Color.white - ); - Utils.FONT_DEFAULT.drawString( - (width / 2) - (Utils.FONT_DEFAULT.getWidth("Click or drag an option to change it.") / 2), - 10 + Utils.FONT_XLARGE.getLineHeight(), - "Click or drag an option to change it.", Color.white - ); - - // game options - g.setLineWidth(1f); - switch (currentTab) { - case TAB_DISPLAY: - for (int i = 0; i < displayOptions.length; i++) - drawOption(displayOptions[i], i); - break; - case TAB_MUSIC: - for (int i = 0; i < musicOptions.length; i++) - drawOption(musicOptions[i], i); - break; - case TAB_GAMEPLAY: - for (int i = 0; i < gameplayOptions.length; i++) - drawOption(gameplayOptions[i], i); - break; - case TAB_CUSTOM: - for (int i = 0; i < customOptions.length; i++) - drawOption(customOptions[i], i); - break; - } - - // option tabs - int hoverTab = -1; - for (int i = 0; i < optionTabs.length; i++) { - if (optionTabs[i].contains(mouseX, mouseY)) { - hoverTab = i; - break; - } - } - for (int i = optionTabs.length - 1; i >= 0; i--) { - if (i != currentTab) - Utils.drawTab(optionTabs[i].getX(), optionTabs[i].getY(), - TAB_NAMES[i], false, i == hoverTab); - } - Utils.drawTab(optionTabs[currentTab].getX(), optionTabs[currentTab].getY(), - TAB_NAMES[currentTab], true, false); - g.setColor(Color.white); - g.setLineWidth(2f); - float lineY = optionTabs[0].getY() + (GameImage.MENU_TAB.getImage().getHeight() / 2f); - g.drawLine(0, lineY, width, lineY); - g.resetLineWidth(); - - // game mods - Utils.FONT_LARGE.drawString(width / 30, height * 0.8f, "Game Mods:", Color.white); - for (GameMod mod : GameMod.values()) - mod.draw(); - - Utils.getBackButton().draw(); - - // key entry state - if (keyEntryLeft || keyEntryRight) { - g.setColor(Utils.COLOR_BLACK_ALPHA); - g.fillRect(0, 0, width, height); - g.setColor(Color.white); - Utils.FONT_LARGE.drawString( - (width / 2) - (Utils.FONT_LARGE.getWidth("Please enter a letter or digit.") / 2), - (height / 2) - Utils.FONT_LARGE.getLineHeight(), "Please enter a letter or digit." - ); - } - - Utils.drawVolume(g); - Utils.drawFPS(); - Utils.drawCursor(); - } - - @Override - public void update(GameContainer container, StateBasedGame game, int delta) - throws SlickException { - Utils.updateCursor(delta); - Utils.updateVolumeDisplay(delta); - int mouseX = input.getMouseX(), mouseY = input.getMouseY(); - Utils.getBackButton().hoverUpdate(delta, mouseX, mouseY); - for (GameMod mod : GameMod.values()) - mod.hoverUpdate(delta, mouseX, mouseY); - } - - @Override - public int getID() { return state; } - - @Override - public void mousePressed(int button, int x, int y) { - // key entry state - if (keyEntryLeft || keyEntryRight) { - keyEntryLeft = keyEntryRight = false; - return; - } - - // check mouse button - if (button == Input.MOUSE_MIDDLE_BUTTON) - return; - - // back - if (Utils.getBackButton().contains(x, y)) { - SoundController.playSound(SoundEffect.MENUBACK); - game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition(Color.black)); - return; - } - - // option tabs - for (int i = 0; i < optionTabs.length; i++) { - if (optionTabs[i].contains(x, y)) { - if (i != currentTab) { - currentTab = i; - SoundController.playSound(SoundEffect.MENUCLICK); - } - return; - } - } - - // game mods - for (GameMod mod : GameMod.values()) { - if (mod.contains(x, y)) { - boolean prevState = mod.isActive(); - mod.toggle(true); - if (mod.isActive() != prevState) - SoundController.playSound(SoundEffect.MENUCLICK); - return; - } - } - - // options (click only) - GameOption option = getClickedOption(y); - if (option != GameOption.NULL) - option.click(container); - } - - @Override - public void mouseDragged(int oldx, int oldy, int newx, int newy) { - // key entry state - if (keyEntryLeft || keyEntryRight) - return; - - // check mouse button (right click scrolls faster) - int multiplier; - if (input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON)) - multiplier = 4; - else if (input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)) - multiplier = 1; - else - return; - - // get direction - int diff = newx - oldx; - if (diff == 0) - return; - diff = ((diff > 0) ? 1 : -1) * multiplier; - - // options (drag only) - GameOption option = getClickedOption(oldy); - if (option != GameOption.NULL) - option.drag(container, diff); - } - - @Override - public void keyPressed(int key, char c) { - // key entry state - if (keyEntryLeft || keyEntryRight) { - if (Character.isLetterOrDigit(c)) { - if (keyEntryLeft && keyRight != key) - keyLeft = key; - else if (keyEntryRight && keyLeft != key) - keyRight = key; - } - keyEntryLeft = keyEntryRight = false; - return; - } - - switch (key) { - case Input.KEY_ESCAPE: - SoundController.playSound(SoundEffect.MENUBACK); - game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition(Color.black)); - break; - case Input.KEY_F5: - // restart application - if ((input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) && - (input.isKeyDown(Input.KEY_RSHIFT) || input.isKeyDown(Input.KEY_LSHIFT))) { - container.setForceExit(false); - container.exit(); - } - break; - case Input.KEY_F12: - Utils.takeScreenShot(); - break; - case Input.KEY_TAB: - // change tabs - int i = 1; - if (input.isKeyDown(Input.KEY_LSHIFT) || input.isKeyDown(Input.KEY_RSHIFT)) - i = TAB_MAX - 1; - currentTab = (currentTab + i) % TAB_MAX; - SoundController.playSound(SoundEffect.MENUCLICK); - break; - default: - // check mod shortcut keys - for (GameMod mod : GameMod.values()) { - if (key == mod.getKey()) { - mod.toggle(true); - break; - } - } - break; - } - } - - @Override - public void enter(GameContainer container, StateBasedGame game) - throws SlickException { - currentTab = TAB_DISPLAY; - Utils.getBackButton().setScale(1f); - for (GameMod mod : GameMod.values()) - mod.setScale(1f); - } - - /** - * Draws a game option. - * @param option the option (OPTION_* constant) - * @param pos the position to draw at - */ - private void drawOption(GameOption option, int pos) { - int width = container.getWidth(); - int textHeight = Utils.FONT_LARGE.getLineHeight(); - float y = textY + (pos * offsetY); - - Utils.FONT_LARGE.drawString(width / 30, y, option.getName(), Color.white); - Utils.FONT_LARGE.drawString(width / 2, y, option.getValueString(), Color.white); - Utils.FONT_SMALL.drawString(width / 30, y + textHeight, option.getDescription(), Color.white); - g.setColor(Utils.COLOR_WHITE_ALPHA); - g.drawLine(0, y + textHeight, width, y + textHeight); - } - - /** - * Returns the option clicked. - * If no option clicked, -1 will be returned. - * @param y the y coordinate - * @return the option (OPTION_* constant) - */ - private GameOption getClickedOption(int y) { - GameOption option = GameOption.NULL; - - if (y < textY || y > textY + (offsetY * maxOptionsScreen)) - return option; - - int index = (y - textY + Utils.FONT_LARGE.getLineHeight()) / offsetY; - switch (currentTab) { - case TAB_DISPLAY: - if (index < displayOptions.length) - option = displayOptions[index]; - break; - case TAB_MUSIC: - if (index < musicOptions.length) - option = musicOptions[index]; - break; - case TAB_GAMEPLAY: - if (index < gameplayOptions.length) - option = gameplayOptions[index]; - break; - case TAB_CUSTOM: - if (index < customOptions.length) - option = customOptions[index]; - } - return option; - } + // This class should not be instantiated. + private Options() {} /** * Returns the target frame rate. @@ -1296,6 +850,18 @@ public class Options extends BasicGameState { return keyRight; } + /** + * Sets the left game key. + * @param key the keyboard key + */ + public static void setGameKeyLeft(int key) { keyLeft = key; } + + /** + * Sets the right game key. + * @param key the keyboard key + */ + public static void setGameKeyRight(int key) { keyRight = key; } + /** * Returns the beatmap directory. * If invalid, this will attempt to search for the directory, diff --git a/src/itdelatrisu/opsu/OsuFile.java b/src/itdelatrisu/opsu/OsuFile.java index 053057b4..e8c63741 100644 --- a/src/itdelatrisu/opsu/OsuFile.java +++ b/src/itdelatrisu/opsu/OsuFile.java @@ -18,8 +18,6 @@ package itdelatrisu.opsu; -import itdelatrisu.opsu.states.Options; - import java.io.File; import java.util.ArrayList; import java.util.HashMap; diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index a895d756..4f72b763 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -20,7 +20,6 @@ package itdelatrisu.opsu; import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.audio.SoundEffect; -import itdelatrisu.opsu.states.Options; import java.awt.Font; import java.awt.image.BufferedImage; @@ -151,7 +150,7 @@ public class Utils { // game settings container.setTargetFrameRate(Options.getTargetFPS()); container.setVSync(Options.getTargetFPS() == 60); - container.setMusicVolume(Options.getMusicVolume()); + container.setMusicVolume(Options.getMusicVolume() * Options.getMasterVolume()); container.setShowFPS(false); container.getInput().enableKeyRepeat(); container.setAlwaysRender(true); diff --git a/src/itdelatrisu/opsu/audio/MusicController.java b/src/itdelatrisu/opsu/audio/MusicController.java index 5053d99a..821c0ca6 100644 --- a/src/itdelatrisu/opsu/audio/MusicController.java +++ b/src/itdelatrisu/opsu/audio/MusicController.java @@ -19,9 +19,9 @@ package itdelatrisu.opsu.audio; import itdelatrisu.opsu.ErrorHandler; +import itdelatrisu.opsu.Options; import itdelatrisu.opsu.OsuFile; import itdelatrisu.opsu.OsuParser; -import itdelatrisu.opsu.states.Options; import java.io.File; import java.lang.reflect.Field; diff --git a/src/itdelatrisu/opsu/audio/SoundController.java b/src/itdelatrisu/opsu/audio/SoundController.java index b8ff5a62..6e25086f 100644 --- a/src/itdelatrisu/opsu/audio/SoundController.java +++ b/src/itdelatrisu/opsu/audio/SoundController.java @@ -19,9 +19,9 @@ package itdelatrisu.opsu.audio; import itdelatrisu.opsu.ErrorHandler; +import itdelatrisu.opsu.Options; import itdelatrisu.opsu.OsuHitObject; import itdelatrisu.opsu.audio.HitSound.SampleSet; -import itdelatrisu.opsu.states.Options; import java.io.IOException; import java.net.URL; diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index df9f63ee..26dd8a66 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -24,6 +24,7 @@ import itdelatrisu.opsu.GameMod; import itdelatrisu.opsu.GameScore; import itdelatrisu.opsu.MenuButton; import itdelatrisu.opsu.Opsu; +import itdelatrisu.opsu.Options; import itdelatrisu.opsu.OsuFile; import itdelatrisu.opsu.OsuHitObject; import itdelatrisu.opsu.OsuTimingPoint; diff --git a/src/itdelatrisu/opsu/states/GamePauseMenu.java b/src/itdelatrisu/opsu/states/GamePauseMenu.java index 54d38854..b5971712 100644 --- a/src/itdelatrisu/opsu/states/GamePauseMenu.java +++ b/src/itdelatrisu/opsu/states/GamePauseMenu.java @@ -21,6 +21,7 @@ package itdelatrisu.opsu.states; import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.MenuButton; import itdelatrisu.opsu.Opsu; +import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.SoundController; diff --git a/src/itdelatrisu/opsu/states/MainMenu.java b/src/itdelatrisu/opsu/states/MainMenu.java index 8894a865..35494b8f 100644 --- a/src/itdelatrisu/opsu/states/MainMenu.java +++ b/src/itdelatrisu/opsu/states/MainMenu.java @@ -22,6 +22,7 @@ import itdelatrisu.opsu.ErrorHandler; import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.MenuButton; import itdelatrisu.opsu.Opsu; +import itdelatrisu.opsu.Options; import itdelatrisu.opsu.OsuFile; import itdelatrisu.opsu.OsuGroupList; import itdelatrisu.opsu.OsuGroupNode; diff --git a/src/itdelatrisu/opsu/states/OptionsMenu.java b/src/itdelatrisu/opsu/states/OptionsMenu.java new file mode 100644 index 00000000..2f73b2dd --- /dev/null +++ b/src/itdelatrisu/opsu/states/OptionsMenu.java @@ -0,0 +1,470 @@ +/* + * opsu! - an open-source osu! client + * Copyright (C) 2014, 2015 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.states; + +import java.util.Arrays; +import java.util.Collections; + +import itdelatrisu.opsu.GameImage; +import itdelatrisu.opsu.GameMod; +import itdelatrisu.opsu.MenuButton; +import itdelatrisu.opsu.Opsu; +import itdelatrisu.opsu.Options; +import itdelatrisu.opsu.Options.GameOption; +import itdelatrisu.opsu.Utils; +import itdelatrisu.opsu.audio.SoundController; +import itdelatrisu.opsu.audio.SoundEffect; + +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; +import org.newdawn.slick.state.StateBasedGame; +import org.newdawn.slick.state.transition.EmptyTransition; +import org.newdawn.slick.state.transition.FadeInTransition; + +/** + * "Game OptionsMenu" state. + */ +public class OptionsMenu extends BasicGameState { + /** + * Option tabs. + */ + private enum OptionTab { + DISPLAY ("Display", new GameOption[] { + GameOption.SCREEN_RESOLUTION, +// GameOption.FULLSCREEN, + GameOption.TARGET_FPS, + GameOption.SHOW_FPS, + GameOption.SHOW_UNICODE, + GameOption.SCREENSHOT_FORMAT, + GameOption.NEW_CURSOR, + GameOption.DYNAMIC_BACKGROUND, + GameOption.LOAD_VERBOSE + }), + MUSIC ("Music", new GameOption[] { + GameOption.MASTER_VOLUME, + GameOption.MUSIC_VOLUME, + GameOption.EFFECT_VOLUME, + GameOption.HITSOUND_VOLUME, + GameOption.MUSIC_OFFSET, + GameOption.DISABLE_SOUNDS, + GameOption.ENABLE_THEME_SONG + }), + GAMEPLAY ("Gameplay", new GameOption[] { + GameOption.KEY_LEFT, + GameOption.KEY_RIGHT, + GameOption.BACKGROUND_DIM, + GameOption.FORCE_DEFAULT_PLAYFIELD, + GameOption.IGNORE_BEATMAP_SKINS, + GameOption.SHOW_HIT_LIGHTING, + GameOption.SHOW_COMBO_BURSTS, + GameOption.SHOW_PERFECT_HIT + }), + CUSTOM ("Custom", new GameOption[] { + GameOption.FIXED_CS, + GameOption.FIXED_HP, + GameOption.FIXED_AR, + GameOption.FIXED_OD, + GameOption.CHECKPOINT + }); + + /** + * Total number of mods. + */ + public static final int SIZE = OptionTab.values().length; + + /** + * Enum values. + */ + private static OptionTab[] values = OptionTab.values(); + + /** + * Returns an array of OptionTab objects in reverse order. + * @return all tabs in reverse order + */ + public static OptionTab[] valuesReversed() { + OptionTab[] tabs = OptionTab.values(); + Collections.reverse(Arrays.asList(tabs)); + return tabs; + } + + /** + * Tab name. + */ + private String name; + + /** + * Options array. + */ + public GameOption[] options; + + /** + * Associated tab button. + */ + public MenuButton button; + + /** + * Constructor. + * @param name the tab name + * @param options the options to display under the tab + */ + OptionTab(String name, GameOption[] options) { + this.name = name; + this.options = options; + } + + /** + * Returns the tab name. + */ + public String getName() { return name; } + + /** + * Returns the next tab. + */ + public OptionTab next() { return values[(this.ordinal() + 1) % values.length]; } + + /** + * Returns the previous tab. + */ + public OptionTab prev() { return values[(this.ordinal() + (SIZE - 1)) % values.length]; } + } + + /** + * Current tab. + */ + private OptionTab currentTab; + + /** + * Max number of options displayed on one screen. + */ + private int maxOptionsScreen = Math.max( + Math.max(OptionTab.DISPLAY.options.length, OptionTab.MUSIC.options.length), + Math.max(OptionTab.GAMEPLAY.options.length, OptionTab.CUSTOM.options.length)); + + /** + * Key entry states. + */ + private boolean keyEntryLeft = false, keyEntryRight = false; + + /** + * Game option coordinate modifiers (for drawing). + */ + private int textY, offsetY; + + // game-related variables + private GameContainer container; + private StateBasedGame game; + private Input input; + private Graphics g; + private int state; + + public OptionsMenu(int state) { + this.state = state; + } + + @Override + public void init(GameContainer container, StateBasedGame game) + throws SlickException { + this.container = container; + this.game = game; + this.input = container.getInput(); + this.g = container.getGraphics(); + + int width = container.getWidth(); + int height = container.getHeight(); + + // game option coordinate modifiers + textY = 20 + (Utils.FONT_XLARGE.getLineHeight() * 3 / 2); + offsetY = (int) (((height * 0.8f) - textY) / maxOptionsScreen); + + // option tabs + Image tabImage = GameImage.MENU_TAB.getImage(); + int subtextWidth = Utils.FONT_DEFAULT.getWidth("Click or drag an option to change it."); + float tabX = (width / 50) + (tabImage.getWidth() / 2f); + float tabY = 15 + Utils.FONT_XLARGE.getLineHeight() + (tabImage.getHeight() / 2f); + int tabOffset = Math.min(tabImage.getWidth(), + ((width - subtextWidth - tabImage.getWidth()) / 2) / OptionTab.SIZE); + for (OptionTab tab : OptionTab.values()) + tab.button = new MenuButton(tabImage, tabX + (tab.ordinal() * tabOffset), tabY); + } + + @Override + public void render(GameContainer container, StateBasedGame game, Graphics g) + throws SlickException { + g.setBackground(Utils.COLOR_BLACK_ALPHA); + + int width = container.getWidth(); + int height = container.getHeight(); + int mouseX = input.getMouseX(), mouseY = input.getMouseY(); + + // title + Utils.FONT_XLARGE.drawString( + (width / 2) - (Utils.FONT_XLARGE.getWidth("GAME OPTIONS") / 2), 10, + "GAME OPTIONS", Color.white + ); + Utils.FONT_DEFAULT.drawString( + (width / 2) - (Utils.FONT_DEFAULT.getWidth("Click or drag an option to change it.") / 2), + 10 + Utils.FONT_XLARGE.getLineHeight(), + "Click or drag an option to change it.", Color.white + ); + + // game options + g.setLineWidth(1f); + for (int i = 0; i < currentTab.options.length; i++) + drawOption(currentTab.options[i], i); + + // option tabs + OptionTab hoverTab = null; + for (OptionTab tab : OptionTab.values()) { + if (tab.button.contains(mouseX, mouseY)) { + hoverTab = tab; + break; + } + } + for (OptionTab tab : OptionTab.valuesReversed()) { + if (tab != currentTab) + Utils.drawTab(tab.button.getX(), tab.button.getY(), + tab.getName(), false, tab == hoverTab); + } + Utils.drawTab(currentTab.button.getX(), currentTab.button.getY(), + currentTab.getName(), true, false); + g.setColor(Color.white); + g.setLineWidth(2f); + float lineY = OptionTab.DISPLAY.button.getY() + (GameImage.MENU_TAB.getImage().getHeight() / 2f); + g.drawLine(0, lineY, width, lineY); + g.resetLineWidth(); + + // game mods + Utils.FONT_LARGE.drawString(width / 30, height * 0.8f, "Game Mods:", Color.white); + for (GameMod mod : GameMod.values()) + mod.draw(); + + Utils.getBackButton().draw(); + + // key entry state + if (keyEntryLeft || keyEntryRight) { + g.setColor(Utils.COLOR_BLACK_ALPHA); + g.fillRect(0, 0, width, height); + g.setColor(Color.white); + Utils.FONT_LARGE.drawString( + (width / 2) - (Utils.FONT_LARGE.getWidth("Please enter a letter or digit.") / 2), + (height / 2) - Utils.FONT_LARGE.getLineHeight(), "Please enter a letter or digit." + ); + } + + Utils.drawVolume(g); + Utils.drawFPS(); + Utils.drawCursor(); + } + + @Override + public void update(GameContainer container, StateBasedGame game, int delta) + throws SlickException { + Utils.updateCursor(delta); + Utils.updateVolumeDisplay(delta); + int mouseX = input.getMouseX(), mouseY = input.getMouseY(); + Utils.getBackButton().hoverUpdate(delta, mouseX, mouseY); + for (GameMod mod : GameMod.values()) + mod.hoverUpdate(delta, mouseX, mouseY); + } + + @Override + public int getID() { return state; } + + @Override + public void mousePressed(int button, int x, int y) { + // key entry state + if (keyEntryLeft || keyEntryRight) { + keyEntryLeft = keyEntryRight = false; + return; + } + + // check mouse button + if (button == Input.MOUSE_MIDDLE_BUTTON) + return; + + // back + if (Utils.getBackButton().contains(x, y)) { + SoundController.playSound(SoundEffect.MENUBACK); + game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition(Color.black)); + return; + } + + // option tabs + for (OptionTab tab : OptionTab.values()) { + if (tab.button.contains(x, y)) { + if (tab != currentTab) { + currentTab = tab; + SoundController.playSound(SoundEffect.MENUCLICK); + } + return; + } + } + + // game mods + for (GameMod mod : GameMod.values()) { + if (mod.contains(x, y)) { + boolean prevState = mod.isActive(); + mod.toggle(true); + if (mod.isActive() != prevState) + SoundController.playSound(SoundEffect.MENUCLICK); + return; + } + } + + // options (click only) + GameOption option = getClickedOption(y); + if (option != GameOption.NULL) + option.click(container); + + // special key entry states + if (option == GameOption.KEY_LEFT) { + keyEntryLeft = true; + keyEntryRight = false; + } else if (option == GameOption.KEY_RIGHT) { + keyEntryLeft = false; + keyEntryRight = true; + } + } + + @Override + public void mouseDragged(int oldx, int oldy, int newx, int newy) { + // key entry state + if (keyEntryLeft || keyEntryRight) + return; + + // check mouse button (right click scrolls faster) + int multiplier; + if (input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON)) + multiplier = 4; + else if (input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)) + multiplier = 1; + else + return; + + // get direction + int diff = newx - oldx; + if (diff == 0) + return; + diff = ((diff > 0) ? 1 : -1) * multiplier; + + // options (drag only) + GameOption option = getClickedOption(oldy); + if (option != GameOption.NULL) + option.drag(container, diff); + } + + @Override + public void keyPressed(int key, char c) { + // key entry state + if (keyEntryLeft || keyEntryRight) { + if (Character.isLetterOrDigit(c)) { + if (keyEntryLeft && Options.getGameKeyRight() != key) + Options.setGameKeyLeft(key); + else if (keyEntryRight && Options.getGameKeyLeft() != key) + Options.setGameKeyRight(key); + } + keyEntryLeft = keyEntryRight = false; + return; + } + + switch (key) { + case Input.KEY_ESCAPE: + SoundController.playSound(SoundEffect.MENUBACK); + game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition(Color.black)); + break; + case Input.KEY_F5: + // restart application + if ((input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) && + (input.isKeyDown(Input.KEY_RSHIFT) || input.isKeyDown(Input.KEY_LSHIFT))) { + container.setForceExit(false); + container.exit(); + } + break; + case Input.KEY_F12: + Utils.takeScreenShot(); + break; + case Input.KEY_TAB: + // change tabs + if (input.isKeyDown(Input.KEY_LSHIFT) || input.isKeyDown(Input.KEY_RSHIFT)) + currentTab = currentTab.prev(); + else + currentTab = currentTab.next(); + SoundController.playSound(SoundEffect.MENUCLICK); + break; + default: + // check mod shortcut keys + for (GameMod mod : GameMod.values()) { + if (key == mod.getKey()) { + mod.toggle(true); + break; + } + } + break; + } + } + + @Override + public void enter(GameContainer container, StateBasedGame game) + throws SlickException { + currentTab = OptionTab.DISPLAY; + Utils.getBackButton().setScale(1f); + for (GameMod mod : GameMod.values()) + mod.setScale(1f); + } + + /** + * Draws a game option. + * @param option the option + * @param pos the position to draw at + */ + private void drawOption(GameOption option, int pos) { + int width = container.getWidth(); + int textHeight = Utils.FONT_LARGE.getLineHeight(); + float y = textY + (pos * offsetY); + + Utils.FONT_LARGE.drawString(width / 30, y, option.getName(), Color.white); + Utils.FONT_LARGE.drawString(width / 2, y, option.getValueString(), Color.white); + Utils.FONT_SMALL.drawString(width / 30, y + textHeight, option.getDescription(), Color.white); + g.setColor(Utils.COLOR_WHITE_ALPHA); + g.drawLine(0, y + textHeight, width, y + textHeight); + } + + /** + * Returns the option clicked. + * If no option clicked, -1 will be returned. + * @param y the y coordinate + * @return the option + */ + private GameOption getClickedOption(int y) { + GameOption option = GameOption.NULL; + + if (y < textY || y > textY + (offsetY * maxOptionsScreen)) + return option; + + int index = (y - textY + Utils.FONT_LARGE.getLineHeight()) / offsetY; + if (index < currentTab.options.length) + option = currentTab.options[index]; + return option; + } +} diff --git a/src/itdelatrisu/opsu/states/Splash.java b/src/itdelatrisu/opsu/states/Splash.java index f934eb69..2f06687e 100644 --- a/src/itdelatrisu/opsu/states/Splash.java +++ b/src/itdelatrisu/opsu/states/Splash.java @@ -20,6 +20,7 @@ package itdelatrisu.opsu.states; import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.Opsu; +import itdelatrisu.opsu.Options; import itdelatrisu.opsu.OsuGroupList; import itdelatrisu.opsu.OsuParser; import itdelatrisu.opsu.OszUnpacker;