Replaced native cursors with skinnable ones.

- Added support for both the new (2013+) and old cursor styles.  These can be toggled on/off in the configuration file.
- Added cursor images by XinCrin (old style) and teinecthel (new style).

Other changes:
- Refactoring: Created a "Utils" module, containing methods and constants that didn't belong in the "Options" state and some duplicated drawing methods.
- Mouse is now grabbed during gameplay.
- Removed 'optionsChanged' switch, simplifying adding new options.

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han 2014-07-01 19:32:03 -04:00
parent 0604a25822
commit ab487c5e80
20 changed files with 523 additions and 266 deletions

View File

@ -17,6 +17,7 @@ The images included in opsu! belong to their respective authors.
* pictuga - "osu! web" * pictuga - "osu! web"
* sherrie__fay * sherrie__fay
* kouyang * kouyang
* teinecthel
Projects Projects
-------- --------

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
res/cursor2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
res/cursormiddle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

BIN
res/cursortrail.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
res/cursortrail2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -509,7 +509,7 @@ public class GameScore {
// header & "Ranking" text // header & "Ranking" text
float rankingHeight = (rankingImage.getHeight() * 0.75f) + 3; float rankingHeight = (rankingImage.getHeight() * 0.75f) + 3;
g.setColor(Options.COLOR_BLACK_ALPHA); g.setColor(Utils.COLOR_BLACK_ALPHA);
g.fillRect(0, 0, width, rankingHeight); g.fillRect(0, 0, width, rankingHeight);
rankingImage.draw((width * 0.97f) - rankingImage.getWidth(), 0); rankingImage.draw((width * 0.97f) - rankingImage.getWidth(), 0);

View File

@ -18,8 +18,6 @@
package itdelatrisu.opsu; package itdelatrisu.opsu;
import itdelatrisu.opsu.states.Options;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
@ -128,21 +126,21 @@ public class OsuGroupNode implements Comparable<OsuGroupNode> {
textColor = Color.white; textColor = Color.white;
} else { } else {
xOffset = bg.getWidth() * 0.05f; xOffset = bg.getWidth() * 0.05f;
bg.draw(x + xOffset, y, Options.COLOR_BLUE_BUTTON); bg.draw(x + xOffset, y, Utils.COLOR_BLUE_BUTTON);
} }
osu = osuFiles.get(osuFileIndex); osu = osuFiles.get(osuFileIndex);
} else { } else {
bg.draw(x, y, Options.COLOR_ORANGE_BUTTON); bg.draw(x, y, Utils.COLOR_ORANGE_BUTTON);
osu = osuFiles.get(0); osu = osuFiles.get(0);
} }
float cx = x + (bg.getWidth() * 0.05f) + xOffset; float cx = x + (bg.getWidth() * 0.05f) + xOffset;
float cy = y + (bg.getHeight() * 0.2f) - 3; float cy = y + (bg.getHeight() * 0.2f) - 3;
Options.FONT_MEDIUM.drawString(cx, cy, osu.title, textColor); Utils.FONT_MEDIUM.drawString(cx, cy, osu.title, textColor);
Options.FONT_DEFAULT.drawString(cx, cy + Options.FONT_MEDIUM.getLineHeight() - 4, String.format("%s // %s", osu.artist, osu.creator), textColor); Utils.FONT_DEFAULT.drawString(cx, cy + Utils.FONT_MEDIUM.getLineHeight() - 4, String.format("%s // %s", osu.artist, osu.creator), textColor);
if (expanded) if (expanded)
Options.FONT_BOLD.drawString(cx, cy + Options.FONT_MEDIUM.getLineHeight() + Options.FONT_DEFAULT.getLineHeight() - 8, osu.version, textColor); Utils.FONT_BOLD.drawString(cx, cy + Utils.FONT_MEDIUM.getLineHeight() + Utils.FONT_DEFAULT.getLineHeight() - 8, osu.version, textColor);
} }
/** /**

View File

@ -18,15 +18,12 @@
package itdelatrisu.opsu; package itdelatrisu.opsu;
import itdelatrisu.opsu.states.Options;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
@ -104,8 +101,6 @@ public class OsuParser {
try (BufferedReader in = new BufferedReader(new FileReader(file))) { try (BufferedReader in = new BufferedReader(new FileReader(file))) {
// copy the default combo colors
osu.combo = Arrays.copyOf(Options.DEFAULT_COMBO, Options.DEFAULT_COMBO.length);
// initialize timing point list // initialize timing point list
osu.timingPoints = new ArrayList<OsuTimingPoint>(); osu.timingPoints = new ArrayList<OsuTimingPoint>();
@ -403,7 +398,7 @@ public class OsuParser {
// if no custom colors, use the default color scheme // if no custom colors, use the default color scheme
if (osu.combo == null) if (osu.combo == null)
osu.combo = Options.DEFAULT_COMBO; osu.combo = Utils.DEFAULT_COMBO;
// add tags // add tags
if (!tags.isEmpty()) { if (!tags.isEmpty()) {

View File

@ -0,0 +1,369 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu;
import itdelatrisu.opsu.states.Options;
import java.awt.Font;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Cursor;
import org.newdawn.slick.Animation;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.TrueTypeFont;
import org.newdawn.slick.imageout.ImageOut;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.util.Log;
/**
* Contains miscellaneous utilities.
*/
public class Utils {
/**
* Game colors.
*/
public static final Color
COLOR_BLACK_ALPHA = new Color(0, 0, 0, 0.5f),
COLOR_BLUE_DIVIDER = new Color(49, 94, 237),
COLOR_BLUE_BACKGROUND = new Color(74, 130, 255),
COLOR_BLUE_BUTTON = new Color(50, 189, 237),
COLOR_ORANGE_BUTTON = new Color(230, 151, 87),
COLOR_GREEN_OBJECT = new Color(26, 207, 26),
COLOR_BLUE_OBJECT = new Color(46, 136, 248),
COLOR_RED_OBJECT = new Color(243, 48, 77),
COLOR_ORANGE_OBJECT = new Color(255, 200, 32);
/**
* The default map colors, used when a map does not provide custom colors.
*/
public static final Color[] DEFAULT_COMBO = {
COLOR_GREEN_OBJECT, COLOR_BLUE_OBJECT,
COLOR_RED_OBJECT, COLOR_ORANGE_OBJECT
};
/**
* Game fonts.
*/
public static TrueTypeFont
FONT_DEFAULT, FONT_BOLD,
FONT_XLARGE, FONT_LARGE, FONT_MEDIUM, FONT_SMALL;
/**
* Back button (shared by other states).
*/
private static GUIMenuButton backButton;
/**
* Cursor image and trail.
*/
private static Image cursor, cursorTrail, cursorMiddle;
/**
* Last cursor coordinates.
*/
private static int lastX = -1, lastY = -1;
/**
* Stores all previous cursor locations to display a trail.
*/
private static LinkedList<Integer>
cursorX = new LinkedList<Integer>(),
cursorY = new LinkedList<Integer>();
// game-related variables
private static GameContainer container;
// private static StateBasedGame game;
private static Input input;
// This class should not be instantiated.
private Utils() {}
/**
* Initializes game settings and class data.
* @param container the game container
* @param game the game object
* @throws SlickException
*/
public static void init(GameContainer container, StateBasedGame game)
throws SlickException {
Utils.container = container;
// Utils.game = game;
Utils.input = container.getInput();
// game settings
container.setTargetFrameRate(Options.getTargetFPS());
container.setMusicVolume(Options.getMusicVolume());
container.setShowFPS(false);
container.getInput().enableKeyRepeat();
container.setAlwaysRender(true);
// hide the cursor
try {
Cursor emptyCursor = new Cursor(1, 1, 0, 0, 1, BufferUtils.createIntBuffer(1), null);
container.setMouseCursor(emptyCursor, 0, 0);
} catch (LWJGLException e) {
Log.error("Failed to set the cursor.", e);
}
// load cursor images
if (Options.isNewCursorEnabled()) {
// load new cursor type
try {
cursorMiddle = new Image("cursormiddle.png");
cursor = new Image("cursor.png");
cursorTrail = new Image("cursortrail.png");
} catch (Exception e) {
// optional
}
}
if (cursorMiddle == null) {
// load old cursor type
cursor = new Image("cursor2.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));
}
/**
* Draws the cursor.
*/
public static void drawCursor() {
// TODO: use an image buffer
int x = input.getMouseX();
int y = input.getMouseY();
int removeCount = 0;
int FPSmod = (Options.getTargetFPS() / 60);
// if middle exists, add all points between cursor movements
if (cursorMiddle != null) {
if (lastX < 0) {
lastX = x;
lastY = y;
return;
}
addCursorPoints(lastX, lastY, x, y);
lastX = x;
lastY = y;
removeCount = (cursorX.size() / (6 * FPSmod)) + 1;
}
// else, sample one point at a time
else {
cursorX.add(x);
cursorY.add(y);
int max = 10 * FPSmod;
if (cursorX.size() > max)
removeCount = cursorX.size() - max;
}
// remove points from the lists
for (int i = 0; i < removeCount && !cursorX.isEmpty(); i++) {
cursorX.remove();
cursorY.remove();
}
// draw a fading trail
float alpha = 0f;
float t = 2f / cursorX.size();
Iterator<Integer> iterX = cursorX.iterator();
Iterator<Integer> iterY = cursorY.iterator();
while (iterX.hasNext()) {
int cx = iterX.next();
int cy = iterY.next();
alpha += t;
cursorTrail.setAlpha(alpha);
// if (cx != x || cy != y)
cursorTrail.drawCentered(cx, cy);
}
cursorTrail.drawCentered(x, y);
// draw the other components
cursor.drawCentered(x, y);
if (cursorMiddle != null)
cursorMiddle.drawCentered(x, y);
}
/**
* Adds all points between (x1, y1) and (x2, y2) to the cursor point lists.
* @author http://rosettacode.org/wiki/Bitmap/Bresenham's_line_algorithm#Java
*/
private static void addCursorPoints(int x1, int y1, int x2, int y2) {
// delta of exact value and rounded value of the dependent variable
int d = 0;
int dy = Math.abs(y2 - y1);
int dx = Math.abs(x2 - x1);
int dy2 = (dy << 1); // slope scaling factors to avoid floating
int dx2 = (dx << 1); // point
int ix = x1 < x2 ? 1 : -1; // increment direction
int iy = y1 < y2 ? 1 : -1;
int k = 5; // sample size
if (dy <= dx) {
for (int i = 0; ; i++) {
if (i == k) {
cursorX.add(x1);
cursorY.add(y1);
i = 0;
}
if (x1 == x2)
break;
x1 += ix;
d += dy2;
if (d > dx) {
y1 += iy;
d -= dx2;
}
}
} else {
for (int i = 0; ; i++) {
if (i == k) {
cursorX.add(x1);
cursorY.add(y1);
i = 0;
}
if (y1 == y2)
break;
y1 += iy;
d += dx2;
if (d > dy) {
x1 += ix;
d -= dy2;
}
}
}
}
/**
* Draws the FPS at the bottom-right corner of the game container.
* If the option is not activated, this will do nothing.
*/
public static void drawFPS() {
if (!Options.isFPSCounterEnabled())
return;
String fps = String.format("FPS: %d", container.getFPS());
FONT_DEFAULT.drawString(
container.getWidth() - 15 - FONT_DEFAULT.getWidth(fps),
container.getHeight() - 15 - FONT_DEFAULT.getHeight(fps),
fps, Color.white
);
}
/**
* Takes a screenshot.
* @return true if successful
*/
public static boolean takeScreenShot() {
// TODO: should this be threaded?
try {
// create the screenshot directory
if (!Options.SCREENSHOT_DIR.isDirectory()) {
if (!Options.SCREENSHOT_DIR.mkdir())
return false;
}
// create file name
SimpleDateFormat date = new SimpleDateFormat("yyyyMMdd_HHmmss");
String file = date.format(new Date());
SoundController.playSound(SoundController.SOUND_SHUTTER);
// copy the screen
Image screen = new Image(container.getWidth(), container.getHeight());
container.getGraphics().copyArea(screen, 0, 0);
ImageOut.write(screen, String.format("%s%sscreenshot_%s.%s",
Options.SCREENSHOT_DIR.getName(), File.separator,
file, Options.getScreenshotFormat()), false
);
screen.destroy();
} catch (SlickException e) {
Log.warn("Failed to take a screenshot.", e);
return false;
}
return true;
}
}

View File

@ -21,6 +21,7 @@ package itdelatrisu.opsu.objects;
import itdelatrisu.opsu.GameScore; import itdelatrisu.opsu.GameScore;
import itdelatrisu.opsu.MusicController; import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.OsuHitObject; import itdelatrisu.opsu.OsuHitObject;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.states.Game; import itdelatrisu.opsu.states.Game;
import itdelatrisu.opsu.states.Options; import itdelatrisu.opsu.states.Options;
@ -120,21 +121,14 @@ public class Circle {
if (timeDiff >= 0) { if (timeDiff >= 0) {
float approachScale = 1 + (timeDiff * 2f / game.getApproachTime()); float approachScale = 1 + (timeDiff * 2f / game.getApproachTime());
drawCentered(approachCircle.getScaledCopy(approachScale), hitObject.x, hitObject.y, color); Utils.drawCentered(approachCircle.getScaledCopy(approachScale), hitObject.x, hitObject.y, color);
drawCentered(hitCircleOverlay, hitObject.x, hitObject.y, Color.white); Utils.drawCentered(hitCircleOverlay, hitObject.x, hitObject.y, Color.white);
drawCentered(hitCircle, hitObject.x, hitObject.y, color); Utils.drawCentered(hitCircle, hitObject.x, hitObject.y, color);
score.drawSymbolNumber(hitObject.comboNumber, hitObject.x, hitObject.y, score.drawSymbolNumber(hitObject.comboNumber, hitObject.x, hitObject.y,
hitCircle.getWidth() * 0.40f / score.getDefaultSymbolImage(0).getHeight()); hitCircle.getWidth() * 0.40f / score.getDefaultSymbolImage(0).getHeight());
} }
} }
/**
* Draws an image based on its center with a color filter.
*/
private void drawCentered(Image img, float x, float y, Color color) {
img.draw(x - (img.getWidth() / 2f), y - (img.getHeight() / 2f), color);
}
/** /**
* Calculates the circle hit result. * Calculates the circle hit result.
* @param time the hit object time (difference between track time) * @param time the hit object time (difference between track time)

View File

@ -22,6 +22,7 @@ import itdelatrisu.opsu.GameScore;
import itdelatrisu.opsu.MusicController; import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.OsuFile; import itdelatrisu.opsu.OsuFile;
import itdelatrisu.opsu.OsuHitObject; import itdelatrisu.opsu.OsuHitObject;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.states.Game; import itdelatrisu.opsu.states.Game;
import itdelatrisu.opsu.states.Options; import itdelatrisu.opsu.states.Options;
@ -240,9 +241,9 @@ public class Slider {
// draw overlay and hit circle // draw overlay and hit circle
for (int i = curveX.length - 1; i >= 0; i--) for (int i = curveX.length - 1; i >= 0; i--)
drawCentered(hitCircleOverlay, curveX[i], curveY[i], Color.white); Utils.drawCentered(hitCircleOverlay, curveX[i], curveY[i], Color.white);
for (int i = curveX.length - 1; i >= 0; i--) for (int i = curveX.length - 1; i >= 0; i--)
drawCentered(hitCircle, curveX[i], curveY[i], color); Utils.drawCentered(hitCircle, curveX[i], curveY[i], color);
} }
} }
@ -312,12 +313,12 @@ public class Slider {
// end circle // end circle
int lastIndex = hitObject.sliderX.length - 1; int lastIndex = hitObject.sliderX.length - 1;
drawCentered(hitCircleOverlay, hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex], Color.white); Utils.drawCentered(hitCircleOverlay, hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex], Color.white);
drawCentered(hitCircle, hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex], color); Utils.drawCentered(hitCircle, hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex], color);
// start circle // start circle
drawCentered(hitCircleOverlay, hitObject.x, hitObject.y, Color.white); Utils.drawCentered(hitCircleOverlay, hitObject.x, hitObject.y, Color.white);
drawCentered(hitCircle, hitObject.x, hitObject.y, color); Utils.drawCentered(hitCircle, hitObject.x, hitObject.y, color);
if (sliderClicked) if (sliderClicked)
; // don't draw current combo number if already clicked ; // don't draw current combo number if already clicked
else else
@ -335,12 +336,13 @@ public class Slider {
if (timeDiff >= 0) { if (timeDiff >= 0) {
// approach circle // approach circle
float approachScale = 1 + (timeDiff * 2f / game.getApproachTime()); float approachScale = 1 + (timeDiff * 2f / game.getApproachTime());
drawCentered(Circle.getApproachCircle().getScaledCopy(approachScale), hitObject.x, hitObject.y, color); Utils.drawCentered(Circle.getApproachCircle().getScaledCopy(approachScale),
hitObject.x, hitObject.y, color);
} else { } else {
float[] c = bezier.pointAt(getT(trackPosition, false)); float[] c = bezier.pointAt(getT(trackPosition, false));
// slider ball // slider ball
drawCentered(sliderBall, c[0], c[1]); Utils.drawCentered(sliderBall, c[0], c[1]);
// follow circle // follow circle
if (followCircleActive) if (followCircleActive)
@ -348,20 +350,6 @@ public class Slider {
} }
} }
/**
* Draws an image based on its center with a color filter.
*/
private 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 with a color filter.
*/
private static void drawCentered(Animation anim, float x, float y) {
anim.draw(x - (anim.getWidth() / 2f), y - (anim.getHeight() / 2f));
}
/** /**
* Calculates the slider hit result. * Calculates the slider hit result.
* @param time the hit object time (difference between track time) * @param time the hit object time (difference between track time)

View File

@ -22,6 +22,7 @@ import itdelatrisu.opsu.GameScore;
import itdelatrisu.opsu.MusicController; import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.OsuHitObject; import itdelatrisu.opsu.OsuHitObject;
import itdelatrisu.opsu.SoundController; import itdelatrisu.opsu.SoundController;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.states.Game; import itdelatrisu.opsu.states.Game;
import itdelatrisu.opsu.states.Options; import itdelatrisu.opsu.states.Options;
@ -127,7 +128,7 @@ public class Spinner {
//spinnerOsuImage.drawCentered(width / 2, height / 4); //spinnerOsuImage.drawCentered(width / 2, height / 4);
// darken screen // darken screen
g.setColor(Options.COLOR_BLACK_ALPHA); g.setColor(Utils.COLOR_BLACK_ALPHA);
g.fillRect(0, 0, width, height); g.fillRect(0, 0, width, height);
if (timeDiff > 0) if (timeDiff > 0)

View File

@ -26,6 +26,7 @@ import itdelatrisu.opsu.OsuFile;
import itdelatrisu.opsu.OsuHitObject; import itdelatrisu.opsu.OsuHitObject;
import itdelatrisu.opsu.OsuTimingPoint; import itdelatrisu.opsu.OsuTimingPoint;
import itdelatrisu.opsu.SoundController; import itdelatrisu.opsu.SoundController;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.objects.Circle; import itdelatrisu.opsu.objects.Circle;
import itdelatrisu.opsu.objects.Slider; import itdelatrisu.opsu.objects.Slider;
import itdelatrisu.opsu.objects.Spinner; import itdelatrisu.opsu.objects.Spinner;
@ -264,8 +265,6 @@ public class Game extends BasicGameState {
if (!osu.drawBG(width, height, 0.7f)) if (!osu.drawBG(width, height, 0.7f))
g.setBackground(Color.black); g.setBackground(Color.black);
Options.drawFPS();
int trackPosition = MusicController.getPosition(); int trackPosition = MusicController.getPosition();
if (pauseTime > -1) // returning from pause screen if (pauseTime > -1) // returning from pause screen
trackPosition = pauseTime; trackPosition = pauseTime;
@ -313,6 +312,9 @@ public class Game extends BasicGameState {
warningArrowL.draw(width * 0.75f, height * 0.75f); warningArrowL.draw(width * 0.75f, height * 0.75f);
} }
} }
Utils.drawFPS();
Utils.drawCursor();
return; return;
} }
} }
@ -413,7 +415,7 @@ public class Game extends BasicGameState {
// returning from pause screen // returning from pause screen
if (pauseTime > -1 && pausedMouseX > -1 && pausedMouseY > -1) { if (pauseTime > -1 && pausedMouseX > -1 && pausedMouseY > -1) {
// darken the screen // darken the screen
g.setColor(Options.COLOR_BLACK_ALPHA); g.setColor(Utils.COLOR_BLACK_ALPHA);
g.fillRect(0, 0, width, height); g.fillRect(0, 0, width, height);
// draw glowing hit select circle and pulse effect // draw glowing hit select circle and pulse effect
@ -425,6 +427,9 @@ public class Game extends BasicGameState {
cursorCirclePulse.setAlpha(1f - pausePulse); cursorCirclePulse.setAlpha(1f - pausePulse);
cursorCirclePulse.drawCentered(pausedMouseX, pausedMouseY); cursorCirclePulse.drawCentered(pausedMouseX, pausedMouseY);
} }
Utils.drawFPS();
Utils.drawCursor();
} }
@Override @Override
@ -589,7 +594,7 @@ public class Game extends BasicGameState {
mousePressed(Input.MOUSE_RIGHT_BUTTON, input.getMouseX(), input.getMouseY()); mousePressed(Input.MOUSE_RIGHT_BUTTON, input.getMouseX(), input.getMouseY());
break; break;
case Input.KEY_F12: case Input.KEY_F12:
Options.takeScreenShot(); Utils.takeScreenShot();
break; break;
} }
} }
@ -647,6 +652,9 @@ public class Game extends BasicGameState {
if (osu == null || osu.objects == null) if (osu == null || osu.objects == null)
throw new RuntimeException("Running game with no OsuFile loaded."); throw new RuntimeException("Running game with no OsuFile loaded.");
// grab the mouse
container.setMouseGrabbed(true);
// restart the game // restart the game
if (restart != RESTART_FALSE) { if (restart != RESTART_FALSE) {
// new game // new game
@ -720,6 +728,12 @@ public class Game extends BasicGameState {
} }
} }
@Override
public void leave(GameContainer container, StateBasedGame game)
throws SlickException {
container.setMouseGrabbed(false);
}
/** /**
* Skips the beginning of a track. * Skips the beginning of a track.
* @return true if skipped, false otherwise * @return true if skipped, false otherwise

View File

@ -22,6 +22,7 @@ import itdelatrisu.opsu.GUIMenuButton;
import itdelatrisu.opsu.MusicController; import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.Opsu; import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.SoundController; import itdelatrisu.opsu.SoundController;
import itdelatrisu.opsu.Utils;
import org.newdawn.slick.Color; import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer; import org.newdawn.slick.GameContainer;
@ -117,13 +118,14 @@ public class GamePauseMenu extends BasicGameState {
else else
g.setBackground(Color.black); g.setBackground(Color.black);
Options.drawFPS();
// draw buttons // draw buttons
if (Game.getRestart() != Game.RESTART_LOSE) if (Game.getRestart() != Game.RESTART_LOSE)
continueButton.draw(); continueButton.draw();
retryButton.draw(); retryButton.draw();
backButton.draw(); backButton.draw();
Utils.drawFPS();
Utils.drawCursor();
} }
@Override @Override
@ -149,7 +151,7 @@ public class GamePauseMenu extends BasicGameState {
unPause(Game.RESTART_FALSE); unPause(Game.RESTART_FALSE);
break; break;
case Input.KEY_F12: case Input.KEY_F12:
Options.takeScreenShot(); Utils.takeScreenShot();
break; break;
} }
} }

View File

@ -24,6 +24,7 @@ import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.Opsu; import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.OsuFile; import itdelatrisu.opsu.OsuFile;
import itdelatrisu.opsu.SoundController; import itdelatrisu.opsu.SoundController;
import itdelatrisu.opsu.Utils;
import org.lwjgl.opengl.Display; import org.lwjgl.opengl.Display;
import org.newdawn.slick.Color; import org.newdawn.slick.Color;
@ -100,7 +101,7 @@ public class GameRanking extends BasicGameState {
// background // background
if (!osu.drawBG(width, height, 0.7f)) if (!osu.drawBG(width, height, 0.7f))
g.setBackground(Options.COLOR_BLACK_ALPHA); g.setBackground(Utils.COLOR_BLACK_ALPHA);
// ranking screen elements // ranking screen elements
score.drawRankingElements(g, width, height); score.drawRankingElements(g, width, height);
@ -118,17 +119,18 @@ public class GameRanking extends BasicGameState {
// header text // header text
g.setColor(Color.white); g.setColor(Color.white);
Options.FONT_LARGE.drawString(10, 0, Utils.FONT_LARGE.drawString(10, 0,
String.format("%s - %s [%s]", osu.artist, osu.title, osu.version)); String.format("%s - %s [%s]", osu.artist, osu.title, osu.version));
Options.FONT_MEDIUM.drawString(10, Options.FONT_LARGE.getLineHeight() - 6, Utils.FONT_MEDIUM.drawString(10, Utils.FONT_LARGE.getLineHeight() - 6,
String.format("Beatmap by %s", osu.creator)); String.format("Beatmap by %s", osu.creator));
// buttons // buttons
retryButton.draw(); retryButton.draw();
exitButton.draw(); exitButton.draw();
Options.getBackButton().draw(); Utils.getBackButton().draw();
Options.drawFPS(); Utils.drawFPS();
Utils.drawCursor();
} }
@Override @Override
@ -150,7 +152,7 @@ public class GameRanking extends BasicGameState {
game.enterState(Opsu.STATE_SONGMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black)); game.enterState(Opsu.STATE_SONGMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
break; break;
case Input.KEY_F12: case Input.KEY_F12:
Options.takeScreenShot(); Utils.takeScreenShot();
break; break;
} }
} }
@ -170,7 +172,7 @@ public class GameRanking extends BasicGameState {
} else if (exitButton.contains(x, y)) { } else if (exitButton.contains(x, y)) {
SoundController.playSound(SoundController.SOUND_MENUBACK); SoundController.playSound(SoundController.SOUND_MENUBACK);
game.enterState(Opsu.STATE_MAINMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black)); game.enterState(Opsu.STATE_MAINMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
} else if (Options.getBackButton().contains(x, y)) { } else if (Utils.getBackButton().contains(x, y)) {
MusicController.pause(); MusicController.pause();
MusicController.playAt(Game.getOsuFile().previewTime, true); MusicController.playAt(Game.getOsuFile().previewTime, true);
SoundController.playSound(SoundController.SOUND_MENUBACK); SoundController.playSound(SoundController.SOUND_MENUBACK);

View File

@ -23,6 +23,7 @@ import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.Opsu; import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.OsuGroupNode; import itdelatrisu.opsu.OsuGroupNode;
import itdelatrisu.opsu.SoundController; import itdelatrisu.opsu.SoundController;
import itdelatrisu.opsu.Utils;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
@ -153,8 +154,8 @@ public class MainMenu extends BasicGameState {
if (backgroundImage != null) if (backgroundImage != null)
backgroundImage.draw(); backgroundImage.draw();
else else
g.setBackground(Options.COLOR_BLUE_BACKGROUND); g.setBackground(Utils.COLOR_BLUE_BACKGROUND);
g.setFont(Options.FONT_MEDIUM); g.setFont(Utils.FONT_MEDIUM);
int width = container.getWidth(); int width = container.getWidth();
int height = container.getHeight(); int height = container.getHeight();
@ -173,7 +174,7 @@ public class MainMenu extends BasicGameState {
musicPlay.draw(); musicPlay.draw();
musicNext.draw(); musicNext.draw();
musicPrevious.draw(); musicPrevious.draw();
g.setColor(Options.COLOR_BLACK_ALPHA); g.setColor(Utils.COLOR_BLACK_ALPHA);
g.fillRoundRect(width - 168, 54, 148, 5, 4); g.fillRoundRect(width - 168, 54, 148, 5, 4);
g.setColor(Color.white); g.setColor(Color.white);
if (!MusicController.isConverting()) if (!MusicController.isConverting())
@ -181,7 +182,7 @@ public class MainMenu extends BasicGameState {
148f * MusicController.getPosition() / MusicController.getTrackLength(), 5, 4); 148f * MusicController.getPosition() / MusicController.getTrackLength(), 5, 4);
// draw text // draw text
int lineHeight = Options.FONT_MEDIUM.getLineHeight(); int lineHeight = Utils.FONT_MEDIUM.getLineHeight();
g.drawString(String.format("Loaded %d songs and %d beatmaps.", g.drawString(String.format("Loaded %d songs and %d beatmaps.",
Opsu.groups.size(), Opsu.groups.getMapCount()), 25, 25); Opsu.groups.size(), Opsu.groups.getMapCount()), 25, 25);
if (MusicController.isConverting()) if (MusicController.isConverting())
@ -203,7 +204,8 @@ public class MainMenu extends BasicGameState {
new SimpleDateFormat("h:mm a").format(new Date())), new SimpleDateFormat("h:mm a").format(new Date())),
25, height - 25 - lineHeight); 25, height - 25 - lineHeight);
Options.drawFPS(); Utils.drawFPS();
Utils.drawCursor();
} }
@Override @Override
@ -317,7 +319,7 @@ public class MainMenu extends BasicGameState {
game.enterState(Opsu.STATE_MAINMENUEXIT); game.enterState(Opsu.STATE_MAINMENUEXIT);
break; break;
case Input.KEY_F12: case Input.KEY_F12:
Options.takeScreenShot(); Utils.takeScreenShot();
break; break;
} }

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.Utils;
import org.newdawn.slick.Color; import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer; import org.newdawn.slick.GameContainer;
@ -90,22 +91,23 @@ public class MainMenuExit extends BasicGameState {
// draw text // draw text
float c = container.getWidth() * 0.02f; float c = container.getWidth() * 0.02f;
Options.FONT_LARGE.drawString(c, c, "Are you sure you want to exit opsu!?"); Utils.FONT_LARGE.drawString(c, c, "Are you sure you want to exit opsu!?");
// draw buttons // draw buttons
yesButton.draw(Color.green); yesButton.draw(Color.green);
noButton.draw(Color.red); noButton.draw(Color.red);
g.setFont(Options.FONT_XLARGE); g.setFont(Utils.FONT_XLARGE);
g.drawString("1. Yes", g.drawString("1. Yes",
yesButton.getX() - (Options.FONT_XLARGE.getWidth("1. Yes") / 2f), yesButton.getX() - (Utils.FONT_XLARGE.getWidth("1. Yes") / 2f),
yesButton.getY() - (Options.FONT_XLARGE.getHeight() / 2f) yesButton.getY() - (Utils.FONT_XLARGE.getHeight() / 2f)
); );
g.drawString("2. No", g.drawString("2. No",
noButton.getX() - (Options.FONT_XLARGE.getWidth("2. No") / 2f), noButton.getX() - (Utils.FONT_XLARGE.getWidth("2. No") / 2f),
noButton.getY() - (Options.FONT_XLARGE.getHeight() / 2f) noButton.getY() - (Utils.FONT_XLARGE.getHeight() / 2f)
); );
Options.drawFPS(); Utils.drawFPS();
Utils.drawCursor();
} }
@Override @Override
@ -150,7 +152,7 @@ public class MainMenuExit extends BasicGameState {
game.enterState(Opsu.STATE_MAINMENU, new EmptyTransition(), new FadeInTransition(Color.black)); game.enterState(Opsu.STATE_MAINMENU, new EmptyTransition(), new FadeInTransition(Color.black));
break; break;
case Input.KEY_F12: case Input.KEY_F12:
Options.takeScreenShot(); Utils.takeScreenShot();
break; break;
} }
} }

View File

@ -20,9 +20,8 @@ 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 java.awt.Font;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.File; import java.io.File;
@ -39,8 +38,6 @@ import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image; import org.newdawn.slick.Image;
import org.newdawn.slick.Input; import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException; import org.newdawn.slick.SlickException;
import org.newdawn.slick.TrueTypeFont;
import org.newdawn.slick.imageout.ImageOut;
import org.newdawn.slick.state.BasicGameState; import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame; import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.state.transition.EmptyTransition; import org.newdawn.slick.state.transition.EmptyTransition;
@ -85,41 +82,6 @@ public class Options extends BasicGameState {
*/ */
private static final String OPTIONS_FILE = ".opsu.cfg"; private static final String OPTIONS_FILE = ".opsu.cfg";
/**
* Whether or not any changes were made to options.
* If false, the options file will not be modified.
*/
private static boolean optionsChanged = false;
/**
* Game colors.
*/
public static final Color
COLOR_BLACK_ALPHA = new Color(0, 0, 0, 0.5f),
COLOR_BLUE_DIVIDER = new Color(49, 94, 237),
COLOR_BLUE_BACKGROUND = new Color(74, 130, 255),
COLOR_BLUE_BUTTON = new Color(50, 189, 237),
COLOR_ORANGE_BUTTON = new Color(230, 151, 87),
COLOR_GREEN_OBJECT = new Color(26, 207, 26),
COLOR_BLUE_OBJECT = new Color(46, 136, 248),
COLOR_RED_OBJECT = new Color(243, 48, 77),
COLOR_ORANGE_OBJECT = new Color(255, 200, 32);
/**
* The default map colors, used when a map does not provide custom colors.
*/
public static final Color[] DEFAULT_COMBO = {
COLOR_GREEN_OBJECT, COLOR_BLUE_OBJECT,
COLOR_RED_OBJECT, COLOR_ORANGE_OBJECT
};
/**
* Game fonts.
*/
public static TrueTypeFont
FONT_DEFAULT, FONT_BOLD,
FONT_XLARGE, FONT_LARGE, FONT_MEDIUM, FONT_SMALL;
/** /**
* Game mods. * Game mods.
*/ */
@ -235,12 +197,12 @@ public class Options extends BasicGameState {
/** /**
* Port binding. * Port binding.
*/ */
private static int port = 0; private static int port = 49250;
/** /**
* Back button (shared by other states). * Whether or not to use the new cursor type.
*/ */
private static GUIMenuButton backButton; private static boolean newCursor = true;
/** /**
* Game option coordinate modifiers (for drawing). * Game option coordinate modifiers (for drawing).
@ -248,8 +210,8 @@ public class Options extends BasicGameState {
private int textY, offsetY; private int textY, offsetY;
// game-related variables // game-related variables
private static GameContainer container; private GameContainer container;
private static StateBasedGame game; private StateBasedGame game;
private Input input; private Input input;
private int state; private int state;
private boolean init = false; private boolean init = false;
@ -261,42 +223,17 @@ public class Options extends BasicGameState {
@Override @Override
public void init(GameContainer container, StateBasedGame game) public void init(GameContainer container, StateBasedGame game)
throws SlickException { throws SlickException {
Options.container = container; this.container = container;
Options.game = game; this.game = game;
this.input = container.getInput(); this.input = container.getInput();
// game settings Utils.init(container, game);
container.setTargetFrameRate(targetFPS[targetFPSindex]);
container.setMouseCursor("cursor.png", 16, 16);
container.setMusicVolume(getMusicVolume());
container.setShowFPS(false);
container.getInput().enableKeyRepeat();
container.setAlwaysRender(true);
// create fonts
float fontBase;
if (container.getHeight() <= 600)
fontBase = 9f;
else if (container.getHeight() < 800)
fontBase = 10f;
else if (container.getHeight() <= 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);
int width = container.getWidth(); int width = container.getWidth();
int height = container.getHeight(); int height = container.getHeight();
// game option coordinate modifiers // game option coordinate modifiers
textY = 10 + (FONT_XLARGE.getLineHeight() * 3 / 2); textY = 10 + (Utils.FONT_XLARGE.getLineHeight() * 3 / 2);
offsetY = (int) (((height * 0.8f) - textY) / OPTIONS_MAX); offsetY = (int) (((height * 0.8f) - textY) / OPTIONS_MAX);
// game mods // game mods
@ -330,14 +267,6 @@ public class Options extends BasicGameState {
for (int i = 0; i < modButtons.length; i++) for (int i = 0; i < modButtons.length; i++)
modButtons[i].getImage().setAlpha(0.5f); modButtons[i].getImage().setAlpha(0.5f);
// 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));
game.enterState(Opsu.STATE_MAINMENU, new EmptyTransition(), new FadeInTransition(Color.black)); game.enterState(Opsu.STATE_MAINMENU, new EmptyTransition(), new FadeInTransition(Color.black));
} }
@ -347,25 +276,25 @@ public class Options extends BasicGameState {
if (!init) if (!init)
return; return;
g.setBackground(COLOR_BLACK_ALPHA); g.setBackground(Utils.COLOR_BLACK_ALPHA);
g.setColor(Color.white); g.setColor(Color.white);
int width = container.getWidth(); int width = container.getWidth();
int height = container.getHeight(); int height = container.getHeight();
// title // title
FONT_XLARGE.drawString( Utils.FONT_XLARGE.drawString(
(width / 2) - (FONT_XLARGE.getWidth("GAME OPTIONS") / 2), (width / 2) - (Utils.FONT_XLARGE.getWidth("GAME OPTIONS") / 2),
10, "GAME OPTIONS" 10, "GAME OPTIONS"
); );
FONT_DEFAULT.drawString( Utils.FONT_DEFAULT.drawString(
(width / 2) - (FONT_DEFAULT.getWidth("Click or drag an option to change it.") / 2), (width / 2) - (Utils.FONT_DEFAULT.getWidth("Click or drag an option to change it.") / 2),
10 + FONT_XLARGE.getHeight(), "Click or drag an option to change it." 10 + Utils.FONT_XLARGE.getHeight(), "Click or drag an option to change it."
); );
// game options // game options
g.setLineWidth(1f); g.setLineWidth(1f);
g.setFont(FONT_LARGE); g.setFont(Utils.FONT_LARGE);
this.drawOption(g, OPTIONS_SCREEN_RESOLUTION, "Screen Resolution", this.drawOption(g, OPTIONS_SCREEN_RESOLUTION, "Screen Resolution",
String.format("%dx%d", resolutions[resolutionIndex][0], resolutions[resolutionIndex][1]), String.format("%dx%d", resolutions[resolutionIndex][0], resolutions[resolutionIndex][1]),
"Restart to apply resolution changes." "Restart to apply resolution changes."
@ -375,7 +304,7 @@ public class Options extends BasicGameState {
// "Restart to apply changes." // "Restart to apply changes."
// ); // );
this.drawOption(g, OPTIONS_TARGET_FPS, "Frame Limiter", this.drawOption(g, OPTIONS_TARGET_FPS, "Frame Limiter",
String.format("%dfps", targetFPS[targetFPSindex]), String.format("%dfps", getTargetFPS()),
"Higher values may cause high CPU usage." "Higher values may cause high CPU usage."
); );
this.drawOption(g, OPTIONS_MUSIC_VOLUME, "Music Volume", this.drawOption(g, OPTIONS_MUSIC_VOLUME, "Music Volume",
@ -408,13 +337,14 @@ public class Options extends BasicGameState {
); );
// game mods // game mods
FONT_LARGE.drawString(width * 0.02f, height * 0.8f, "Game Mods:", Color.white); Utils.FONT_LARGE.drawString(width * 0.02f, 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();
backButton.draw(); Utils.getBackButton().draw();
drawFPS(); Utils.drawFPS();
Utils.drawCursor();
} }
@Override @Override
@ -433,7 +363,7 @@ public class Options extends BasicGameState {
return; return;
// back // back
if (backButton.contains(x, y)) { if (Utils.getBackButton().contains(x, y)) {
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition(Color.black)); game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition(Color.black));
return; return;
} }
@ -472,7 +402,7 @@ public class Options extends BasicGameState {
// } // }
if (isOptionClicked(OPTIONS_TARGET_FPS, y)) { if (isOptionClicked(OPTIONS_TARGET_FPS, y)) {
targetFPSindex = (targetFPSindex + 1) % targetFPS.length; targetFPSindex = (targetFPSindex + 1) % targetFPS.length;
container.setTargetFrameRate(targetFPS[targetFPSindex]); container.setTargetFrameRate(getTargetFPS());
return; return;
} }
if (isOptionClicked(OPTIONS_SCREENSHOT_FORMAT, y)) { if (isOptionClicked(OPTIONS_SCREENSHOT_FORMAT, y)) {
@ -545,7 +475,7 @@ public class Options extends BasicGameState {
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:
Options.takeScreenShot(); Utils.takeScreenShot();
break; break;
} }
} }
@ -560,14 +490,14 @@ public class Options extends BasicGameState {
*/ */
private void drawOption(Graphics g, int pos, String label, String value, String notes) { private void drawOption(Graphics g, int pos, String label, String value, String notes) {
int width = container.getWidth(); int width = container.getWidth();
int textHeight = 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.drawString(label, width / 50, y);
g.drawString(value, width / 2, y); g.drawString(value, width / 2, y);
g.drawLine(0, y + textHeight, width, y + textHeight); g.drawLine(0, y + textHeight, width, y + textHeight);
if (notes != null) if (notes != null)
FONT_SMALL.drawString(width / 50, y + textHeight, notes); Utils.FONT_SMALL.drawString(width / 50, y + textHeight, notes);
} }
/** /**
@ -577,12 +507,8 @@ public class Options extends BasicGameState {
* @return true if clicked * @return true if clicked
*/ */
private boolean isOptionClicked(int pos, int y) { private boolean isOptionClicked(int pos, int y) {
if (y > textY + (offsetY * pos) - FONT_LARGE.getHeight() && return (y > textY + (offsetY * pos) - Utils.FONT_LARGE.getHeight() &&
y < textY + (offsetY * pos) + FONT_LARGE.getHeight()) { y < textY + (offsetY * pos) + Utils.FONT_LARGE.getHeight());
optionsChanged = true;
return true;
}
return false;
} }
/** /**
@ -610,9 +536,10 @@ public class Options extends BasicGameState {
public static Image getModImage(int mod) { return modButtons[mod].getImage(); } public static Image getModImage(int mod) { return modButtons[mod].getImage(); }
/** /**
* Returns the 'back' GUIMenuButton. * Returns the target frame rate.
* @return the target FPS
*/ */
public static GUIMenuButton getBackButton() { return backButton; } public static int getTargetFPS() { return targetFPS[targetFPSindex]; }
/** /**
* Returns the default music volume. * Returns the default music volume.
@ -633,53 +560,10 @@ public class Options extends BasicGameState {
public static int getMusicOffset() { return musicOffset; } public static int getMusicOffset() { return musicOffset; }
/** /**
* Draws the FPS at the bottom-right corner of the game container. * Returns the screenshot file format.
* If the option is not activated, this will do nothing. * @return the file extension ("png", "jpg", "bmp")
*/ */
public static void drawFPS() { public static String getScreenshotFormat() { return screenshotFormat[screenshotFormatIndex]; }
if (showFPS) {
String fps = String.format("FPS: %d", container.getFPS());
FONT_DEFAULT.drawString(
container.getWidth() - 15 - FONT_DEFAULT.getWidth(fps),
container.getHeight() - 15 - FONT_DEFAULT.getHeight(fps),
fps, Color.white
);
}
}
/**
* Takes a screenshot.
* @return true if successful
*/
public static boolean takeScreenShot() {
// TODO: should this be threaded?
try {
// create the screenshot directory
if (!SCREENSHOT_DIR.isDirectory()) {
if (!SCREENSHOT_DIR.mkdir())
return false;
}
// create file name
SimpleDateFormat date = new SimpleDateFormat("yyyyMMdd_HHmmss");
String file = date.format(new Date());
SoundController.playSound(SoundController.SOUND_SHUTTER);
// copy the screen
Image screen = new Image(container.getWidth(), container.getHeight());
container.getGraphics().copyArea(screen, 0, 0);
ImageOut.write(screen, String.format("%s%sscreenshot_%s.%s",
SCREENSHOT_DIR.getName(), File.separator,
file, screenshotFormat[screenshotFormatIndex]), false
);
screen.destroy();
} catch (SlickException e) {
Log.warn("Failed to take a screenshot.", e);
return false;
}
return true;
}
/** /**
* Returns the screen resolution. * Returns the screen resolution.
@ -693,6 +577,12 @@ public class Options extends BasicGameState {
// */ // */
// public static boolean isFullscreen() { return fullscreen; } // public static boolean isFullscreen() { return fullscreen; }
/**
* Returns whether or not the FPS counter display is enabled.
* @return true if enabled
*/
public static boolean isFPSCounterEnabled() { return showFPS; }
/** /**
* Returns whether or not hit lighting effects are enabled. * Returns whether or not hit lighting effects are enabled.
* @return true if enabled * @return true if enabled
@ -709,14 +599,13 @@ public class Options extends BasicGameState {
* Returns the port number to bind to. * Returns the port number to bind to.
* @return the port * @return the port
*/ */
public static int getPort() { public static int getPort() { return port; }
if (port == 0) {
// choose a random port /**
port = 49250; * Returns whether or not the new cursor type is enabled.
optionsChanged = true; // force file creation * @return true if enabled
} */
return port; public static boolean isNewCursorEnabled() { return newCursor; }
}
/** /**
* Returns the current beatmap directory. * Returns the current beatmap directory.
@ -730,15 +619,14 @@ public class Options extends BasicGameState {
// search for directory // search for directory
for (int i = 0; i < BEATMAP_DIRS.length; i++) { for (int i = 0; i < BEATMAP_DIRS.length; i++) {
beatmapDir = new File(BEATMAP_DIRS[i]); beatmapDir = new File(BEATMAP_DIRS[i]);
if (beatmapDir.isDirectory()) { if (beatmapDir.isDirectory())
optionsChanged = true; // force config file creation
return beatmapDir; return beatmapDir;
} }
}
beatmapDir.mkdir(); // none found, create new directory beatmapDir.mkdir(); // none found, create new directory
return beatmapDir; return beatmapDir;
} }
/** /**
* Reads user options from the options file, if it exists. * Reads user options from the options file, if it exists.
*/ */
@ -746,9 +634,7 @@ public class Options extends BasicGameState {
// if no config file, use default settings // if no config file, use default settings
File file = new File(OPTIONS_FILE); File file = new File(OPTIONS_FILE);
if (!file.isFile()) { if (!file.isFile()) {
optionsChanged = true; // force file creation
saveOptions(); saveOptions();
optionsChanged = false;
return; return;
} }
@ -816,6 +702,9 @@ public class Options extends BasicGameState {
if (i > 0 && i <= 65535) if (i > 0 && i <= 65535)
port = i; port = i;
break; break;
case "NewCursor": // custom
newCursor = Boolean.parseBoolean(value);
break;
} }
} }
} catch (IOException e) { } catch (IOException e) {
@ -830,10 +719,6 @@ public class Options extends BasicGameState {
* (Over)writes user options to a file. * (Over)writes user options to a file.
*/ */
public static void saveOptions() { public static void saveOptions() {
// only overwrite when needed
if (!optionsChanged)
return;
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(OPTIONS_FILE), "utf-8"))) { new FileOutputStream(OPTIONS_FILE), "utf-8"))) {
// header // header
@ -873,6 +758,8 @@ public class Options extends BasicGameState {
writer.newLine(); writer.newLine();
writer.write(String.format("Port = %d", port)); writer.write(String.format("Port = %d", port));
writer.newLine(); writer.newLine();
writer.write(String.format("NewCursor = %b", newCursor)); // custom
writer.newLine();
writer.close(); writer.close();
} catch (IOException e) { } catch (IOException e) {
Log.error(String.format("Failed to write to file '%s'.", OPTIONS_FILE), e); Log.error(String.format("Failed to write to file '%s'.", OPTIONS_FILE), e);

View File

@ -26,6 +26,7 @@ import itdelatrisu.opsu.OsuGroupList;
import itdelatrisu.opsu.OsuGroupNode; import itdelatrisu.opsu.OsuGroupNode;
import itdelatrisu.opsu.OsuParser; import itdelatrisu.opsu.OsuParser;
import itdelatrisu.opsu.SoundController; import itdelatrisu.opsu.SoundController;
import itdelatrisu.opsu.Utils;
import org.lwjgl.opengl.Display; import org.lwjgl.opengl.Display;
import org.newdawn.slick.Color; import org.newdawn.slick.Color;
@ -170,13 +171,13 @@ public class SongMenu extends BasicGameState {
searchResultString = "Type to search!"; searchResultString = "Type to search!";
searchIcon = new Image("search.png"); searchIcon = new Image("search.png");
float iconScale = Options.FONT_BOLD.getLineHeight() * 2f / searchIcon.getHeight(); float iconScale = Utils.FONT_BOLD.getLineHeight() * 2f / searchIcon.getHeight();
searchIcon = searchIcon.getScaledCopy(iconScale); searchIcon = searchIcon.getScaledCopy(iconScale);
search = new TextField( search = new TextField(
container, Options.FONT_DEFAULT, container, Utils.FONT_DEFAULT,
(int) tabX + searchIcon.getWidth(), (int) ((height * 0.15f) - (tab.getHeight() * 5 / 2f)), (int) tabX + searchIcon.getWidth(), (int) ((height * 0.15f) - (tab.getHeight() * 5 / 2f)),
(int) (buttonWidth / 2), Options.FONT_DEFAULT.getHeight() (int) (buttonWidth / 2), Utils.FONT_DEFAULT.getHeight()
); );
search.setBackgroundColor(Color.transparent); search.setBackgroundColor(Color.transparent);
search.setBorderColor(Color.transparent); search.setBorderColor(Color.transparent);
@ -205,9 +206,9 @@ public class SongMenu extends BasicGameState {
// header setup // header setup
float lowerBound = height * 0.15f; float lowerBound = height * 0.15f;
g.setColor(Options.COLOR_BLACK_ALPHA); g.setColor(Utils.COLOR_BLACK_ALPHA);
g.fillRect(0, 0, width, lowerBound); g.fillRect(0, 0, width, lowerBound);
g.setColor(Options.COLOR_BLUE_DIVIDER); g.setColor(Utils.COLOR_BLUE_DIVIDER);
g.setLineWidth(2f); g.setLineWidth(2f);
g.drawLine(0, lowerBound, width, lowerBound); g.drawLine(0, lowerBound, width, lowerBound);
g.resetLineWidth(); g.resetLineWidth();
@ -220,17 +221,17 @@ public class SongMenu extends BasicGameState {
String[] info = focusNode.getInfo(); String[] info = focusNode.getInfo();
g.setColor(Color.white); g.setColor(Color.white);
Options.FONT_LARGE.drawString( Utils.FONT_LARGE.drawString(
musicNoteWidth + 5, -3, info[0]); musicNoteWidth + 5, -3, info[0]);
float y1 = -3 + Options.FONT_LARGE.getHeight() * 0.75f; float y1 = -3 + Utils.FONT_LARGE.getHeight() * 0.75f;
Options.FONT_DEFAULT.drawString( Utils.FONT_DEFAULT.drawString(
musicNoteWidth + 5, y1, info[1]); musicNoteWidth + 5, y1, info[1]);
Options.FONT_BOLD.drawString( Utils.FONT_BOLD.drawString(
5, Math.max(y1 + 4, musicNoteHeight - 3), info[2]); 5, Math.max(y1 + 4, musicNoteHeight - 3), info[2]);
Options.FONT_DEFAULT.drawString( Utils.FONT_DEFAULT.drawString(
5, musicNoteHeight + Options.FONT_BOLD.getLineHeight() - 9, info[3]); 5, musicNoteHeight + Utils.FONT_BOLD.getLineHeight() - 9, info[3]);
Options.FONT_SMALL.drawString( Utils.FONT_SMALL.drawString(
5, musicNoteHeight + Options.FONT_BOLD.getLineHeight() + Options.FONT_DEFAULT.getLineHeight() - 13, info[4]); 5, musicNoteHeight + Utils.FONT_BOLD.getLineHeight() + Utils.FONT_DEFAULT.getLineHeight() - 13, info[4]);
} }
// song buttons // song buttons
@ -248,17 +249,17 @@ public class SongMenu extends BasicGameState {
for (int i = sortTabs.length - 1; i >= 0; i--) { for (int i = sortTabs.length - 1; i >= 0; i--) {
sortTabs[i].getImage().setAlpha((i == currentSort) ? 1.0f : 0.7f); sortTabs[i].getImage().setAlpha((i == currentSort) ? 1.0f : 0.7f);
sortTabs[i].draw(); sortTabs[i].draw();
float tabTextX = sortTabs[i].getX() - (Options.FONT_MEDIUM.getWidth(OsuGroupList.SORT_NAMES[i]) / 2); float tabTextX = sortTabs[i].getX() - (Utils.FONT_MEDIUM.getWidth(OsuGroupList.SORT_NAMES[i]) / 2);
Options.FONT_MEDIUM.drawString(tabTextX, tabTextY, OsuGroupList.SORT_NAMES[i], Color.white); Utils.FONT_MEDIUM.drawString(tabTextX, tabTextY, OsuGroupList.SORT_NAMES[i], Color.white);
} }
// search // search
Options.FONT_BOLD.drawString( Utils.FONT_BOLD.drawString(
search.getX(), search.getY() - Options.FONT_BOLD.getLineHeight(), search.getX(), search.getY() - Utils.FONT_BOLD.getLineHeight(),
searchResultString, Color.white searchResultString, Color.white
); );
searchIcon.draw(search.getX() - searchIcon.getWidth(), searchIcon.draw(search.getX() - searchIcon.getWidth(),
search.getY() - Options.FONT_DEFAULT.getLineHeight()); search.getY() - Utils.FONT_DEFAULT.getLineHeight());
g.setColor(Color.white); g.setColor(Color.white);
search.render(container, g); search.render(container, g);
@ -266,16 +267,17 @@ public class SongMenu extends BasicGameState {
if (focusNode != null) { if (focusNode != null) {
float scrollStartY = height * 0.16f; float scrollStartY = height * 0.16f;
float scrollEndY = height * 0.82f; float scrollEndY = height * 0.82f;
g.setColor(Options.COLOR_BLACK_ALPHA); g.setColor(Utils.COLOR_BLACK_ALPHA);
g.fillRoundRect(width - 10, scrollStartY, 5, scrollEndY, 4); g.fillRoundRect(width - 10, scrollStartY, 5, scrollEndY, 4);
g.setColor(Color.white); g.setColor(Color.white);
g.fillRoundRect(width - 10, scrollStartY + (scrollEndY * startNode.index / Opsu.groups.size()), 5, 20, 4); g.fillRoundRect(width - 10, scrollStartY + (scrollEndY * startNode.index / Opsu.groups.size()), 5, 20, 4);
} }
// back button // back button
Options.getBackButton().draw(); Utils.getBackButton().draw();
Options.drawFPS(); Utils.drawFPS();
Utils.drawCursor();
} }
@Override @Override
@ -343,7 +345,7 @@ public class SongMenu extends BasicGameState {
return; return;
// back // back
if (Options.getBackButton().contains(x, y)) { if (Utils.getBackButton().contains(x, y)) {
SoundController.playSound(SoundController.SOUND_MENUBACK); SoundController.playSound(SoundController.SOUND_MENUBACK);
game.enterState(Opsu.STATE_MAINMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black)); game.enterState(Opsu.STATE_MAINMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
return; return;
@ -436,7 +438,7 @@ public class SongMenu extends BasicGameState {
setFocus(Opsu.groups.getRandomNode(), -1, true); setFocus(Opsu.groups.getRandomNode(), -1, true);
break; break;
case Input.KEY_F12: case Input.KEY_F12:
Options.takeScreenShot(); Utils.takeScreenShot();
break; break;
case Input.KEY_ENTER: case Input.KEY_ENTER:
if (focusNode != null) if (focusNode != null)