diff --git a/res/volume-bg.png b/res/volume-bg.png new file mode 100644 index 00000000..9858abce Binary files /dev/null and b/res/volume-bg.png differ diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index 02feaf65..e27afae4 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -208,6 +208,12 @@ public enum GameImage { SCORE_X ("score-x", "png"), // Non-Game Components + VOLUME ("volume-bg", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.3f) / img.getHeight()); + } + }, MENU_BACK ("menu-back", "png", false, false) { @Override protected Image process_sub(Image img, int w, int h) { diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index 5d1b6555..b804ca94 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -43,6 +43,7 @@ import org.lwjgl.opengl.GL11; import org.newdawn.slick.Animation; 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; @@ -112,6 +113,16 @@ public class Utils { cursorX = new LinkedList(), cursorY = new LinkedList(); + /** + * Time to show volume image, in milliseconds. + */ + private static final int VOLUME_DISPLAY_TIME = 1500; + + /** + * Volume display elapsed time. + */ + private static int volumeDisplay = -1; + /** * Set of all Unicode strings already loaded. */ @@ -517,6 +528,63 @@ public class Utils { ); } + /** + * Draws the volume bar on the middle right-hand side of the game container. + * Only draws if the volume has recently been changed using with {@link #changeVolume(int)}. + * @param g the graphics context + */ + public static void drawVolume(Graphics g) { + if (volumeDisplay == -1) + return; + + int width = container.getWidth(), height = container.getHeight(); + Image img = GameImage.VOLUME.getImage(); + + // move image in/out + float xOffset = 0; + float ratio = (float) volumeDisplay / VOLUME_DISPLAY_TIME; + if (ratio <= 0.1f) + xOffset = img.getWidth() * (1 - (ratio * 10f)); + else if (ratio >= 0.9f) + xOffset = img.getWidth() * (1 - ((1 - ratio) * 10f)); + + img.drawCentered(width - img.getWidth() / 2f + xOffset, height / 2f); + float barHeight = img.getHeight() * 0.9f; + float volume = Options.getMasterVolume(); + g.setColor(Color.white); + g.fillRoundRect( + width - (img.getWidth() * 0.368f) + xOffset, + (height / 2f) - (img.getHeight() * 0.47f) + (barHeight * (1 - volume)), + img.getWidth() * 0.15f, barHeight * volume, 3 + ); + } + + /** + * Updates volume display by a delta interval. + * @param delta the delta interval since the last call + */ + public static void updateVolumeDisplay(int delta) { + if (volumeDisplay == -1) + return; + + volumeDisplay += delta; + if (volumeDisplay > VOLUME_DISPLAY_TIME) + volumeDisplay = -1; + } + + /** + * Changes the master volume by a unit (positive or negative). + * @param units the number of units + */ + public static void changeVolume(int units) { + final float UNIT_OFFSET = 0.05f; + Options.setMasterVolume(container, Utils.getBoundedValue(Options.getMasterVolume(), UNIT_OFFSET * units, 0f, 1f)); + if (volumeDisplay == -1) + volumeDisplay = 0; + else if (volumeDisplay >= VOLUME_DISPLAY_TIME / 10) + volumeDisplay = VOLUME_DISPLAY_TIME / 10; + } + /** * Takes a screenshot. * @author http://wiki.lwjgl.org/index.php?title=Taking_Screen_Shots diff --git a/src/itdelatrisu/opsu/audio/MusicController.java b/src/itdelatrisu/opsu/audio/MusicController.java index 2fdca837..5ce69f93 100644 --- a/src/itdelatrisu/opsu/audio/MusicController.java +++ b/src/itdelatrisu/opsu/audio/MusicController.java @@ -133,7 +133,7 @@ public class MusicController { */ public static void playAt(final int position, final boolean loop) { if (trackExists()) { - SoundStore.get().setMusicVolume(Options.getMusicVolume()); + setVolume(Options.getMusicVolume() * Options.getMasterVolume()); player.setPosition(position / 1000f); if (loop) player.loop(); @@ -303,7 +303,8 @@ public class MusicController { * Toggles the volume dim state of the current track. */ public static void toggleTrackDimmed() { - setVolume((trackDimmed) ? Options.getMusicVolume() : Options.getMusicVolume() / 3f); + float volume = Options.getMusicVolume() * Options.getMasterVolume(); + setVolume((trackDimmed) ? volume : volume / 3f); trackDimmed = !trackDimmed; } diff --git a/src/itdelatrisu/opsu/audio/SoundController.java b/src/itdelatrisu/opsu/audio/SoundController.java index 03c4dba7..b8ff5a62 100644 --- a/src/itdelatrisu/opsu/audio/SoundController.java +++ b/src/itdelatrisu/opsu/audio/SoundController.java @@ -169,7 +169,7 @@ public class SoundController { * @param s the sound effect */ public static void playSound(SoundComponent s) { - playClip(s.getClip(), Options.getEffectVolume()); + playClip(s.getClip(), Options.getEffectVolume() * Options.getMasterVolume()); } /** @@ -180,7 +180,7 @@ public class SoundController { if (hitSound < 0) return; - float volume = Options.getHitSoundVolume() * sampleVolumeMultiplier; + float volume = Options.getHitSoundVolume() * sampleVolumeMultiplier * Options.getMasterVolume(); if (volume == 0f) return; @@ -202,7 +202,7 @@ public class SoundController { * @param s the hit sound */ public static void playHitSound(SoundComponent s) { - playClip(s.getClip(), Options.getHitSoundVolume() * sampleVolumeMultiplier); + playClip(s.getClip(), Options.getHitSoundVolume() * sampleVolumeMultiplier * Options.getMasterVolume()); } /** diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 6d8b52f6..5ee08b62 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -421,6 +421,7 @@ public class Game extends BasicGameState { cursorCirclePulse.drawCentered(pausedMouseX, pausedMouseY); } + Utils.drawVolume(g); Utils.drawFPS(); Utils.drawCursor(); } @@ -429,6 +430,7 @@ public class Game extends BasicGameState { public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException { Utils.updateCursor(delta); + Utils.updateVolumeDisplay(delta); int mouseX = input.getMouseX(), mouseY = input.getMouseY(); skipButton.hoverUpdate(delta, mouseX, mouseY); @@ -707,6 +709,11 @@ public class Game extends BasicGameState { hitObjects[objectIndex].mousePressed(x, y); } + @Override + public void mouseWheelMoved(int newValue) { + Utils.changeVolume((newValue < 0) ? -1 : 1); + } + @Override public void enter(GameContainer container, StateBasedGame game) throws SlickException { diff --git a/src/itdelatrisu/opsu/states/GamePauseMenu.java b/src/itdelatrisu/opsu/states/GamePauseMenu.java index c291487f..54d38854 100644 --- a/src/itdelatrisu/opsu/states/GamePauseMenu.java +++ b/src/itdelatrisu/opsu/states/GamePauseMenu.java @@ -104,6 +104,7 @@ public class GamePauseMenu extends BasicGameState { retryButton.draw(); backButton.draw(); + Utils.drawVolume(g); Utils.drawFPS(); Utils.drawCursor(); } @@ -112,6 +113,7 @@ public class GamePauseMenu extends BasicGameState { public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException { Utils.updateCursor(delta); + Utils.updateVolumeDisplay(delta); int mouseX = input.getMouseX(), mouseY = input.getMouseY(); continueButton.hoverUpdate(delta, mouseX, mouseY); retryButton.hoverUpdate(delta, mouseX, mouseY); diff --git a/src/itdelatrisu/opsu/states/GameRanking.java b/src/itdelatrisu/opsu/states/GameRanking.java index 4c6b28fb..eeacc8b1 100644 --- a/src/itdelatrisu/opsu/states/GameRanking.java +++ b/src/itdelatrisu/opsu/states/GameRanking.java @@ -129,6 +129,7 @@ public class GameRanking extends BasicGameState { exitButton.draw(); Utils.getBackButton().draw(); + Utils.drawVolume(g); Utils.drawFPS(); Utils.drawCursor(); } @@ -137,6 +138,7 @@ public class GameRanking extends BasicGameState { 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); } diff --git a/src/itdelatrisu/opsu/states/MainMenu.java b/src/itdelatrisu/opsu/states/MainMenu.java index 8c3d5228..b4b7bd07 100644 --- a/src/itdelatrisu/opsu/states/MainMenu.java +++ b/src/itdelatrisu/opsu/states/MainMenu.java @@ -242,6 +242,7 @@ public class MainMenu extends BasicGameState { new SimpleDateFormat("h:mm a").format(new Date())), marginX, height - marginY - lineHeight); + Utils.drawVolume(g); Utils.drawFPS(); Utils.drawCursor(); } @@ -250,6 +251,7 @@ public class MainMenu extends BasicGameState { public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException { Utils.updateCursor(delta); + Utils.updateVolumeDisplay(delta); int mouseX = input.getMouseX(), mouseY = input.getMouseY(); logo.hoverUpdate(delta, mouseX, mouseY); playButton.hoverUpdate(delta, mouseX, mouseY); @@ -402,6 +404,11 @@ public class MainMenu extends BasicGameState { } } + @Override + public void mouseWheelMoved(int newValue) { + Utils.changeVolume((newValue < 0) ? -1 : 1); + } + @Override public void keyPressed(int key, char c) { switch (key) { diff --git a/src/itdelatrisu/opsu/states/MainMenuExit.java b/src/itdelatrisu/opsu/states/MainMenuExit.java index 7323f21a..c98bf1ed 100644 --- a/src/itdelatrisu/opsu/states/MainMenuExit.java +++ b/src/itdelatrisu/opsu/states/MainMenuExit.java @@ -107,6 +107,7 @@ public class MainMenuExit extends BasicGameState { "2. No", Color.white ); + Utils.drawVolume(g); Utils.drawFPS(); Utils.drawCursor(); } @@ -115,6 +116,7 @@ public class MainMenuExit extends BasicGameState { public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException { Utils.updateCursor(delta); + Utils.updateVolumeDisplay(delta); // move buttons to center float yesX = yesButton.getX(), noX = noButton.getX(); diff --git a/src/itdelatrisu/opsu/states/Options.java b/src/itdelatrisu/opsu/states/Options.java index 65b5df1a..cdf1f756 100644 --- a/src/itdelatrisu/opsu/states/Options.java +++ b/src/itdelatrisu/opsu/states/Options.java @@ -172,14 +172,24 @@ public class Options extends BasicGameState { container.setVSync(getTargetFPS() == 60); } }, - MUSIC_VOLUME ("Music Volume", "Global music volume.") { + MASTER_VOLUME ("Master Volume", "Global volume level.") { + @Override + public String getValueString() { return String.format("%d%%", masterVolume); } + + @Override + public void drag(GameContainer container, int d) { + masterVolume = Utils.getBoundedValue(masterVolume, d, 0, 100); + container.setMusicVolume(getMasterVolume() * getMusicVolume()); + } + }, + MUSIC_VOLUME ("Music Volume", "Volume of music.") { @Override public String getValueString() { return String.format("%d%%", musicVolume); } @Override public void drag(GameContainer container, int d) { musicVolume = Utils.getBoundedValue(musicVolume, d, 0, 100); - container.setMusicVolume(getMusicVolume()); + container.setMusicVolume(getMasterVolume() * getMusicVolume()); } }, EFFECT_VOLUME ("Effect Volume", "Volume of menu and game sounds.") { @@ -480,6 +490,7 @@ public class Options extends BasicGameState { * Music options. */ private static final GameOption[] musicOptions = { + GameOption.MASTER_VOLUME, GameOption.MUSIC_VOLUME, GameOption.EFFECT_VOLUME, GameOption.HITSOUND_VOLUME, @@ -612,20 +623,25 @@ public class Options extends BasicGameState { */ private static boolean showComboBursts = true; + /** + * Global volume level. + */ + private static int masterVolume = 35; + /** * Default music volume. */ - private static int musicVolume = 30; + private static int musicVolume = 80; /** * Default sound effect volume. */ - private static int effectVolume = 20; + private static int effectVolume = 70; /** * Default hit sound volume. */ - private static int hitSoundVolume = 20; + private static int hitSoundVolume = 70; /** * Offset time, in milliseconds, for music position-related elements. @@ -847,6 +863,7 @@ public class Options extends BasicGameState { ); } + Utils.drawVolume(g); Utils.drawFPS(); Utils.drawCursor(); } @@ -855,6 +872,7 @@ public class Options extends BasicGameState { 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()) @@ -1044,6 +1062,24 @@ public class Options extends BasicGameState { */ public static int getTargetFPS() { return targetFPS[targetFPSindex]; } + /** + * Returns the master volume level. + * @return the volume [0, 1] + */ + public static float getMasterVolume() { return masterVolume / 100f; } + + /** + * Sets the master volume level (if within valid range). + * @param container the game container + * @param volume the volume [0, 1] + */ + public static void setMasterVolume(GameContainer container, float volume) { + if (volume >= 0f && volume <= 1f) { + masterVolume = (int) (volume * 100f); + container.setMusicVolume(getMasterVolume() * getMusicVolume()); + } + } + /** * Returns the default music volume. * @return the volume [0, 1] @@ -1417,6 +1453,11 @@ public class Options extends BasicGameState { case "LoadVerbose": loadVerbose = Boolean.parseBoolean(value); break; + case "VolumeUniversal": + i = Integer.parseInt(value); + if (i >= 0 && i <= 100) + masterVolume = i; + break; case "VolumeMusic": i = Integer.parseInt(value); if (i >= 0 && i <= 100) @@ -1551,6 +1592,8 @@ public class Options extends BasicGameState { writer.newLine(); writer.write(String.format("LoadVerbose = %b", loadVerbose)); writer.newLine(); + writer.write(String.format("VolumeUniversal = %d", masterVolume)); + writer.newLine(); writer.write(String.format("VolumeMusic = %d", musicVolume)); writer.newLine(); writer.write(String.format("VolumeEffect = %d", effectVolume)); diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index ac53a59c..394abbc7 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -343,6 +343,7 @@ public class SongMenu extends BasicGameState { // back button Utils.getBackButton().draw(); + Utils.drawVolume(g); Utils.drawFPS(); Utils.drawCursor(); } @@ -351,6 +352,7 @@ public class SongMenu extends BasicGameState { 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); optionsButton.hoverUpdate(delta, mouseX, mouseY);