Game options overhaul & new options.

- Separated options into "Display", "Music", and "Gameplay" tabs.
- New options: VolumeHitSound, DimLevel, PerfectHit
- Added options that were previously missing from Game Options screen.
- Added sounds to Game Options screen.

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han 2014-07-02 18:24:19 -04:00
parent 812b83af45
commit 2c2f28b441
6 changed files with 539 additions and 197 deletions

View File

@ -759,7 +759,10 @@ public class GameScore {
if (hitValue > 0) { if (hitValue > 0) {
score += hitValue; score += hitValue;
hitResultList.add(new OsuHitObjectResult(time, result, x, y, null)); if (!Options.isPerfectHitBurstEnabled())
; // hide perfect hit results
else
hitResultList.add(new OsuHitObjectResult(time, result, x, y, null));
} }
} }
@ -776,8 +779,10 @@ public class GameScore {
public void hitResult(int time, int result, float x, float y, Color color, public void hitResult(int time, int result, float x, float y, Color color,
boolean end, byte hitSound) { boolean end, byte hitSound) {
int hitValue = 0; int hitValue = 0;
boolean perfectHit = false;
switch (result) { switch (result) {
case HIT_300: case HIT_300:
perfectHit = true;
hitValue = 300; hitValue = 300;
changeHealth(5f); changeHealth(5f);
objectCount++; objectCount++;
@ -852,6 +857,9 @@ public class GameScore {
comboEnd = 0; comboEnd = 0;
} }
hitResultList.add(new OsuHitObjectResult(time, result, x, y, color)); if (perfectHit && !Options.isPerfectHitBurstEnabled())
; // hide perfect hit results
else
hitResultList.add(new OsuHitObjectResult(time, result, x, y, color));
} }
} }

View File

@ -228,7 +228,7 @@ public class SoundController {
// set volume // set volume
FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN); FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
float dB = (float) (Math.log(volume) / Math.log(10.0) * 20.0); float dB = (float) (Math.log(volume) / Math.log(10.0) * 20.0);
gainControl.setValue(dB * sampleVolumeMultiplier); gainControl.setValue(dB);
// play clip // play clip
clip.setFramePosition(0); clip.setFramePosition(0);
@ -255,7 +255,7 @@ public class SoundController {
if (sampleSetIndex < 0 || hitSound < 0) if (sampleSetIndex < 0 || hitSound < 0)
return; return;
float volume = Options.getEffectVolume(); float volume = Options.getHitSoundVolume() * sampleVolumeMultiplier;
if (volume == 0f) if (volume == 0f)
return; return;
@ -280,6 +280,7 @@ public class SoundController {
if (sampleSetIndex < 0 || sound < 0 || sound > HIT_MAX) if (sampleSetIndex < 0 || sound < 0 || sound > HIT_MAX)
return; return;
playClip(hitSounds[sampleSetIndex][sound], Options.getEffectVolume()); playClip(hitSounds[sampleSetIndex][sound],
Options.getHitSoundVolume() * sampleVolumeMultiplier);
} }
} }

View File

@ -50,6 +50,7 @@ public class Utils {
*/ */
public static final Color public static final Color
COLOR_BLACK_ALPHA = new Color(0, 0, 0, 0.5f), COLOR_BLACK_ALPHA = new Color(0, 0, 0, 0.5f),
COLOR_WHITE_ALPHA = new Color(255, 255, 255, 0.5f),
COLOR_BLUE_DIVIDER = new Color(49, 94, 237), COLOR_BLUE_DIVIDER = new Color(49, 94, 237),
COLOR_BLUE_BACKGROUND = new Color(74, 130, 255), COLOR_BLUE_BACKGROUND = new Color(74, 130, 255),
COLOR_BLUE_BUTTON = new Color(50, 189, 237), COLOR_BLUE_BUTTON = new Color(50, 189, 237),
@ -79,6 +80,11 @@ public class Utils {
*/ */
private static GUIMenuButton backButton; private static GUIMenuButton backButton;
/**
* Tab image (shared by other states).
*/
private static Image tab;
/** /**
* Cursor image and trail. * Cursor image and trail.
*/ */
@ -131,7 +137,88 @@ public class Utils {
Log.error("Failed to set the cursor.", e); Log.error("Failed to set the cursor.", e);
} }
// load cursor images (TODO: cleanup) loadCursor();
// create fonts
int height = container.getHeight();
float fontBase;
if (height <= 600)
fontBase = 9f;
else if (height < 800)
fontBase = 10f;
else if (height <= 900)
fontBase = 12f;
else
fontBase = 14f;
Font font = new Font("Lucida Sans Unicode", Font.PLAIN, (int) (fontBase * 4 / 3));
FONT_DEFAULT = new TrueTypeFont(font, false);
FONT_BOLD = new TrueTypeFont(font.deriveFont(Font.BOLD), false);
FONT_XLARGE = new TrueTypeFont(font.deriveFont(fontBase * 4), false);
FONT_LARGE = new TrueTypeFont(font.deriveFont(fontBase * 2), false);
FONT_MEDIUM = new TrueTypeFont(font.deriveFont(fontBase * 3 / 2), false);
FONT_SMALL = new TrueTypeFont(font.deriveFont(fontBase), false);
// tab image
tab = new Image("selection-tab.png");
float tabScale = (height * 0.033f) / tab.getHeight();
tab = tab.getScaledCopy(tabScale);
// back button
Image back = new Image("menu-back.png");
float scale = (height * 0.1f) / back.getHeight();
back = back.getScaledCopy(scale);
backButton = new GUIMenuButton(back,
back.getWidth() / 2f,
height - (back.getHeight() / 2f));
}
/**
* Returns the 'selection-tab' image.
*/
public static Image getTabImage() { return tab; }
/**
* Returns the 'menu-back' GUIMenuButton.
*/
public static GUIMenuButton getBackButton() { return backButton; }
/**
* Draws an image based on its center with a color filter.
* @param img the image to draw
* @param x the center x coordinate
* @param y the center y coordinate
* @param color the color filter to apply
*/
public static void drawCentered(Image img, float x, float y, Color color) {
img.draw(x - (img.getWidth() / 2f), y - (img.getHeight() / 2f), color);
}
/**
* Draws an animation based on its center.
* @param anim the animation to draw
* @param x the center x coordinate
* @param y the center y coordinate
*/
public static void drawCentered(Animation anim, float x, float y) {
anim.draw(x - (anim.getWidth() / 2f), y - (anim.getHeight() / 2f));
}
/**
* Loads the cursor images.
* @throws SlickException
*/
public static void loadCursor() throws SlickException {
// destroy old cursors, if they exist
if (cursor != null)
cursor.destroy();
if (cursorTrail != null)
cursorTrail.destroy();
if (cursorMiddle != null)
cursorMiddle.destroy();
cursor = cursorTrail = cursorMiddle = null;
// TODO: cleanup
boolean skinCursor = new File(Options.getSkinDir(), "cursor.png").isFile(); boolean skinCursor = new File(Options.getSkinDir(), "cursor.png").isFile();
if (Options.isNewCursorEnabled()) { if (Options.isNewCursorEnabled()) {
// load new cursor type // load new cursor type
@ -156,60 +243,6 @@ public class Utils {
else else
cursorTrail = new Image("cursortrail2.png"); cursorTrail = new Image("cursortrail2.png");
} }
// create fonts
int height = container.getHeight();
float fontBase;
if (height <= 600)
fontBase = 9f;
else if (height < 800)
fontBase = 10f;
else if (height <= 900)
fontBase = 12f;
else
fontBase = 14f;
Font font = new Font("Lucida Sans Unicode", Font.PLAIN, (int) (fontBase * 4 / 3));
FONT_DEFAULT = new TrueTypeFont(font, false);
FONT_BOLD = new TrueTypeFont(font.deriveFont(Font.BOLD), false);
FONT_XLARGE = new TrueTypeFont(font.deriveFont(fontBase * 4), false);
FONT_LARGE = new TrueTypeFont(font.deriveFont(fontBase * 2), false);
FONT_MEDIUM = new TrueTypeFont(font.deriveFont(fontBase * 3 / 2), false);
FONT_SMALL = new TrueTypeFont(font.deriveFont(fontBase), false);
// back button
Image back = new Image("menu-back.png");
float scale = (height * 0.1f) / back.getHeight();
back = back.getScaledCopy(scale);
backButton = new GUIMenuButton(back,
back.getWidth() / 2f,
height - (back.getHeight() / 2f));
}
/**
* Returns the 'back' GUIMenuButton.
*/
public static GUIMenuButton getBackButton() { return backButton; }
/**
* Draws an image based on its center with a color filter.
* @param img the image to draw
* @param x the center x coordinate
* @param y the center y coordinate
* @param color the color filter to apply
*/
public static void drawCentered(Image img, float x, float y, Color color) {
img.draw(x - (img.getWidth() / 2f), y - (img.getHeight() / 2f), color);
}
/**
* Draws an animation based on its center.
* @param anim the animation to draw
* @param x the center x coordinate
* @param y the center y coordinate
*/
public static void drawCentered(Animation anim, float x, float y) {
anim.draw(x - (anim.getWidth() / 2f), y - (anim.getHeight() / 2f));
} }
/** /**

View File

@ -262,8 +262,8 @@ public class Game extends BasicGameState {
int height = container.getHeight(); int height = container.getHeight();
// background // background
if (!osu.drawBG(width, height, 0.7f)) g.setBackground(Color.black);
g.setBackground(Color.black); osu.drawBG(width, height, Options.getBackgroundDim());
int trackPosition = MusicController.getPosition(); int trackPosition = MusicController.getPosition();
if (pauseTime > -1) // returning from pause screen if (pauseTime > -1) // returning from pause screen

View File

@ -20,6 +20,7 @@ package itdelatrisu.opsu.states;
import itdelatrisu.opsu.GUIMenuButton; import itdelatrisu.opsu.GUIMenuButton;
import itdelatrisu.opsu.Opsu; import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.SoundController;
import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.Utils;
import java.io.BufferedReader; import java.io.BufferedReader;
@ -109,20 +110,94 @@ public class Options extends BasicGameState {
private static GUIMenuButton[] modButtons; private static GUIMenuButton[] modButtons;
/** /**
* Game option constants. * Game options.
*/
private enum GameOption {
NULL,
SCREEN_RESOLUTION,
// FULLSCREEN,
TARGET_FPS,
MUSIC_VOLUME,
EFFECT_VOLUME,
HITSOUND_VOLUME,
MUSIC_OFFSET,
SCREENSHOT_FORMAT,
SHOW_FPS,
SHOW_HIT_LIGHTING,
SHOW_COMBO_BURSTS,
NEW_CURSOR,
DYNAMIC_BACKGROUND,
SHOW_PERFECT_HIT,
BACKGROUND_DIM;
};
/**
* Option tab constants.
*/ */
private static final int private static final int
OPTIONS_SCREEN_RESOLUTION = 0, TAB_DISPLAY = 0,
// OPTIONS_FULLSCREEN = , TAB_MUSIC = 1,
OPTIONS_TARGET_FPS = 1, TAB_GAMEPLAY = 2,
OPTIONS_MUSIC_VOLUME = 2, TAB_MAX = 3; // not a tab
OPTIONS_EFFECT_VOLUME = 3,
OPTIONS_MUSIC_OFFSET = 4, /**
OPTIONS_SCREENSHOT_FORMAT = 5, * Option tab names.
OPTIONS_DISPLAY_FPS = 6, */
OPTIONS_HIT_LIGHTING = 7, private static final String[] TAB_NAMES = {
OPTIONS_COMBO_BURSTS = 8, "Display",
OPTIONS_MAX = 9; // not an option "Music",
"Gameplay"
};
/**
* Option tab buttons.
*/
private GUIMenuButton[] optionTabs = new GUIMenuButton[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.SCREENSHOT_FORMAT,
GameOption.NEW_CURSOR,
GameOption.DYNAMIC_BACKGROUND
};
/**
* Music options.
*/
private static final GameOption[] musicOptions = {
GameOption.MUSIC_VOLUME,
GameOption.EFFECT_VOLUME,
GameOption.HITSOUND_VOLUME,
GameOption.MUSIC_OFFSET
};
/**
* Gameplay options.
*/
private static final GameOption[] gameplayOptions = {
GameOption.BACKGROUND_DIM,
GameOption.SHOW_HIT_LIGHTING,
GameOption.SHOW_COMBO_BURSTS,
GameOption.SHOW_PERFECT_HIT
};
/**
* Max number of options displayed on one screen.
*/
private static int maxOptionsScreen = Math.max(
Math.max(displayOptions.length, musicOptions.length),
gameplayOptions.length);
/** /**
* Screen resolutions. * Screen resolutions.
@ -184,6 +259,11 @@ public class Options extends BasicGameState {
*/ */
private static int effectVolume = 20; private static int effectVolume = 20;
/**
* Default hit sound volume.
*/
private static int hitSoundVolume = 20;
/** /**
* Offset time, in milliseconds, for music position-related elements. * Offset time, in milliseconds, for music position-related elements.
*/ */
@ -214,6 +294,16 @@ public class Options extends BasicGameState {
*/ */
private static boolean dynamicBackground = true; private static boolean dynamicBackground = true;
/**
* Whether or not to display perfect hit results.
*/
private static boolean showPerfectHit = true;
/**
* Percentage to dim background images during gameplay.
*/
private static int backgroundDim = 30;
/** /**
* Game option coordinate modifiers (for drawing). * Game option coordinate modifiers (for drawing).
*/ */
@ -223,6 +313,7 @@ public class Options extends BasicGameState {
private GameContainer container; private GameContainer container;
private StateBasedGame game; private StateBasedGame game;
private Input input; private Input input;
private Graphics g;
private int state; private int state;
private boolean init = false; private boolean init = false;
@ -236,6 +327,7 @@ public class Options extends BasicGameState {
this.container = container; this.container = container;
this.game = game; this.game = game;
this.input = container.getInput(); this.input = container.getInput();
this.g = container.getGraphics();
Utils.init(container, game); Utils.init(container, game);
@ -243,8 +335,16 @@ public class Options extends BasicGameState {
int height = container.getHeight(); int height = container.getHeight();
// game option coordinate modifiers // game option coordinate modifiers
textY = 10 + (Utils.FONT_XLARGE.getLineHeight() * 3 / 2); textY = 20 + (Utils.FONT_XLARGE.getLineHeight() * 3 / 2);
offsetY = (int) (((height * 0.8f) - textY) / OPTIONS_MAX); offsetY = (int) (((height * 0.8f) - textY) / maxOptionsScreen);
// option tabs
Image tab = Utils.getTabImage();
float tabX = (width / 50) + (tab.getWidth() / 2f);
float tabY = 15 + Utils.FONT_XLARGE.getHeight() + (tab.getHeight() / 2f);
float tabOffset = tab.getWidth() * 0.85f;
for (int i = 0; i < optionTabs.length; i++)
optionTabs[i] = new GUIMenuButton(tab, tabX + (i * tabOffset), tabY);
// game mods // game mods
modsActive = new boolean[MOD_MAX]; modsActive = new boolean[MOD_MAX];
@ -305,49 +405,38 @@ public class Options extends BasicGameState {
// game options // game options
g.setLineWidth(1f); g.setLineWidth(1f);
g.setFont(Utils.FONT_LARGE); g.setFont(Utils.FONT_LARGE);
this.drawOption(g, OPTIONS_SCREEN_RESOLUTION, "Screen Resolution", switch (currentTab) {
String.format("%dx%d", resolutions[resolutionIndex][0], resolutions[resolutionIndex][1]), case TAB_DISPLAY:
"Restart to apply resolution changes." for (int i = 0; i < displayOptions.length; i++)
); drawOption(displayOptions[i], i);
// this.drawOption(g, OPTIONS_FULLSCREEN, "Fullscreen Mode", break;
// fullscreen ? "Yes" : "No", case TAB_MUSIC:
// "Restart to apply changes." for (int i = 0; i < musicOptions.length; i++)
// ); drawOption(musicOptions[i], i);
this.drawOption(g, OPTIONS_TARGET_FPS, "Frame Limiter", break;
String.format("%dfps", getTargetFPS()), case TAB_GAMEPLAY:
"Higher values may cause high CPU usage." for (int i = 0; i < gameplayOptions.length; i++)
); drawOption(gameplayOptions[i], i);
this.drawOption(g, OPTIONS_MUSIC_VOLUME, "Music Volume", break;
String.format("%d%%", musicVolume), }
"Global music volume."
); // option tabs
this.drawOption(g, OPTIONS_EFFECT_VOLUME, "Effect Volume", g.setColor(Color.white);
String.format("%d%%", effectVolume), Image tab = optionTabs[0].getImage();
"Sound effect volume." float tabTextY = optionTabs[0].getY() - (tab.getHeight() / 2f);
); for (int i = optionTabs.length - 1; i >= 0; i--) {
this.drawOption(g, OPTIONS_MUSIC_OFFSET, "Music Offset", tab.setAlpha((i == currentTab) ? 1.0f : 0.7f);
String.format("%dms", musicOffset), optionTabs[i].draw();
"Adjust this value if hit objects are out of sync." float tabTextX = optionTabs[i].getX() - (Utils.FONT_MEDIUM.getWidth(TAB_NAMES[i]) / 2);
); Utils.FONT_MEDIUM.drawString(tabTextX, tabTextY, TAB_NAMES[i], Color.white);
this.drawOption(g, OPTIONS_SCREENSHOT_FORMAT, "Screenshot Format", }
screenshotFormat[screenshotFormatIndex].toUpperCase(), g.setLineWidth(2f);
"Press F12 to take a screenshot." float lineY = optionTabs[0].getY() + (tab.getHeight() / 2f);
); g.drawLine(0, lineY, width, lineY);
this.drawOption(g, OPTIONS_DISPLAY_FPS, "Show FPS Counter", g.resetLineWidth();
showFPS ? "Yes" : "No",
null
);
this.drawOption(g, OPTIONS_HIT_LIGHTING, "Show Hit Lighting",
showHitLighting ? "Yes" : "No",
null
);
this.drawOption(g, OPTIONS_COMBO_BURSTS, "Show Combo Bursts",
showComboBursts ? "Yes" : "No",
null
);
// game mods // game mods
Utils.FONT_LARGE.drawString(width * 0.02f, height * 0.8f, "Game Mods:", Color.white); Utils.FONT_LARGE.drawString(width / 30, height * 0.8f, "Game Mods:", Color.white);
for (int i = 0; i < modButtons.length; i++) for (int i = 0; i < modButtons.length; i++)
modButtons[i].draw(); modButtons[i].draw();
@ -374,13 +463,26 @@ public class Options extends BasicGameState {
// back // back
if (Utils.getBackButton().contains(x, y)) { if (Utils.getBackButton().contains(x, y)) {
SoundController.playSound(SoundController.SOUND_MENUBACK);
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition(Color.black)); game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition(Color.black));
return; return;
} }
// option tabs
for (int i = 0; i < optionTabs.length; i++) {
if (optionTabs[i].contains(x, y)) {
if (i != currentTab) {
currentTab = i;
SoundController.playSound(SoundController.SOUND_MENUCLICK);
}
return;
}
}
// game mods // game mods
for (int i = 0; i < modButtons.length; i++) { for (int i = 0; i < modButtons.length; i++) {
if (modButtons[i].contains(x, y)) { if (modButtons[i].contains(x, y)) {
boolean prev = modsActive[i];
toggleMod(i); toggleMod(i);
// mutually exclusive mods // mutually exclusive mods
@ -397,39 +499,52 @@ public class Options extends BasicGameState {
} else if (modsActive[MOD_SUDDEN_DEATH] && modsActive[MOD_NO_FAIL]) } else if (modsActive[MOD_SUDDEN_DEATH] && modsActive[MOD_NO_FAIL])
toggleMod((i == MOD_SUDDEN_DEATH) ? MOD_NO_FAIL : MOD_SUDDEN_DEATH); toggleMod((i == MOD_SUDDEN_DEATH) ? MOD_NO_FAIL : MOD_SUDDEN_DEATH);
if (modsActive[i] != prev)
SoundController.playSound(SoundController.SOUND_MENUCLICK);
return; return;
} }
} }
// options (click only) // options (click only)
if (isOptionClicked(OPTIONS_SCREEN_RESOLUTION, y)) { switch (getClickedOption(y)) {
case SCREEN_RESOLUTION:
resolutionIndex = (resolutionIndex + 1) % resolutions.length; resolutionIndex = (resolutionIndex + 1) % resolutions.length;
return; break;
} // case FULLSCREEN:
// if (isOptionClicked(OPTIONS_FULLSCREEN, y)) {
// fullscreen = !fullscreen; // fullscreen = !fullscreen;
// return; // break;
// } case TARGET_FPS:
if (isOptionClicked(OPTIONS_TARGET_FPS, y)) {
targetFPSindex = (targetFPSindex + 1) % targetFPS.length; targetFPSindex = (targetFPSindex + 1) % targetFPS.length;
container.setTargetFrameRate(getTargetFPS()); container.setTargetFrameRate(getTargetFPS());
return; break;
} case SCREENSHOT_FORMAT:
if (isOptionClicked(OPTIONS_SCREENSHOT_FORMAT, y)) {
screenshotFormatIndex = (screenshotFormatIndex + 1) % screenshotFormat.length; screenshotFormatIndex = (screenshotFormatIndex + 1) % screenshotFormat.length;
return; break;
} case SHOW_FPS:
if (isOptionClicked(OPTIONS_DISPLAY_FPS, y)) {
showFPS = !showFPS; showFPS = !showFPS;
return; break;
} case SHOW_HIT_LIGHTING:
if (isOptionClicked(OPTIONS_HIT_LIGHTING, y)) {
showHitLighting = !showHitLighting; showHitLighting = !showHitLighting;
return; break;
} case SHOW_COMBO_BURSTS:
if (isOptionClicked(OPTIONS_COMBO_BURSTS, y)) {
showComboBursts = !showComboBursts; showComboBursts = !showComboBursts;
return; break;
case NEW_CURSOR:
newCursor = !newCursor;
try {
Utils.loadCursor();
} catch (SlickException e) {
Log.error("Failed to load cursor.", e);
}
break;
case DYNAMIC_BACKGROUND:
dynamicBackground = !dynamicBackground;
break;
case SHOW_PERFECT_HIT:
showPerfectHit = !showPerfectHit;
break;
default:
break;
} }
} }
@ -451,30 +566,45 @@ public class Options extends BasicGameState {
diff = ((diff > 0) ? 1 : -1) * multiplier; diff = ((diff > 0) ? 1 : -1) * multiplier;
// options (drag only) // options (drag only)
if (isOptionClicked(OPTIONS_MUSIC_VOLUME, oldy)) { switch (getClickedOption(oldy)) {
case MUSIC_VOLUME:
musicVolume += diff; musicVolume += diff;
if (musicVolume < 0) if (musicVolume < 0)
musicVolume = 0; musicVolume = 0;
else if (musicVolume > 100) else if (musicVolume > 100)
musicVolume = 100; musicVolume = 100;
container.setMusicVolume(getMusicVolume()); container.setMusicVolume(getMusicVolume());
return; break;
} case EFFECT_VOLUME:
if (isOptionClicked(OPTIONS_EFFECT_VOLUME, oldy)) {
effectVolume += diff; effectVolume += diff;
if (effectVolume < 0) if (effectVolume < 0)
effectVolume = 0; effectVolume = 0;
else if (effectVolume > 100) else if (effectVolume > 100)
effectVolume = 100; effectVolume = 100;
return; break;
} case HITSOUND_VOLUME:
if (isOptionClicked(OPTIONS_MUSIC_OFFSET, oldy)) { hitSoundVolume += diff;
if (hitSoundVolume < 0)
hitSoundVolume = 0;
else if (hitSoundVolume > 100)
hitSoundVolume = 100;
break;
case MUSIC_OFFSET:
musicOffset += diff; musicOffset += diff;
if (musicOffset < -500) if (musicOffset < -500)
musicOffset = -500; musicOffset = -500;
else if (musicOffset > 500) else if (musicOffset > 500)
musicOffset = 500; musicOffset = 500;
return; break;
case BACKGROUND_DIM:
backgroundDim += diff;
if (backgroundDim < 0)
backgroundDim = 0;
else if (backgroundDim > 100)
backgroundDim = 100;
break;
default:
break;
} }
} }
@ -482,43 +612,179 @@ public class Options extends BasicGameState {
public void keyPressed(int key, char c) { public void keyPressed(int key, char c) {
switch (key) { switch (key) {
case Input.KEY_ESCAPE: case Input.KEY_ESCAPE:
SoundController.playSound(SoundController.SOUND_MENUBACK);
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition(Color.black)); game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition(Color.black));
break; break;
case Input.KEY_F12: case Input.KEY_F12:
Utils.takeScreenShot(); Utils.takeScreenShot();
break; break;
case Input.KEY_TAB:
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(SoundController.SOUND_MENUCLICK);
break;
}
}
@Override
public void enter(GameContainer container, StateBasedGame game)
throws SlickException {
currentTab = TAB_DISPLAY;
}
/**
* Draws a game option.
* @param option the option (OPTION_* constant)
* @param pos the position to draw at
*/
private void drawOption(GameOption option, int pos) {
switch (option) {
case SCREEN_RESOLUTION:
drawOption(pos, "Screen Resolution",
String.format("%dx%d", resolutions[resolutionIndex][0], resolutions[resolutionIndex][1]),
"Restart to apply resolution changes."
);
break;
// case FULLSCREEN:
// drawOption(pos, "Fullscreen Mode",
// fullscreen ? "Yes" : "No",
// "Restart to apply changes."
// );
// break;
case TARGET_FPS:
drawOption(pos, "Frame Limiter",
String.format("%dfps", getTargetFPS()),
"Higher values may cause high CPU usage."
);
break;
case SCREENSHOT_FORMAT:
drawOption(pos, "Screenshot Format",
screenshotFormat[screenshotFormatIndex].toUpperCase(),
"Press F12 to take a screenshot."
);
break;
case SHOW_FPS:
drawOption(pos, "Show FPS Counter",
showFPS ? "Yes" : "No",
"Show an FPS counter in the bottom-right hand corner."
);
break;
case NEW_CURSOR:
drawOption(pos, "Enable New Cursor",
newCursor ? "Yes" : "No",
"Use the new cursor style (may cause higher CPU usage)."
);
break;
case DYNAMIC_BACKGROUND:
drawOption(pos, "Enable Dynamic Backgrounds",
dynamicBackground ? "Yes" : "No",
"The song background will be used as the main menu background."
);
break;
case MUSIC_VOLUME:
drawOption(pos, "Music Volume",
String.format("%d%%", musicVolume),
"Global music volume."
);
break;
case EFFECT_VOLUME:
drawOption(pos, "Effect Volume",
String.format("%d%%", effectVolume),
"Volume of menu and game sounds."
);
break;
case HITSOUND_VOLUME:
drawOption(pos, "Hit Sound Volume",
String.format("%d%%", hitSoundVolume),
"Volume of hit sounds."
);
break;
case MUSIC_OFFSET:
drawOption(pos, "Music Offset",
String.format("%dms", musicOffset),
"Adjust this value if hit objects are out of sync."
);
break;
case BACKGROUND_DIM:
drawOption(pos, "Background Dim",
String.format("%d%%", backgroundDim),
"Percentage to dim the background image during gameplay."
);
break;
case SHOW_HIT_LIGHTING:
drawOption(pos, "Show Hit Lighting",
showHitLighting ? "Yes" : "No",
"Adds an effect behind hit explosions."
);
break;
case SHOW_COMBO_BURSTS:
drawOption(pos, "Show Combo Bursts",
showComboBursts ? "Yes" : "No",
"A character image is displayed at combo milestones."
);
break;
case SHOW_PERFECT_HIT:
drawOption(pos, "Show Perfect Hits",
showPerfectHit ? "Yes" : "No",
"Whether to show perfect hit result bursts (300s, slider ticks)."
);
break;
default:
break;
} }
} }
/** /**
* Draws a game option. * Draws a game option.
* @param g the graphics context * @param pos the element position
* @param pos the element position (OPTIONS_* constants)
* @param label the option name * @param label the option name
* @param value the option value * @param value the option value
* @param notes additional notes (optional) * @param notes additional notes (optional)
*/ */
private void drawOption(Graphics g, int pos, String label, String value, String notes) { private void drawOption(int pos, String label, String value, String notes) {
int width = container.getWidth(); int width = container.getWidth();
int textHeight = Utils.FONT_LARGE.getHeight(); int textHeight = Utils.FONT_LARGE.getHeight();
float y = textY + (pos * offsetY); float y = textY + (pos * offsetY);
g.drawString(label, width / 50, y); g.setColor(Color.white);
g.drawString(label, width / 30, y);
g.drawString(value, width / 2, y); g.drawString(value, width / 2, y);
g.drawLine(0, y + textHeight, width, y + textHeight);
if (notes != null) if (notes != null)
Utils.FONT_SMALL.drawString(width / 50, y + textHeight, notes); Utils.FONT_SMALL.drawString(width / 30, y + textHeight, notes);
g.setColor(Utils.COLOR_WHITE_ALPHA);
g.drawLine(0, y + textHeight, width, y + textHeight);
} }
/** /**
* Returns whether or not an option was clicked. * Returns the option clicked.
* @param pos the element position (OPTIONS_* constants) * If no option clicked, -1 will be returned.
* @param y the y coordinate of the click * @param y the y coordinate
* @return true if clicked * @return the option (OPTION_* constant)
*/ */
private boolean isOptionClicked(int pos, int y) { private GameOption getClickedOption(int y) {
return (y > textY + (offsetY * pos) - Utils.FONT_LARGE.getHeight() && GameOption option = GameOption.NULL;
y < textY + (offsetY * pos) + Utils.FONT_LARGE.getHeight());
if (y < textY || y > textY + (offsetY * maxOptionsScreen))
return option;
int index = (y - textY + Utils.FONT_LARGE.getHeight()) / 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;
}
return option;
} }
/** /**
@ -563,6 +829,12 @@ public class Options extends BasicGameState {
*/ */
public static float getEffectVolume() { return effectVolume / 100f; } public static float getEffectVolume() { return effectVolume / 100f; }
/**
* Returns the default sound effect volume.
* @return the sound volume [0, 1]
*/
public static float getHitSoundVolume() { return hitSoundVolume / 100f; }
/** /**
* Returns the music offset time. * Returns the music offset time.
* @return the offset (in milliseconds) * @return the offset (in milliseconds)
@ -623,6 +895,18 @@ public class Options extends BasicGameState {
*/ */
public static boolean isDynamicBackgroundEnabled() { return dynamicBackground; } public static boolean isDynamicBackgroundEnabled() { return dynamicBackground; }
/**
* Returns whether or not to show perfect hit result bursts.
* @return true if enabled
*/
public static boolean isPerfectHitBurstEnabled() { return showPerfectHit; }
/**
* Returns the background dim level.
* @return the alpha level [0, 1]
*/
public static float getBackgroundDim() { return (100 - backgroundDim) / 100f; }
/** /**
* Returns the current beatmap directory. * Returns the current beatmap directory.
* If invalid, this will attempt to search for the directory, * If invalid, this will attempt to search for the directory,
@ -687,6 +971,11 @@ public class Options extends BasicGameState {
case "Skin": case "Skin":
skinDir = new File(value); skinDir = new File(value);
break; break;
case "Port":
i = Integer.parseInt(value);
if (i > 0 && i <= 65535)
port = i;
break;
case "ScreenResolution": case "ScreenResolution":
i = Integer.parseInt(value); i = Integer.parseInt(value);
if (i >= 0 && i < resolutions.length) if (i >= 0 && i < resolutions.length)
@ -700,6 +989,20 @@ public class Options extends BasicGameState {
if (i >= 0 && i <= targetFPS.length) if (i >= 0 && i <= targetFPS.length)
targetFPSindex = i; targetFPSindex = i;
break; break;
case "ScreenshotFormat":
i = Integer.parseInt(value);
if (i >= 0 && i < screenshotFormat.length)
screenshotFormatIndex = i;
break;
case "FpsCounter":
showFPS = Boolean.parseBoolean(value);
break;
case "NewCursor":
newCursor = Boolean.parseBoolean(value);
break;
case "DynamicBackground":
dynamicBackground = Boolean.parseBoolean(value);
break;
case "VolumeMusic": case "VolumeMusic":
i = Integer.parseInt(value); i = Integer.parseInt(value);
if (i >= 0 && i <= 100) if (i >= 0 && i <= 100)
@ -710,18 +1013,20 @@ public class Options extends BasicGameState {
if (i >= 0 && i <= 100) if (i >= 0 && i <= 100)
effectVolume = i; effectVolume = i;
break; break;
case "VolumeHitSound":
i = Integer.parseInt(value);
if (i >= 0 && i <= 100)
hitSoundVolume = i;
break;
case "Offset": case "Offset":
i = Integer.parseInt(value); i = Integer.parseInt(value);
if (i >= -500 && i <= 500) if (i >= -500 && i <= 500)
musicOffset = i; musicOffset = i;
break; break;
case "ScreenshotFormat": case "DimLevel":
i = Integer.parseInt(value); i = Integer.parseInt(value);
if (i >= 0 && i < screenshotFormat.length) if (i >= 0 && i <= 100)
screenshotFormatIndex = i; backgroundDim = i;
break;
case "FpsCounter":
showFPS = Boolean.parseBoolean(value);
break; break;
case "HitLighting": case "HitLighting":
showHitLighting = Boolean.parseBoolean(value); showHitLighting = Boolean.parseBoolean(value);
@ -729,18 +1034,8 @@ public class Options extends BasicGameState {
case "ComboBurst": case "ComboBurst":
showComboBursts = Boolean.parseBoolean(value); showComboBursts = Boolean.parseBoolean(value);
break; break;
case "PerfectHit":
// custom entries showPerfectHit = Boolean.parseBoolean(value);
case "Port":
i = Integer.parseInt(value);
if (i > 0 && i <= 65535)
port = i;
break;
case "NewCursor":
newCursor = Boolean.parseBoolean(value);
break;
case "DynamicBackground":
dynamicBackground = Boolean.parseBoolean(value);
break; break;
} }
} }
@ -773,33 +1068,37 @@ public class Options extends BasicGameState {
writer.newLine(); writer.newLine();
writer.write(String.format("Skin = %s", getSkinDir().getAbsolutePath())); writer.write(String.format("Skin = %s", getSkinDir().getAbsolutePath()));
writer.newLine(); writer.newLine();
writer.write(String.format("Port = %d", port));
writer.newLine();
writer.write(String.format("ScreenResolution = %d", resolutionIndex)); writer.write(String.format("ScreenResolution = %d", resolutionIndex));
writer.newLine(); writer.newLine();
// writer.write(String.format("Fullscreen = %b", fullscreen)); // writer.write(String.format("Fullscreen = %b", fullscreen));
// writer.newLine(); // writer.newLine();
writer.write(String.format("FrameSync = %d", targetFPSindex)); writer.write(String.format("FrameSync = %d", targetFPSindex));
writer.newLine(); writer.newLine();
writer.write(String.format("FpsCounter = %b", showFPS));
writer.newLine();
writer.write(String.format("ScreenshotFormat = %d", screenshotFormatIndex));
writer.newLine();
writer.write(String.format("NewCursor = %b", newCursor));
writer.newLine();
writer.write(String.format("DynamicBackground = %b", dynamicBackground));
writer.newLine();
writer.write(String.format("VolumeMusic = %d", musicVolume)); writer.write(String.format("VolumeMusic = %d", musicVolume));
writer.newLine(); writer.newLine();
writer.write(String.format("VolumeEffect = %d", effectVolume)); writer.write(String.format("VolumeEffect = %d", effectVolume));
writer.newLine(); writer.newLine();
writer.write(String.format("VolumeHitSound = %d", hitSoundVolume));
writer.newLine();
writer.write(String.format("Offset = %d", musicOffset)); writer.write(String.format("Offset = %d", musicOffset));
writer.newLine(); writer.newLine();
writer.write(String.format("ScreenshotFormat = %d", screenshotFormatIndex)); writer.write(String.format("DimLevel = %d", backgroundDim));
writer.newLine();
writer.write(String.format("FpsCounter = %b", showFPS));
writer.newLine(); writer.newLine();
writer.write(String.format("HitLighting = %b", showHitLighting)); writer.write(String.format("HitLighting = %b", showHitLighting));
writer.newLine(); writer.newLine();
writer.write(String.format("ComboBurst = %b", showComboBursts)); writer.write(String.format("ComboBurst = %b", showComboBursts));
writer.newLine(); writer.newLine();
writer.write(String.format("PerfectHit = %b", showPerfectHit));
// custom entries
writer.write(String.format("Port = %d", port));
writer.newLine();
writer.write(String.format("NewCursor = %b", newCursor));
writer.newLine();
writer.write(String.format("DynamicBackground = %b", dynamicBackground));
writer.newLine(); writer.newLine();
writer.close(); writer.close();
} catch (IOException e) { } catch (IOException e) {

View File

@ -156,10 +156,7 @@ public class SongMenu extends BasicGameState {
// sorting tabs // sorting tabs
sortTabs = new GUIMenuButton[OsuGroupList.SORT_MAX]; sortTabs = new GUIMenuButton[OsuGroupList.SORT_MAX];
Image tab = new Image("selection-tab.png"); Image tab = Utils.getTabImage();
float tabScale = (height * 0.033f) / tab.getHeight();
tab = tab.getScaledCopy(tabScale);
float tabX = buttonX + (tab.getWidth() / 2f); float tabX = buttonX + (tab.getWidth() / 2f);
float tabY = (height * 0.15f) - (tab.getHeight() / 2f) - 2f; float tabY = (height * 0.15f) - (tab.getHeight() / 2f) - 2f;
float tabOffset = (width - buttonX) / sortTabs.length; float tabOffset = (width - buttonX) / sortTabs.length;
@ -353,6 +350,7 @@ public class SongMenu extends BasicGameState {
// options // options
if (optionsButton.contains(x, y)) { if (optionsButton.contains(x, y)) {
SoundController.playSound(SoundController.SOUND_MENUHIT);
game.enterState(Opsu.STATE_OPTIONS, new EmptyTransition(), new FadeInTransition(Color.black)); game.enterState(Opsu.STATE_OPTIONS, new EmptyTransition(), new FadeInTransition(Color.black));
return; return;
} }
@ -362,13 +360,16 @@ public class SongMenu extends BasicGameState {
// sorting buttons // sorting buttons
for (byte i = 0; i < sortTabs.length; i++) { for (byte i = 0; i < sortTabs.length; i++) {
if (sortTabs[i].contains(x, y) && i != currentSort) { if (sortTabs[i].contains(x, y)) {
currentSort = i; if (i != currentSort) {
OsuGroupNode oldFocusBase = Opsu.groups.getBaseNode(focusNode.index); currentSort = i;
int oldFocusFileIndex = focusNode.osuFileIndex; OsuGroupNode oldFocusBase = Opsu.groups.getBaseNode(focusNode.index);
focusNode = null; int oldFocusFileIndex = focusNode.osuFileIndex;
Opsu.groups.init(i); focusNode = null;
setFocus(oldFocusBase, oldFocusFileIndex + 1, true); Opsu.groups.init(i);
setFocus(oldFocusBase, oldFocusFileIndex + 1, true);
}
return;
} }
} }