Merge branch 'switch-base' into reorganise

This commit is contained in:
yugecin 2017-01-21 13:11:54 +01:00
commit dd731592aa
70 changed files with 2716 additions and 4320 deletions

View File

@ -1,184 +0,0 @@
/*
* opsu! - an open-source osu! client
* Copyright (C) 2014, 2015 Jeffrey Han
*
* opsu! is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu! is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu;
import itdelatrisu.opsu.audio.MusicController;
import itdelatrisu.opsu.audio.SoundController;
import itdelatrisu.opsu.beatmap.Beatmap;
import itdelatrisu.opsu.beatmap.BeatmapGroup;
import itdelatrisu.opsu.beatmap.BeatmapSetList;
import itdelatrisu.opsu.beatmap.BeatmapSortOrder;
import itdelatrisu.opsu.beatmap.BeatmapWatchService;
import itdelatrisu.opsu.downloads.DownloadList;
import itdelatrisu.opsu.downloads.Updater;
import itdelatrisu.opsu.render.CurveRenderState;
import itdelatrisu.opsu.ui.UI;
import org.lwjgl.opengl.Display;
import org.newdawn.slick.AppGameContainer;
import org.newdawn.slick.Game;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.opengl.InternalTextureLoader;
/**
* AppGameContainer extension that sends critical errors to ErrorHandler.
*/
public class Container extends AppGameContainer {
/** Exception causing game failure. */
protected Exception e = null;
public static Container instance;
/**
* Create a new container wrapping a game
*
* @param game The game to be wrapped
* @throws SlickException Indicates a failure to initialise the display
*/
public Container(Game game) throws SlickException {
super(game);
instance = this;
}
/**
* Create a new container wrapping a game
*
* @param game The game to be wrapped
* @param width The width of the display required
* @param height The height of the display required
* @param fullscreen True if we want fullscreen mode
* @throws SlickException Indicates a failure to initialise the display
*/
public Container(Game game, int width, int height, boolean fullscreen) throws SlickException {
super(game, width, height, fullscreen);
}
@Override
public void start() throws SlickException {
try {
setup();
ErrorHandler.setGlString();
getDelta();
while (running())
gameLoop();
} catch (Exception e) {
this.e = e;
}
// destroy the game container
try {
close_sub();
} catch (Exception e) {
if (this.e == null) // suppress if caused by a previous exception
this.e = e;
}
destroy();
// report any critical errors
if (e != null) {
ErrorHandler.error(null, e, true);
e = null;
forceExit = true;
}
if (forceExit) {
Opsu.close();
System.exit(0);
}
}
@Override
protected void gameLoop() throws SlickException {
int delta = getDelta();
if (!Display.isVisible() && updateOnlyOnVisible) {
try { Thread.sleep(100); } catch (Exception e) {}
} else {
try {
updateAndRender(delta);
} catch (SlickException e) {
this.e = e; // store exception to display later
running = false;
return;
}
}
updateFPS();
Display.update();
if (Display.isCloseRequested()) {
if (game.closeRequested())
running = false;
}
}
/**
* Actions to perform before destroying the game container.
*/
private void close_sub() {
// save user options
Options.saveOptions();
// reset cursor
UI.getCursor().reset();
// destroy images
InternalTextureLoader.get().clear();
// reset image references
GameImage.clearReferences();
GameData.Grade.clearReferences();
Beatmap.clearBackgroundImageCache();
// prevent loading tracks from re-initializing OpenAL
MusicController.reset();
// stop any playing track
SoundController.stopTrack();
// reset BeatmapSetList data
BeatmapGroup.set(BeatmapGroup.ALL);
BeatmapSortOrder.set(BeatmapSortOrder.TITLE);
if (BeatmapSetList.get() != null)
BeatmapSetList.get().reset();
// delete OpenGL objects involved in the Curve rendering
CurveRenderState.shutdown();
// destroy watch service
if (!Options.isWatchServiceEnabled())
BeatmapWatchService.destroy();
BeatmapWatchService.removeListeners();
// delete temporary directory
Utils.deleteDirectory(Options.TEMP_DIR);
}
@Override
public void exit() {
// show confirmation dialog if any downloads are active
if (forceExit) {
if (DownloadList.get().hasActiveDownloads() &&
UI.showExitConfirmation(DownloadList.EXIT_CONFIRMATION))
return;
if (Updater.get().getStatus() == Updater.Status.UPDATE_DOWNLOADING &&
UI.showExitConfirmation(Updater.EXIT_CONFIRMATION))
return;
}
super.exit();
}
}

View File

@ -1,242 +0,0 @@
/*
* opsu! - an open-source osu! client
* Copyright (C) 2014, 2015 Jeffrey Han
*
* opsu! is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu! is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu;
import java.awt.Cursor;
import java.awt.Desktop;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.Properties;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import org.lwjgl.opengl.GL11;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
/**
* Error handler to log and display errors.
*/
public class ErrorHandler {
/** Error popup title. */
private static final String title = "Error";
/** Error popup description text. */
private static final String
desc = "opsu! has encountered an error.",
descReport = "opsu! has encountered an error. Please report this!";
/** Error popup button options. */
private static final String[]
optionsLog = {"View Error Log", "Close"},
optionsReport = {"Send Report", "Close"},
optionsLogReport = {"Send Report", "View Error Log", "Close"};
/** Text area for Exception. */
private static final JTextArea textArea = new JTextArea(15, 100);
static {
textArea.setEditable(false);
textArea.setBackground(UIManager.getColor("Panel.background"));
textArea.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
textArea.setTabSize(2);
textArea.setLineWrap(false);
textArea.setWrapStyleWord(true);
}
/** Scroll pane holding JTextArea. */
private static final JScrollPane scroll = new JScrollPane(textArea);
/** Error popup objects. */
private static final Object[]
message = { desc, scroll },
messageReport = { descReport, scroll };
/** OpenGL string (if any). */
private static String glString = null;
// This class should not be instantiated.
private ErrorHandler() {}
/**
* Sets the OpenGL version string.
*/
public static void setGlString() {
try {
String glVersion = GL11.glGetString(GL11.GL_VERSION);
String glVendor = GL11.glGetString(GL11.GL_VENDOR);
glString = String.format("%s (%s)", glVersion, glVendor);
} catch (Exception e) {}
}
/**
* Displays an error popup and logs the given error.
* @param error a description of the error
* @param e the exception causing the error
* @param report whether to ask to report the error
*/
public static void error(String error, Throwable e, boolean report) {
if (error == null && e == null)
return;
// log the error
if (error == null)
Log.error(e);
else if (e == null)
Log.error(error);
else
Log.error(error, e);
// set the textArea to the error message
textArea.setText(null);
if (error != null) {
textArea.append(error);
textArea.append("\n");
}
String trace = null;
if (e != null) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
trace = sw.toString();
textArea.append(trace);
}
// display popup
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
Desktop desktop = null;
boolean isBrowseSupported = false, isOpenSupported = false;
if (Desktop.isDesktopSupported()) {
desktop = Desktop.getDesktop();
isBrowseSupported = desktop.isSupported(Desktop.Action.BROWSE);
isOpenSupported = desktop.isSupported(Desktop.Action.OPEN);
}
if (desktop != null && (isOpenSupported || (report && isBrowseSupported))) { // try to open the log file and/or issues webpage
if (report && isBrowseSupported) { // ask to report the error
if (isOpenSupported) { // also ask to open the log
int n = JOptionPane.showOptionDialog(null, messageReport, title,
JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE,
null, optionsLogReport, optionsLogReport[2]);
if (n == 0)
desktop.browse(getIssueURI(error, e, trace));
else if (n == 1)
desktop.open(Options.LOG_FILE);
} else { // only ask to report the error
int n = JOptionPane.showOptionDialog(null, message, title,
JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE,
null, optionsReport, optionsReport[1]);
if (n == 0)
desktop.browse(getIssueURI(error, e, trace));
}
} else { // don't report the error
int n = JOptionPane.showOptionDialog(null, message, title,
JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE,
null, optionsLog, optionsLog[1]);
if (n == 0)
desktop.open(Options.LOG_FILE);
}
} else { // display error only
JOptionPane.showMessageDialog(null, report ? messageReport : message,
title, JOptionPane.ERROR_MESSAGE);
}
} catch (Exception e1) {
Log.error("An error occurred in the crash popup.", e1);
}
}
/**
* Returns the issue reporting URI.
* This will auto-fill the report with the relevant information if possible.
* @param error a description of the error
* @param e the exception causing the error
* @param trace the stack trace
* @return the created URI
*/
private static URI getIssueURI(String error, Throwable e, String trace) {
// generate report information
String issueTitle = (error != null) ? error : e.getMessage();
StringBuilder sb = new StringBuilder();
try {
// read version and build date from version file, if possible
Properties props = new Properties();
props.load(ResourceLoader.getResourceAsStream(Options.VERSION_FILE));
String version = props.getProperty("version");
if (version != null && !version.equals("${pom.version}")) {
sb.append("**Version:** ");
sb.append(version);
String hash = Utils.getGitHash();
if (hash != null) {
sb.append(" (");
sb.append(hash.substring(0, 12));
sb.append(')');
}
sb.append('\n');
}
String timestamp = props.getProperty("build.date");
if (timestamp != null &&
!timestamp.equals("${maven.build.timestamp}") && !timestamp.equals("${timestamp}")) {
sb.append("**Build date:** ");
sb.append(timestamp);
sb.append('\n');
}
} catch (IOException e1) {
Log.warn("Could not read version file.", e1);
}
sb.append("**OS:** ");
sb.append(System.getProperty("os.name"));
sb.append(" (");
sb.append(System.getProperty("os.arch"));
sb.append(")\n");
sb.append("**JRE:** ");
sb.append(System.getProperty("java.version"));
sb.append('\n');
if (glString != null) {
sb.append("**OpenGL Version:** ");
sb.append(glString);
sb.append('\n');
}
if (error != null) {
sb.append("**Error:** `");
sb.append(error);
sb.append("`\n");
}
if (trace != null) {
sb.append("**Stack trace:**");
sb.append("\n```\n");
sb.append(trace);
sb.append("```");
}
// return auto-filled URI
try {
return URI.create(String.format(Options.ISSUES_URL,
URLEncoder.encode(issueTitle, "UTF-8"),
URLEncoder.encode(sb.toString(), "UTF-8")));
} catch (UnsupportedEncodingException e1) {
Log.warn("URLEncoder failed to encode the auto-filled issue report URL.");
return URI.create(String.format(Options.ISSUES_URL, "", ""));
}
}
}

View File

@ -42,7 +42,7 @@ import org.newdawn.slick.Animation;
import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import yugecin.opsudance.Dancer;
import yugecin.opsudance.utils.SlickUtil;
/**
* Holds game data and renders all related elements.
@ -101,8 +101,16 @@ public class GameData {
* This does NOT destroy images, so be careful of memory leaks!
*/
public static void clearReferences() {
for (Grade grade : Grade.values())
for (Grade grade : Grade.values()) {
grade.menuImage = null;
}
}
public static void destroyImages() {
for (Grade grade : Grade.values()) {
SlickUtil.destroyImage(grade.menuImage);
grade.menuImage = null;
}
}
/**

View File

@ -27,7 +27,12 @@ import java.util.List;
import org.newdawn.slick.Animation;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.events.BubbleNotificationEvent;
import yugecin.opsudance.utils.SlickUtil;
/**
* Game images.
@ -461,6 +466,18 @@ public enum GameImage {
}
}
public static void destroyImages() {
for (GameImage img : GameImage.values()) {
SlickUtil.destroyImages(img.defaultImages);
SlickUtil.destroyImage(img.defaultImage);
SlickUtil.destroyImages(img.skinImages);
SlickUtil.destroyImage(img.skinImage);
img.isSkinned = false;
img.defaultImages = img.skinImages = null;
img.defaultImage = img.skinImage = null;
}
}
/**
* Returns the bitmask image type from a type string.
* @param type the type string
@ -693,7 +710,9 @@ public enum GameImage {
return;
}
ErrorHandler.error(String.format("Could not find default image '%s'.", filename), null, false);
String err = String.format("Could not find default image '%s'.", filename);
Log.warn(err);
EventBus.instance.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED));
}
/**
@ -752,7 +771,7 @@ public enum GameImage {
img = img.getScaledCopy(0.5f);
list.add(img);
} catch (SlickException e) {
ErrorHandler.error(String.format("Failed to set image '%s'.", name), null, false);
EventBus.instance.post(new BubbleNotificationEvent(String.format("Failed to set image '%s'.", name), BubbleNotificationEvent.COMMONCOLOR_RED));
break;
}
}
@ -766,7 +785,7 @@ public enum GameImage {
img = img.getScaledCopy(0.5f);
list.add(img);
} catch (SlickException e) {
ErrorHandler.error(String.format("Failed to set image '%s'.", name), null, false);
EventBus.instance.post(new BubbleNotificationEvent(String.format("Failed to set image '%s'.", name), BubbleNotificationEvent.COMMONCOLOR_RED));
break;
}
}
@ -793,7 +812,7 @@ public enum GameImage {
img = img.getScaledCopy(0.5f);
return img;
} catch (SlickException e) {
ErrorHandler.error(String.format("Failed to set image '%s'.", filename), null, false);
EventBus.instance.post(new BubbleNotificationEvent(String.format("Failed to set image '%s'.", filename), BubbleNotificationEvent.COMMONCOLOR_RED));
}
}
}
@ -838,7 +857,7 @@ public enum GameImage {
skinImages = null;
}
} catch (SlickException e) {
ErrorHandler.error(String.format("Failed to destroy beatmap skin images for '%s'.", this.name()), e, true);
ErrorHandler.error(String.format("Failed to destroy beatmap skin images for '%s'.", this.name()), e).show();
}
}

View File

@ -1,309 +0,0 @@
/*
* opsu! - an open-source osu! client
* Copyright (C) 2014, 2015 Jeffrey Han
*
* opsu! is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu! is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu;
import itdelatrisu.opsu.audio.MusicController;
import itdelatrisu.opsu.db.DBController;
import itdelatrisu.opsu.downloads.DownloadList;
import itdelatrisu.opsu.downloads.Updater;
import itdelatrisu.opsu.states.ButtonMenu;
import itdelatrisu.opsu.states.DownloadsMenu;
import itdelatrisu.opsu.states.Game;
import itdelatrisu.opsu.states.GamePauseMenu;
import itdelatrisu.opsu.states.GameRanking;
import itdelatrisu.opsu.states.MainMenu;
import itdelatrisu.opsu.states.OptionsMenu;
import itdelatrisu.opsu.states.SongMenu;
import itdelatrisu.opsu.states.Splash;
import itdelatrisu.opsu.ui.UI;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.state.transition.FadeInTransition;
import org.newdawn.slick.state.transition.EasedFadeOutTransition;
import org.newdawn.slick.util.DefaultLogSystem;
import org.newdawn.slick.util.FileSystemLocation;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
/**
* Main class.
* <p>
* Creates game container, adds all other states, and initializes song data.
*/
public class Opsu extends StateBasedGame {
/** Game states. */
public static final int
STATE_SPLASH = 0,
STATE_MAINMENU = 1,
STATE_BUTTONMENU = 2,
STATE_SONGMENU = 3,
STATE_GAME = 4,
STATE_GAMEPAUSEMENU = 5,
STATE_GAMERANKING = 6,
STATE_OPTIONSMENU = 7,
STATE_DOWNLOADSMENU = 8;
/** Server socket for restricting the program to a single instance. */
private static ServerSocket SERVER_SOCKET;
/**
* Constructor.
* @param name the program name
*/
public Opsu(String name) {
super(name);
}
@Override
public void initStatesList(GameContainer container) throws SlickException {
addState(new Splash(STATE_SPLASH));
addState(new MainMenu(STATE_MAINMENU));
addState(new ButtonMenu(STATE_BUTTONMENU));
addState(new SongMenu(STATE_SONGMENU));
addState(new Game(STATE_GAME));
addState(new GamePauseMenu(STATE_GAMEPAUSEMENU));
addState(new GameRanking(STATE_GAMERANKING));
addState(new OptionsMenu(STATE_OPTIONSMENU));
addState(new DownloadsMenu(STATE_DOWNLOADSMENU));
}
/**
* Launches opsu!.
*/
public static void main(String[] args) {
// log all errors to a file
Log.setVerbose(false);
try {
DefaultLogSystem.out = new PrintStream(new FileOutputStream(Options.LOG_FILE, true));
} catch (FileNotFoundException e) {
Log.error(e);
}
// set default exception handler
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
ErrorHandler.error("** Uncaught Exception! **", e, true);
System.exit(1);
}
});
// parse configuration file
Options.parseOptions();
// only allow a single instance
if (!Options.noSingleInstance()) {
try {
SERVER_SOCKET = new ServerSocket(Options.getPort(), 1, InetAddress.getLocalHost());
} catch (UnknownHostException e) {
// shouldn't happen
} catch (IOException e) {
errorAndExit(
null,
String.format(
"opsu! could not be launched for one of these reasons:\n" +
"- An instance of opsu! is already running.\n" +
"- Another program is bound to port %d. " +
"You can change the port opsu! uses by editing the \"Port\" field in the configuration file.",
Options.getPort()
),
false
);
}
}
// load natives
File nativeDir;
if (!Utils.isJarRunning() && (
(nativeDir = new File("./target/natives/")).isDirectory() ||
(nativeDir = new File("./build/natives/")).isDirectory()))
;
else {
nativeDir = Options.NATIVE_DIR;
try {
new NativeLoader(nativeDir).loadNatives();
} catch (IOException e) {
Log.error("Error loading natives.", e);
}
}
System.setProperty("org.lwjgl.librarypath", nativeDir.getAbsolutePath());
System.setProperty("java.library.path", nativeDir.getAbsolutePath());
try {
// Workaround for "java.library.path" property being read-only.
// http://stackoverflow.com/a/24988095
Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
fieldSysPath.setAccessible(true);
fieldSysPath.set(null, null);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
Log.warn("Failed to set 'sys_paths' field.", e);
}
// set the resource paths
ResourceLoader.addResourceLocation(new FileSystemLocation(new File("./res/")));
// initialize databases
try {
DBController.init();
} catch (UnsatisfiedLinkError e) {
errorAndExit(e, "The databases could not be initialized.", true);
}
// check if just updated
if (args.length >= 2)
Updater.get().setUpdateInfo(args[0], args[1]);
// check for updates
if (!Options.isUpdaterDisabled()) {
new Thread() {
@Override
public void run() {
try {
Updater.get().checkForUpdates();
} catch (IOException e) {
Log.warn("Check for updates failed.", e);
}
}
}.start();
}
// disable jinput
Input.disableControllers();
// start the game
try {
// loop until force exit
while (true) {
Opsu opsu = new Opsu("opsu!");
Container app = new Container(opsu);
// basic game settings
Options.setDisplayMode(app);
String[] icons = { "icon16.png", "icon32.png" };
try {
app.setIcons(icons);
} catch (Exception e) {
Log.error("could not set icon");
}
app.setForceExit(true);
app.start();
// run update if available
if (Updater.get().getStatus() == Updater.Status.UPDATE_FINAL) {
close();
Updater.get().runUpdate();
break;
}
}
} catch (SlickException e) {
errorAndExit(e, "An error occurred while creating the game container.", true);
}
}
@Override
public boolean closeRequested() {
int id = this.getCurrentStateID();
// intercept close requests in game-related states and return to song menu
if (id == STATE_GAME || id == STATE_GAMEPAUSEMENU || id == STATE_GAMERANKING) {
// start playing track at preview position
SongMenu songMenu = (SongMenu) this.getState(Opsu.STATE_SONGMENU);
if (id == STATE_GAMERANKING) {
GameData data = ((GameRanking) this.getState(Opsu.STATE_GAMERANKING)).getGameData();
if (data != null && data.isGameplay())
songMenu.resetTrackOnLoad();
} else {
if (id == STATE_GAME) {
MusicController.pause();
MusicController.setPitch(1.0f);
MusicController.resume();
} else
songMenu.resetTrackOnLoad();
}
// reset game data
if (UI.getCursor().isBeatmapSkinned())
UI.getCursor().reset();
songMenu.resetGameDataOnLoad();
this.enterState(Opsu.STATE_SONGMENU, new EasedFadeOutTransition(), new FadeInTransition());
return false;
}
// show confirmation dialog if any downloads are active
if (DownloadList.get().hasActiveDownloads() &&
UI.showExitConfirmation(DownloadList.EXIT_CONFIRMATION))
return false;
if (Updater.get().getStatus() == Updater.Status.UPDATE_DOWNLOADING &&
UI.showExitConfirmation(Updater.EXIT_CONFIRMATION))
return false;
return true;
}
/**
* Closes all resources.
*/
public static void close() {
// close databases
DBController.closeConnections();
// cancel all downloads
DownloadList.get().cancelAllDownloads();
// close server socket
if (SERVER_SOCKET != null) {
try {
SERVER_SOCKET.close();
} catch (IOException e) {
ErrorHandler.error("Failed to close server socket.", e, false);
}
}
}
/**
* Throws an error and exits the application with the given message.
* @param e the exception that caused the crash
* @param message the message to display
* @param report whether to ask to report the error
*/
private static void errorAndExit(Throwable e, String message, boolean report) {
// JARs will not run properly inside directories containing '!'
// http://bugs.java.com/view_bug.do?bug_id=4523159
if (Utils.isJarRunning() && Utils.getRunningDirectory() != null &&
Utils.getRunningDirectory().getAbsolutePath().indexOf('!') != -1)
ErrorHandler.error("JARs cannot be run from some paths containing '!'. Please move or rename the file and try again.", null, false);
else
ErrorHandler.error(message, e, report);
System.exit(1);
}
}

View File

@ -59,6 +59,10 @@ import com.sun.jna.platform.win32.Advapi32Util;
import com.sun.jna.platform.win32.Win32Exception;
import com.sun.jna.platform.win32.WinReg;
import yugecin.opsudance.*;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.events.BubbleNotificationEvent;
import yugecin.opsudance.movers.factories.ExgonMoverFactory;
import yugecin.opsudance.movers.factories.QuadraticBezierMoverFactory;
import yugecin.opsudance.movers.slidermovers.DefaultSliderMoverController;
@ -195,7 +199,7 @@ public class Options {
}
File dir = new File(rootPath, "opsu");
if (!dir.isDirectory() && !dir.mkdir())
ErrorHandler.error(String.format("Failed to create configuration folder at '%s/opsu'.", rootPath), null, false);
ErrorHandler.error(String.format("Failed to create configuration folder at '%s/opsu'.", rootPath), new Exception("empty")).preventReport().show();
return dir;
} else
return workingDir;
@ -393,8 +397,7 @@ public class Options {
@Override
public void clickListItem(int index) {
targetFPSindex = index;
Container.instance.setTargetFrameRate(targetFPS[index]);
Container.instance.setVSync(targetFPS[index] == 60);
displayContainer.setFPS(targetFPS[index]);
}
@Override
@ -414,8 +417,7 @@ public class Options {
SHOW_FPS ("Show FPS Counter", "FpsCounter", "Show an FPS counter in the bottom-right hand corner.", true),
SHOW_UNICODE ("Prefer Non-English Metadata", "ShowUnicode", "Where available, song titles will be shown in their native language.", false) {
@Override
public void click(GameContainer container) {
super.click(container);
public void click() {
if (bool) {
try {
Fonts.LARGE.loadGlyphs();
@ -465,13 +467,7 @@ public class Options {
val = i;
}
},
NEW_CURSOR ("Enable New Cursor", "NewCursor", "Use the new cursor style (may cause higher CPU usage).", true) {
@Override
public void click(GameContainer container) {
super.click(container);
UI.getCursor().reset();
}
},
NEW_CURSOR ("Enable New Cursor", "NewCursor", "Use the new cursor style (may cause higher CPU usage).", true),
DYNAMIC_BACKGROUND ("Enable Dynamic Backgrounds", "DynamicBackground", "The song background will be used as the main menu background.", true),
LOAD_VERBOSE ("Show Detailed Loading Progress", "LoadVerbose", "Display more specific loading information in the splash screen.", false),
COLOR_MAIN_MENU_LOGO ("Use cursor color as main menu logo tint", "ColorMainMenuLogo", "Colorful main menu logo", false),
@ -983,6 +979,7 @@ public class Options {
PIPPI_SLIDER_FOLLOW_EXPAND ("Followcircle expand", "PippiFollowExpand", "Increase radius in followcircles", false),
PIPPI_PREVENT_WOBBLY_STREAMS ("Prevent wobbly streams", "PippiPreventWobblyStreams", "Force linear mover while doing streams to prevent wobbly pippi", true);
public static DisplayContainer displayContainer;
/** Option name. */
private final String name;
@ -1129,9 +1126,8 @@ public class Options {
* Processes a mouse click action (via override).
* <p>
* By default, this inverts the current {@code bool} field.
* @param container the game container
*/
public void click(GameContainer container) { bool = !bool; }
public void click() { bool = !bool; }
/**
* Get a list of values to choose from
@ -1279,9 +1275,9 @@ public class Options {
/**
* Sets the target frame rate to the next available option, and sends a
* bar notification about the action.
* @param container the game container
*/
public static void setNextFPS(GameContainer container) {
public static void setNextFPS(DisplayContainer displayContainer) {
GameOption.displayContainer = displayContainer; // TODO dirty shit
GameOption.TARGET_FPS.clickListItem((targetFPSindex + 1) % targetFPS.length);
UI.sendBarNotification(String.format("Frame limiter: %s", GameOption.TARGET_FPS.getValueString()));
}
@ -1294,10 +1290,9 @@ public class Options {
/**
* 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) {
public static void setMasterVolume(float volume) {
if (volume >= 0f && volume <= 1f) {
GameOption.MASTER_VOLUME.setValue((int) (volume * 100f));
MusicController.setVolume(getMasterVolume() * getMusicVolume());
@ -1346,11 +1341,10 @@ public class Options {
* <p>
* If the configured resolution is larger than the screen size, the smallest
* available resolution will be used.
* @param app the game container
*/
public static void setDisplayMode(Container app) {
int screenWidth = app.getScreenWidth();
int screenHeight = app.getScreenHeight();
public static void setDisplayMode(DisplayContainer container) {
int screenWidth = container.nativeDisplayMode.getWidth();
int screenHeight = container.nativeDisplayMode.getHeight();
resolutions[0] = screenWidth + "x" + screenHeight;
if (resolutionIdx < 0 || resolutionIdx > resolutions.length) {
@ -1370,9 +1364,10 @@ public class Options {
}
try {
app.setDisplayMode(width, height, isFullscreen());
} catch (SlickException e) {
ErrorHandler.error("Failed to set display mode.", e, true);
container.setDisplayMode(width, height, isFullscreen());
} catch (Exception e) {
container.eventBus.post(new BubbleNotificationEvent("Failed to change resolution", BubbleNotificationEvent.COMMONCOLOR_RED));
Log.error("Failed to set display mode.", e);
}
if (!isFullscreen()) {
@ -1696,7 +1691,7 @@ public class Options {
* sends a bar notification about the action.
*/
public static void toggleMouseDisabled() {
GameOption.DISABLE_MOUSE_BUTTONS.click(null);
GameOption.DISABLE_MOUSE_BUTTONS.click();
UI.sendBarNotification((GameOption.DISABLE_MOUSE_BUTTONS.getBooleanValue()) ?
"Mouse buttons are disabled." : "Mouse buttons are enabled.");
}
@ -1787,7 +1782,7 @@ public class Options {
// use default directory
beatmapDir = BEATMAP_DIR;
if (!beatmapDir.isDirectory() && !beatmapDir.mkdir())
ErrorHandler.error(String.format("Failed to create beatmap directory at '%s'.", beatmapDir.getAbsolutePath()), null, false);
EventBus.instance.post(new BubbleNotificationEvent(String.format("Failed to create beatmap directory at '%s'.", beatmapDir.getAbsolutePath()), BubbleNotificationEvent.COMMONCOLOR_RED));
return beatmapDir;
}
@ -1802,7 +1797,7 @@ public class Options {
oszDir = new File(DATA_DIR, "SongPacks/");
if (!oszDir.isDirectory() && !oszDir.mkdir())
ErrorHandler.error(String.format("Failed to create song packs directory at '%s'.", oszDir.getAbsolutePath()), null, false);
EventBus.instance.post(new BubbleNotificationEvent(String.format("Failed to create song packs directory at '%s'.", oszDir.getAbsolutePath()), BubbleNotificationEvent.COMMONCOLOR_RED));
return oszDir;
}
@ -1817,7 +1812,7 @@ public class Options {
replayImportDir = new File(DATA_DIR, "ReplayImport/");
if (!replayImportDir.isDirectory() && !replayImportDir.mkdir())
ErrorHandler.error(String.format("Failed to create replay import directory at '%s'.", replayImportDir.getAbsolutePath()), null, false);
EventBus.instance.post(new BubbleNotificationEvent(String.format("Failed to create replay import directory at '%s'.", replayImportDir.getAbsolutePath()), BubbleNotificationEvent.COMMONCOLOR_RED));
return replayImportDir;
}
@ -1867,7 +1862,7 @@ public class Options {
// use default directory
skinRootDir = SKIN_ROOT_DIR;
if (!skinRootDir.isDirectory() && !skinRootDir.mkdir())
ErrorHandler.error(String.format("Failed to create skins directory at '%s'.", skinRootDir.getAbsolutePath()), null, false);
EventBus.instance.post(new BubbleNotificationEvent(String.format("Failed to create skins directory at '%s'.", skinRootDir.getAbsolutePath()), BubbleNotificationEvent.COMMONCOLOR_RED));
return skinRootDir;
}
@ -2000,7 +1995,9 @@ public class Options {
}
GameOption.DANCE_HIDE_WATERMARK.setValue(false);
} catch (IOException e) {
ErrorHandler.error(String.format("Failed to read file '%s'.", OPTIONS_FILE.getAbsolutePath()), e, false);
String err = String.format("Failed to read file '%s'.", OPTIONS_FILE.getAbsolutePath());
Log.error(err, e);
EventBus.instance.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED));
}
}
@ -2029,7 +2026,9 @@ public class Options {
}
writer.close();
} catch (IOException e) {
ErrorHandler.error(String.format("Failed to write to file '%s'.", OPTIONS_FILE.getAbsolutePath()), e, false);
String err = String.format("Failed to write to file '%s'.", OPTIONS_FILE.getAbsolutePath());
Log.error(err, e);
EventBus.instance.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED));
}
}
}

View File

@ -18,6 +18,7 @@
package itdelatrisu.opsu;
import itdelatrisu.opsu.audio.MusicController;
import itdelatrisu.opsu.audio.SoundController;
import itdelatrisu.opsu.audio.SoundEffect;
import itdelatrisu.opsu.beatmap.HitObject;
@ -71,6 +72,10 @@ import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.util.Log;
import com.sun.jna.platform.FileUtils;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.events.BubbleNotificationEvent;
/**
* Contains miscellaneous utilities.
@ -89,40 +94,18 @@ public class Utils {
Arrays.sort(illegalChars);
}
// game-related variables
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
*/
public static void init(GameContainer container, StateBasedGame game) {
input = container.getInput();
int width = container.getWidth();
int height = container.getHeight();
public static void init(DisplayContainer displayContainer) {
// TODO clean this up
// game settings
container.setTargetFrameRate(Options.getTargetFPS());
container.setVSync(Options.getTargetFPS() == 60);
container.setMusicVolume(Options.getMusicVolume() * Options.getMasterVolume());
container.setShowFPS(false);
container.getInput().enableKeyRepeat();
container.setAlwaysRender(true);
container.setUpdateOnlyWhenVisible(false);
// calculate UI scale
GameImage.init(width, height);
// create fonts
try {
Fonts.init();
} catch (Exception e) {
ErrorHandler.error("Failed to load fonts.", e, true);
}
displayContainer.setFPS(Options.getTargetFPS()); // TODO move this elsewhere
MusicController.setMusicVolume(Options.getMusicVolume() * Options.getMasterVolume());
// load skin
Options.loadSkin();
@ -134,19 +117,19 @@ public class Utils {
}
// initialize game mods
GameMod.init(width, height);
GameMod.init(displayContainer.width, displayContainer.height);
// initialize playback buttons
PlaybackSpeed.init(width, height);
PlaybackSpeed.init(displayContainer.width, displayContainer.height);
// initialize hit objects
HitObject.init(width, height);
HitObject.init(displayContainer.width, displayContainer.height);
// initialize download nodes
DownloadNode.init(width, height);
DownloadNode.init(displayContainer.width, displayContainer.height);
// initialize UI components
UI.init(container, game);
UI.init(displayContainer);
}
/**
@ -246,12 +229,15 @@ public class Utils {
* @return true if pressed
*/
public static boolean isGameKeyPressed() {
/*
boolean mouseDown = !Options.isMouseDisabled() && (
input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON) ||
input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON));
return (mouseDown ||
input.isKeyDown(Options.getGameKeyLeft()) ||
input.isKeyDown(Options.getGameKeyRight()));
*/
return true;
}
/**
@ -262,14 +248,14 @@ public class Utils {
// create the screenshot directory
File dir = Options.getScreenshotDir();
if (!dir.isDirectory() && !dir.mkdir()) {
ErrorHandler.error(String.format("Failed to create screenshot directory at '%s'.", dir.getAbsolutePath()), null, false);
EventBus.instance.post(new BubbleNotificationEvent(String.format("Failed to create screenshot directory at '%s'.", dir.getAbsolutePath()), BubbleNotificationEvent.COMMONCOLOR_RED));
return;
}
// create file name
SimpleDateFormat date = new SimpleDateFormat("yyyyMMdd_HHmmss");
final File file = new File(dir, String.format("screenshot_%s.%s",
date.format(new Date()), Options.getScreenshotFormat()));
final String fileName = String.format("screenshot_%s.%s", date.format(new Date()), Options.getScreenshotFormat());
final File file = new File(dir, fileName);
SoundController.playSound(SoundEffect.SHUTTER);
@ -296,8 +282,9 @@ public class Utils {
}
}
ImageIO.write(image, Options.getScreenshotFormat(), file);
EventBus.instance.post(new BubbleNotificationEvent("Created " + fileName, BubbleNotificationEvent.COMMONCOLOR_PURPLE));
} catch (Exception e) {
ErrorHandler.error("Failed to take a screenshot.", e, true);
ErrorHandler.error("Failed to take a screenshot.", e).show();
}
}
}.start();
@ -446,7 +433,7 @@ public class Utils {
try {
json = new JSONObject(s);
} catch (JSONException e) {
ErrorHandler.error("Failed to create JSON object.", e, true);
ErrorHandler.error("Failed to create JSON object.", e).show();
}
}
return json;
@ -465,7 +452,7 @@ public class Utils {
try {
json = new JSONArray(s);
} catch (JSONException e) {
ErrorHandler.error("Failed to create JSON array.", e, true);
ErrorHandler.error("Failed to create JSON array.", e).show();
}
}
return json;
@ -507,7 +494,7 @@ public class Utils {
result.append(String.format("%02x", b));
return result.toString();
} catch (NoSuchAlgorithmException | IOException e) {
ErrorHandler.error("Failed to calculate MD5 hash.", e, true);
ErrorHandler.error("Failed to calculate MD5 hash.", e).show();
}
return null;
}
@ -531,7 +518,7 @@ public class Utils {
* @return true if JAR, false if file
*/
public static boolean isJarRunning() {
return Opsu.class.getResource(String.format("%s.class", Opsu.class.getSimpleName())).toString().startsWith("jar:");
return Utils.class.getResource(String.format("%s.class", Utils.class.getSimpleName())).toString().startsWith("jar:");
}
/**
@ -543,7 +530,7 @@ public class Utils {
return null;
try {
return new JarFile(new File(Opsu.class.getProtectionDomain().getCodeSource().getLocation().toURI()), false);
return new JarFile(new File(Utils.class.getProtectionDomain().getCodeSource().getLocation().toURI()), false);
} catch (URISyntaxException | IOException e) {
Log.error("Could not determine the JAR file.", e);
return null;
@ -556,7 +543,7 @@ public class Utils {
*/
public static File getRunningDirectory() {
try {
return new File(Opsu.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
return new File(Utils.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
} catch (URISyntaxException e) {
Log.error("Could not get the running directory.", e);
return null;

View File

@ -1,6 +1,6 @@
package itdelatrisu.opsu.audio;
import itdelatrisu.opsu.ErrorHandler;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
import java.io.IOException;
import java.util.Iterator;
@ -194,7 +194,7 @@ public class MultiClip {
try {
audioIn.close();
} catch (IOException e) {
ErrorHandler.error(String.format("Could not close AudioInputStream for MultiClip %s.", name), e, true);
ErrorHandler.error(String.format("Could not close AudioInputStream for MultiClip %s.", name), e).show();
}
}
}

View File

@ -18,7 +18,6 @@
package itdelatrisu.opsu.audio;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.beatmap.Beatmap;
import itdelatrisu.opsu.beatmap.BeatmapParser;
@ -44,8 +43,12 @@ import org.newdawn.slick.MusicListener;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.openal.Audio;
import org.newdawn.slick.openal.SoundStore;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
import org.tritonus.share.sampled.file.TAudioFileFormat;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.events.BubbleNotificationEvent;
/**
* Controller for all music.
@ -152,7 +155,9 @@ public class MusicController {
});
playAt(position, loop);
} catch (Exception e) {
ErrorHandler.error(String.format("Could not play track '%s'.", file.getName()), e, false);
String err = String.format("Could not play track '%s'.", file.getName());
Log.error(err, e);
EventBus.instance.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED));
}
}
@ -496,9 +501,7 @@ public class MusicController {
trackLoader.interrupt();
try {
trackLoader.join();
} catch (InterruptedException e) {
ErrorHandler.error(null, e, true);
}
} catch (InterruptedException ignored) { }
}
trackLoader = null;
@ -575,7 +578,16 @@ public class MusicController {
player = null;
} catch (Exception e) {
ErrorHandler.error("Failed to destroy OpenAL.", e, true);
ErrorHandler.error("Failed to destroy OpenAL.", e).show();
}
}
/**
* Set the default volume for music
* @param volume the new default value for music volume
*/
public static void setMusicVolume(float volume) {
SoundStore.get().setMusicVolume(volume);
}
}

View File

@ -18,7 +18,6 @@
package itdelatrisu.opsu.audio;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.audio.HitSound.SampleSet;
import itdelatrisu.opsu.beatmap.HitObject;
@ -41,6 +40,9 @@ import javax.sound.sampled.LineUnavailableException;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.util.ResourceLoader;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.events.BubbleNotificationEvent;
/**
* Controller for all (non-music) sound components.
@ -99,7 +101,7 @@ public class SoundController {
AudioInputStream audioIn = AudioSystem.getAudioInputStream(url);
return loadClip(ref, audioIn, isMP3);
} catch (Exception e) {
ErrorHandler.error(String.format("Failed to load file '%s'.", ref), e, true);
ErrorHandler.error(String.format("Failed to load file '%s'.", ref), e).show();
return null;
}
}
@ -214,7 +216,7 @@ public class SoundController {
// menu and game sounds
for (SoundEffect s : SoundEffect.values()) {
if ((currentFileName = getSoundFileName(s.getFileName())) == null) {
ErrorHandler.error(String.format("Could not find sound file '%s'.", s.getFileName()), null, false);
EventBus.instance.post(new BubbleNotificationEvent("Could not find sound file " + s.getFileName(), BubbleNotificationEvent.COLOR_ORANGE));
continue;
}
MultiClip newClip = loadClip(currentFileName, currentFileName.endsWith(".mp3"));
@ -233,7 +235,7 @@ public class SoundController {
for (HitSound s : HitSound.values()) {
String filename = String.format("%s-%s", ss.getName(), s.getFileName());
if ((currentFileName = getSoundFileName(filename)) == null) {
ErrorHandler.error(String.format("Could not find hit sound file '%s'.", filename), null, false);
EventBus.instance.post(new BubbleNotificationEvent("Could not find hit sound file " + filename, BubbleNotificationEvent.COLOR_ORANGE));
continue;
}
MultiClip newClip = loadClip(currentFileName, false);
@ -277,7 +279,7 @@ public class SoundController {
try {
clip.start(volume, listener);
} catch (LineUnavailableException e) {
ErrorHandler.error(String.format("Could not start a clip '%s'.", clip.getName()), e, true);
ErrorHandler.error(String.format("Could not start a clip '%s'.", clip.getName()), e).show();
}
}
}

View File

@ -22,6 +22,7 @@ import itdelatrisu.opsu.Options;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
@ -59,6 +60,14 @@ public class Beatmap implements Comparable<Beatmap> {
*/
public static void clearBackgroundImageCache() { bgImageCache.clear(); }
public static void destroyBackgroundImageCache() {
Collection<ImageLoader> values = bgImageCache.values();
for (ImageLoader value : values) {
value.destroy();
}
bgImageCache.clear();
}
/** The OSU File object associated with this beatmap. */
private File file;

View File

@ -18,7 +18,6 @@
package itdelatrisu.opsu.beatmap;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.db.BeatmapDB;
@ -35,6 +34,9 @@ import java.util.Map;
import org.newdawn.slick.Color;
import org.newdawn.slick.util.Log;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.events.BubbleNotificationEvent;
/**
* Parser for beatmaps.
@ -243,9 +245,11 @@ public class BeatmapParser {
}
map.timingPoints.trimToSize();
} catch (IOException e) {
ErrorHandler.error(String.format("Failed to read file '%s'.", map.getFile().getAbsolutePath()), e, false);
String err = String.format("Failed to read file '%s'.", map.getFile().getAbsolutePath());
Log.error(err, e);
EventBus.instance.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED));
} catch (NoSuchAlgorithmException e) {
ErrorHandler.error("Failed to get MD5 hash stream.", e, true);
ErrorHandler.error("Failed to get MD5 hash stream.", e).show();
// retry without MD5
hasNoMD5Algorithm = true;
@ -646,9 +650,11 @@ public class BeatmapParser {
if (md5stream != null)
beatmap.md5Hash = md5stream.getMD5();
} catch (IOException e) {
ErrorHandler.error(String.format("Failed to read file '%s'.", file.getAbsolutePath()), e, false);
String err = String.format("Failed to read file '%s'.", file.getAbsolutePath());
Log.error(err, e);
EventBus.instance.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED));
} catch (NoSuchAlgorithmException e) {
ErrorHandler.error("Failed to get MD5 hash stream.", e, true);
ErrorHandler.error("Failed to get MD5 hash stream.", e).show();
// retry without MD5
hasNoMD5Algorithm = true;
@ -727,7 +733,9 @@ public class BeatmapParser {
}
beatmap.timingPoints.trimToSize();
} catch (IOException e) {
ErrorHandler.error(String.format("Failed to read file '%s'.", beatmap.getFile().getAbsolutePath()), e, false);
String err = String.format("Failed to read file '%s'.", beatmap.getFile().getAbsolutePath());
Log.error(err, e);
EventBus.instance.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED));
}
}
@ -803,9 +811,11 @@ public class BeatmapParser {
// check that all objects were parsed
if (objectIndex != beatmap.objects.length)
ErrorHandler.error(String.format("Parsed %d objects for beatmap '%s', %d objects expected.",
objectIndex, beatmap.toString(), beatmap.objects.length), null, true);
objectIndex, beatmap.toString(), beatmap.objects.length), new Exception("no")).show();
} catch (IOException e) {
ErrorHandler.error(String.format("Failed to read file '%s'.", beatmap.getFile().getAbsolutePath()), e, false);
String err = String.format("Failed to read file '%s'.", beatmap.getFile().getAbsolutePath());
Log.error(err, e);
EventBus.instance.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED));
}
}

View File

@ -18,11 +18,12 @@
package itdelatrisu.opsu.beatmap;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.audio.MusicController;
import itdelatrisu.opsu.db.BeatmapDB;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.events.BubbleNotificationEvent;
import java.io.File;
import java.io.IOException;
@ -215,7 +216,7 @@ public class BeatmapSetList {
try {
Utils.deleteToTrash(dir);
} catch (IOException e) {
ErrorHandler.error("Could not delete song group.", e, true);
EventBus.instance.post(new BubbleNotificationEvent("Could not delete song group", BubbleNotificationEvent.COLOR_ORANGE));
}
if (ws != null)
ws.resume();
@ -270,7 +271,7 @@ public class BeatmapSetList {
try {
Utils.deleteToTrash(file);
} catch (IOException e) {
ErrorHandler.error("Could not delete song.", e, true);
EventBus.instance.post(new BubbleNotificationEvent("Could not delete song", BubbleNotificationEvent.COLOR_ORANGE));
}
if (ws != null)
ws.resume();

View File

@ -18,7 +18,6 @@
package itdelatrisu.opsu.beatmap;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Options;
import java.io.IOException;
@ -42,6 +41,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.newdawn.slick.util.Log;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.events.BubbleNotificationEvent;
/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
@ -96,7 +97,8 @@ public class BeatmapWatchService {
ws = new BeatmapWatchService();
ws.register(Options.getBeatmapDir().toPath());
} catch (IOException e) {
ErrorHandler.error("An I/O exception occurred while creating the watch service.", e, true);
Log.error("Could not create watch service", e);
EventBus.instance.post(new BubbleNotificationEvent("Could not create watch service", BubbleNotificationEvent.COMMONCOLOR_RED));
return;
}
@ -117,8 +119,9 @@ public class BeatmapWatchService {
ws.service.shutdownNow();
ws = null;
} catch (IOException e) {
Log.error("An I/O exception occurred while closing the previous watch service.", e);
EventBus.instance.post(new BubbleNotificationEvent("An I/O exception occurred while closing the previous watch service.", BubbleNotificationEvent.COMMONCOLOR_RED));
ws = null;
ErrorHandler.error("An I/O exception occurred while closing the previous watch service.", e, true);
}
}

View File

@ -18,7 +18,6 @@
package itdelatrisu.opsu.beatmap;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Options;
import java.io.File;
@ -28,6 +27,9 @@ import java.util.List;
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import org.newdawn.slick.util.Log;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.events.BubbleNotificationEvent;
/**
* Unpacker for OSZ (ZIP) archives.
@ -97,8 +99,9 @@ public class OszUnpacker {
ZipFile zipFile = new ZipFile(file);
zipFile.extractAll(dest.getAbsolutePath());
} catch (ZipException e) {
ErrorHandler.error(String.format("Failed to unzip file %s to dest %s.",
file.getAbsolutePath(), dest.getAbsolutePath()), e, false);
String err = String.format("Failed to unzip file %s to dest %s.", file.getAbsolutePath(), dest.getAbsolutePath());
Log.error(err, e);
EventBus.instance.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED));
}
}

View File

@ -18,7 +18,6 @@
package itdelatrisu.opsu.db;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.beatmap.Beatmap;
import itdelatrisu.opsu.beatmap.BeatmapParser;
@ -35,6 +34,7 @@ import java.util.List;
import java.util.Map;
import org.newdawn.slick.util.Log;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
/**
* Handles connections and queries with the cached beatmap database.
@ -110,7 +110,7 @@ public class BeatmapDB {
try {
updateSizeStmt = connection.prepareStatement("REPLACE INTO info (key, value) VALUES ('size', ?)");
} catch (SQLException e) {
ErrorHandler.error("Failed to prepare beatmap statements.", e, true);
ErrorHandler.error("Failed to prepare beatmap statements.", e).show();
}
// retrieve the cache size
@ -132,7 +132,7 @@ public class BeatmapDB {
updatePlayStatsStmt = connection.prepareStatement("UPDATE beatmaps SET playCount = ?, lastPlayed = ? WHERE dir = ? AND file = ?");
setFavoriteStmt = connection.prepareStatement("UPDATE beatmaps SET favorite = ? WHERE dir = ? AND file = ?");
} catch (SQLException e) {
ErrorHandler.error("Failed to prepare beatmap statements.", e, true);
ErrorHandler.error("Failed to prepare beatmap statements.", e).show();
}
}
@ -170,7 +170,7 @@ public class BeatmapDB {
sql = String.format("INSERT OR IGNORE INTO info(key, value) VALUES('version', '%s')", DATABASE_VERSION);
stmt.executeUpdate(sql);
} catch (SQLException e) {
ErrorHandler.error("Could not create beatmap database.", e, true);
ErrorHandler.error("Coudl not create beatmap database.", e).show();
}
}
@ -222,7 +222,7 @@ public class BeatmapDB {
ps.close();
}
} catch (SQLException e) {
ErrorHandler.error("Failed to update beatmap database.", e, true);
ErrorHandler.error("Failed to update beatmap database.", e).show();
}
}
@ -240,7 +240,7 @@ public class BeatmapDB {
}
rs.close();
} catch (SQLException e) {
ErrorHandler.error("Could not get beatmap cache size.", e, true);
ErrorHandler.error("Could not get beatmap cache size.", e).show();
}
}
@ -255,7 +255,7 @@ public class BeatmapDB {
updateSizeStmt.setString(1, Integer.toString(Math.max(cacheSize, 0)));
updateSizeStmt.executeUpdate();
} catch (SQLException e) {
ErrorHandler.error("Could not update beatmap cache size.", e, true);
ErrorHandler.error("Could not update beatmap cache size.", e).show();
}
}
@ -273,7 +273,7 @@ public class BeatmapDB {
cacheSize = 0;
updateCacheSize();
} catch (SQLException e) {
ErrorHandler.error("Could not drop beatmap database.", e, true);
ErrorHandler.error("Could not drop beatmap database.", e).show();
}
createDatabase();
}
@ -291,7 +291,7 @@ public class BeatmapDB {
cacheSize += insertStmt.executeUpdate();
updateCacheSize();
} catch (SQLException e) {
ErrorHandler.error("Failed to add beatmap to database.", e, true);
ErrorHandler.error("Failed to add beatmap to database.", e).show();
}
}
@ -344,7 +344,7 @@ public class BeatmapDB {
// update cache size
updateCacheSize();
} catch (SQLException e) {
ErrorHandler.error("Failed to add beatmaps to database.", e, true);
ErrorHandler.error("Failed to add beatmaps to database.", e).show();
}
}
@ -432,7 +432,7 @@ public class BeatmapDB {
}
rs.close();
} catch (SQLException e) {
ErrorHandler.error("Failed to load Beatmap from database.", e, true);
ErrorHandler.error("Failed to load Beatmap from database.", e).show();
}
}
@ -495,7 +495,7 @@ public class BeatmapDB {
}
rs.close();
} catch (SQLException e) {
ErrorHandler.error("Failed to load beatmaps from database.", e, true);
ErrorHandler.error("Failed to load beatmaps from database.", e).show();
}
}
@ -596,7 +596,7 @@ public class BeatmapDB {
rs.close();
return map;
} catch (SQLException e) {
ErrorHandler.error("Failed to get last modified map from database.", e, true);
ErrorHandler.error("Failed to get last modified map from database.", e).show();
return null;
}
}
@ -616,7 +616,7 @@ public class BeatmapDB {
cacheSize -= deleteMapStmt.executeUpdate();
updateCacheSize();
} catch (SQLException e) {
ErrorHandler.error("Failed to delete beatmap entry from database.", e, true);
ErrorHandler.error("Failed to delete beatmap entry from database.", e).show();
}
}
@ -633,7 +633,7 @@ public class BeatmapDB {
cacheSize -= deleteGroupStmt.executeUpdate();
updateCacheSize();
} catch (SQLException e) {
ErrorHandler.error("Failed to delete beatmap group entry from database.", e, true);
ErrorHandler.error("Failed to delete beatmap group entry from database.", e).show();
}
}
@ -652,7 +652,7 @@ public class BeatmapDB {
setStarsStmt.executeUpdate();
} catch (SQLException e) {
ErrorHandler.error(String.format("Failed to save star rating '%.4f' for beatmap '%s' in database.",
beatmap.starRating, beatmap.toString()), e, true);
beatmap.starRating, beatmap.toString()), e).show();
}
}
@ -672,7 +672,7 @@ public class BeatmapDB {
updatePlayStatsStmt.executeUpdate();
} catch (SQLException e) {
ErrorHandler.error(String.format("Failed to update play statistics for beatmap '%s' in database.",
beatmap.toString()), e, true);
beatmap.toString()), e).show();
}
}
@ -691,7 +691,7 @@ public class BeatmapDB {
setFavoriteStmt.executeUpdate();
} catch (SQLException e) {
ErrorHandler.error(String.format("Failed to update favorite status for beatmap '%s' in database.",
beatmap.toString()), e, true);
beatmap.toString()), e).show();
}
}
@ -711,7 +711,7 @@ public class BeatmapDB {
connection.close();
connection = null;
} catch (SQLException e) {
ErrorHandler.error("Failed to close beatmap database.", e, true);
ErrorHandler.error("Failed to close beatmap database.", e).show();
}
}
}

View File

@ -18,7 +18,7 @@
package itdelatrisu.opsu.db;
import itdelatrisu.opsu.ErrorHandler;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
import java.sql.Connection;
import java.sql.DriverManager;
@ -39,7 +39,7 @@ public class DBController {
try {
Class.forName("org.sqlite.JDBC");
} catch (ClassNotFoundException e) {
ErrorHandler.error("Could not load sqlite-JDBC driver.", e, true);
ErrorHandler.error("Could not load sqlite-JDBC driver.", e).show();
}
// initialize the databases
@ -65,7 +65,7 @@ public class DBController {
return DriverManager.getConnection(String.format("jdbc:sqlite:%s", path));
} catch (SQLException e) {
// if the error message is "out of memory", it probably means no database file is found
ErrorHandler.error(String.format("Could not connect to database: '%s'.", path), e, true);
ErrorHandler.error(String.format("Could not connect to database: '%s'.", path), e).show();
return null;
}
}

View File

@ -18,10 +18,10 @@
package itdelatrisu.opsu.db;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.ScoreData;
import itdelatrisu.opsu.beatmap.Beatmap;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
import java.sql.Connection;
import java.sql.PreparedStatement;
@ -124,7 +124,7 @@ public class ScoreDB {
// TODO: extra playerName checks not needed if name is guaranteed not null
);
} catch (SQLException e) {
ErrorHandler.error("Failed to prepare score statements.", e, true);
ErrorHandler.error("Failed to prepare score statements.", e).show();
}
}
@ -157,7 +157,7 @@ public class ScoreDB {
sql = String.format("INSERT OR IGNORE INTO info(key, value) VALUES('version', %d)", DATABASE_VERSION);
stmt.executeUpdate(sql);
} catch (SQLException e) {
ErrorHandler.error("Could not create score database.", e, true);
ErrorHandler.error("Could not create score database.", e).show();
}
}
@ -209,7 +209,7 @@ public class ScoreDB {
ps.close();
}
} catch (SQLException e) {
ErrorHandler.error("Failed to update score database.", e, true);
ErrorHandler.error("Failed to update score database.", e).show();
}
}
@ -227,7 +227,7 @@ public class ScoreDB {
insertStmt.setString(19, data.playerName);
insertStmt.executeUpdate();
} catch (SQLException e) {
ErrorHandler.error("Failed to save score to database.", e, true);
ErrorHandler.error("Failed to save score to database.", e).show();
}
}
@ -247,7 +247,7 @@ public class ScoreDB {
deleteScoreStmt.setString(21, data.playerName);
deleteScoreStmt.executeUpdate();
} catch (SQLException e) {
ErrorHandler.error("Failed to delete score from database.", e, true);
ErrorHandler.error("Failed to delete score from database.", e).show();
}
}
@ -267,7 +267,7 @@ public class ScoreDB {
deleteSongStmt.setString(5, beatmap.version);
deleteSongStmt.executeUpdate();
} catch (SQLException e) {
ErrorHandler.error("Failed to delete scores from database.", e, true);
ErrorHandler.error("Failed to delete scores from database.", e).show();
}
}
@ -335,7 +335,7 @@ public class ScoreDB {
}
rs.close();
} catch (SQLException e) {
ErrorHandler.error("Failed to read scores from database.", e, true);
ErrorHandler.error("Failed to read scores from database.", e).show();
return null;
}
return getSortedArray(list);
@ -377,7 +377,7 @@ public class ScoreDB {
map.put(version, getSortedArray(list));
rs.close();
} catch (SQLException e) {
ErrorHandler.error("Failed to read scores from database.", e, true);
ErrorHandler.error("Failed to read scores from database.", e).show();
return null;
}
return map;
@ -406,7 +406,7 @@ public class ScoreDB {
connection.close();
connection = null;
} catch (SQLException e) {
ErrorHandler.error("Failed to close score database.", e, true);
ErrorHandler.error("Failed to close score database.", e).show();
}
}
}

View File

@ -18,7 +18,6 @@
package itdelatrisu.opsu.downloads;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Utils;
import java.io.File;
@ -35,6 +34,9 @@ import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import org.newdawn.slick.util.Log;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.events.BubbleNotificationEvent;
/**
* File download.
@ -142,7 +144,7 @@ public class Download {
this.url = new URL(remoteURL);
} catch (MalformedURLException e) {
this.status = Status.ERROR;
ErrorHandler.error(String.format("Bad download URL: '%s'", remoteURL), e, true);
ErrorHandler.error(String.format("Bad download URL: '%s'", remoteURL), e).show();
return;
}
this.localPath = localPath;
@ -215,7 +217,7 @@ public class Download {
else if (redirectCount > MAX_REDIRECTS)
error = String.format("Download for URL '%s' is attempting too many redirects (over %d).", base.toString(), MAX_REDIRECTS);
if (error != null) {
ErrorHandler.error(error, null, false);
EventBus.instance.post(new BubbleNotificationEvent(error, BubbleNotificationEvent.COLOR_ORANGE));
throw new IOException();
}
@ -417,7 +419,7 @@ public class Download {
}
} catch (IOException e) {
this.status = Status.ERROR;
ErrorHandler.error("Failed to cancel download.", e, true);
ErrorHandler.error("Failed to cancel download.", e).show();
}
}
}

View File

@ -18,7 +18,6 @@
package itdelatrisu.opsu.downloads;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils;
@ -35,6 +34,8 @@ import java.io.File;
import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.events.BubbleNotificationEvent;
/**
* Node containing song data and a Download object.
@ -402,7 +403,7 @@ public class DownloadNode {
public void drawDownload(Graphics g, float position, int id, boolean hover) {
Download download = this.download; // in case clearDownload() is called asynchronously
if (download == null) {
ErrorHandler.error("Trying to draw download information for button without Download object.", null, false);
EventBus.instance.post(new BubbleNotificationEvent("Trying to draw download information for button without Download object", BubbleNotificationEvent.COLOR_ORANGE));
return;
}

View File

@ -18,7 +18,6 @@
package itdelatrisu.opsu.downloads;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.downloads.Download.DownloadListener;
@ -38,6 +37,7 @@ import java.util.Properties;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
/**
* Handles automatic program updates.
@ -298,7 +298,7 @@ public class Updater {
pb.start();
} catch (IOException e) {
status = Status.INTERNAL_ERROR;
ErrorHandler.error("Failed to start new process.", e, true);
ErrorHandler.error("Failed to start new process.", e).show();
}
}
}

View File

@ -18,7 +18,6 @@
package itdelatrisu.opsu.downloads.servers;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.downloads.DownloadNode;
@ -34,6 +33,7 @@ import java.util.Date;
import org.json.JSONArray;
import org.json.JSONObject;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
/**
* Download server: http://bloodcat.com/osu/
@ -97,7 +97,7 @@ public class BloodcatServer extends DownloadServer {
resultCount++;
this.totalResults = resultCount;
} catch (MalformedURLException | UnsupportedEncodingException e) {
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e).show();
}
return nodes;
}

View File

@ -18,7 +18,6 @@
package itdelatrisu.opsu.downloads.servers;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.downloads.DownloadNode;
@ -30,6 +29,7 @@ import java.net.URLEncoder;
import org.json.JSONArray;
import org.json.JSONObject;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
/**
* Download server: https://osu.hexide.com/
@ -128,7 +128,7 @@ public class HexideServer extends DownloadServer {
// all results at once; this approach just gets pagination correct.
this.totalResults = arr.length() + resultIndex;
} catch (MalformedURLException | UnsupportedEncodingException e) {
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e).show();
}
return nodes;
}

View File

@ -18,7 +18,6 @@
package itdelatrisu.opsu.downloads.servers;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.downloads.DownloadNode;
@ -30,6 +29,7 @@ import java.net.URLEncoder;
import org.json.JSONArray;
import org.json.JSONObject;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
/**
* Download server: http://osu.mengsky.net/
@ -101,7 +101,7 @@ public class MengSkyServer extends DownloadServer {
resultCount = 1 + (pageTotal - 1) * PAGE_LIMIT;
this.totalResults = resultCount;
} catch (MalformedURLException | UnsupportedEncodingException e) {
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e).show();
}
return nodes;
}

View File

@ -18,9 +18,9 @@
package itdelatrisu.opsu.downloads.servers;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.downloads.DownloadNode;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@ -120,7 +120,7 @@ public class MnetworkServer extends DownloadServer {
// store total result count
this.totalResults = nodes.length;
} catch (MalformedURLException | UnsupportedEncodingException e) {
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e).show();
}
return nodes;
}

View File

@ -18,7 +18,6 @@
package itdelatrisu.opsu.downloads.servers;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.downloads.DownloadNode;
@ -36,6 +35,7 @@ import java.util.TimeZone;
import org.json.JSONArray;
import org.json.JSONObject;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
/**
* Download server: http://loli.al/
@ -123,7 +123,7 @@ public class OsuMirrorServer extends DownloadServer {
else
this.totalResults = maxServerID;
} catch (MalformedURLException | UnsupportedEncodingException e) {
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e).show();
}
return nodes;
}

View File

@ -17,7 +17,6 @@
*/
package itdelatrisu.opsu.downloads.servers;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.downloads.DownloadNode;
@ -34,6 +33,7 @@ import java.util.Iterator;
import java.util.List;
import org.json.JSONObject;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
/**
* Download server: http://osu.yas-online.net/
@ -114,7 +114,7 @@ public class YaSOnlineServer extends DownloadServer {
String downloadLink = item.getString("downloadLink");
return String.format(DOWNLOAD_FETCH_URL, downloadLink);
} catch (MalformedURLException | UnsupportedEncodingException e) {
ErrorHandler.error(String.format("Problem retrieving download URL for beatmap '%d'.", beatmapSetID), e, true);
ErrorHandler.error(String.format("Problem retrieving download URL for beatmap '%d'.", beatmapSetID), e).show();
return null;
} finally {
Utils.setSSLCertValidation(true);
@ -186,7 +186,7 @@ public class YaSOnlineServer extends DownloadServer {
else
this.totalResults = maxServerID;
} catch (MalformedURLException | UnsupportedEncodingException e) {
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e).show();
} finally {
Utils.setSSLCertValidation(true);
}

View File

@ -64,10 +64,9 @@ public class Circle extends GameObject {
/**
* Initializes the Circle data type with map modifiers, images, and dimensions.
* @param container the game container
* @param circleDiameter the circle diameter
*/
public static void init(GameContainer container, float circleDiameter) {
public static void init(float circleDiameter) {
diameter = circleDiameter * HitObject.getXMultiplier(); // convert from Osupixels (640x480)
int diameterInt = (int) diameter;
GameImage.HITCIRCLE.setImage(GameImage.HITCIRCLE.getImage().getScaledCopy(diameterInt, diameterInt));

View File

@ -37,6 +37,7 @@ import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import yugecin.opsudance.Dancer;
import yugecin.opsudance.core.DisplayContainer;
/**
* Data type representing a slider object.
@ -127,13 +128,12 @@ public class Slider extends GameObject {
/**
* Initializes the Slider data type with images and dimensions.
* @param container the game container
* @param circleDiameter the circle diameter
* @param beatmap the associated beatmap
*/
public static void init(GameContainer container, float circleDiameter, Beatmap beatmap) {
containerWidth = container.getWidth();
containerHeight = container.getHeight();
public static void init(DisplayContainer displayContainer, float circleDiameter, Beatmap beatmap) {
containerWidth = displayContainer.width;
containerHeight = displayContainer.height;
diameter = circleDiameter * HitObject.getXMultiplier(); // convert from Osupixels (640x480)
int diameterInt = (int) diameter;

View File

@ -35,6 +35,7 @@ import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import yugecin.opsudance.core.DisplayContainer;
/**
* Data type representing a spinner object.
@ -109,12 +110,11 @@ public class Spinner extends GameObject {
/**
* Initializes the Spinner data type with images and dimensions.
* @param container the game container
* @param difficulty the map's overall difficulty value
*/
public static void init(GameContainer container, float difficulty) {
width = container.getWidth();
height = container.getHeight();
public static void init(DisplayContainer displayContainer, float difficulty) {
width = displayContainer.width;
height = displayContainer.height;
overallDifficulty = difficulty;
}

View File

@ -96,7 +96,7 @@ public class CurveRenderState {
*/
public static void shutdown() {
staticState.shutdown();
FrameBufferCache.shutdown();
//FrameBufferCache.shutdown();
}
/**

View File

@ -128,6 +128,7 @@ public class FrameBufferCache {
* <p>
* This is necessary for cases when the game gets restarted with a
* different resolution without closing the process.
* // TODO d do we still need this
*/
public static void shutdown() {
FrameBufferCache fbcInstance = FrameBufferCache.getInstance();

View File

@ -18,7 +18,6 @@
package itdelatrisu.opsu.replay;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.ScoreData;
import itdelatrisu.opsu.Utils;
@ -45,6 +44,9 @@ import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream;
import org.newdawn.slick.util.Log;
import lzma.streams.LzmaOutputStream;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.events.BubbleNotificationEvent;
/**
* Captures osu! replay data.
@ -273,7 +275,7 @@ public class Replay {
File dir = Options.getReplayDir();
if (!dir.isDirectory()) {
if (!dir.mkdir()) {
ErrorHandler.error("Failed to create replay directory.", null, false);
EventBus.instance.post(new BubbleNotificationEvent("Failed to create replay directory.", BubbleNotificationEvent.COMMONCOLOR_RED));
return;
}
}
@ -343,7 +345,7 @@ public class Replay {
compressedOut.write(bytes);
} catch (IOException e) {
// possible OOM: https://github.com/jponge/lzma-java/issues/9
ErrorHandler.error("LZMA compression failed (possible out-of-memory error).", e, true);
ErrorHandler.error("LZMA compression failed (possible out-of-memory error).", e).show();
}
compressedOut.close();
bout.close();
@ -357,7 +359,7 @@ public class Replay {
writer.close();
} catch (IOException e) {
ErrorHandler.error("Could not save replay data.", e, true);
ErrorHandler.error("Could not save replay data.", e).show();
}
}
}.start();

View File

@ -18,7 +18,7 @@
package itdelatrisu.opsu.replay;
import itdelatrisu.opsu.ErrorHandler;
import com.sun.deploy.security.EnhancedJarVerifier;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.beatmap.Beatmap;
import itdelatrisu.opsu.beatmap.BeatmapSetList;
@ -31,6 +31,8 @@ import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import org.newdawn.slick.util.Log;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.events.BubbleNotificationEvent;
/**
* Importer for replay files.
@ -70,7 +72,9 @@ public class ReplayImporter {
File replayDir = Options.getReplayDir();
if (!replayDir.isDirectory()) {
if (!replayDir.mkdir()) {
ErrorHandler.error(String.format("Failed to create replay directory '%s'.", replayDir.getAbsolutePath()), null, false);
String err = String.format("Failed to create replay directory '%s'.", replayDir.getAbsolutePath());
Log.error(err);
EventBus.instance.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED));
return;
}
}
@ -83,7 +87,9 @@ public class ReplayImporter {
r.loadHeader();
} catch (IOException e) {
moveToFailedDirectory(file);
ErrorHandler.error(String.format("Failed to import replay '%s'. The replay file could not be parsed.", file.getName()), e, false);
String err = String.format("Failed to import replay '%s'. The replay file could not be parsed.", file.getName());
Log.error(err, e);
EventBus.instance.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED));
continue;
}
Beatmap beatmap = BeatmapSetList.get().getBeatmapFromHash(r.beatmapHash);
@ -100,8 +106,9 @@ public class ReplayImporter {
}
} else {
moveToFailedDirectory(file);
ErrorHandler.error(String.format("Failed to import replay '%s'. The associated beatmap could not be found.", file.getName()), null, false);
continue;
String err = String.format("Failed to import replay '%s'. The associated beatmap could not be found.", file.getName());
Log.error(err);
EventBus.instance.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED));
}
}

View File

@ -18,7 +18,6 @@
package itdelatrisu.opsu.skins;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.Utils;
@ -32,6 +31,8 @@ import java.util.LinkedList;
import org.newdawn.slick.Color;
import org.newdawn.slick.util.Log;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.events.BubbleNotificationEvent;
/**
* Loads skin configuration files.
@ -290,7 +291,9 @@ public class SkinLoader {
}
}
} catch (IOException e) {
ErrorHandler.error(String.format("Failed to read file '%s'.", skinFile.getAbsolutePath()), e, false);
String err = String.format("Failed to read file '%s'.", skinFile.getAbsolutePath());
Log.error(err, e);
EventBus.instance.post(new BubbleNotificationEvent(err, BubbleNotificationEvent.COMMONCOLOR_RED));
}
return skin;

View File

@ -20,7 +20,6 @@ package itdelatrisu.opsu.states;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.GameMod;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.ScoreData;
import itdelatrisu.opsu.Utils;
@ -39,114 +38,112 @@ import java.util.ArrayList;
import java.util.List;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.state.transition.EmptyTransition;
import org.newdawn.slick.state.transition.FadeInTransition;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.inject.InstanceContainer;
import yugecin.opsudance.core.state.BaseOpsuState;
/**
* Generic button menu state.
* <p>
* Displays a header and a set of defined options to the player.
*/
public class ButtonMenu extends BasicGameState {
public class ButtonMenu extends BaseOpsuState {
/** Menu states. */
public enum MenuState {
/** The exit confirmation screen. */
EXIT (new Button[] { Button.YES, Button.NO }) {
@Override
public String[] getTitle(GameContainer container, StateBasedGame game) {
public String[] getTitle() {
return new String[] { "Are you sure you want to exit opsu!?" };
}
@Override
public void leave(GameContainer container, StateBasedGame game) {
Button.NO.click(container, game);
public void leave() {
Button.NO.click();
}
},
/** The initial beatmap management screen (for a non-"favorite" beatmap). */
BEATMAP (new Button[] { Button.CLEAR_SCORES, Button.FAVORITE_ADD, Button.DELETE, Button.CANCEL }) {
@Override
public String[] getTitle(GameContainer container, StateBasedGame game) {
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
public String[] getTitle() {
BeatmapSetNode node = instanceContainer.provide(ButtonMenu.class).getNode();
String beatmapString = (node != null) ? BeatmapSetList.get().getBaseNode(node.index).toString() : "";
return new String[] { beatmapString, "What do you want to do with this beatmap?" };
}
@Override
public void leave(GameContainer container, StateBasedGame game) {
Button.CANCEL.click(container, game);
public void leave() {
Button.CANCEL.click();
}
@Override
public void scroll(GameContainer container, StateBasedGame game, int newValue) {
Input input = container.getInput();
if (input.isKeyDown(Input.KEY_LALT) || input.isKeyDown(Input.KEY_RALT))
super.scroll(container, game, newValue);
public void mouseWheelMoved(int newValue) {
if (displayContainer.input.isKeyDown(Input.KEY_LALT) || displayContainer.input.isKeyDown(Input.KEY_RALT)) {
super.mouseWheelMoved(newValue);
}
}
},
/** The initial beatmap management screen (for a "favorite" beatmap). */
BEATMAP_FAVORITE (new Button[] { Button.CLEAR_SCORES, Button.FAVORITE_REMOVE, Button.DELETE, Button.CANCEL }) {
@Override
public String[] getTitle(GameContainer container, StateBasedGame game) {
return BEATMAP.getTitle(container, game);
public String[] getTitle() {
return BEATMAP.getTitle();
}
@Override
public void leave(GameContainer container, StateBasedGame game) {
BEATMAP.leave(container, game);
public void leave() {
BEATMAP.leave();
}
@Override
public void scroll(GameContainer container, StateBasedGame game, int newValue) {
BEATMAP.scroll(container, game, newValue);
public void mouseWheelMoved(int newValue) {
BEATMAP.mouseWheelMoved(newValue);
}
},
/** The beatmap deletion screen for a beatmap set with multiple beatmaps. */
BEATMAP_DELETE_SELECT (new Button[] { Button.DELETE_GROUP, Button.DELETE_SONG, Button.CANCEL_DELETE }) {
@Override
public String[] getTitle(GameContainer container, StateBasedGame game) {
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
public String[] getTitle() {
BeatmapSetNode node = instanceContainer.provide(ButtonMenu.class).getNode();
String beatmapString = (node != null) ? node.toString() : "";
return new String[] { String.format("Are you sure you wish to delete '%s' from disk?", beatmapString) };
}
@Override
public void leave(GameContainer container, StateBasedGame game) {
Button.CANCEL_DELETE.click(container, game);
public void leave() {
Button.CANCEL_DELETE.click();
}
@Override
public void scroll(GameContainer container, StateBasedGame game, int newValue) {
MenuState.BEATMAP.scroll(container, game, newValue);
public void mouseWheelMoved(int newValue) {
MenuState.BEATMAP.mouseWheelMoved(newValue);
}
},
/** The beatmap deletion screen for a single beatmap. */
BEATMAP_DELETE_CONFIRM (new Button[] { Button.DELETE_CONFIRM, Button.CANCEL_DELETE }) {
@Override
public String[] getTitle(GameContainer container, StateBasedGame game) {
return BEATMAP_DELETE_SELECT.getTitle(container, game);
public String[] getTitle() {
return BEATMAP_DELETE_SELECT.getTitle();
}
@Override
public void leave(GameContainer container, StateBasedGame game) {
Button.CANCEL_DELETE.click(container, game);
public void leave() {
Button.CANCEL_DELETE.click();
}
@Override
public void scroll(GameContainer container, StateBasedGame game, int newValue) {
MenuState.BEATMAP.scroll(container, game, newValue);
public void mouseWheelMoved(int newValue) {
MenuState.BEATMAP.mouseWheelMoved(newValue);
}
},
/** The beatmap reloading confirmation screen. */
RELOAD (new Button[] { Button.RELOAD_CONFIRM, Button.RELOAD_CANCEL }) {
@Override
public String[] getTitle(GameContainer container, StateBasedGame game) {
public String[] getTitle() {
return new String[] {
"You have requested a full process of your beatmaps.",
"This could take a few minutes.",
@ -155,70 +152,68 @@ public class ButtonMenu extends BasicGameState {
}
@Override
public void leave(GameContainer container, StateBasedGame game) {
Button.RELOAD_CANCEL.click(container, game);
public void leave() {
Button.RELOAD_CANCEL.click();
}
@Override
public void scroll(GameContainer container, StateBasedGame game, int newValue) {
MenuState.BEATMAP.scroll(container, game, newValue);
public void mouseWheelMoved(int newValue) {
MenuState.BEATMAP.mouseWheelMoved(newValue);
}
},
/** The score management screen. */
SCORE (new Button[] { Button.DELETE_SCORE, Button.CLOSE }) {
@Override
public String[] getTitle(GameContainer container, StateBasedGame game) {
public String[] getTitle() {
return new String[] { "Score Management" };
}
@Override
public void leave(GameContainer container, StateBasedGame game) {
Button.CLOSE.click(container, game);
public void leave() {
Button.CLOSE.click();
}
@Override
public void scroll(GameContainer container, StateBasedGame game, int newValue) {
MenuState.BEATMAP.scroll(container, game, newValue);
public void mouseWheelMoved(int newValue) {
MenuState.BEATMAP.mouseWheelMoved(newValue);
}
},
/** The game mod selection screen. */
MODS (new Button[] { Button.RESET_MODS, Button.CLOSE }) {
@Override
public String[] getTitle(GameContainer container, StateBasedGame game) {
public String[] getTitle() {
return new String[] {
"Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun."
};
}
@Override
protected float getBaseY(GameContainer container, StateBasedGame game) {
return container.getHeight() * 2f / 3;
protected float getBaseY(DisplayContainer displayContainer) {
return displayContainer.height * 2f / 3;
}
@Override
public void enter(GameContainer container, StateBasedGame game) {
super.enter(container, game);
for (GameMod mod : GameMod.values())
public void enter() {
super.enter();
for (GameMod mod : GameMod.values()) {
mod.resetHover();
}
}
@Override
public void leave(GameContainer container, StateBasedGame game) {
Button.CLOSE.click(container, game);
public void leave() {
Button.CLOSE.click();
}
@Override
public void draw(GameContainer container, StateBasedGame game, Graphics g) {
int width = container.getWidth();
int height = container.getHeight();
public void render(Graphics g) {
// score multiplier (TODO: fade in color changes)
float mult = GameMod.getScoreMultiplier();
String multString = String.format("Score Multiplier: %.2fx", mult);
Color multColor = (mult == 1f) ? Color.white : (mult > 1f) ? Color.green : Color.red;
float multY = Fonts.LARGE.getLineHeight() * 2 + height * 0.06f;
float multY = Fonts.LARGE.getLineHeight() * 2 + displayContainer.height * 0.06f;
Fonts.LARGE.drawString(
(width - Fonts.LARGE.getWidth(multString)) / 2f,
(displayContainer.width - Fonts.LARGE.getWidth(multString)) / 2f,
multY, multString, multColor);
// category text
@ -232,27 +227,28 @@ public class ButtonMenu extends BasicGameState {
for (GameMod mod : GameMod.values())
mod.draw();
super.draw(container, game, g);
super.render(g);
}
@Override
public void update(GameContainer container, int delta, int mouseX, int mouseY) {
super.update(container, delta, mouseX, mouseY);
public void preRenderUpdate() {
super.preRenderUpdate();
GameMod hoverMod = null;
for (GameMod mod : GameMod.values()) {
mod.hoverUpdate(delta, mod.isActive());
if (hoverMod == null && mod.contains(mouseX, mouseY))
mod.hoverUpdate(displayContainer.renderDelta, mod.isActive());
if (hoverMod == null && mod.contains(displayContainer.mouseX, displayContainer.mouseY))
hoverMod = mod;
}
// tooltips
if (hoverMod != null)
UI.updateTooltip(delta, hoverMod.getDescription(), true);
if (hoverMod != null) {
UI.updateTooltip(displayContainer.renderDelta, hoverMod.getDescription(), true);
}
}
@Override
public void keyPress(GameContainer container, StateBasedGame game, int key, char c) {
super.keyPress(container, game, key, c);
public void keyPressed(int key, char c) {
super.keyPressed(key, c);
for (GameMod mod : GameMod.values()) {
if (key == mod.getKey()) {
mod.toggle(true);
@ -262,8 +258,8 @@ public class ButtonMenu extends BasicGameState {
}
@Override
public void click(GameContainer container, StateBasedGame game, int cx, int cy) {
super.click(container, game, cx, cy);
public void mousePressed(int cx, int cy) {
super.mousePressed(cx, cy);
for (GameMod mod : GameMod.values()) {
if (mod.contains(cx, cy)) {
boolean prevState = mod.isActive();
@ -276,11 +272,14 @@ public class ButtonMenu extends BasicGameState {
}
@Override
public void scroll(GameContainer container, StateBasedGame game, int newValue) {
MenuState.BEATMAP.scroll(container, game, newValue);
public void mouseWheelMoved(int newValue) {
MenuState.BEATMAP.mouseWheelMoved(newValue);
}
};
public static DisplayContainer displayContainer;
public static InstanceContainer instanceContainer;
/** The buttons in the state. */
private final Button[] buttons;
@ -306,15 +305,10 @@ public class ButtonMenu extends BasicGameState {
/**
* Initializes the menu state.
* @param container the game container
* @param game the game
* @param button the center button image
* @param buttonL the left button image
* @param buttonR the right button image
*/
public void init(GameContainer container, StateBasedGame game, Image button, Image buttonL, Image buttonR) {
float center = container.getWidth() / 2f;
float baseY = getBaseY(container, game);
public void revalidate(Image button, Image buttonL, Image buttonR) {
float center = displayContainer.width / 2;
float baseY = getBaseY(displayContainer);
float offsetY = button.getHeight() * 1.25f;
menuButtons = new MenuButton[buttons.length];
@ -328,25 +322,21 @@ public class ButtonMenu extends BasicGameState {
/**
* Returns the base Y coordinate for the buttons.
* @param container the game container
* @param game the game
*/
protected float getBaseY(GameContainer container, StateBasedGame game) {
float baseY = container.getHeight() * 0.2f;
baseY += ((getTitle(container, game).length - 1) * Fonts.LARGE.getLineHeight());
protected float getBaseY(DisplayContainer displayContainer) {
float baseY = displayContainer.height * 0.2f;
baseY += ((getTitle().length - 1) * Fonts.LARGE.getLineHeight());
return baseY;
}
/**
* Draws the title and buttons to the graphics context.
* @param container the game container
* @param game the game
* @param g the graphics context
*/
public void draw(GameContainer container, StateBasedGame game, Graphics g) {
public void render(Graphics g) {
// draw title
if (actualTitle != null) {
float marginX = container.getWidth() * 0.015f, marginY = container.getHeight() * 0.01f;
float marginX = displayContainer.width * 0.015f, marginY = displayContainer.height * 0.01f;
int lineHeight = Fonts.LARGE.getLineHeight();
for (int i = 0, size = actualTitle.size(); i < size; i++)
Fonts.LARGE.drawString(marginX, marginY + (i * lineHeight), actualTitle.get(i), Color.white);
@ -361,17 +351,13 @@ public class ButtonMenu extends BasicGameState {
/**
* Updates the menu state.
* @param container the game container
* @param delta the delta interval
* @param mouseX the mouse x coordinate
* @param mouseY the mouse y coordinate
*/
public void update(GameContainer container, int delta, int mouseX, int mouseY) {
float center = container.getWidth() / 2f;
boolean centerOffsetUpdated = centerOffset.update(delta);
public void preRenderUpdate() {
float center = displayContainer.width / 2f;
boolean centerOffsetUpdated = centerOffset.update(displayContainer.renderDelta);
float centerOffsetX = centerOffset.getValue();
for (int i = 0; i < buttons.length; i++) {
menuButtons[i].hoverUpdate(delta, mouseX, mouseY);
menuButtons[i].hoverUpdate(displayContainer.renderDelta, displayContainer.mouseX, displayContainer.mouseY);
// move button to center
if (centerOffsetUpdated)
@ -381,15 +367,11 @@ public class ButtonMenu extends BasicGameState {
/**
* Processes a mouse click action.
* @param container the game container
* @param game the game
* @param cx the x coordinate
* @param cy the y coordinate
*/
public void click(GameContainer container, StateBasedGame game, int cx, int cy) {
public void mousePressed(int x, int y) {
for (int i = 0; i < buttons.length; i++) {
if (menuButtons[i].contains(cx, cy)) {
buttons[i].click(container, game);
if (menuButtons[i].contains(x, y)) {
buttons[i].click();
break;
}
}
@ -397,42 +379,34 @@ public class ButtonMenu extends BasicGameState {
/**
* Processes a key press action.
* @param container the game container
* @param game the game
* @param key the key code that was pressed (see {@link org.newdawn.slick.Input})
* @param c the character of the key that was pressed
*/
public void keyPress(GameContainer container, StateBasedGame game, int key, char c) {
public void keyPressed(int key, char c) {
int index = Character.getNumericValue(c) - 1;
if (index >= 0 && index < buttons.length)
buttons[index].click(container, game);
buttons[index].click();
}
/**
* Retrieves the title strings for the menu state (via override).
* @param container the game container
* @param game the game
*/
public String[] getTitle(GameContainer container, StateBasedGame game) { return new String[0]; }
public String[] getTitle() { return new String[0]; }
/**
* Processes a mouse wheel movement.
* @param container the game container
* @param game the game
* @param newValue the amount that the mouse wheel moved
*/
public void scroll(GameContainer container, StateBasedGame game, int newValue) {
public void mouseWheelMoved(int newValue) {
UI.changeVolume((newValue < 0) ? -1 : 1);
}
/**
* Processes a state enter request.
* @param container the game container
* @param game the game
*/
public void enter(GameContainer container, StateBasedGame game) {
float center = container.getWidth() / 2f;
float centerOffsetX = container.getWidth() * OFFSET_WIDTH_RATIO;
public void enter() {
float center = displayContainer.width / 2f;
float centerOffsetX = displayContainer.width * OFFSET_WIDTH_RATIO;
centerOffset = new AnimatedValue(700, centerOffsetX, 0, AnimationEquation.OUT_BOUNCE);
for (int i = 0; i < buttons.length; i++) {
menuButtons[i].setX(center + ((i % 2 == 0) ? centerOffsetX : centerOffsetX * -1));
@ -440,150 +414,149 @@ public class ButtonMenu extends BasicGameState {
}
// create title string list
actualTitle = new ArrayList<String>();
String[] title = getTitle(container, game);
int maxLineWidth = (int) (container.getWidth() * 0.96f);
for (int i = 0; i < title.length; i++) {
actualTitle = new ArrayList<>();
String[] title = getTitle();
int maxLineWidth = (int) (displayContainer.width * 0.96f);
for (String aTitle : title) {
// wrap text if too long
if (Fonts.LARGE.getWidth(title[i]) > maxLineWidth) {
List<String> list = Fonts.wrap(Fonts.LARGE, title[i], maxLineWidth, false);
if (Fonts.LARGE.getWidth(aTitle) > maxLineWidth) {
List<String> list = Fonts.wrap(Fonts.LARGE, aTitle, maxLineWidth, false);
actualTitle.addAll(list);
} else
actualTitle.add(title[i]);
} else {
actualTitle.add(aTitle);
}
}
}
/**
* Processes a state exit request (via override).
* @param container the game container
* @param game the game
*/
public void leave(GameContainer container, StateBasedGame game) {}
};
public void leave() {}
}
/** Button types. */
private enum Button {
YES ("Yes", Color.green) {
@Override
public void click(GameContainer container, StateBasedGame game) {
container.exit();
public void click() {
displayContainer.exitRequested = true;
}
},
NO ("No", Color.red) {
@Override
public void click(GameContainer container, StateBasedGame game) {
public void click() {
SoundController.playSound(SoundEffect.MENUBACK);
game.enterState(Opsu.STATE_MAINMENU, new EmptyTransition(), new FadeInTransition());
displayContainer.switchState(MainMenu.class);
}
},
CLEAR_SCORES ("Clear local scores", Color.magenta) {
@Override
public void click(GameContainer container, StateBasedGame game) {
public void click() {
SoundController.playSound(SoundEffect.MENUHIT);
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).doStateActionOnLoad(MenuState.BEATMAP, node);
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
BeatmapSetNode node = instanceContainer.provide(ButtonMenu.class).getNode();
instanceContainer.provide(SongMenu.class).doStateActionOnLoad(MenuState.BEATMAP, node);
displayContainer.switchState(SongMenu.class);
}
},
FAVORITE_ADD ("Add to Favorites", Color.blue) {
@Override
public void click(GameContainer container, StateBasedGame game) {
public void click() {
SoundController.playSound(SoundEffect.MENUHIT);
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
BeatmapSetNode node = instanceContainer.provide(ButtonMenu.class).getNode();
node.getBeatmapSet().setFavorite(true);
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
displayContainer.switchState(SongMenu.class);
}
},
FAVORITE_REMOVE ("Remove from Favorites", Color.blue) {
@Override
public void click(GameContainer container, StateBasedGame game) {
public void click() {
SoundController.playSound(SoundEffect.MENUHIT);
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
BeatmapSetNode node = instanceContainer.provide(ButtonMenu.class).getNode();
node.getBeatmapSet().setFavorite(false);
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).doStateActionOnLoad(MenuState.BEATMAP_FAVORITE);
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
instanceContainer.provide(SongMenu.class).doStateActionOnLoad(MenuState.BEATMAP_FAVORITE);
displayContainer.switchState(SongMenu.class);
}
},
DELETE ("Delete...", Color.red) {
@Override
public void click(GameContainer container, StateBasedGame game) {
public void click() {
SoundController.playSound(SoundEffect.MENUHIT);
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
BeatmapSetNode node = instanceContainer.provide(ButtonMenu.class).getNode();
MenuState ms = (node.beatmapIndex == -1 || node.getBeatmapSet().size() == 1) ?
MenuState.BEATMAP_DELETE_CONFIRM : MenuState.BEATMAP_DELETE_SELECT;
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(ms, node);
game.enterState(Opsu.STATE_BUTTONMENU);
instanceContainer.provide(ButtonMenu.class).setMenuState(ms, node);
displayContainer.switchState(ButtonMenu.class);
}
},
CANCEL ("Cancel", Color.gray) {
@Override
public void click(GameContainer container, StateBasedGame game) {
public void click() {
SoundController.playSound(SoundEffect.MENUBACK);
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
displayContainer.switchState(SongMenu.class);
}
},
DELETE_CONFIRM ("Yes, delete this beatmap!", Color.red) {
@Override
public void click(GameContainer container, StateBasedGame game) {
public void click() {
SoundController.playSound(SoundEffect.MENUHIT);
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).doStateActionOnLoad(MenuState.BEATMAP_DELETE_CONFIRM, node);
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
BeatmapSetNode node = instanceContainer.provide(ButtonMenu.class).getNode();
instanceContainer.provide(SongMenu.class).doStateActionOnLoad(MenuState.BEATMAP_DELETE_CONFIRM, node);
displayContainer.switchState(SongMenu.class);
}
},
DELETE_GROUP ("Yes, delete all difficulties!", Color.red) {
@Override
public void click(GameContainer container, StateBasedGame game) {
DELETE_CONFIRM.click(container, game);
public void click() {
DELETE_CONFIRM.click();
}
},
DELETE_SONG ("Yes, but only this difficulty", Color.red) {
@Override
public void click(GameContainer container, StateBasedGame game) {
public void click() {
SoundController.playSound(SoundEffect.MENUHIT);
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).doStateActionOnLoad(MenuState.BEATMAP_DELETE_SELECT, node);
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
BeatmapSetNode node = instanceContainer.provide(ButtonMenu.class).getNode();
instanceContainer.provide(SongMenu.class).doStateActionOnLoad(MenuState.BEATMAP_DELETE_SELECT, node);
displayContainer.switchState(SongMenu.class);
}
},
CANCEL_DELETE ("Nooooo! I didn't mean to!", Color.gray) {
@Override
public void click(GameContainer container, StateBasedGame game) {
CANCEL.click(container, game);
public void click() {
CANCEL.click();
}
},
RELOAD_CONFIRM ("Let's do it!", Color.green) {
@Override
public void click(GameContainer container, StateBasedGame game) {
public void click() {
SoundController.playSound(SoundEffect.MENUHIT);
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).doStateActionOnLoad(MenuState.RELOAD);
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
instanceContainer.provide(SongMenu.class).doStateActionOnLoad(MenuState.RELOAD);
displayContainer.switchState(SongMenu.class);
}
},
RELOAD_CANCEL ("Cancel", Color.red) {
@Override
public void click(GameContainer container, StateBasedGame game) {
CANCEL.click(container, game);
public void click() {
CANCEL.click();
}
},
DELETE_SCORE ("Delete score", Color.green) {
@Override
public void click(GameContainer container, StateBasedGame game) {
public void click() {
SoundController.playSound(SoundEffect.MENUHIT);
ScoreData scoreData = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getScoreData();
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).doStateActionOnLoad(MenuState.SCORE, scoreData);
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
ScoreData scoreData = instanceContainer.provide(ButtonMenu.class).getScoreData();
instanceContainer.provide(SongMenu.class).doStateActionOnLoad(MenuState.SCORE, scoreData);
displayContainer.switchState(SongMenu.class);
}
},
CLOSE ("Close", Color.gray) {
@Override
public void click(GameContainer container, StateBasedGame game) {
CANCEL.click(container, game);
public void click() {
CANCEL.click();
}
},
RESET_MODS ("Reset All Mods", Color.red) {
@Override
public void click(GameContainer container, StateBasedGame game) {
public void click() {
SoundController.playSound(SoundEffect.MENUCLICK);
for (GameMod mod : GameMod.values()) {
if (mod.isActive())
@ -592,6 +565,9 @@ public class ButtonMenu extends BasicGameState {
}
};
public static DisplayContainer displayContainer;
public static InstanceContainer instanceContainer;
/** The text to show on the button. */
private final String text;
@ -620,10 +596,8 @@ public class ButtonMenu extends BasicGameState {
/**
* Processes a mouse click action (via override).
* @param container the game container
* @param game the game
*/
public void click(GameContainer container, StateBasedGame game) {}
public void click() {}
}
/** The current menu state. */
@ -635,97 +609,83 @@ public class ButtonMenu extends BasicGameState {
/** The score data to process in the state. */
private ScoreData scoreData;
// game-related variables
private GameContainer container;
private StateBasedGame game;
private Input input;
private final int state;
public ButtonMenu(int state) {
this.state = state;
public ButtonMenu(DisplayContainer displayContainer, InstanceContainer instanceContainer) {
super(displayContainer);
Button.displayContainer = MenuState.displayContainer = displayContainer;
Button.instanceContainer = MenuState.instanceContainer = instanceContainer;
}
@Override
public void init(GameContainer container, StateBasedGame game)
throws SlickException {
this.container = container;
this.game = game;
this.input = container.getInput();
public void revalidate() {
super.revalidate();
// initialize buttons
Image button = GameImage.MENU_BUTTON_MID.getImage();
button = button.getScaledCopy(container.getWidth() / 2, button.getHeight());
button = button.getScaledCopy(displayContainer.width / 2, button.getHeight());
Image buttonL = GameImage.MENU_BUTTON_LEFT.getImage();
Image buttonR = GameImage.MENU_BUTTON_RIGHT.getImage();
for (MenuState ms : MenuState.values())
ms.init(container, game, button, buttonL, buttonR);
}
@Override
public void render(GameContainer container, StateBasedGame game, Graphics g)
throws SlickException {
g.setBackground(Color.black);
if (menuState != null)
menuState.draw(container, game, g);
}
@Override
public void update(GameContainer container, StateBasedGame game, int delta)
throws SlickException {
UI.update(delta);
MusicController.loopTrackIfEnded(false);
if (menuState != null)
menuState.update(container, delta, input.getMouseX(), input.getMouseY());
}
@Override
public int getID() { return state; }
@Override
public void mousePressed(int button, int x, int y) {
// check mouse button
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
if (menuState != null)
menuState.click(container, game, x, y);
}
@Override
public void mouseWheelMoved(int newValue) {
if (menuState != null)
menuState.scroll(container, game, newValue);
}
@Override
public void keyPressed(int key, char c) {
switch (key) {
case Input.KEY_ESCAPE:
if (menuState != null)
menuState.leave(container, game);
break;
case Input.KEY_F7:
Options.setNextFPS(container);
break;
case Input.KEY_F10:
Options.toggleMouseDisabled();
break;
case Input.KEY_F12:
Utils.takeScreenShot();
break;
default:
if (menuState != null)
menuState.keyPress(container, game, key, c);
break;
for (MenuState ms : MenuState.values()) {
ms.revalidate(button, buttonL, buttonR);
}
}
@Override
public void enter(GameContainer container, StateBasedGame game)
throws SlickException {
public void render(Graphics g) {
super.render(g);
g.setBackground(Color.black);
if (menuState == null) {
return;
}
menuState.render(g);
}
@Override
public void preRenderUpdate() {
super.preRenderUpdate();
UI.update(displayContainer.renderDelta);
MusicController.loopTrackIfEnded(false);
menuState.preRenderUpdate();
}
@Override
public boolean mousePressed(int button, int x, int y) {
if (button == Input.MOUSE_MIDDLE_BUTTON) {
return false;
}
menuState.mousePressed(x, y);
return true;
}
@Override
public boolean mouseWheelMoved(int newValue) {
menuState.mouseWheelMoved(newValue);
return true;
}
@Override
public boolean keyPressed(int key, char c) {
if (super.keyPressed(key, c)) {
return true;
}
if (key == Input.KEY_ESCAPE) {
menuState.leave();
return true;
}
menuState.keyPressed(key, c);
return true;
}
@Override
public void enter() {
super.enter();
UI.enter();
if (menuState != null)
menuState.enter(container, game);
menuState.enter();
}
/**

View File

@ -19,7 +19,6 @@
package itdelatrisu.opsu.states;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.audio.MusicController;
@ -51,17 +50,15 @@ import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.gui.TextField;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.state.transition.EasedFadeOutTransition;
import org.newdawn.slick.state.transition.FadeInTransition;
import org.newdawn.slick.util.Log;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.inject.InstanceContainer;
import yugecin.opsudance.core.state.ComplexOpsuState;
/**
* Downloads menu.
@ -69,7 +66,10 @@ import org.newdawn.slick.util.Log;
* Players are able to download beatmaps off of various servers and import them
* from this state.
*/
public class DownloadsMenu extends BasicGameState {
public class DownloadsMenu extends ComplexOpsuState {
private final InstanceContainer instanceContainer;
/** Delay time, in milliseconds, between each search. */
private static final int SEARCH_DELAY = 700;
@ -288,45 +288,42 @@ public class DownloadsMenu extends BasicGameState {
}
}
// game-related variables
private GameContainer container;
private StateBasedGame game;
private Input input;
private final int state;
public DownloadsMenu(int state) {
this.state = state;
public DownloadsMenu(DisplayContainer displayContainer, InstanceContainer instanceContainer) {
super(displayContainer);
this.instanceContainer = instanceContainer;
}
@Override
public void init(GameContainer container, StateBasedGame game)
throws SlickException {
this.container = container;
this.game = game;
this.input = container.getInput();
public void revalidate() {
super.revalidate();
int width = container.getWidth();
int height = container.getHeight();
float baseX = width * 0.024f;
float searchY = (height * 0.04f) + Fonts.LARGE.getLineHeight();
float searchWidth = width * 0.3f;
components.clear();
int width = displayContainer.width;
int height = displayContainer.height;
int baseX = (int) (width * 0.024f);
int searchY = (int) (height * 0.04f + Fonts.LARGE.getLineHeight());
int searchWidth = (int) (width * 0.3f);
// search
searchTimer = SEARCH_DELAY;
searchResultString = "Loading data from server...";
search = new TextField(
container, Fonts.DEFAULT, (int) baseX, (int) searchY,
(int) searchWidth, Fonts.MEDIUM.getLineHeight()
);
search = new TextField(displayContainer, Fonts.DEFAULT, baseX, searchY, searchWidth, Fonts.MEDIUM.getLineHeight()) {
@Override
public boolean isFocusable() {
return false;
}
};
search.setFocused(true);
search.setBackgroundColor(Colors.BLACK_BG_NORMAL);
search.setBorderColor(Color.white);
search.setTextColor(Color.white);
search.setConsumeEvents(false);
search.setMaxLength(255);
components.add(search);
// page buttons
float pageButtonY = height * 0.2f;
float pageButtonWidth = width * 0.7f;
int pageButtonY = (int) (height * 0.2f);
int pageButtonWidth = (int) (width * 0.7f);
Image prevImg = GameImage.MUSIC_PREVIOUS.getImage();
Image nextImg = GameImage.MUSIC_NEXT.getImage();
prevPage = new MenuButton(prevImg, baseX + prevImg.getWidth() / 2f,
@ -337,25 +334,25 @@ public class DownloadsMenu extends BasicGameState {
nextPage.setHoverExpand(1.5f);
// buttons
float buttonMarginX = width * 0.004f;
float buttonHeight = height * 0.038f;
float resetWidth = width * 0.085f;
float rankedWidth = width * 0.15f;
float lowerWidth = width * 0.12f;
float topButtonY = searchY + Fonts.MEDIUM.getLineHeight() / 2f;
float lowerButtonY = height * 0.995f - searchY - buttonHeight / 2f;
int buttonMarginX = (int) (width * 0.004f);
int buttonHeight = (int) (height * 0.038f);
int resetWidth = (int) (width * 0.085f);
int rankedWidth = (int) (width * 0.15f);
int lowerWidth = (int) (width * 0.12f);
int topButtonY = (int) (searchY + Fonts.MEDIUM.getLineHeight() / 2f);
int lowerButtonY = (int) (height * 0.995f - searchY - buttonHeight / 2f);
Image button = GameImage.MENU_BUTTON_MID.getImage();
Image buttonL = GameImage.MENU_BUTTON_LEFT.getImage();
Image buttonR = GameImage.MENU_BUTTON_RIGHT.getImage();
buttonL = buttonL.getScaledCopy(buttonHeight / buttonL.getHeight());
buttonR = buttonR.getScaledCopy(buttonHeight / buttonR.getHeight());
int lrButtonWidth = buttonL.getWidth() + buttonR.getWidth();
Image resetButtonImage = button.getScaledCopy((int) resetWidth - lrButtonWidth, (int) buttonHeight);
Image rankedButtonImage = button.getScaledCopy((int) rankedWidth - lrButtonWidth, (int) buttonHeight);
Image lowerButtonImage = button.getScaledCopy((int) lowerWidth - lrButtonWidth, (int) buttonHeight);
float resetButtonWidth = resetButtonImage.getWidth() + lrButtonWidth;
float rankedButtonWidth = rankedButtonImage.getWidth() + lrButtonWidth;
float lowerButtonWidth = lowerButtonImage.getWidth() + lrButtonWidth;
Image resetButtonImage = button.getScaledCopy(resetWidth - lrButtonWidth, buttonHeight);
Image rankedButtonImage = button.getScaledCopy(rankedWidth - lrButtonWidth, buttonHeight);
Image lowerButtonImage = button.getScaledCopy(lowerWidth - lrButtonWidth, buttonHeight);
int resetButtonWidth = resetButtonImage.getWidth() + lrButtonWidth;
int rankedButtonWidth = rankedButtonImage.getWidth() + lrButtonWidth;
int lowerButtonWidth = lowerButtonImage.getWidth() + lrButtonWidth;
clearButton = new MenuButton(lowerButtonImage, buttonL, buttonR,
width * 0.75f + buttonMarginX + lowerButtonWidth / 2f, lowerButtonY);
importButton = new MenuButton(lowerButtonImage, buttonL, buttonR,
@ -374,8 +371,8 @@ public class DownloadsMenu extends BasicGameState {
// dropdown menu
int serverWidth = (int) (width * 0.12f);
serverMenu = new DropdownMenu<DownloadServer>(container, SERVERS,
baseX + searchWidth + buttonMarginX * 3f + resetButtonWidth + rankedButtonWidth, searchY, serverWidth) {
int x = baseX + searchWidth + buttonMarginX * 3 + resetButtonWidth + rankedButtonWidth;
serverMenu = new DropdownMenu<DownloadServer>(displayContainer, SERVERS, x, searchY, serverWidth) {
@Override
public void itemSelected(int index, DownloadServer item) {
resultList = null;
@ -388,13 +385,14 @@ public class DownloadsMenu extends BasicGameState {
searchResultString = "Loading data from server...";
lastQuery = null;
pageDir = Page.RESET;
if (searchQuery != null)
if (searchQuery != null) {
searchQuery.interrupt();
}
resetSearchTimer();
}
@Override
public boolean menuClicked(int index) {
public boolean canSelect(int index) {
// block input during beatmap importing
if (importThread != null)
return false;
@ -406,28 +404,25 @@ public class DownloadsMenu extends BasicGameState {
serverMenu.setBackgroundColor(Colors.BLACK_BG_HOVER);
serverMenu.setBorderColor(Color.black);
serverMenu.setChevronRightColor(Color.white);
components.add(serverMenu);
}
@Override
public void render(GameContainer container, StateBasedGame game, Graphics g)
throws SlickException {
int width = container.getWidth();
int height = container.getHeight();
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
boolean inDropdownMenu = serverMenu.contains(mouseX, mouseY);
public void render(Graphics g) {
super.render(g);
// background
GameImage.SEARCH_BG.getImage().draw();
// title
Fonts.LARGE.drawString(width * 0.024f, height * 0.03f, "Download Beatmaps!", Color.white);
Fonts.LARGE.drawString(displayContainer.width * 0.024f, displayContainer.height * 0.03f, "Download Beatmaps!", Color.white);
// search
g.setColor(Color.white);
g.setLineWidth(2f);
search.render(container, g);
search.render(g);
Fonts.BOLD.drawString(
search.getX() + search.getWidth() * 0.01f, search.getY() + search.getHeight() * 1.3f,
search.x + search.width * 0.01f, search.y + search.height * 1.3f,
searchResultString, Color.white
);
@ -446,7 +441,7 @@ public class DownloadsMenu extends BasicGameState {
if (index >= nodes.length)
break;
nodes[index].drawResult(g, offset + i * DownloadNode.getButtonOffset(),
DownloadNode.resultContains(mouseX, mouseY - offset, i) && !inDropdownMenu,
DownloadNode.resultContains(displayContainer.mouseX, displayContainer.mouseY - offset, i) && !serverMenu.isHovered(),
(index == focusResult), (previewID == nodes[index].getID()));
}
g.clearClip();
@ -457,9 +452,9 @@ public class DownloadsMenu extends BasicGameState {
// pages
if (nodes.length > 0) {
float baseX = width * 0.024f;
float buttonY = height * 0.2f;
float buttonWidth = width * 0.7f;
float baseX = displayContainer.width * 0.024f;
float buttonY = displayContainer.height * 0.2f;
float buttonWidth = displayContainer.width * 0.7f;
Fonts.BOLD.drawString(
baseX + (buttonWidth - Fonts.BOLD.getWidth("Page 1")) / 2f,
buttonY - Fonts.BOLD.getLineHeight() * 1.3f,
@ -473,11 +468,11 @@ public class DownloadsMenu extends BasicGameState {
}
// downloads
float downloadsX = width * 0.75f, downloadsY = search.getY();
float downloadsX = displayContainer.width * 0.75f, downloadsY = search.y;
g.setColor(Colors.BLACK_BG_NORMAL);
g.fillRect(downloadsX, downloadsY,
width * 0.25f, height - downloadsY * 2f);
Fonts.LARGE.drawString(downloadsX + width * 0.015f, downloadsY + height * 0.015f, "Downloads", Color.white);
displayContainer.width * 0.25f, displayContainer.height - downloadsY * 2f);
Fonts.LARGE.drawString(downloadsX + displayContainer.width * 0.015f, downloadsY + displayContainer.height * 0.015f, "Downloads", Color.white);
int downloadsSize = DownloadList.get().size();
if (downloadsSize > 0) {
int maxDownloadsShown = DownloadNode.maxDownloadsShown();
@ -493,7 +488,7 @@ public class DownloadsMenu extends BasicGameState {
if (node == null)
break;
node.drawDownload(g, i * DownloadNode.getInfoHeight() + offset, index,
DownloadNode.downloadContains(mouseX, mouseY - offset, i));
DownloadNode.downloadContains(displayContainer.mouseX, displayContainer.mouseY - offset, i));
}
g.clearClip();
@ -510,13 +505,13 @@ public class DownloadsMenu extends BasicGameState {
rankedButton.draw(Color.magenta);
// dropdown menu
serverMenu.render(container, g);
serverMenu.render(g);
// importing beatmaps
if (importThread != null) {
// darken the screen
g.setColor(Colors.BLACK_ALPHA);
g.fillRect(0, 0, width, height);
g.fillRect(0, 0, displayContainer.width, displayContainer.height);
UI.drawLoadingProgress(g);
}
@ -529,8 +524,10 @@ public class DownloadsMenu extends BasicGameState {
}
@Override
public void update(GameContainer container, StateBasedGame game, int delta)
throws SlickException {
public void preRenderUpdate() {
super.preRenderUpdate();
int delta = displayContainer.renderDelta;
UI.update(delta);
if (importThread == null)
MusicController.loopTrackIfEnded(false);
@ -547,11 +544,12 @@ public class DownloadsMenu extends BasicGameState {
// focus new beatmap
// NOTE: This can't be called in another thread because it makes OpenGL calls.
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).setFocus(importedNode, -1, true, true);
instanceContainer.provide(SongMenu.class).setFocus(importedNode, -1, true, true);
}
importThread = null;
}
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
int mouseX = displayContainer.mouseX;
int mouseY = displayContainer.mouseY;
UI.getBackButton().hoverUpdate(delta, mouseX, mouseY);
prevPage.hoverUpdate(delta, mouseX, mouseY);
nextPage.hoverUpdate(delta, mouseX, mouseY);
@ -572,7 +570,6 @@ public class DownloadsMenu extends BasicGameState {
focusTimer += delta;
// search
search.setFocus(true);
searchTimer += delta;
if (searchTimer >= SEARCH_DELAY && importThread == null) {
searchTimer = 0;
@ -608,24 +605,25 @@ public class DownloadsMenu extends BasicGameState {
}
@Override
public int getID() { return state; }
public boolean mousePressed(int button, int x, int y) {
if (super.mousePressed(button, x, y)) {
return true;
}
@Override
public void mousePressed(int button, int x, int y) {
// check mouse button
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
if (button == Input.MOUSE_MIDDLE_BUTTON) {
return false;
}
// block input during beatmap importing
if (importThread != null)
return;
if (importThread != null) {
return true;
}
// back
if (UI.getBackButton().contains(x, y)) {
SoundController.playSound(SoundEffect.MENUBACK);
((MainMenu) game.getState(Opsu.STATE_MAINMENU)).reset();
game.enterState(Opsu.STATE_MAINMENU, new EasedFadeOutTransition(), new FadeInTransition());
return;
displayContainer.switchState(MainMenu.class);
return true;
}
// search results
@ -694,11 +692,12 @@ public class DownloadsMenu extends BasicGameState {
}
}.start();
}
return;
return true;
}
if (isLoaded)
return;
if (isLoaded) {
return true;
}
SoundController.playSound(SoundEffect.MENUCLICK);
if (index == focusResult) {
@ -725,7 +724,7 @@ public class DownloadsMenu extends BasicGameState {
break;
}
}
return;
return true;
}
// pages
@ -741,7 +740,7 @@ public class DownloadsMenu extends BasicGameState {
searchQuery.interrupt();
resetSearchTimer();
}
return;
return true;
}
if (pageResultTotal < totalResults && nextPage.contains(x, y)) {
if (lastQueryDir == Page.NEXT && searchQuery != null && !searchQuery.isComplete())
@ -753,7 +752,7 @@ public class DownloadsMenu extends BasicGameState {
if (searchQuery != null)
searchQuery.interrupt();
resetSearchTimer();
return;
return true;
}
}
}
@ -763,7 +762,7 @@ public class DownloadsMenu extends BasicGameState {
if (clearButton.contains(x, y)) {
SoundController.playSound(SoundEffect.MENUCLICK);
DownloadList.get().clearInactiveDownloads();
return;
return true;
}
if (importButton.contains(x, y)) {
SoundController.playSound(SoundEffect.MENUCLICK);
@ -771,7 +770,7 @@ public class DownloadsMenu extends BasicGameState {
// import songs in new thread
importThread = new BeatmapImportThread();
importThread.start();
return;
return true;
}
if (resetButton.contains(x, y)) {
SoundController.playSound(SoundEffect.MENUCLICK);
@ -781,7 +780,7 @@ public class DownloadsMenu extends BasicGameState {
if (searchQuery != null)
searchQuery.interrupt();
resetSearchTimer();
return;
return true;
}
if (rankedButton.contains(x, y)) {
SoundController.playSound(SoundEffect.MENUCLICK);
@ -791,7 +790,7 @@ public class DownloadsMenu extends BasicGameState {
if (searchQuery != null)
searchQuery.interrupt();
resetSearchTimer();
return;
return true;
}
// downloads
@ -807,8 +806,9 @@ public class DownloadsMenu extends BasicGameState {
if (DownloadNode.downloadIconContains(x, y - offset, i)) {
SoundController.playSound(SoundEffect.MENUCLICK);
DownloadNode node = DownloadList.get().getNode(index);
if (node == null)
return;
if (node == null) {
return true;
}
Download dl = node.getDownload();
switch (dl.getStatus()) {
case CANCELLED:
@ -822,62 +822,78 @@ public class DownloadsMenu extends BasicGameState {
dl.cancel();
break;
}
return;
return true;
}
}
}
return false;
}
@Override
public void mouseReleased(int button, int x, int y) {
// check mouse button
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
public boolean mouseReleased(int button, int x, int y) {
if (super.mouseReleased(button, x, y)) {
return true;
}
if (button == Input.MOUSE_MIDDLE_BUTTON) {
return false;
}
startDownloadIndexPos.released();
startResultPos.released();
return true;
}
@Override
public void mouseWheelMoved(int newValue) {
public boolean mouseWheelMoved(int newValue) {
// change volume
if (input.isKeyDown(Input.KEY_LALT) || input.isKeyDown(Input.KEY_RALT)) {
if (displayContainer.input.isKeyDown(Input.KEY_LALT) || displayContainer.input.isKeyDown(Input.KEY_RALT)) {
UI.changeVolume((newValue < 0) ? -1 : 1);
return;
return true;
}
// block input during beatmap importing
if (importThread != null)
return;
if (importThread != null) {
return true;
}
int shift = (newValue < 0) ? 1 : -1;
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
scrollLists(mouseX, mouseY, shift);
scrollLists(displayContainer.mouseX, displayContainer.mouseY, shift);
return true;
}
@Override
public void mouseDragged(int oldx, int oldy, int newx, int newy) {
public boolean mouseDragged(int oldx, int oldy, int newx, int newy) {
// block input during beatmap importing
if (importThread != null)
return;
// check mouse button
if (!input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON) &&
!input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON))
return;
if (importThread != null) {
return true;
}
int diff = newy - oldy;
if (diff == 0)
return;
if (diff == 0) {
return false;
}
startDownloadIndexPos.dragged(-diff);
startResultPos.dragged(-diff);
return true;
}
@Override
public void keyPressed(int key, char c) {
public boolean keyReleased(int key, char c) {
return super.keyReleased(key, c);
}
@Override
public boolean keyPressed(int key, char c) {
if (super.keyPressed(key, c)) {
return true;
}
// block input during beatmap importing
if (importThread != null && !(key == Input.KEY_ESCAPE || key == Input.KEY_F12))
return;
if (importThread != null && key != Input.KEY_ESCAPE) {
return true;
}
switch (key) {
case Input.KEY_ESCAPE:
@ -892,16 +908,15 @@ public class DownloadsMenu extends BasicGameState {
} else {
// return to main menu
SoundController.playSound(SoundEffect.MENUBACK);
((MainMenu) game.getState(Opsu.STATE_MAINMENU)).reset();
game.enterState(Opsu.STATE_MAINMENU, new EasedFadeOutTransition(), new FadeInTransition());
displayContainer.switchState(MainMenu.class);
}
break;
return true;
case Input.KEY_ENTER:
if (!search.getText().isEmpty()) {
pageDir = Page.RESET;
resetSearchTimer();
}
break;
return true;
case Input.KEY_F5:
SoundController.playSound(SoundEffect.MENUCLICK);
lastQuery = null;
@ -909,29 +924,21 @@ public class DownloadsMenu extends BasicGameState {
if (searchQuery != null)
searchQuery.interrupt();
resetSearchTimer();
break;
case Input.KEY_F7:
Options.setNextFPS(container);
break;
case Input.KEY_F10:
Options.toggleMouseDisabled();
break;
case Input.KEY_F12:
Utils.takeScreenShot();
break;
default:
// wait for user to finish typing
if (Character.isLetterOrDigit(c) || key == Input.KEY_BACK) {
searchTimer = 0;
pageDir = Page.RESET;
}
break;
return true;
}
// wait for user to finish typing
if (Character.isLetterOrDigit(c) || key == Input.KEY_BACK) {
search.keyPressed(key, c);
searchTimer = 0;
pageDir = Page.RESET;
}
return true;
}
@Override
public void enter(GameContainer container, StateBasedGame game)
throws SlickException {
public void enter() {
super.enter();
UI.enter();
prevPage.resetHover();
nextPage.resetHover();
@ -939,7 +946,6 @@ public class DownloadsMenu extends BasicGameState {
importButton.resetHover();
resetButton.resetHover();
rankedButton.resetHover();
serverMenu.activate();
serverMenu.reset();
focusResult = -1;
startResultPos.setPosition(0);
@ -953,10 +959,10 @@ public class DownloadsMenu extends BasicGameState {
}
@Override
public void leave(GameContainer container, StateBasedGame game)
throws SlickException {
search.setFocus(false);
serverMenu.deactivate();
public void leave() {
super.leave();
focusComponent(search);
SoundController.stopTrack();
MusicController.resume();
}

File diff suppressed because it is too large Load Diff

View File

@ -19,9 +19,7 @@
package itdelatrisu.opsu.states;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.audio.MusicController;
import itdelatrisu.opsu.audio.SoundController;
import itdelatrisu.opsu.audio.SoundEffect;
@ -31,14 +29,11 @@ import itdelatrisu.opsu.ui.animations.AnimationEquation;
import org.lwjgl.input.Keyboard;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.state.transition.FadeInTransition;
import org.newdawn.slick.state.transition.EasedFadeOutTransition;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.inject.InstanceContainer;
import yugecin.opsudance.core.state.BaseOpsuState;
/**
* "Game Pause/Fail" state.
@ -46,33 +41,22 @@ import org.newdawn.slick.state.transition.EasedFadeOutTransition;
* Players are able to continue the game (if applicable), retry the beatmap,
* or return to the song menu from this state.
*/
public class GamePauseMenu extends BasicGameState {
/** "Continue", "Retry", and "Back" buttons. */
public class GamePauseMenu extends BaseOpsuState {
private final InstanceContainer instanceContainer;
private MenuButton continueButton, retryButton, backButton;
// game-related variables
private GameContainer container;
private StateBasedGame game;
private Input input;
private final int state;
private Game gameState;
private final Game gameState;
public GamePauseMenu(int state) {
this.state = state;
public GamePauseMenu(DisplayContainer displayContainer, InstanceContainer instanceContainer, Game gameState) {
super(displayContainer);
this.instanceContainer = instanceContainer;
this.gameState = gameState;
}
@Override
public void init(GameContainer container, StateBasedGame game)
throws SlickException {
this.container = container;
this.game = game;
this.input = container.getInput();
this.gameState = (Game) game.getState(Opsu.STATE_GAME);
}
@Override
public void render(GameContainer container, StateBasedGame game, Graphics g)
throws SlickException {
public void render(Graphics g) {
// get background image
GameImage bg = (gameState.getRestart() == Game.Restart.LOSE) ?
GameImage.FAIL_BACKGROUND : GameImage.PAUSE_OVERLAY;
@ -97,102 +81,107 @@ public class GamePauseMenu extends BasicGameState {
}
@Override
public void update(GameContainer container, StateBasedGame game, int delta)
throws SlickException {
public void preRenderUpdate() {
int delta = displayContainer.renderDelta;
UI.update(delta);
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
continueButton.hoverUpdate(delta, mouseX, mouseY);
retryButton.hoverUpdate(delta, mouseX, mouseY);
backButton.hoverUpdate(delta, mouseX, mouseY);
continueButton.hoverUpdate(delta, displayContainer.mouseX, displayContainer.mouseY);
retryButton.hoverUpdate(delta, displayContainer.mouseX, displayContainer.mouseY);
backButton.hoverUpdate(delta, displayContainer.mouseX, displayContainer.mouseY);
}
@Override
public int getID() { return state; }
@Override
public void keyPressed(int key, char c) {
// game keys
if (!Keyboard.isRepeatEvent()) {
if (key == Options.getGameKeyLeft())
mousePressed(Input.MOUSE_LEFT_BUTTON, input.getMouseX(), input.getMouseY());
else if (key == Options.getGameKeyRight())
mousePressed(Input.MOUSE_RIGHT_BUTTON, input.getMouseX(), input.getMouseY());
public boolean keyPressed(int key, char c) {
if (super.keyPressed(key, c)) {
return true;
}
switch (key) {
case Input.KEY_ESCAPE:
// game keys
if (!Keyboard.isRepeatEvent()) {
if (key == Options.getGameKeyLeft()) {
mousePressed(Input.MOUSE_LEFT_BUTTON, displayContainer.mouseX, displayContainer.mouseY);
} else if (key == Options.getGameKeyRight()) {
mousePressed(Input.MOUSE_RIGHT_BUTTON, displayContainer.mouseX, displayContainer.mouseY);
}
}
if (key == Input.KEY_ESCAPE) {
// 'esc' will normally unpause, but will return to song menu if health is zero
if (gameState.getRestart() == Game.Restart.LOSE) {
SoundController.playSound(SoundEffect.MENUBACK);
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).resetGameDataOnLoad();
instanceContainer.provide(SongMenu.class).resetGameDataOnLoad();
MusicController.playAt(MusicController.getBeatmap().previewTime, true);
if (UI.getCursor().isBeatmapSkinned())
UI.getCursor().reset();
game.enterState(Opsu.STATE_SONGMENU, new EasedFadeOutTransition(), new FadeInTransition());
displayContainer.switchState(SongMenu.class);
} else {
SoundController.playSound(SoundEffect.MENUBACK);
gameState.setRestart(Game.Restart.FALSE);
game.enterState(Opsu.STATE_GAME);
displayContainer.switchState(Game.class);
}
break;
case Input.KEY_R:
// restart
if (input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) {
gameState.setRestart(Game.Restart.MANUAL);
game.enterState(Opsu.STATE_GAME);
}
break;
case Input.KEY_F7:
Options.setNextFPS(container);
break;
case Input.KEY_F10:
Options.toggleMouseDisabled();
break;
case Input.KEY_F12:
Utils.takeScreenShot();
break;
return true;
}
if (key == Input.KEY_R && (displayContainer.input.isKeyDown(Input.KEY_RCONTROL) || displayContainer.input.isKeyDown(Input.KEY_LCONTROL))) {
gameState.setRestart(Game.Restart.MANUAL);
displayContainer.switchState(Game.class);
return true;
}
return false;
}
@Override
public void mousePressed(int button, int x, int y) {
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
public boolean mousePressed(int button, int x, int y) {
if (super.mousePressed(button, x, y)) {
return true;
}
if (button == Input.MOUSE_MIDDLE_BUTTON) {
return true;
}
boolean loseState = (gameState.getRestart() == Game.Restart.LOSE);
if (continueButton.contains(x, y) && !loseState) {
SoundController.playSound(SoundEffect.MENUBACK);
gameState.setRestart(Game.Restart.FALSE);
game.enterState(Opsu.STATE_GAME);
displayContainer.switchState(Game.class);
} else if (retryButton.contains(x, y)) {
SoundController.playSound(SoundEffect.MENUHIT);
gameState.setRestart(Game.Restart.MANUAL);
game.enterState(Opsu.STATE_GAME);
displayContainer.switchState(Game.class);
} else if (backButton.contains(x, y)) {
SoundController.playSound(SoundEffect.MENUBACK);
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).resetGameDataOnLoad();
instanceContainer.provide(SongMenu.class).resetGameDataOnLoad();
if (loseState)
MusicController.playAt(MusicController.getBeatmap().previewTime, true);
else
MusicController.resume();
if (UI.getCursor().isBeatmapSkinned())
UI.getCursor().reset();
if (displayContainer.cursor.isBeatmapSkinned()) {
displayContainer.resetCursor();
}
MusicController.setPitch(1.0f);
game.enterState(Opsu.STATE_SONGMENU, new EasedFadeOutTransition(), new FadeInTransition());
displayContainer.switchState(SongMenu.class);
}
return true;
}
@Override
public void mouseWheelMoved(int newValue) {
if (Options.isMouseWheelDisabled())
return;
public boolean mouseWheelMoved(int newValue) {
if (super.mouseWheelMoved(newValue)) {
return true;
}
if (Options.isMouseWheelDisabled()) {
return true;
}
UI.changeVolume((newValue < 0) ? -1 : 1);
return true;
}
@Override
public void enter(GameContainer container, StateBasedGame game)
throws SlickException {
public void enter() {
super.enter();
UI.enter();
MusicController.pause();
continueButton.resetHover();
@ -200,17 +189,23 @@ public class GamePauseMenu extends BasicGameState {
backButton.resetHover();
}
@Override
public boolean onCloseRequest() {
SongMenu songmenu = instanceContainer.provide(SongMenu.class);
songmenu.resetTrackOnLoad();
songmenu.resetGameDataOnLoad();
displayContainer.switchState(SongMenu.class);
return false;
}
/**
* Loads all game pause/fail menu images.
*/
public void loadImages() {
int width = container.getWidth();
int height = container.getHeight();
// initialize buttons
continueButton = new MenuButton(GameImage.PAUSE_CONTINUE.getImage(), width / 2f, height * 0.25f);
retryButton = new MenuButton(GameImage.PAUSE_RETRY.getImage(), width / 2f, height * 0.5f);
backButton = new MenuButton(GameImage.PAUSE_BACK.getImage(), width / 2f, height * 0.75f);
continueButton = new MenuButton(GameImage.PAUSE_CONTINUE.getImage(), displayContainer.width / 2f, displayContainer.height * 0.25f);
retryButton = new MenuButton(GameImage.PAUSE_RETRY.getImage(), displayContainer.width / 2f, displayContainer.height * 0.5f);
backButton = new MenuButton(GameImage.PAUSE_BACK.getImage(), displayContainer.width / 2f, displayContainer.height * 0.75f);
final int buttonAnimationDuration = 300;
continueButton.setHoverAnimationDuration(buttonAnimationDuration);
retryButton.setHoverAnimationDuration(buttonAnimationDuration);
@ -223,4 +218,5 @@ public class GamePauseMenu extends BasicGameState {
retryButton.setHoverExpand();
backButton.setHoverExpand();
}
}

View File

@ -21,9 +21,6 @@ package itdelatrisu.opsu.states;
import itdelatrisu.opsu.GameData;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.GameMod;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.audio.MusicController;
import itdelatrisu.opsu.audio.SoundController;
import itdelatrisu.opsu.audio.SoundEffect;
@ -35,17 +32,13 @@ import itdelatrisu.opsu.ui.UI;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.lwjgl.opengl.Display;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.state.transition.FadeInTransition;
import org.newdawn.slick.state.transition.EasedFadeOutTransition;
import org.newdawn.slick.util.Log;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.inject.InstanceContainer;
import yugecin.opsudance.core.state.BaseOpsuState;
/**
* "Game Ranking" (score card) state.
@ -54,7 +47,10 @@ import org.newdawn.slick.util.Log;
* or watch a replay of the game from this state.
* </ul>
*/
public class GameRanking extends BasicGameState {
public class GameRanking extends BaseOpsuState {
private final InstanceContainer instanceContainer;
/** Associated GameData object. */
private GameData data;
@ -64,48 +60,35 @@ public class GameRanking extends BasicGameState {
/** Button coordinates. */
private float retryY, replayY;
// game-related variables
private GameContainer container;
private StateBasedGame game;
private final int state;
private Input input;
public GameRanking(DisplayContainer displayContainer, InstanceContainer instanceContainer) {
super(displayContainer);
this.instanceContainer = instanceContainer;
public GameRanking(int state) {
this.state = state;
}
@Override
public void init(GameContainer container, StateBasedGame game)
throws SlickException {
this.container = container;
this.game = game;
this.input = container.getInput();
int width = container.getWidth();
int height = container.getHeight();
public void revalidate() {
super.revalidate();
// buttons
Image retry = GameImage.PAUSE_RETRY.getImage();
Image replay = GameImage.PAUSE_REPLAY.getImage();
replayY = (height * 0.985f) - replay.getHeight() / 2f;
replayY = (displayContainer.height * 0.985f) - replay.getHeight() / 2f;
retryY = replayY - (replay.getHeight() / 2f) - (retry.getHeight() / 1.975f);
retryButton = new MenuButton(retry, width - (retry.getWidth() / 2f), retryY);
replayButton = new MenuButton(replay, width - (replay.getWidth() / 2f), replayY);
retryButton = new MenuButton(retry, displayContainer.width - (retry.getWidth() / 2f), retryY);
replayButton = new MenuButton(replay, displayContainer.width - (replay.getWidth() / 2f), replayY);
retryButton.setHoverFade();
replayButton.setHoverFade();
}
@Override
public void render(GameContainer container, StateBasedGame game, Graphics g)
throws SlickException {
int width = container.getWidth();
int height = container.getHeight();
public void render(Graphics g) {
Beatmap beatmap = MusicController.getBeatmap();
// background
if (!beatmap.drawBackground(width, height, 0.7f, true))
GameImage.PLAYFIELD.getImage().draw(0,0);
if (!beatmap.drawBackground(displayContainer.width, displayContainer.height, 0.7f, true)) {
GameImage.PLAYFIELD.getImage().draw(0, 0);
}
// ranking screen elements
data.drawRankingElements(g, beatmap);
@ -117,62 +100,62 @@ public class GameRanking extends BasicGameState {
UI.getBackButton().draw();
UI.draw(g);
super.render(g);
}
@Override
public void update(GameContainer container, StateBasedGame game, int delta)
throws SlickException {
public void preRenderUpdate() {
int delta = displayContainer.renderDelta;
UI.update(delta);
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
replayButton.hoverUpdate(delta, mouseX, mouseY);
if (data.isGameplay())
retryButton.hoverUpdate(delta, mouseX, mouseY);
else
replayButton.hoverUpdate(delta, displayContainer.mouseX, displayContainer.mouseY);
if (data.isGameplay()) {
retryButton.hoverUpdate(delta, displayContainer.mouseX, displayContainer.mouseY);
} else {
MusicController.loopTrackIfEnded(true);
UI.getBackButton().hoverUpdate(delta, mouseX, mouseY);
}
@Override
public int getID() { return state; }
@Override
public void mouseWheelMoved(int newValue) {
if (input.isKeyDown(Input.KEY_LALT) || input.isKeyDown(Input.KEY_RALT))
UI.changeVolume((newValue < 0) ? -1 : 1);
}
@Override
public void keyPressed(int key, char c) {
switch (key) {
case Input.KEY_ESCAPE:
returnToSongMenu();
break;
case Input.KEY_F7:
Options.setNextFPS(container);
break;
case Input.KEY_F10:
Options.toggleMouseDisabled();
break;
case Input.KEY_F12:
Utils.takeScreenShot();
break;
}
UI.getBackButton().hoverUpdate(delta, displayContainer.mouseX, displayContainer.mouseY);
}
@Override
public void mousePressed(int button, int x, int y) {
public boolean mouseWheelMoved(int newValue) {
if (displayContainer.input.isKeyDown(Input.KEY_LALT) || displayContainer.input.isKeyDown(Input.KEY_RALT)) {
UI.changeVolume((newValue < 0) ? -1 : 1);
}
return true;
}
@Override
public boolean keyPressed(int key, char c) {
if (super.keyPressed(key, c)) {
return true;
}
if (key == Input.KEY_ESCAPE) {
returnToSongMenu();
}
return true;
}
@Override
public boolean mousePressed(int button, int x, int y) {
if (super.mousePressed(button, x, y)) {
return true;
}
// check mouse button
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
if (button == Input.MOUSE_MIDDLE_BUTTON) {
return false;
}
// back to menu
if (UI.getBackButton().contains(x, y)) {
returnToSongMenu();
return;
return true;
}
// replay
Game gameState = (Game) game.getState(Opsu.STATE_GAME);
Game gameState = instanceContainer.provide(Game.class);
boolean returnToGame = false;
boolean replayButtonPressed = replayButton.contains(x, y);
if (replayButtonPressed && !(data.isGameplay() && GameMod.AUTO.isActive())) {
@ -206,16 +189,16 @@ public class GameRanking extends BasicGameState {
Beatmap beatmap = MusicController.getBeatmap();
gameState.loadBeatmap(beatmap);
SoundController.playSound(SoundEffect.MENUHIT);
game.enterState(Opsu.STATE_GAME, new EasedFadeOutTransition(), new FadeInTransition());
return;
// TODO d displayContainer.switchState(Game.class);
}
return true;
}
@Override
public void enter(GameContainer container, StateBasedGame game)
throws SlickException {
public void enter() {
super.enter();
UI.enter();
Display.setTitle(game.getTitle());
if (!data.isGameplay()) {
if (!MusicController.isTrackDimmed())
MusicController.toggleTrackDimmed(0.5f);
@ -229,11 +212,24 @@ public class GameRanking extends BasicGameState {
}
@Override
public void leave(GameContainer container, StateBasedGame game)
throws SlickException {
public void leave() {
super.leave();
this.data = null;
if (MusicController.isTrackDimmed())
if (MusicController.isTrackDimmed()) {
MusicController.toggleTrackDimmed(1f);
}
}
@Override
public boolean onCloseRequest() {
SongMenu songmenu = instanceContainer.provide(SongMenu.class);
if (data != null && data.isGameplay()) {
songmenu.resetTrackOnLoad();
}
songmenu.resetGameDataOnLoad();
displayContainer.switchState(SongMenu.class);
return false;
}
/**
@ -242,13 +238,15 @@ public class GameRanking extends BasicGameState {
private void returnToSongMenu() {
SoundController.muteSoundComponent();
SoundController.playSound(SoundEffect.MENUBACK);
SongMenu songMenu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
if (data.isGameplay())
SongMenu songMenu = instanceContainer.provide(SongMenu.class);
if (data.isGameplay()) {
songMenu.resetTrackOnLoad();
}
songMenu.resetGameDataOnLoad();
if (UI.getCursor().isBeatmapSkinned())
UI.getCursor().reset();
game.enterState(Opsu.STATE_SONGMENU, new EasedFadeOutTransition(), new FadeInTransition());
if (displayContainer.cursor.isBeatmapSkinned()) {
displayContainer.resetCursor();
}
displayContainer.switchState(SongMenu.class);
}
/**

View File

@ -18,9 +18,7 @@
package itdelatrisu.opsu.states;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.audio.MusicController;
@ -29,7 +27,6 @@ import itdelatrisu.opsu.audio.SoundEffect;
import itdelatrisu.opsu.beatmap.Beatmap;
import itdelatrisu.opsu.beatmap.BeatmapSetList;
import itdelatrisu.opsu.beatmap.BeatmapSetNode;
import itdelatrisu.opsu.beatmap.TimingPoint;
import itdelatrisu.opsu.downloads.Updater;
import itdelatrisu.opsu.states.ButtonMenu.MenuState;
import itdelatrisu.opsu.ui.*;
@ -43,23 +40,28 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Stack;
import org.lwjgl.opengl.Display;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.state.transition.EasedFadeOutTransition;
import org.newdawn.slick.state.transition.FadeInTransition;
import org.newdawn.slick.util.Log;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.core.inject.InstanceContainer;
import yugecin.opsudance.core.state.BaseOpsuState;
import yugecin.opsudance.core.state.OpsuState;
import yugecin.opsudance.events.BubbleNotificationEvent;
/**
* "Main Menu" state.
* <p>
* Players are able to enter the song menu or downloads menu from this state.
*/
public class MainMenu extends BasicGameState {
public class MainMenu extends BaseOpsuState {
private final InstanceContainer instanceContainer;
/** Idle time, in milliseconds, before returning the logo to its original position. */
private static final short LOGO_IDLE_DELAY = 10000;
@ -123,40 +125,27 @@ public class MainMenu extends BasicGameState {
/** The star fountain. */
private StarFountain starFountain;
// game-related variables
private GameContainer container;
private StateBasedGame game;
private Input input;
private final int state;
public MainMenu(int state) {
this.state = state;
public MainMenu(DisplayContainer displayContainer, InstanceContainer instanceContainer) {
super(displayContainer);
this.instanceContainer = instanceContainer;
}
@Override
public void init(GameContainer container, StateBasedGame game)
throws SlickException {
this.container = container;
this.game = game;
this.input = container.getInput();
protected void revalidate() {
programStartTime = System.currentTimeMillis();
previous = new Stack<Integer>();
int width = container.getWidth();
int height = container.getHeight();
previous = new Stack<>();
// initialize menu buttons
Image logoImg = GameImage.MENU_LOGO.getImage();
Image playImg = GameImage.MENU_PLAY.getImage();
Image exitImg = GameImage.MENU_EXIT.getImage();
float exitOffset = (playImg.getWidth() - exitImg.getWidth()) / 3f;
logo = new MenuButton(logoImg, width / 2f, height / 2f);
logo = new MenuButton(logoImg, displayContainer.width / 2f, displayContainer.height / 2f);
playButton = new MenuButton(playImg,
width * 0.75f, (height / 2) - (logoImg.getHeight() / 5f)
displayContainer.width * 0.75f, (displayContainer.height / 2) - (logoImg.getHeight() / 5f)
);
exitButton = new MenuButton(exitImg,
width * 0.75f - exitOffset, (height / 2) + (exitImg.getHeight() / 2f)
displayContainer.width * 0.75f - exitOffset, (displayContainer.height / 2) + (exitImg.getHeight() / 2f)
);
final int logoAnimationDuration = 350;
logo.setHoverAnimationDuration(logoAnimationDuration);
@ -174,30 +163,30 @@ public class MainMenu extends BasicGameState {
// initialize music buttons
int musicWidth = GameImage.MUSIC_PLAY.getImage().getWidth();
int musicHeight = GameImage.MUSIC_PLAY.getImage().getHeight();
musicPlay = new MenuButton(GameImage.MUSIC_PLAY.getImage(), width - (2 * musicWidth), musicHeight / 1.5f);
musicPause = new MenuButton(GameImage.MUSIC_PAUSE.getImage(), width - (2 * musicWidth), musicHeight / 1.5f);
musicNext = new MenuButton(GameImage.MUSIC_NEXT.getImage(), width - musicWidth, musicHeight / 1.5f);
musicPrevious = new MenuButton(GameImage.MUSIC_PREVIOUS.getImage(), width - (3 * musicWidth), musicHeight / 1.5f);
musicPlay = new MenuButton(GameImage.MUSIC_PLAY.getImage(), displayContainer.width - (2 * musicWidth), musicHeight / 1.5f);
musicPause = new MenuButton(GameImage.MUSIC_PAUSE.getImage(), displayContainer.width - (2 * musicWidth), musicHeight / 1.5f);
musicNext = new MenuButton(GameImage.MUSIC_NEXT.getImage(), displayContainer.width - musicWidth, musicHeight / 1.5f);
musicPrevious = new MenuButton(GameImage.MUSIC_PREVIOUS.getImage(), displayContainer.width - (3 * musicWidth), musicHeight / 1.5f);
musicPlay.setHoverExpand(1.5f);
musicPause.setHoverExpand(1.5f);
musicNext.setHoverExpand(1.5f);
musicPrevious.setHoverExpand(1.5f);
// initialize music position bar location
musicBarX = width - musicWidth * 3.5f;
musicBarX = displayContainer.width - musicWidth * 3.5f;
musicBarY = musicHeight * 1.25f;
musicBarWidth = musicWidth * 3f;
musicBarHeight = musicHeight * 0.11f;
// initialize downloads button
Image dlImg = GameImage.DOWNLOADS.getImage();
downloadsButton = new MenuButton(dlImg, width - dlImg.getWidth() / 2f, height / 2f);
downloadsButton = new MenuButton(dlImg, displayContainer.width - dlImg.getWidth() / 2f, displayContainer.height / 2f);
downloadsButton.setHoverAnimationDuration(350);
downloadsButton.setHoverAnimationEquation(AnimationEquation.IN_OUT_BACK);
downloadsButton.setHoverExpand(1.03f, Expand.LEFT);
// initialize repository button
float startX = width * 0.997f, startY = height * 0.997f;
float startX = displayContainer.width * 0.997f, startY = displayContainer.height * 0.997f;
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { // only if a webpage can be opened
Image repoImg;
repoImg = GameImage.REPOSITORY.getImage();
@ -217,7 +206,7 @@ public class MainMenu extends BasicGameState {
}
// initialize update buttons
float updateX = width / 2f, updateY = height * 17 / 18f;
float updateX = displayContainer.width / 2f, updateY = displayContainer.height * 17 / 18f;
Image downloadImg = GameImage.DOWNLOAD.getImage();
updateButton = new MenuButton(downloadImg, updateX, updateY);
updateButton.setHoverAnimationDuration(400);
@ -230,22 +219,19 @@ public class MainMenu extends BasicGameState {
restartButton.setHoverRotate(360);
// initialize star fountain
starFountain = new StarFountain(width, height);
starFountain = new StarFountain(displayContainer.width, displayContainer.height);
// logo animations
float centerOffsetX = width / 6.5f;
float centerOffsetX = displayContainer.width / 6.5f;
logoOpen = new AnimatedValue(100, 0, centerOffsetX, AnimationEquation.OUT_QUAD);
logoClose = new AnimatedValue(2200, centerOffsetX, 0, AnimationEquation.OUT_QUAD);
logoButtonAlpha = new AnimatedValue(200, 0f, 1f, AnimationEquation.LINEAR);
reset();
}
@Override
public void render(GameContainer container, StateBasedGame game, Graphics g)
throws SlickException {
int width = container.getWidth();
int height = container.getHeight();
public void render(Graphics g) {
int width = displayContainer.width;
int height = displayContainer.height;
// draw background
Beatmap beatmap = MusicController.getBeatmap();
@ -305,7 +291,8 @@ public class MainMenu extends BasicGameState {
musicPrevious.draw();
// draw music position bar
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
int mouseX = displayContainer.mouseX;
int mouseY = displayContainer.mouseY;
g.setColor((musicPositionBarContains(mouseX, mouseY)) ? Colors.BLACK_BG_HOVER : Colors.BLACK_BG_NORMAL);
g.fillRoundRect(musicBarX, musicBarY, musicBarWidth, musicBarHeight, 4);
g.setColor(Color.white);
@ -366,12 +353,14 @@ public class MainMenu extends BasicGameState {
}
@Override
public void update(GameContainer container, StateBasedGame game, int delta)
throws SlickException {
public void preRenderUpdate() {
int delta = displayContainer.renderDelta;
UI.update(delta);
if (MusicController.trackEnded())
nextTrack(false); // end of track: go to next track
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
int mouseX = displayContainer.mouseX;
int mouseY = displayContainer.mouseY;
logo.hoverUpdate(delta, mouseX, mouseY, 0.25f);
playButton.hoverUpdate(delta, mouseX, mouseY, 0.25f);
exitButton.hoverUpdate(delta, mouseX, mouseY, 0.25f);
@ -396,7 +385,7 @@ public class MainMenu extends BasicGameState {
// window focus change: increase/decrease theme song volume
if (MusicController.isThemePlaying() &&
MusicController.isTrackDimmed() == container.hasFocus())
MusicController.isTrackDimmed() == Display.isActive())
MusicController.toggleTrackDimmed(0.33f);
// fade in background
@ -413,7 +402,7 @@ public class MainMenu extends BasicGameState {
}
// buttons
int centerX = container.getWidth() / 2;
int centerX = displayContainer.width / 2;
float currentLogoButtonAlpha;
switch (logoState) {
case DEFAULT:
@ -468,11 +457,16 @@ public class MainMenu extends BasicGameState {
}
@Override
public int getID() { return state; }
public void enter() {
super.enter();
logo.setX(displayContainer.width / 2);
logoOpen.setTime(0);
logoClose.setTime(0);
logoButtonAlpha.setTime(0);
logoTimer = 0;
logoState = LogoState.DEFAULT;
@Override
public void enter(GameContainer container, StateBasedGame game)
throws SlickException {
UI.enter();
if (!enterNotification) {
if (Updater.get().getStatus() == Updater.Status.UPDATE_AVAILABLE) {
@ -489,7 +483,8 @@ public class MainMenu extends BasicGameState {
starFountain.clear();
// reset button hover states if mouse is not currently hovering over the button
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
int mouseX = displayContainer.mouseX;
int mouseY = displayContainer.mouseY;
if (!logo.contains(mouseX, mouseY, 0.25f))
logo.resetHover();
if (!playButton.contains(mouseX, mouseY, 0.25f))
@ -515,17 +510,17 @@ public class MainMenu extends BasicGameState {
}
@Override
public void leave(GameContainer container, StateBasedGame game)
throws SlickException {
public void leave() {
super.leave();
if (MusicController.isTrackDimmed())
MusicController.toggleTrackDimmed(1f);
}
@Override
public void mousePressed(int button, int x, int y) {
public boolean mousePressed(int button, int x, int y) {
// check mouse button
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
return false;
// music position bar
if (MusicController.isPlaying()) {
@ -533,7 +528,7 @@ public class MainMenu extends BasicGameState {
lastMeasureProgress = 0f;
float pos = (x - musicBarX) / musicBarWidth;
MusicController.setPosition((int) (pos * MusicController.getDuration()));
return;
return true;
}
}
@ -546,29 +541,28 @@ public class MainMenu extends BasicGameState {
MusicController.resume();
UI.sendBarNotification("Play");
}
return;
return true;
} else if (musicNext.contains(x, y)) {
nextTrack(true);
UI.sendBarNotification(">> Next");
return;
return true;
} else if (musicPrevious.contains(x, y)) {
lastMeasureProgress = 0f;
if (!previous.isEmpty()) {
SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
menu.setFocus(BeatmapSetList.get().getBaseNode(previous.pop()), -1, true, false);
instanceContainer.provide(SongMenu.class).setFocus(BeatmapSetList.get().getBaseNode(previous.pop()), -1, true, false);
if (Options.isDynamicBackgroundEnabled())
bgAlpha.setTime(0);
} else
MusicController.setPosition(0);
UI.sendBarNotification("<< Previous");
return;
return true;
}
// downloads button actions
if (downloadsButton.contains(x, y)) {
SoundController.playSound(SoundEffect.MENUHIT);
game.enterState(Opsu.STATE_DOWNLOADSMENU, new EasedFadeOutTransition(), new FadeInTransition());
return;
displayContainer.switchState(DownloadsMenu.class);
return true;
}
// repository button actions
@ -578,9 +572,10 @@ public class MainMenu extends BasicGameState {
} catch (UnsupportedOperationException e) {
UI.sendBarNotification("The repository web page could not be opened.");
} catch (IOException e) {
ErrorHandler.error("Could not browse to repository URI.", e, false);
Log.error("could not browse to repo", e);
displayContainer.eventBus.post(new BubbleNotificationEvent("Could not browse to repo", BubbleNotificationEvent.COLOR_ORANGE));
}
return;
return true;
}
if (danceRepoButton != null && danceRepoButton.contains(x, y)) {
@ -589,9 +584,10 @@ public class MainMenu extends BasicGameState {
} catch (UnsupportedOperationException e) {
UI.sendBarNotification("The repository web page could not be opened.");
} catch (IOException e) {
ErrorHandler.error("Could not browse to repository URI.", e, false);
Log.error("could not browse to repo", e);
displayContainer.eventBus.post(new BubbleNotificationEvent("Could not browse to repo", BubbleNotificationEvent.COLOR_ORANGE));
}
return;
return true;
}
// update button actions
@ -604,13 +600,12 @@ public class MainMenu extends BasicGameState {
updateButton.setHoverAnimationDuration(800);
updateButton.setHoverAnimationEquation(AnimationEquation.IN_OUT_QUAD);
updateButton.setHoverFade(0.6f);
return;
return true;
} else if (restartButton.contains(x, y) && status == Updater.Status.UPDATE_DOWNLOADED) {
SoundController.playSound(SoundEffect.MENUHIT);
Updater.get().prepareUpdate();
container.setForceExit(false);
container.exit();
return;
displayContainer.exitRequested = true;
return true;
}
}
@ -623,7 +618,7 @@ public class MainMenu extends BasicGameState {
playButton.getImage().setAlpha(0f);
exitButton.getImage().setAlpha(0f);
SoundController.playSound(SoundEffect.MENUHIT);
return;
return true;
}
}
@ -632,21 +627,27 @@ public class MainMenu extends BasicGameState {
if (logo.contains(x, y, 0.25f) || playButton.contains(x, y, 0.25f)) {
SoundController.playSound(SoundEffect.MENUHIT);
enterSongMenu();
return;
return true;
} else if (exitButton.contains(x, y, 0.25f)) {
container.exit();
return;
displayContainer.exitRequested = true;
return true;
}
}
return false;
}
@Override
public void mouseWheelMoved(int newValue) {
public boolean mouseWheelMoved(int newValue) {
UI.changeVolume((newValue < 0) ? -1 : 1);
return true;
}
@Override
public void keyPressed(int key, char c) {
public boolean keyPressed(int key, char c) {
if (super.keyPressed(key, c)) {
return true;
}
switch (key) {
case Input.KEY_ESCAPE:
case Input.KEY_Q:
@ -656,9 +657,9 @@ public class MainMenu extends BasicGameState {
logoTimer = 0;
break;
}
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(MenuState.EXIT);
game.enterState(Opsu.STATE_BUTTONMENU);
break;
instanceContainer.provide(ButtonMenu.class).setMenuState(MenuState.EXIT);
displayContainer.switchState(ButtonMenu.class);
return true;
case Input.KEY_P:
SoundController.playSound(SoundEffect.MENUHIT);
if (logoState == LogoState.DEFAULT || logoState == LogoState.CLOSING) {
@ -669,30 +670,22 @@ public class MainMenu extends BasicGameState {
exitButton.getImage().setAlpha(0f);
} else
enterSongMenu();
break;
return true;
case Input.KEY_D:
SoundController.playSound(SoundEffect.MENUHIT);
game.enterState(Opsu.STATE_DOWNLOADSMENU, new EasedFadeOutTransition(), new FadeInTransition());
break;
displayContainer.switchState(DownloadsMenu.class);
return true;
case Input.KEY_R:
nextTrack(true);
break;
return true;
case Input.KEY_UP:
UI.changeVolume(1);
break;
return true;
case Input.KEY_DOWN:
UI.changeVolume(-1);
break;
case Input.KEY_F7:
Options.setNextFPS(container);
break;
case Input.KEY_F10:
Options.toggleMouseDisabled();
break;
case Input.KEY_F12:
Utils.takeScreenShot();
break;
return true;
}
return false;
}
/**
@ -705,34 +698,6 @@ public class MainMenu extends BasicGameState {
(cy > musicBarY && cy < musicBarY + musicBarHeight));
}
/**
* Resets the button states.
*/
public void reset() {
// reset logo
logo.setX(container.getWidth() / 2);
logoOpen.setTime(0);
logoClose.setTime(0);
logoButtonAlpha.setTime(0);
logoTimer = 0;
logoState = LogoState.DEFAULT;
logo.resetHover();
playButton.resetHover();
exitButton.resetHover();
musicPlay.resetHover();
musicPause.resetHover();
musicNext.resetHover();
musicPrevious.resetHover();
if (repoButton != null)
repoButton.resetHover();
if (danceRepoButton != null)
danceRepoButton.resetHover();
updateButton.resetHover();
restartButton.resetHover();
downloadsButton.resetHover();
}
/**
* Plays the next track, and adds the previous one to the stack.
* @param user {@code true} if this was user-initiated, false otherwise (track end)
@ -746,8 +711,7 @@ public class MainMenu extends BasicGameState {
MusicController.playAt(0, false);
return;
}
SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
BeatmapSetNode node = menu.setFocus(BeatmapSetList.get().getRandomNode(), -1, true, false);
BeatmapSetNode node = instanceContainer.provide(SongMenu.class).setFocus(BeatmapSetList.get().getRandomNode(), -1, true, false);
boolean sameAudio = false;
if (node != null) {
sameAudio = MusicController.getBeatmap().audioFilename.equals(node.getBeatmapSet().get(0).audioFilename);
@ -762,11 +726,11 @@ public class MainMenu extends BasicGameState {
* Enters the song menu, or the downloads menu if no beatmaps are loaded.
*/
private void enterSongMenu() {
int state = Opsu.STATE_SONGMENU;
Class<? extends OpsuState> state = SongMenu.class;
if (BeatmapSetList.get().getMapSetCount() == 0) {
((DownloadsMenu) game.getState(Opsu.STATE_DOWNLOADSMENU)).notifyOnLoad("Download some beatmaps to get started!");
state = Opsu.STATE_DOWNLOADSMENU;
instanceContainer.provide(DownloadsMenu.class).notifyOnLoad("Download some beatmaps to get started!");
state = DownloadsMenu.class;
}
game.enterState(state, new EasedFadeOutTransition(), new FadeInTransition());
displayContainer.switchState(state);
}
}

View File

@ -18,35 +18,14 @@
package itdelatrisu.opsu.states;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Options.GameOption;
import itdelatrisu.opsu.audio.MusicController;
import itdelatrisu.opsu.audio.SoundController;
import itdelatrisu.opsu.audio.SoundEffect;
import itdelatrisu.opsu.ui.UI;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.state.transition.FadeInTransition;
import org.newdawn.slick.state.transition.EmptyTransition;
import yugecin.opsudance.ui.OptionsOverlay;
import yugecin.opsudance.ui.OptionsOverlay.OptionTab;
/**
* "Game Options" state.
* <p>
* Players are able to view and change various game settings in this state.
*/
public class OptionsMenu extends BasicGameState implements OptionsOverlay.Parent {
public class OptionsMenu {
/** Option tabs. */
private static final OptionTab[] options = new OptionsOverlay.OptionTab[]{
public static final OptionTab[] normalOptions = new OptionsOverlay.OptionTab[]{
new OptionTab("Display", new GameOption[]{
GameOption.SCREEN_RESOLUTION,
GameOption.FULLSCREEN,
@ -154,104 +133,63 @@ public class OptionsMenu extends BasicGameState implements OptionsOverlay.Parent
})
};
private StateBasedGame game;
private Input input;
private final int state;
private OptionsOverlay optionsOverlay;
public OptionsMenu(int state) {
this.state = state;
}
@Override
public void init(GameContainer container, StateBasedGame game)
throws SlickException {
this.game = game;
this.input = container.getInput();
optionsOverlay = new OptionsOverlay(this, options, 5, container);
}
@Override
public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException {
// background
GameImage.OPTIONS_BG.getImage().draw();
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
optionsOverlay.render(g, mouseX, mouseY);
UI.draw(g);
}
@Override
public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException {
UI.update(delta);
MusicController.loopTrackIfEnded(false);
optionsOverlay.update(delta, input.getMouseX(), input.getMouseY());
}
@Override
public int getID() {
return state;
}
@Override
public void mouseReleased(int button, int x, int y) {
optionsOverlay.mouseReleased(button, x, y);
}
@Override
public void mousePressed(int button, int x, int y) {
optionsOverlay.mousePressed(button, x, y);
}
@Override
public void mouseDragged(int oldx, int oldy, int newx, int newy) {
optionsOverlay.mouseDragged(oldx, oldy, newx, newy);
}
@Override
public void mouseWheelMoved(int newValue) {
optionsOverlay.mouseWheelMoved(newValue);
}
@Override
public void keyPressed(int key, char c) {
optionsOverlay.keyPressed(key, c);
}
/**
* This string is built with option values when entering the options menu.
* When leaving the options menu, this string is checked against the new optionstring with the same options.
* If those do not match, it means some option has change which requires a restart
*/
private String restartOptions;
@Override
public void enter(GameContainer container, StateBasedGame game)
throws SlickException {
UI.enter();
restartOptions = "" + Options.getResolutionIdx() + Options.isFullscreen() + Options.allowLargeResolutions() + Options.getSkinName();
}
@Override
public void leave(GameContainer container, StateBasedGame game) throws SlickException {
if (!("" + Options.getResolutionIdx() + Options.isFullscreen() + Options.allowLargeResolutions() + Options.getSkinName()).equals(restartOptions)) {
container.setForceExit(false);
container.exit();
return;
}
SoundController.playSound(SoundEffect.MENUBACK);
}
@Override
public void onLeave() {
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
}
@Override
public void onSaveOption(GameOption option) {
}
public static final OptionTab[] storyboardOptions = new OptionsOverlay.OptionTab[]{
new OptionTab("Gameplay", new GameOption[] {
GameOption.BACKGROUND_DIM,
GameOption.DANCE_REMOVE_BG,
GameOption.SNAKING_SLIDERS,
GameOption.SHRINKING_SLIDERS,
GameOption.SHOW_HIT_LIGHTING,
GameOption.SHOW_HIT_ANIMATIONS,
GameOption.SHOW_COMBO_BURSTS,
GameOption.SHOW_PERFECT_HIT,
GameOption.SHOW_FOLLOW_POINTS,
}),
new OptionTab("Input", new GameOption[] {
GameOption.CURSOR_SIZE,
GameOption.NEW_CURSOR,
GameOption.DISABLE_CURSOR
}),
new OptionTab("Dance", new GameOption[] {
GameOption.DANCE_MOVER,
GameOption.DANCE_EXGON_DELAY,
GameOption.DANCE_QUAD_BEZ_AGGRESSIVENESS,
GameOption.DANCE_QUAD_BEZ_SLIDER_AGGRESSIVENESS_FACTOR,
GameOption.DANCE_QUAD_BEZ_USE_CUBIC_ON_SLIDERS,
GameOption.DANCE_QUAD_BEZ_CUBIC_AGGRESSIVENESS_FACTOR,
GameOption.DANCE_MOVER_DIRECTION,
GameOption.DANCE_SLIDER_MOVER_TYPE,
GameOption.DANCE_SPINNER,
GameOption.DANCE_SPINNER_DELAY,
GameOption.DANCE_LAZY_SLIDERS,
GameOption.DANCE_CIRCLE_STREAMS,
GameOption.DANCE_ONLY_CIRCLE_STACKS,
GameOption.DANCE_CIRLCE_IN_SLOW_SLIDERS,
GameOption.DANCE_CIRLCE_IN_LAZY_SLIDERS,
GameOption.DANCE_MIRROR,
}),
new OptionTab("Dance display", new GameOption[] {
GameOption.DANCE_DRAW_APPROACH,
GameOption.DANCE_OBJECT_COLOR_OVERRIDE,
GameOption.DANCE_OBJECT_COLOR_OVERRIDE_MIRRORED,
GameOption.DANCE_RGB_OBJECT_INC,
GameOption.DANCE_CURSOR_COLOR_OVERRIDE,
GameOption.DANCE_CURSOR_MIRROR_COLOR_OVERRIDE,
GameOption.DANCE_CURSOR_ONLY_COLOR_TRAIL,
GameOption.DANCE_RGB_CURSOR_INC,
GameOption.DANCE_CURSOR_TRAIL_OVERRIDE,
GameOption.DANCE_HIDE_OBJECTS,
GameOption.DANCE_HIDE_UI,
GameOption.DANCE_HIDE_WATERMARK,
}),
new OptionTab ("Pippi", new GameOption[] {
GameOption.PIPPI_ENABLE,
GameOption.PIPPI_RADIUS_PERCENT,
GameOption.PIPPI_ANGLE_INC_MUL,
GameOption.PIPPI_ANGLE_INC_MUL_SLIDER,
GameOption.PIPPI_SLIDER_FOLLOW_EXPAND,
GameOption.PIPPI_PREVENT_WOBBLY_STREAMS,
})
};
}

View File

@ -22,7 +22,6 @@ import itdelatrisu.opsu.GameData;
import itdelatrisu.opsu.GameData.Grade;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.GameMod;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.ScoreData;
import itdelatrisu.opsu.Utils;
@ -62,21 +61,17 @@ import java.nio.file.WatchEvent.Kind;
import java.util.Map;
import java.util.Stack;
import org.lwjgl.opengl.Display;
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;
import org.newdawn.slick.SpriteSheet;
import org.newdawn.slick.gui.TextField;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.state.transition.EasedFadeOutTransition;
import org.newdawn.slick.state.transition.EmptyTransition;
import org.newdawn.slick.state.transition.FadeInTransition;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.inject.InstanceContainer;
import yugecin.opsudance.core.state.ComplexOpsuState;
import yugecin.opsudance.ui.OptionsOverlay;
/**
* "Song Selection" state.
@ -84,7 +79,10 @@ import org.newdawn.slick.state.transition.FadeInTransition;
* Players are able to select a beatmap to play, view previous scores, choose game mods,
* manage beatmaps, or change game options from this state.
*/
public class SongMenu extends BasicGameState {
public class SongMenu extends ComplexOpsuState {
private final InstanceContainer instanceContainer;
/** The max number of song buttons to be shown on each screen. */
public static final int MAX_SONG_BUTTONS = 6;
@ -169,7 +167,7 @@ public class SongMenu extends BasicGameState {
private MenuButton selectModsButton, selectRandomButton, selectMapOptionsButton, selectOptionsButton;
/** The search textfield. */
private TextField search;
private TextField searchTextField;
/**
* Delay timer, in milliseconds, before running another search.
@ -230,7 +228,7 @@ public class SongMenu extends BasicGameState {
} finally {
finished = true;
}
};
}
/** Reloads all beatmaps. */
private void reloadBeatmaps() {
@ -323,45 +321,41 @@ public class SongMenu extends BasicGameState {
/** Sort order dropdown menu. */
private DropdownMenu<BeatmapSortOrder> sortMenu;
// game-related variables
private GameContainer container;
private StateBasedGame game;
private Input input;
private final int state;
private final OptionsOverlay optionsOverlay;
public SongMenu(int state) {
this.state = state;
public SongMenu(final DisplayContainer displayContainer, InstanceContainer instanceContainer) {
super(displayContainer);
this.instanceContainer = instanceContainer;
optionsOverlay = new OptionsOverlay(displayContainer, OptionsMenu.normalOptions, 0);
overlays.add(optionsOverlay);
}
@Override
public void init(GameContainer container, StateBasedGame game)
throws SlickException {
this.container = container;
this.game = game;
this.input = container.getInput();
public void revalidate() {
super.revalidate();
int width = container.getWidth();
int height = container.getHeight();
components.clear();
// header/footer coordinates
headerY = height * 0.0075f + GameImage.MENU_MUSICNOTE.getImage().getHeight() +
headerY = displayContainer.height * 0.0075f + GameImage.MENU_MUSICNOTE.getImage().getHeight() +
Fonts.BOLD.getLineHeight() + Fonts.DEFAULT.getLineHeight() +
Fonts.SMALL.getLineHeight();
footerY = height - GameImage.SELECTION_MODS.getImage().getHeight();
footerY = displayContainer.height - GameImage.SELECTION_MODS.getImage().getHeight();
// footer logo coordinates
float footerHeight = height - footerY;
float footerHeight = displayContainer.height - footerY;
footerLogoSize = footerHeight * 3.25f;
Image logo = GameImage.MENU_LOGO.getImage();
logo = logo.getScaledCopy(footerLogoSize / logo.getWidth());
footerLogoButton = new MenuButton(logo, width - footerHeight * 0.8f, height - footerHeight * 0.65f);
footerLogoButton = new MenuButton(logo, displayContainer.width - footerHeight * 0.8f, displayContainer.height - footerHeight * 0.65f);
footerLogoButton.setHoverAnimationDuration(1);
footerLogoButton.setHoverExpand(1.2f);
// initialize sorts
int sortWidth = (int) (width * 0.12f);
sortMenu = new DropdownMenu<BeatmapSortOrder>(container, BeatmapSortOrder.values(),
width * 0.87f, headerY - GameImage.MENU_TAB.getImage().getHeight() * 2.25f, sortWidth) {
int sortWidth = (int) (displayContainer.width * 0.12f);
int posX = (int) (displayContainer.width * 0.87f);
int posY = (int) (headerY - GameImage.MENU_TAB.getImage().getHeight() * 2.25f);
sortMenu = new DropdownMenu<BeatmapSortOrder>(displayContainer, BeatmapSortOrder.values(), posX, posY, sortWidth) {
@Override
public void itemSelected(int index, BeatmapSortOrder item) {
BeatmapSortOrder.set(item);
@ -375,7 +369,7 @@ public class SongMenu extends BasicGameState {
}
@Override
public boolean menuClicked(int index) {
public boolean canSelect(int index) {
if (isInputBlocked())
return false;
@ -386,36 +380,40 @@ public class SongMenu extends BasicGameState {
sortMenu.setBackgroundColor(Colors.BLACK_BG_HOVER);
sortMenu.setBorderColor(Colors.BLUE_DIVIDER);
sortMenu.setChevronRightColor(Color.white);
components.add(sortMenu);
// initialize group tabs
for (BeatmapGroup group : BeatmapGroup.values())
group.init(width, headerY - DIVIDER_LINE_WIDTH / 2);
group.init(displayContainer.width, headerY - DIVIDER_LINE_WIDTH / 2);
// initialize score data buttons
ScoreData.init(width, headerY + height * 0.01f);
ScoreData.init(displayContainer.width, headerY + displayContainer.height * 0.01f);
// song button background & graphics context
Image menuBackground = GameImage.MENU_BUTTON_BG.getImage();
// song button coordinates
buttonX = width * 0.6f;
buttonX = displayContainer.width * 0.6f;
//buttonY = headerY;
buttonWidth = menuBackground.getWidth();
buttonHeight = menuBackground.getHeight();
buttonOffset = (footerY - headerY - DIVIDER_LINE_WIDTH) / MAX_SONG_BUTTONS;
// search
int textFieldX = (int) (width * 0.7125f + Fonts.BOLD.getWidth("Search: "));
int textFieldX = (int) (displayContainer.width * 0.7125f + Fonts.BOLD.getWidth("Search: "));
int textFieldY = (int) (headerY + Fonts.BOLD.getLineHeight() / 2);
search = new TextField(
container, Fonts.BOLD, textFieldX, textFieldY,
(int) (width * 0.99f) - textFieldX, Fonts.BOLD.getLineHeight()
);
search.setBackgroundColor(Color.transparent);
search.setBorderColor(Color.transparent);
search.setTextColor(Color.white);
search.setConsumeEvents(false);
search.setMaxLength(60);
searchTextField = new TextField(displayContainer, Fonts.BOLD, textFieldX, textFieldY, (int) (displayContainer.width * 0.99f) - textFieldX, Fonts.BOLD.getLineHeight()) {
@Override
public boolean isFocusable() {
return false;
}
};
searchTextField.setBackgroundColor(Color.transparent);
searchTextField.setBorderColor(Color.transparent);
searchTextField.setTextColor(Color.white);
searchTextField.setMaxLength(60);
searchTextField.setFocused(true);
components.add(searchTextField);
// selection buttons
Image selectionMods = GameImage.SELECTION_MODS.getImage();
@ -427,8 +425,8 @@ public class SongMenu extends BasicGameState {
if (selectButtonsWidth < 20) {
selectButtonsWidth = 100;
}
float selectX = width * 0.183f + selectButtonsWidth / 2f;
float selectY = height - selectButtonsHeight / 2f;
float selectX = displayContainer.width * 0.183f + selectButtonsWidth / 2f;
float selectY = displayContainer.height - selectButtonsHeight / 2f;
float selectOffset = selectButtonsWidth * 1.05f;
selectModsButton = new MenuButton(GameImage.SELECTION_MODS_OVERLAY.getImage(),
selectX, selectY);
@ -449,33 +447,32 @@ public class SongMenu extends BasicGameState {
loader = new Animation(spr, 50);
// beatmap watch service listener
final StateBasedGame game_ = game;
BeatmapWatchService.addListener(new BeatmapWatchServiceListener() {
@Override
public void eventReceived(Kind<?> kind, Path child) {
if (!songFolderChanged && kind != StandardWatchEventKinds.ENTRY_MODIFY) {
songFolderChanged = true;
if (game_.getCurrentStateID() == Opsu.STATE_SONGMENU)
if (displayContainer.isInState(SongMenu.class)) {
UI.sendBarNotification("Changes in Songs folder detected. Hit F5 to refresh.");
}
}
}
});
// star stream
starStream = new StarStream(width, (height - GameImage.STAR.getImage().getHeight()) / 2, -width, 0, MAX_STREAM_STARS);
starStream.setPositionSpread(height / 20f);
starStream = new StarStream(displayContainer.width, (displayContainer.height - GameImage.STAR.getImage().getHeight()) / 2, -displayContainer.width, 0, MAX_STREAM_STARS);
starStream.setPositionSpread(displayContainer.height / 20f);
starStream.setDirectionSpread(10f);
}
@Override
public void render(GameContainer container, StateBasedGame game, Graphics g)
throws SlickException {
public void render(Graphics g) {
g.setBackground(Color.black);
int width = container.getWidth();
int height = container.getHeight();
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
boolean inDropdownMenu = sortMenu.contains(mouseX, mouseY);
int width = displayContainer.width;
int height = displayContainer.height;
int mouseX = displayContainer.mouseX;
int mouseY = displayContainer.mouseY;
// background
if (focusNode != null) {
@ -547,8 +544,9 @@ public class SongMenu extends BasicGameState {
g.clearClip();
// scroll bar
if (focusScores.length > MAX_SCORE_BUTTONS && ScoreData.areaContains(mouseX, mouseY) && !inDropdownMenu)
if (focusScores.length > MAX_SCORE_BUTTONS && ScoreData.areaContains(mouseX, mouseY) && !isAnyComponentFocused()) {
ScoreData.drawScrollbar(g, startScorePos.getPosition(), focusScores.length * ScoreData.getButtonOffset());
}
}
// top/bottom bars
@ -565,7 +563,7 @@ public class SongMenu extends BasicGameState {
Float position = MusicController.getBeatProgress();
if (position == null) // default to 60bpm
position = System.currentTimeMillis() % 1000 / 1000f;
if (footerLogoButton.contains(mouseX, mouseY, 0.25f) && !inDropdownMenu) {
if (footerLogoButton.contains(mouseX, mouseY, 0.25f)) {
// hovering over logo: stop pulsing
footerLogoButton.draw();
} else {
@ -658,7 +656,7 @@ public class SongMenu extends BasicGameState {
// group tabs
BeatmapGroup currentGroup = BeatmapGroup.current();
BeatmapGroup hoverGroup = null;
if (!inDropdownMenu) {
if (!isAnyComponentFocused()) {
for (BeatmapGroup group : BeatmapGroup.values()) {
if (group.contains(mouseX, mouseY)) {
hoverGroup = group;
@ -673,8 +671,9 @@ public class SongMenu extends BasicGameState {
currentGroup.draw(true, false);
// search
boolean searchEmpty = search.getText().isEmpty();
int searchX = search.getX(), searchY = search.getY();
boolean searchEmpty = searchTextField.getText().isEmpty();
int searchX = searchTextField.x;
int searchY = searchTextField.y;
float searchBaseX = width * 0.7f;
float searchTextX = width * 0.7125f;
float searchRectHeight = Fonts.BOLD.getLineHeight() * 2;
@ -693,20 +692,15 @@ public class SongMenu extends BasicGameState {
g.fillRect(searchBaseX, headerY + DIVIDER_LINE_WIDTH / 2, width - searchBaseX, searchRectHeight);
Colors.BLACK_ALPHA.a = oldAlpha;
Fonts.BOLD.drawString(searchTextX, searchY, "Search:", Colors.GREEN_SEARCH);
if (searchEmpty)
if (searchEmpty) {
Fonts.BOLD.drawString(searchX, searchY, "Type to search!", Color.white);
else {
} else {
g.setColor(Color.white);
// TODO: why is this needed to correctly position the TextField?
search.setLocation(searchX - 3, searchY - 1);
search.render(container, g);
search.setLocation(searchX, searchY);
Fonts.DEFAULT.drawString(searchTextX, searchY + Fonts.BOLD.getLineHeight(),
(searchResultString == null) ? "Searching..." : searchResultString, Color.white);
searchTextField.render(g);
Fonts.DEFAULT.drawString(searchTextX, searchY + Fonts.BOLD.getLineHeight(), (searchResultString == null) ? "Searching..." : searchResultString, Color.white);
}
// sorting options
sortMenu.render(container, g);
sortMenu.render(g);
// reloading beatmaps
if (reloadThread != null) {
@ -722,11 +716,15 @@ public class SongMenu extends BasicGameState {
UI.getBackButton().draw();
UI.draw(g);
super.render(g);
}
@Override
public void update(GameContainer container, StateBasedGame game, int delta)
throws SlickException {
public void preRenderUpdate() {
super.preRenderUpdate();
int delta = displayContainer.renderDelta;
UI.update(delta);
if (reloadThread == null)
MusicController.loopTrackIfEnded(true);
@ -742,8 +740,8 @@ public class SongMenu extends BasicGameState {
MusicController.playThemeSong();
reloadThread = null;
}
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
boolean inDropdownMenu = sortMenu.contains(mouseX, mouseY);
int mouseX = displayContainer.mouseX;
int mouseY = displayContainer.mouseY;
UI.getBackButton().hoverUpdate(delta, mouseX, mouseY);
selectModsButton.hoverUpdate(delta, mouseX, mouseY);
selectRandomButton.hoverUpdate(delta, mouseX, mouseY);
@ -759,8 +757,8 @@ public class SongMenu extends BasicGameState {
if (focusNode != null) {
MenuState state = focusNode.getBeatmapSet().isFavorite() ?
MenuState.BEATMAP_FAVORITE : MenuState.BEATMAP;
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(state, focusNode);
game.enterState(Opsu.STATE_BUTTONMENU);
instanceContainer.provide(ButtonMenu.class).setMenuState(state, focusNode);
displayContainer.switchState(ButtonMenu.class);
}
return;
}
@ -782,7 +780,6 @@ public class SongMenu extends BasicGameState {
starStream.update(delta);
// search
search.setFocus(true);
searchTimer += delta;
if (searchTimer >= SEARCH_DELAY && reloadThread == null && beatmapMenuTimer == -1) {
searchTimer = 0;
@ -791,12 +788,12 @@ public class SongMenu extends BasicGameState {
if (focusNode != null)
oldFocusNode = new SongNode(BeatmapSetList.get().getBaseNode(focusNode.index), focusNode.beatmapIndex);
if (BeatmapSetList.get().search(search.getText())) {
if (BeatmapSetList.get().search(searchTextField.getText())) {
// reset song stack
randomStack = new Stack<SongNode>();
randomStack = new Stack<>();
// empty search
if (search.getText().isEmpty())
if (searchTextField.getText().isEmpty())
searchResultString = null;
// search produced new list: re-initialize it
@ -805,7 +802,7 @@ public class SongMenu extends BasicGameState {
focusScores = null;
if (BeatmapSetList.get().size() > 0) {
BeatmapSetList.get().init();
if (search.getText().isEmpty()) { // cleared search
if (searchTextField.getText().isEmpty()) { // cleared search
// use previous start/focus if possible
if (oldFocusNode != null)
setFocus(oldFocusNode.getNode(), oldFocusNode.getIndex(), true, true);
@ -818,7 +815,7 @@ public class SongMenu extends BasicGameState {
setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
}
oldFocusNode = null;
} else if (!search.getText().isEmpty())
} else if (!searchTextField.getText().isEmpty())
searchResultString = "No matches found. Hit ESC to reset.";
}
}
@ -848,7 +845,7 @@ public class SongMenu extends BasicGameState {
// mouse hover
BeatmapSetNode node = getNodeAtPosition(mouseX, mouseY);
if (node != null && !inDropdownMenu) {
if (node != null && !isAnyComponentFocused()) {
if (node == hoverIndex)
hoverOffset.update(delta);
else {
@ -880,66 +877,65 @@ public class SongMenu extends BasicGameState {
}
@Override
public int getID() { return state; }
public boolean mousePressed(int button, int x, int y) {
if (super.mousePressed(button, x, y)) {
return true;
}
@Override
public void mousePressed(int button, int x, int y) {
// check mouse button
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
if (button == Input.MOUSE_MIDDLE_BUTTON) {
return false;
}
if (isScrollingToFocusNode)
return;
if (isScrollingToFocusNode) {
return true;
}
songScrolling.pressed();
startScorePos.pressed();
return true;
}
@Override
public void mouseReleased(int button, int x, int y) {
// check mouse button
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
public boolean mouseReleased(int button, int x, int y) {
if (super.mouseReleased(button, x, y)) {
return true;
}
if (isScrollingToFocusNode)
return;
if (button == Input.MOUSE_MIDDLE_BUTTON) {
return false;
}
if (isScrollingToFocusNode) {
return true;
}
songScrolling.released();
startScorePos.released();
}
@Override
public void mouseClicked(int button, int x, int y, int clickCount) {
// check mouse button
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
if (isInputBlocked()) {
return true;
}
// block input
if (isInputBlocked())
return;
// back
if (UI.getBackButton().contains(x, y)) {
SoundController.playSound(SoundEffect.MENUBACK);
((MainMenu) game.getState(Opsu.STATE_MAINMENU)).reset();
game.enterState(Opsu.STATE_MAINMENU, new EasedFadeOutTransition(), new FadeInTransition());
return;
displayContainer.switchState(MainMenu.class);
return true;
}
// selection buttons
if (selectModsButton.contains(x, y)) {
this.keyPressed(Input.KEY_F1, '\0');
return;
return true;
} else if (selectRandomButton.contains(x, y)) {
this.keyPressed(Input.KEY_F2, '\0');
return;
return true;
} else if (selectMapOptionsButton.contains(x, y)) {
this.keyPressed(Input.KEY_F3, '\0');
return;
return true;
} else if (selectOptionsButton.contains(x, y)) {
SoundController.playSound(SoundEffect.MENUHIT);
game.enterState(Opsu.STATE_OPTIONSMENU, new EmptyTransition(), new FadeInTransition());
return;
optionsOverlay.show();
return true;
}
// group tabs
@ -954,7 +950,7 @@ public class SongMenu extends BasicGameState {
songInfo = null;
scoreMap = null;
focusScores = null;
search.setText("");
searchTextField.setText("");
searchTimer = SEARCH_DELAY;
searchTransitionTimer = SEARCH_TRANSITION_TIME;
searchResultString = null;
@ -965,17 +961,18 @@ public class SongMenu extends BasicGameState {
if (BeatmapSetList.get().size() < 1 && group.getEmptyMessage() != null)
UI.sendBarNotification(group.getEmptyMessage());
}
return;
return true;
}
}
if (focusNode == null)
return;
if (focusNode == null) {
return false;
}
// logo: start game
if (footerLogoButton.contains(x, y, 0.25f)) {
startGame();
return;
return true;
}
// song buttons
@ -1014,7 +1011,7 @@ public class SongMenu extends BasicGameState {
if (button == Input.MOUSE_RIGHT_BUTTON)
beatmapMenuTimer = (node.index == expandedIndex) ? BEATMAP_MENU_DELAY * 4 / 5 : 0;
return;
return true;
}
// score buttons
@ -1027,49 +1024,55 @@ public class SongMenu extends BasicGameState {
SoundController.playSound(SoundEffect.MENUHIT);
if (button != Input.MOUSE_RIGHT_BUTTON) {
// view score
GameData data = new GameData(focusScores[rank], container.getWidth(), container.getHeight());
((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data);
game.enterState(Opsu.STATE_GAMERANKING, new EasedFadeOutTransition(), new FadeInTransition());
instanceContainer.provide(GameRanking.class).setGameData(new GameData(focusScores[rank], displayContainer.width, displayContainer.height));
displayContainer.switchState(GameRanking.class);
} else {
// score management
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(MenuState.SCORE, focusScores[rank]);
game.enterState(Opsu.STATE_BUTTONMENU);
instanceContainer.provide(ButtonMenu.class).setMenuState(MenuState.SCORE, focusScores[rank]);
displayContainer.switchState(ButtonMenu.class);
}
return;
return true;
}
}
}
return true;
}
@Override
public void keyPressed(int key, char c) {
public boolean keyPressed(int key, char c) {
if (super.keyPressed(key, c)) {
return true;
}
// block input
if ((reloadThread != null && !(key == Input.KEY_ESCAPE || key == Input.KEY_F12)) || beatmapMenuTimer > -1 || isScrollingToFocusNode)
return;
if ((reloadThread != null && key != Input.KEY_ESCAPE) || beatmapMenuTimer > -1 || isScrollingToFocusNode) {
return true;
}
Input input = displayContainer.input;
switch (key) {
case Input.KEY_ESCAPE:
if (reloadThread != null) {
// beatmap reloading: stop parsing beatmaps by sending interrupt to BeatmapParser
reloadThread.interrupt();
} else if (!search.getText().isEmpty()) {
} else if (!searchTextField.getText().isEmpty()) {
// clear search text
search.setText("");
searchTextField.setText("");
searchTimer = SEARCH_DELAY;
searchTransitionTimer = 0;
searchResultString = null;
} else {
// return to main menu
SoundController.playSound(SoundEffect.MENUBACK);
((MainMenu) game.getState(Opsu.STATE_MAINMENU)).reset();
game.enterState(Opsu.STATE_MAINMENU, new EasedFadeOutTransition(), new FadeInTransition());
displayContainer.switchState(MainMenu.class);
}
break;
return true;
case Input.KEY_F1:
SoundController.playSound(SoundEffect.MENUHIT);
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(MenuState.MODS);
game.enterState(Opsu.STATE_BUTTONMENU);
break;
instanceContainer.provide(ButtonMenu.class).setMenuState(MenuState.MODS);
displayContainer.switchState(ButtonMenu.class);
return true;
case Input.KEY_F2:
if (focusNode == null)
break;
@ -1089,25 +1092,25 @@ public class SongMenu extends BasicGameState {
randomStack.push(new SongNode(BeatmapSetList.get().getBaseNode(focusNode.index), focusNode.beatmapIndex));
setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
}
break;
return true;
case Input.KEY_F3:
if (focusNode == null)
break;
SoundController.playSound(SoundEffect.MENUHIT);
MenuState state = focusNode.getBeatmapSet().isFavorite() ?
MenuState.BEATMAP_FAVORITE : MenuState.BEATMAP;
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(state, focusNode);
game.enterState(Opsu.STATE_BUTTONMENU);
break;
instanceContainer.provide(ButtonMenu.class).setMenuState(state, focusNode);
displayContainer.switchState(ButtonMenu.class);
return true;
case Input.KEY_F5:
SoundController.playSound(SoundEffect.MENUHIT);
if (songFolderChanged)
reloadBeatmaps(false);
else {
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(MenuState.RELOAD);
game.enterState(Opsu.STATE_BUTTONMENU);
instanceContainer.provide(ButtonMenu.class).setMenuState(MenuState.RELOAD);
displayContainer.switchState(ButtonMenu.class);
}
break;
return true;
case Input.KEY_DELETE:
if (focusNode == null)
break;
@ -1115,30 +1118,21 @@ public class SongMenu extends BasicGameState {
SoundController.playSound(SoundEffect.MENUHIT);
MenuState ms = (focusNode.beatmapIndex == -1 || focusNode.getBeatmapSet().size() == 1) ?
MenuState.BEATMAP_DELETE_CONFIRM : MenuState.BEATMAP_DELETE_SELECT;
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(ms, focusNode);
game.enterState(Opsu.STATE_BUTTONMENU);
instanceContainer.provide(ButtonMenu.class).setMenuState(ms, focusNode);
displayContainer.switchState(ButtonMenu.class);
}
break;
case Input.KEY_F7:
Options.setNextFPS(container);
break;
case Input.KEY_F10:
Options.toggleMouseDisabled();
break;
case Input.KEY_F12:
Utils.takeScreenShot();
break;
return true;
case Input.KEY_ENTER:
if (focusNode == null)
break;
startGame();
break;
return true;
case Input.KEY_DOWN:
changeIndex(1);
break;
return true;
case Input.KEY_UP:
changeIndex(-1);
break;
return true;
case Input.KEY_RIGHT:
if (focusNode == null)
break;
@ -1154,7 +1148,7 @@ public class SongMenu extends BasicGameState {
hoverIndex = oldHoverIndex;
}
}
break;
return true;
case Input.KEY_LEFT:
if (focusNode == null)
break;
@ -1170,24 +1164,25 @@ public class SongMenu extends BasicGameState {
hoverIndex = oldHoverIndex;
}
}
break;
return true;
case Input.KEY_NEXT:
changeIndex(MAX_SONG_BUTTONS);
break;
return true;
case Input.KEY_PRIOR:
changeIndex(-MAX_SONG_BUTTONS);
break;
return true;
case Input.KEY_O:
if (input.isKeyDown(Input.KEY_LCONTROL) || input.isKeyDown(Input.KEY_RCONTROL)) {
game.enterState(Opsu.STATE_OPTIONSMENU, new EmptyTransition(), new FadeInTransition());
optionsOverlay.show();
}
break;
return true;
default:
// wait for user to finish typing
// TODO: accept all characters (current conditions are from TextField class)
if ((c > 31 && c < 127) || key == Input.KEY_BACK) {
searchTimer = 0;
int textLength = search.getText().length();
searchTextField.keyPressed(key, c);
int textLength = searchTextField.getText().length();
if (lastSearchTextLength != textLength) {
if (key == Input.KEY_BACK) {
if (textLength == 0)
@ -1197,49 +1192,60 @@ public class SongMenu extends BasicGameState {
lastSearchTextLength = textLength;
}
}
break;
return true;
}
return true;
}
@Override
public void mouseDragged(int oldx, int oldy, int newx, int newy) {
// block input
if (isInputBlocked())
return;
public boolean mouseDragged(int oldx, int oldy, int newx, int newy) {
if (super.mouseDragged(oldx, oldy, newx, newy)) {
return true;
}
if (isInputBlocked()) {
return true;
}
int diff = newy - oldy;
if (diff == 0)
return;
if (diff == 0) {
return false;
}
// check mouse button (right click scrolls faster on songs)
int multiplier;
if (input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON))
if (displayContainer.input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON)) {
multiplier = 10;
else if (input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON))
} else if (displayContainer.input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)) {
multiplier = 1;
else
return;
} else {
return false;
}
// score buttons
if (focusScores != null && focusScores.length >= MAX_SCORE_BUTTONS && ScoreData.areaContains(oldx, oldy))
if (focusScores != null && focusScores.length >= MAX_SCORE_BUTTONS && ScoreData.areaContains(oldx, oldy)) {
startScorePos.dragged(-diff * multiplier);
// song buttons
else
} else {
songScrolling.dragged(-diff * multiplier);
}
return true;
}
@Override
public void mouseWheelMoved(int newValue) {
// change volume
if (input.isKeyDown(Input.KEY_LALT) || input.isKeyDown(Input.KEY_RALT)) {
UI.changeVolume((newValue < 0) ? -1 : 1);
return;
public boolean mouseWheelMoved(int newValue) {
if (super.mouseWheelMoved(newValue)) {
return true;
}
// block input
if (isInputBlocked())
return;
Input input = displayContainer.input;
if (input.isKeyDown(Input.KEY_LALT) || input.isKeyDown(Input.KEY_RALT)) {
UI.changeVolume((newValue < 0) ? -1 : 1);
return true;
}
if (isInputBlocked()) {
return true;
}
int shift = (newValue < 0) ? 1 : -1;
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
@ -1251,13 +1257,14 @@ public class SongMenu extends BasicGameState {
// song buttons
else
changeIndex(shift);
return false;
}
@Override
public void enter(GameContainer container, StateBasedGame game)
throws SlickException {
public void enter() {
super.enter();
UI.enter();
Display.setTitle(game.getTitle());
selectModsButton.resetHover();
selectRandomButton.resetHover();
selectMapOptionsButton.resetHover();
@ -1275,11 +1282,10 @@ public class SongMenu extends BasicGameState {
songChangeTimer.setTime(songChangeTimer.getDuration());
musicIconBounceTimer.setTime(musicIconBounceTimer.getDuration());
starStream.clear();
sortMenu.activate();
sortMenu.reset();
// reset song stack
randomStack = new Stack<SongNode>();
randomStack = new Stack<>();
// reload beatmaps if song folder changed
if (songFolderChanged && stateAction != MenuState.RELOAD)
@ -1307,7 +1313,7 @@ public class SongMenu extends BasicGameState {
// reset game data
if (resetGame) {
((Game) game.getState(Opsu.STATE_GAME)).resetGameData();
instanceContainer.provide(Game.class).resetGameData();
// destroy extra Clips
MultiClip.destroyExtraClips();
@ -1439,13 +1445,6 @@ public class SongMenu extends BasicGameState {
}
}
@Override
public void leave(GameContainer container, StateBasedGame game)
throws SlickException {
search.setFocus(false);
sortMenu.deactivate();
}
/**
* Shifts the startNode forward (+) or backwards (-) by a given number of nodes.
* Initiates sliding "animation" by shifting the button Y position.
@ -1568,9 +1567,9 @@ public class SongMenu extends BasicGameState {
// change the focus node
if (changeStartNode || (startNode.index == 0 && startNode.beatmapIndex == -1 && startNode.prev == null)) {
if (startNode == null || game.getCurrentStateID() != Opsu.STATE_SONGMENU)
if (startNode == null || displayContainer.isInState(SongMenu.class)) {
songScrolling.setPosition((node.index - 1) * buttonOffset);
else {
} else {
isScrollingToFocusNode = true;
songScrolling.setSpeedMultiplier(2f);
songScrolling.released();
@ -1703,7 +1702,7 @@ public class SongMenu extends BasicGameState {
songInfo = null;
hoverOffset.setTime(0);
hoverIndex = null;
search.setText("");
searchTextField.setText("");
searchTimer = SEARCH_DELAY;
searchTransitionTimer = SEARCH_TRANSITION_TIME;
searchResultString = null;
@ -1784,17 +1783,17 @@ public class SongMenu extends BasicGameState {
}
// turn on "auto" mod if holding "ctrl" key
if (input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) {
if (displayContainer.input.isKeyDown(Input.KEY_RCONTROL) || displayContainer.input.isKeyDown(Input.KEY_LCONTROL)) {
if (!GameMod.AUTO.isActive())
GameMod.AUTO.toggle(true);
}
SoundController.playSound(SoundEffect.MENUHIT);
MultiClip.destroyExtraClips();
Game gameState = (Game) game.getState(Opsu.STATE_GAME);
Game gameState = instanceContainer.provide(Game.class);
gameState.loadBeatmap(beatmap);
gameState.setRestart(Game.Restart.NEW);
gameState.setReplay(null);
game.enterState(Opsu.STATE_GAME, new EasedFadeOutTransition(), new FadeInTransition());
displayContainer.switchState(Game.class);
}
}

View File

@ -19,7 +19,6 @@
package itdelatrisu.opsu.states;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.audio.MusicController;
@ -36,19 +35,22 @@ import itdelatrisu.opsu.ui.animations.AnimationEquation;
import java.io.File;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.util.Log;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.inject.InstanceContainer;
import yugecin.opsudance.core.state.BaseOpsuState;
/**
* "Splash Screen" state.
* <p>
* Loads game resources and enters "Main Menu" state.
*/
public class Splash extends BasicGameState {
public class Splash extends BaseOpsuState {
private final InstanceContainer instanceContainer;
/** Minimum time, in milliseconds, to display the splash screen (and fade in the logo). */
private static final int MIN_SPLASH_TIME = 400;
@ -71,18 +73,16 @@ public class Splash extends BasicGameState {
private AnimatedValue logoAlpha;
// game-related variables
private final int state;
private GameContainer container;
private boolean init = false;
public Splash(int state) {
this.state = state;
public Splash(DisplayContainer displayContainer, InstanceContainer instanceContainer) {
super(displayContainer);
this.instanceContainer = instanceContainer;
}
@Override
public void init(GameContainer container, StateBasedGame game)
throws SlickException {
this.container = container;
protected void revalidate() {
super.revalidate();
// check if skin changed
if (Options.getSkin() != null)
@ -92,7 +92,7 @@ public class Splash extends BasicGameState {
this.watchServiceChange = Options.isWatchServiceEnabled() && BeatmapWatchService.get() == null;
// load Utils class first (needed in other 'init' methods)
Utils.init(container, game);
Utils.init(displayContainer);
// fade in logo
this.logoAlpha = new AnimatedValue(MIN_SPLASH_TIME, 0f, 1f, AnimationEquation.LINEAR);
@ -100,16 +100,14 @@ public class Splash extends BasicGameState {
}
@Override
public void render(GameContainer container, StateBasedGame game, Graphics g)
throws SlickException {
public void render(Graphics g) {
g.setBackground(Color.black);
GameImage.MENU_LOGO.getImage().drawCentered(container.getWidth() / 2, container.getHeight() / 2);
GameImage.MENU_LOGO.getImage().drawCentered(displayContainer.width / 2, displayContainer.height / 2);
UI.drawLoadingProgress(g);
}
@Override
public void update(GameContainer container, StateBasedGame game, int delta)
throws SlickException {
public void preRenderUpdate() {
if (!init) {
init = true;
@ -165,7 +163,7 @@ public class Splash extends BasicGameState {
}
// fade in logo
if (logoAlpha.update(delta))
if (logoAlpha.update(displayContainer.renderDelta))
GameImage.MENU_LOGO.getImage().setAlpha(logoAlpha.getValue());
// change states when loading complete
@ -173,33 +171,41 @@ public class Splash extends BasicGameState {
// initialize song list
if (BeatmapSetList.get().size() > 0) {
BeatmapSetList.get().init();
if (Options.isThemeSongEnabled())
if (Options.isThemeSongEnabled()) {
MusicController.playThemeSong();
else
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
}
// play the theme song
else
} else {
instanceContainer.provide(SongMenu.class).setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
}
} else {
MusicController.playThemeSong();
game.enterState(Opsu.STATE_MAINMENU);
}
displayContainer.switchState(MainMenu.class);
}
}
@Override
public int getID() { return state; }
public boolean onCloseRequest() {
if (thread != null && thread.isAlive()) {
thread.interrupt();
try {
thread.join();
} catch (InterruptedException e) {
Log.warn("InterruptedException while waiting for splash thread to die", e);
}
}
return true;
}
@Override
public void keyPressed(int key, char c) {
if (key == Input.KEY_ESCAPE) {
// close program
if (++escapeCount >= 3)
container.exit();
// stop parsing beatmaps by sending interrupt to BeatmapParser
else if (thread != null)
thread.interrupt();
public boolean keyPressed(int key, char c) {
if (key != Input.KEY_ESCAPE) {
return false;
}
if (++escapeCount >= 3) {
displayContainer.exitRequested = true;
} else if (thread != null) {
thread.interrupt();
}
return true;
}
}

View File

@ -18,31 +18,22 @@
package itdelatrisu.opsu.ui;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.audio.MusicController;
import itdelatrisu.opsu.skins.Skin;
import itdelatrisu.opsu.ui.animations.AnimationEquation;
import java.awt.Point;
import java.nio.IntBuffer;
import java.util.LinkedList;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.newdawn.slick.*;
import org.newdawn.slick.state.StateBasedGame;
import yugecin.opsudance.Dancer;
/**
* Updates and draws the cursor.
*/
public class Cursor {
/** Empty cursor. */
private static org.lwjgl.input.Cursor emptyCursor;
/** Last cursor coordinates. */
private Point lastPosition;
@ -63,15 +54,10 @@ public class Cursor {
private static final float CURSOR_SCALE_TIME = 125;
/** Stores all previous cursor locations to display a trail. */
private LinkedList<Point> trail = new LinkedList<Point>();
private LinkedList<Point> trail = new LinkedList<>();
private boolean newStyle;
// game-related variables
private static GameContainer container;
private static StateBasedGame game;
private static Input input;
public static Color lastObjColor = Color.white;
public static Color lastMirroredObjColor = Color.white;
public static Color nextObjColor = Color.white;
@ -80,26 +66,6 @@ public class Cursor {
private boolean isMirrored;
/**
* Initializes the class.
* @param container the game container
* @param game the game object
*/
public static void init(GameContainer container, StateBasedGame game) {
Cursor.container = container;
Cursor.game = game;
Cursor.input = container.getInput();
// create empty cursor to simulate hiding the cursor
try {
int min = org.lwjgl.input.Cursor.getMinCursorSize();
IntBuffer tmp = BufferUtils.createIntBuffer(min * min);
emptyCursor = new org.lwjgl.input.Cursor(min, min, min/2, min/2, 1, tmp, null);
} catch (LWJGLException e) {
ErrorHandler.error("Failed to create hidden cursor.", e, true);
}
}
/**
* Constructor.
*/
@ -108,29 +74,15 @@ public class Cursor {
}
public Cursor(boolean isMirrored) {
resetLocations();
resetLocations(0, 0);
this.isMirrored = isMirrored;
}
/**
* Draws the cursor.
*/
public void draw() {
int state = game.getCurrentStateID();
boolean mousePressed =
(((state == Opsu.STATE_GAME || state == Opsu.STATE_GAMEPAUSEMENU) && Utils.isGameKeyPressed()) ||
((input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON) || input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON)) &&
!(state == Opsu.STATE_GAME && Options.isMouseDisabled())));
draw(input.getMouseX(), input.getMouseY(), mousePressed);
}
/**
* Draws the cursor.
* @param mouseX the mouse x coordinate
* @param mouseY the mouse y coordinate
* @param mousePressed whether or not the mouse button is pressed
*/
public void draw(int mouseX, int mouseY, boolean mousePressed) {
public void draw(boolean mousePressed) {
if (Options.isCursorDisabled())
return;
@ -172,8 +124,6 @@ public class Cursor {
cursorTrail = cursorTrail.getScaledCopy(cursorScale);
}
setCursorPosition(mouseX, mouseY);
Color filter;
if (isMirrored) {
filter = Dancer.cursorColorMirrorOverride.getMirrorColor();
@ -195,16 +145,16 @@ public class Cursor {
cursorTrailWidth, cursorTrailHeight, cursorTrailRotation);
}
cursorTrail.drawEmbedded(
mouseX - (cursorTrailWidth / 2f), mouseY - (cursorTrailHeight / 2f),
lastPosition.x - (cursorTrailWidth / 2f), lastPosition.y - (cursorTrailHeight / 2f),
cursorTrailWidth, cursorTrailHeight, cursorTrailRotation);
cursorTrail.endUse();
// draw the other components
if (newStyle && skin.isCursorRotated())
cursor.setRotation(cursorAngle);
cursor.drawCentered(mouseX, mouseY, Options.isCursorOnlyColorTrail() ? Color.white : filter);
cursor.drawCentered(lastPosition.x, lastPosition.y, Options.isCursorOnlyColorTrail() ? Color.white : filter);
if (hasMiddle)
cursorMiddle.drawCentered(mouseX, mouseY, Options.isCursorOnlyColorTrail() ? Color.white : filter);
cursorMiddle.drawCentered(lastPosition.x, lastPosition.y, Options.isCursorOnlyColorTrail() ? Color.white : filter);
}
/**
@ -212,10 +162,10 @@ public class Cursor {
* @param mouseX x coordinate to set position to
* @param mouseY y coordinate to set position to
*/
public void setCursorPosition(int mouseX, int mouseY) {
public void setCursorPosition(int delta, int mouseX, int mouseY) {
// TODO: use an image buffer
int removeCount = 0;
float FPSmod = Math.max(container.getFPS(), 1) / 30f;
float FPSmod = Math.max(1000 / Math.max(delta, 1), 1) / 30f; // TODO
if (newStyle) {
// new style: add all points between cursor movements
if ((lastPosition.x == 0 && lastPosition.y == 0) || !addCursorPoints(lastPosition.x, lastPosition.y, mouseX, mouseY)) {
@ -301,7 +251,7 @@ public class Cursor {
* If the old style cursor is being used, this will do nothing.
* @param delta the delta interval since the last call
*/
public void update(int delta) {
public void updateAngle(int delta) {
cursorAngle += delta / 40f;
cursorAngle %= 360;
}
@ -309,14 +259,14 @@ public class Cursor {
/**
* Resets all cursor data and beatmap skins.
*/
public void reset() {
public void reset(int mouseX, int mouseY) {
// destroy skin images
GameImage.CURSOR.destroyBeatmapSkinImage();
GameImage.CURSOR_MIDDLE.destroyBeatmapSkinImage();
GameImage.CURSOR_TRAIL.destroyBeatmapSkinImage();
// reset locations
resetLocations();
resetLocations(mouseX, mouseY);
// reset angles
cursorAngle = 0f;
@ -325,14 +275,12 @@ public class Cursor {
/**
* Resets all cursor location data.
*/
public void resetLocations() {
public void resetLocations(int mouseX, int mouseY) {
trail.clear();
if (lastPosition != null) {
for (int i = 0; i < 50; i++) {
trail.add(new Point(lastPosition));
}
lastPosition = new Point(mouseX, mouseY);
for (int i = 0; i < 50; i++) {
trail.add(new Point(lastPosition));
}
lastPosition = new Point(0, 0);
}
/**
@ -344,23 +292,4 @@ public class Cursor {
GameImage.CURSOR_TRAIL.hasBeatmapSkinImage());
}
/**
* Hides the cursor, if possible.
*/
public void hide() {
if (emptyCursor != null) {
try {
container.setMouseCursor(emptyCursor, 0, 0);
} catch (SlickException e) {
ErrorHandler.error("Failed to hide the cursor.", e, true);
}
}
}
/**
* Unhides the cursor.
*/
public void show() {
container.setDefaultMouseCursor();
}
}

View File

@ -27,152 +27,62 @@ import org.newdawn.slick.Font;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.UnicodeFont;
import org.newdawn.slick.gui.AbstractComponent;
import org.newdawn.slick.gui.GUIContext;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.components.Component;
public class DropdownMenu<E> extends Component {
/**
* Simple dropdown menu.
* <p>
* Basic usage:
* <ul>
* <li>Override {@link #menuClicked(int)} to perform actions when the menu is clicked
* (e.g. play a sound effect, block input under certain conditions).
* <li>Override {@link #itemSelected(int, Object)} to perform actions when a new item is selected.
* <li>Call {@link #activate()}/{@link #deactivate()} whenever the component is needed
* (e.g. in a state's {@code enter} and {@code leave} events.
* </ul>
*
* @param <E> the type of the elements in the menu
*/
public class DropdownMenu<E> extends AbstractComponent {
/** Padding ratios for drawing. */
private static final float PADDING_Y = 0.1f, CHEVRON_X = 0.03f;
/** Whether this component is active. */
private boolean active;
private final DisplayContainer displayContainer;
/** The menu items. */
private E[] items;
/** The menu item names. */
private String[] itemNames;
private int selectedItemIndex = 0;
private boolean expanded;
/** The index of the selected item. */
private int itemIndex = 0;
/** Whether the menu is expanded. */
private boolean expanded = false;
/** The expanding animation progress. */
private AnimatedValue expandProgress = new AnimatedValue(300, 0f, 1f, AnimationEquation.LINEAR);
/** The last update time, in milliseconds. */
private long lastUpdateTime;
/** The top-left coordinates. */
private float x, y;
/** The width and height of the dropdown menu. */
private int width, height;
/** The height of the base item. */
private int baseHeight;
/** The vertical offset between items. */
private float offsetY;
/** The colors to use. */
private Color
textColor = Color.white, backgroundColor = Color.black,
highlightColor = Colors.BLUE_DIVIDER, borderColor = Colors.BLUE_DIVIDER,
chevronDownColor = textColor, chevronRightColor = backgroundColor;
private Color textColor = Color.white;
private Color backgroundColor = Color.black;
private Color highlightColor = Colors.BLUE_DIVIDER;
private Color borderColor = Colors.BLUE_DIVIDER;
private Color chevronDownColor = textColor;
private Color chevronRightColor = backgroundColor;
/** The fonts to use. */
private UnicodeFont fontNormal = Fonts.MEDIUM, fontSelected = Fonts.MEDIUMBOLD;
private UnicodeFont fontNormal = Fonts.MEDIUM;
private UnicodeFont fontSelected = Fonts.MEDIUMBOLD;
/** The chevron images. */
private Image chevronDown, chevronRight;
private Image chevronDown;
private Image chevronRight;
/** Should the next click be blocked? */
private boolean blockClick = false;
/**
* Creates a new dropdown menu.
* @param container the container rendering this menu
* @param items the list of items (with names given as their {@code toString()} methods)
* @param x the top-left x coordinate
* @param y the top-left y coordinate
*/
public DropdownMenu(GUIContext container, E[] items, float x, float y) {
this(container, items, x, y, 0);
}
/**
* Creates a new dropdown menu with the given fonts.
* @param container the container rendering this menu
* @param items the list of items (with names given as their {@code toString()} methods)
* @param x the top-left x coordinate
* @param y the top-left y coordinate
* @param normal the normal font
* @param selected the font for the selected item
*/
public DropdownMenu(GUIContext container, E[] items, float x, float y, UnicodeFont normal, UnicodeFont selected) {
this(container, items, x, y, 0, normal, selected);
}
/**
* Creates a new dropdown menu with the given width.
* @param container the container rendering this menu
* @param items the list of items (with names given as their {@code toString()} methods)
* @param x the top-left x coordinate
* @param y the top-left y coordinate
* @param width the menu width
*/
public DropdownMenu(GUIContext container, E[] items, float x, float y, int width) {
super(container);
public DropdownMenu(DisplayContainer displayContainer, E[] items, int x, int y, int width) {
this.displayContainer = displayContainer;
init(items, x, y, width);
}
/**
* Creates a new dropdown menu with the given width and fonts.
* @param container the container rendering this menu
* @param items the list of items (with names given as their {@code toString()} methods)
* @param x the top-left x coordinate
* @param y the top-left y coordinate
* @param width the menu width
* @param normal the normal font
* @param selected the font for the selected item
*/
public DropdownMenu(GUIContext container, E[] items, float x, float y, int width, UnicodeFont normal, UnicodeFont selected) {
super(container);
this.fontNormal = normal;
this.fontSelected = selected;
init(items, x, y, width);
}
/**
* Returns the maximum item width from the list.
*/
private int getMaxItemWidth() {
int maxWidth = 0;
for (int i = 0; i < itemNames.length; i++) {
int w = fontSelected.getWidth(itemNames[i]);
if (w > maxWidth)
for (String itemName : itemNames) {
int w = fontSelected.getWidth(itemName);
if (w > maxWidth) {
maxWidth = w;
}
}
return maxWidth;
}
/**
* Initializes the component.
*/
private void init(E[] items, float x, float y, int width) {
@SuppressWarnings("SuspiciousNameCombination")
private void init(E[] items, int x, int y, int width) {
this.items = items;
this.itemNames = new String[items.length];
for (int i = 0; i < itemNames.length; i++)
for (int i = 0; i < itemNames.length; i++) {
itemNames[i] = items[i].toString();
}
this.x = x;
this.y = y;
this.baseHeight = fontNormal.getLineHeight();
@ -188,77 +98,27 @@ public class DropdownMenu<E> extends AbstractComponent {
}
@Override
public void setLocation(int x, int y) {
this.x = x;
this.y = y;
public void updateHover(int x, int y) {
this.hovered = this.x <= x && x <= this.x + width && this.y <= y && y <= this.y + (expanded ? height : baseHeight);
}
public boolean baseContains(int x, int y) {
return (x > this.x && x < this.x + width && y > this.y && y < this.y + baseHeight);
}
@Override
public int getX() { return (int) x; }
public void render(Graphics g) {
int delta = displayContainer.renderDelta;
@Override
public int getY() { return (int) y; }
@Override
public int getWidth() { return width; }
@Override
public int getHeight() { return (expanded) ? height : baseHeight; }
/** Activates the component. */
public void activate() { this.active = true; }
/** Deactivates the component. */
public void deactivate() { this.active = false; }
/**
* Returns whether the dropdown menu is currently open.
* @return true if open, false otherwise
*/
public boolean isOpen() { return expanded; }
/**
* Opens or closes the dropdown menu.
* @param flag true to open, false to close
*/
public void open(boolean flag) { this.expanded = flag; }
/**
* Returns true if the coordinates are within the menu bounds.
* @param cx the x coordinate
* @param cy the y coordinate
*/
public boolean contains(float cx, float cy) {
return (cx > x && cx < x + width && (
(cy > y && cy < y + baseHeight) ||
(expanded && cy > y + offsetY && cy < y + height)));
}
/**
* Returns true if the coordinates are within the base item bounds.
* @param cx the x coordinate
* @param cy the y coordinate
*/
public boolean baseContains(float cx, float cy) {
return (cx > x && cx < x + width && cy > y && cy < y + baseHeight);
}
@Override
public void render(GUIContext container, Graphics g) throws SlickException {
// update animation
long time = container.getTime();
if (lastUpdateTime > 0) {
int delta = (int) (time - lastUpdateTime);
expandProgress.update((expanded) ? delta : -delta * 2);
}
this.lastUpdateTime = time;
expandProgress.update((expanded) ? delta : -delta * 2);
// get parameters
Input input = container.getInput();
int idx = getIndexAt(input.getMouseX(), input.getMouseY());
int idx = getIndexAt(displayContainer.mouseY);
float t = expandProgress.getValue();
if (expanded)
if (expanded) {
t = AnimationEquation.OUT_CUBIC.calc(t);
}
// background and border
Color oldGColor = g.getColor();
@ -293,12 +153,12 @@ public class DropdownMenu<E> extends AbstractComponent {
// text
chevronDown.draw(x + width - chevronDown.getWidth() - width * CHEVRON_X, y + (baseHeight - chevronDown.getHeight()) / 2f, chevronDownColor);
fontNormal.drawString(x + (width * 0.03f), y + (fontNormal.getPaddingTop() + fontNormal.getPaddingBottom()) / 2f, itemNames[itemIndex], textColor);
fontNormal.drawString(x + (width * 0.03f), y + (fontNormal.getPaddingTop() + fontNormal.getPaddingBottom()) / 2f, itemNames[selectedItemIndex], textColor);
float oldTextAlpha = textColor.a;
textColor.a *= t;
if (expanded || t >= 0.0001) {
for (int i = 0; i < itemNames.length; i++) {
Font f = (i == itemIndex) ? fontSelected : fontNormal;
Font f = (i == selectedItemIndex) ? fontSelected : fontNormal;
if (i == idx && t >= 0.999)
chevronRight.draw(x, y + offsetY + (offsetY * i) + (offsetY - chevronRight.getHeight()) / 2f, chevronRightColor);
f.drawString(x + chevronRight.getWidth(), y + offsetY + (offsetY * i * t), itemNames[i], textColor);
@ -310,131 +170,89 @@ public class DropdownMenu<E> extends AbstractComponent {
/**
* Returns the index of the item at the given location, -1 for the base item,
* and -2 if there is no item at the location.
* @param cx the x coordinate
* @param cy the y coordinate
* @param y the y coordinate
*/
private int getIndexAt(float cx, float cy) {
if (!contains(cx, cy))
private int getIndexAt(int y) {
if (!hovered) {
return -2;
if (cy <= y + baseHeight)
}
if (y <= this.y + baseHeight) {
return -1;
if (!expanded)
}
if (!expanded) {
return -2;
return (int) ((cy - (y + offsetY)) / offsetY);
}
return (int) ((y - (this.y + offsetY)) / offsetY);
}
/**
* Resets the menu state.
*/
public void reset() {
this.expanded = false;
this.lastUpdateTime = 0;
expandProgress.setTime(0);
blockClick = false;
}
@Override
public void setFocused(boolean focused) {
super.setFocused(focused);
expanded = focused;
}
@Override
public void mousePressed(int button, int x, int y) {
if (!active)
return;
public boolean isFocusable() {
return true;
}
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
@Override
public void mouseReleased(int button) {
super.mouseReleased(button);
int idx = getIndexAt(x, y);
if (button == Input.MOUSE_MIDDLE_BUTTON) {
return;
}
int idx = getIndexAt(displayContainer.mouseY);
if (idx == -2) {
this.expanded = false;
return;
}
if (!menuClicked(idx))
if (!canSelect(selectedItemIndex)) {
return;
this.expanded = (idx == -1) ? !expanded : false;
if (idx >= 0 && itemIndex != idx) {
this.itemIndex = idx;
itemSelected(idx, items[idx]);
}
blockClick = true;
consumeEvent();
}
@Override
public void mouseClicked(int button, int x, int y, int clickCount) {
if (!active)
return;
if (blockClick) {
blockClick = false;
consumeEvent();
this.expanded = (idx == -1) && !expanded;
if (idx >= 0 && selectedItemIndex != idx) {
this.selectedItemIndex = idx;
itemSelected(idx, items[selectedItemIndex]);
}
}
/**
* Notification that a new item was selected (via override).
* @param index the index of the item selected
* @param item the item selected
*/
public void itemSelected(int index, E item) {}
/**
* Notification that the menu was clicked (via override).
* @param index the index of the item clicked, or -1 for the base item
* @return true to process the click, or false to block/intercept it
*/
public boolean menuClicked(int index) { return true; }
@Override
public void setFocus(boolean focus) { /* does not currently use the "focus" concept */ }
@Override
public void mouseReleased(int button, int x, int y) { /* does not currently use the "focus" concept */ }
/**
* Selects the item at the given index.
* @param index the list item index
* @throws IllegalArgumentException if {@code index} is negative or greater than or equal to size
*/
public void setSelectedIndex(int index) {
if (index < 0 || index >= items.length)
throw new IllegalArgumentException();
this.itemIndex = index;
protected boolean canSelect(int index) {
return true;
}
/**
* Returns the index of the selected item.
*/
public int getSelectedIndex() { return itemIndex; }
protected void itemSelected(int index, E item) {
}
/**
* Returns the selected item.
*/
public E getSelectedItem() { return items[itemIndex]; }
public E getSelectedItem() {
return items[selectedItemIndex];
}
/**
* Returns the item at the given index.
* @param index the list item index
*/
public E getItemAt(int index) { return items[index]; }
public void setBackgroundColor(Color c) {
this.backgroundColor = c;
}
/**
* Returns the number of items in the list.
*/
public int getItemCount() { return items.length; }
public void setHighlightColor(Color c) {
this.highlightColor = c;
}
/** Sets the text color. */
public void setTextColor(Color c) { this.textColor = c; }
public void setBorderColor(Color c) {
this.borderColor = c;
}
/** Sets the background color. */
public void setBackgroundColor(Color c) { this.backgroundColor = c; }
public void setChevronDownColor(Color c) {
this.chevronDownColor = c;
}
/** Sets the highlight color. */
public void setHighlightColor(Color c) { this.highlightColor = c; }
/** Sets the border color. */
public void setBorderColor(Color c) { this.borderColor = c; }
/** Sets the down chevron color. */
public void setChevronDownColor(Color c) { this.chevronDownColor = c; }
/** Sets the right chevron color. */
public void setChevronRightColor(Color c) { this.chevronRightColor = c; }
public void setChevronRightColor(Color c) {
this.chevronRightColor = c;
}
}

View File

@ -18,7 +18,6 @@
package itdelatrisu.opsu.ui;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils;
@ -29,23 +28,16 @@ import itdelatrisu.opsu.replay.ReplayImporter;
import itdelatrisu.opsu.ui.animations.AnimatedValue;
import itdelatrisu.opsu.ui.animations.AnimationEquation;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
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.state.StateBasedGame;
import yugecin.opsudance.core.DisplayContainer;
/**
* Draws common UI components.
*/
public class UI {
/** Cursor. */
private static Cursor cursor = new Cursor();
/** Back button. */
private static MenuButton backButton;
@ -75,32 +67,24 @@ public class UI {
private static AnimatedValue tooltipAlpha = new AnimatedValue(200, 0f, 1f, AnimationEquation.LINEAR);
// game-related variables
private static GameContainer container;
private static Input input;
private static DisplayContainer displayContainer;
// This class should not be instantiated.
private UI() {}
/**
* Initializes UI data.
* @param container the game container
* @param game the game object
*/
public static void init(GameContainer container, StateBasedGame game) {
UI.container = container;
UI.input = container.getInput();
// initialize cursor
Cursor.init(container, game);
cursor.hide();
public static void init(DisplayContainer displayContainer) {
UI.displayContainer = displayContainer;
// back button
if (GameImage.MENU_BACK.getImages() != null) {
Animation back = GameImage.MENU_BACK.getAnimation(120);
backButton = new MenuButton(back, back.getWidth() / 2f, container.getHeight() - (back.getHeight() / 2f));
backButton = new MenuButton(back, back.getWidth() / 2f, displayContainer.height - (back.getHeight() / 2f));
} else {
Image back = GameImage.MENU_BACK.getImage();
backButton = new MenuButton(back, back.getWidth() / 2f, container.getHeight() - (back.getHeight() / 2f));
backButton = new MenuButton(back, back.getWidth() / 2f, displayContainer.height - (back.getHeight() / 2f));
}
backButton.setHoverAnimationDuration(350);
backButton.setHoverAnimationEquation(AnimationEquation.IN_OUT_BACK);
@ -112,7 +96,6 @@ public class UI {
* @param delta the delta interval since the last call.
*/
public static void update(int delta) {
cursor.update(delta);
updateVolumeDisplay(delta);
updateBarNotification(delta);
tooltipAlpha.update(-delta);
@ -125,24 +108,6 @@ public class UI {
public static void draw(Graphics g) {
drawBarNotification(g);
drawVolume(g);
drawFPS();
cursor.draw();
drawTooltip(g);
}
/**
* Draws the global UI components: cursor, FPS, volume bar, tooltips, bar notifications.
* @param g the graphics context
* @param mouseX the mouse x coordinate
* @param mouseY the mouse y coordinate
* @param mousePressed whether or not the mouse button is pressed
*/
public static void draw(Graphics g, int mouseX, int mouseY, boolean mousePressed) {
drawBarNotification(g);
drawVolume(g);
drawFPS();
cursor.draw(mouseX, mouseY, mousePressed);
drawTooltip(g);
}
/**
@ -150,16 +115,10 @@ public class UI {
*/
public static void enter() {
backButton.resetHover();
cursor.resetLocations();
resetBarNotification();
resetTooltip();
}
/**
* Returns the game cursor.
*/
public static Cursor getCursor() { return cursor; }
/**
* Returns the 'menu-back' MenuButton.
*/
@ -189,27 +148,6 @@ public class UI {
Fonts.MEDIUM.drawString(tabTextX, tabTextY, text, textColor);
}
/**
* 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("%dFPS", container.getFPS());
Fonts.BOLD.drawString(
container.getWidth() * 0.997f - Fonts.BOLD.getWidth(fps),
container.getHeight() * 0.997f - Fonts.BOLD.getHeight(fps),
Integer.toString(container.getFPS()), Color.white
);
Fonts.DEFAULT.drawString(
container.getWidth() * 0.997f - Fonts.BOLD.getWidth("FPS"),
container.getHeight() * 0.997f - Fonts.BOLD.getHeight("FPS"),
"FPS", Color.white
);
}
/**
* 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)}.
@ -219,7 +157,6 @@ public class UI {
if (volumeDisplay == -1)
return;
int width = container.getWidth(), height = container.getHeight();
Image img = GameImage.VOLUME.getImage();
// move image in/out
@ -230,13 +167,13 @@ public class UI {
else if (ratio >= 0.9f)
xOffset = img.getWidth() * (1 - ((1 - ratio) * 10f));
img.drawCentered(width - img.getWidth() / 2f + xOffset, height / 2f);
img.drawCentered(displayContainer.width - img.getWidth() / 2f + xOffset, displayContainer.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)),
displayContainer.width - (img.getWidth() * 0.368f) + xOffset,
(displayContainer.height / 2f) - (img.getHeight() * 0.47f) + (barHeight * (1 - volume)),
img.getWidth() * 0.15f, barHeight * volume, 3
);
}
@ -260,7 +197,7 @@ public class UI {
*/
public static void changeVolume(int units) {
final float UNIT_OFFSET = 0.05f;
Options.setMasterVolume(container, Utils.clamp(Options.getMasterVolume() + (UNIT_OFFSET * units), 0f, 1f));
Options.setMasterVolume(Utils.clamp(Options.getMasterVolume() + (UNIT_OFFSET * units), 0f, 1f));
if (volumeDisplay == -1)
volumeDisplay = 0;
else if (volumeDisplay >= VOLUME_DISPLAY_TIME / 10)
@ -294,8 +231,8 @@ public class UI {
return;
// draw loading info
float marginX = container.getWidth() * 0.02f, marginY = container.getHeight() * 0.02f;
float lineY = container.getHeight() - marginY;
float marginX = displayContainer.width * 0.02f, marginY = displayContainer.height * 0.02f;
float lineY = displayContainer.height - marginY;
int lineOffsetY = Fonts.MEDIUM.getLineHeight();
if (Options.isLoadVerbose()) {
// verbose: display percentages and file names
@ -308,7 +245,7 @@ public class UI {
Fonts.MEDIUM.drawString(marginX, lineY - (lineOffsetY * 2), text, Color.white);
g.setColor(Color.white);
g.fillRoundRect(marginX, lineY - (lineOffsetY / 2f),
(container.getWidth() - (marginX * 2f)) * progress / 100f, lineOffsetY / 4f, 4
(displayContainer.width - (marginX * 2f)) * progress / 100f, lineOffsetY / 4f, 4
);
}
}
@ -332,7 +269,7 @@ public class UI {
float unitBaseX, float unitBaseY, float unitWidth, float scrollAreaHeight,
Color bgColor, Color scrollbarColor, boolean right
) {
float scrollbarWidth = container.getWidth() * 0.00347f;
float scrollbarWidth = displayContainer.width * 0.00347f;
float scrollbarHeight = scrollAreaHeight * lengthShown / totalLength;
float offsetY = (scrollAreaHeight - scrollbarHeight) * (position / (totalLength - lengthShown));
float scrollbarX = unitBaseX + unitWidth - ((right) ? scrollbarWidth : 0);
@ -368,8 +305,7 @@ public class UI {
if (tooltipAlpha.getTime() == 0 || tooltip == null)
return;
int containerWidth = container.getWidth(), containerHeight = container.getHeight();
int margin = containerWidth / 100, textMarginX = 2;
int margin = displayContainer.width / 100, textMarginX = 2;
int offset = GameImage.CURSOR_MIDDLE.getImage().getWidth() / 2;
int lineHeight = Fonts.SMALL.getLineHeight();
int textWidth = textMarginX * 2, textHeight = lineHeight;
@ -387,13 +323,14 @@ public class UI {
textWidth += Fonts.SMALL.getWidth(tooltip);
// get drawing coordinates
int x = input.getMouseX() + offset, y = input.getMouseY() + offset;
if (x + textWidth > containerWidth - margin)
x = containerWidth - margin - textWidth;
int x = displayContainer.mouseX + offset;
int y = displayContainer.mouseY + offset;
if (x + textWidth > displayContainer.width - margin)
x = displayContainer.width - margin - textWidth;
else if (x < margin)
x = margin;
if (y + textHeight > containerHeight - margin)
y = containerHeight - margin - textHeight;
if (y + textHeight > displayContainer.height - margin)
y = displayContainer.height - margin - textHeight;
else if (y < margin)
y = margin;
@ -467,13 +404,13 @@ public class UI {
float alpha = 1f;
if (barNotifTimer >= BAR_NOTIFICATION_TIME * 0.9f)
alpha -= 1 - ((BAR_NOTIFICATION_TIME - barNotifTimer) / (BAR_NOTIFICATION_TIME * 0.1f));
int midX = container.getWidth() / 2, midY = container.getHeight() / 2;
int midX = displayContainer.width / 2, midY = displayContainer.height / 2;
float barHeight = Fonts.LARGE.getLineHeight() * (1f + 0.6f * Math.min(barNotifTimer * 15f / BAR_NOTIFICATION_TIME, 1f));
float oldAlphaB = Colors.BLACK_ALPHA.a, oldAlphaW = Colors.WHITE_ALPHA.a;
Colors.BLACK_ALPHA.a *= alpha;
Colors.WHITE_ALPHA.a = alpha;
g.setColor(Colors.BLACK_ALPHA);
g.fillRect(0, midY - barHeight / 2f, container.getWidth(), barHeight);
g.fillRect(0, midY - barHeight / 2f, displayContainer.width, barHeight);
Fonts.LARGE.drawString(
midX - Fonts.LARGE.getWidth(barNotif) / 2f,
midY - Fonts.LARGE.getLineHeight() / 2.2f,
@ -482,19 +419,4 @@ public class UI {
Colors.WHITE_ALPHA.a = oldAlphaW;
}
/**
* Shows a confirmation dialog (used before exiting the game).
* @param message the message to display
* @return true if user selects "yes", false otherwise
*/
public static boolean showExitConfirmation(String message) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
ErrorHandler.error("Could not set system look and feel for exit confirmation.", e, true);
}
int n = JOptionPane.showConfirmDialog(null, message, "Warning",
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
return (n != JOptionPane.YES_OPTION);
}
}

View File

@ -1,907 +0,0 @@
/*
* Copyright (c) 2013, Slick2D
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the Slick2D nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.newdawn.slick;
import java.io.IOException;
import java.util.Properties;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.input.Cursor;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.Drawable;
import org.lwjgl.opengl.Pbuffer;
import org.lwjgl.opengl.PixelFormat;
import org.newdawn.slick.gui.GUIContext;
import org.newdawn.slick.openal.SoundStore;
import org.newdawn.slick.opengl.CursorLoader;
import org.newdawn.slick.opengl.ImageData;
import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.opengl.renderer.SGL;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
/**
* A generic game container that handles the game loop, fps recording and
* managing the input system
*
* @author kevin
*/
public abstract class GameContainer implements GUIContext {
/** The renderer to use for all GL operations */
protected static SGL GL = Renderer.get();
/** The shared drawable if any */
protected static Drawable SHARED_DRAWABLE;
/** The time the last frame was rendered */
protected long lastFrame;
/** The last time the FPS recorded */
protected long lastFPS;
/** The last recorded FPS */
protected int recordedFPS;
/** The current count of FPS */
protected int fps;
/** True if we're currently running the game loop */
protected boolean running = true;
/** The width of the display */
protected int width;
/** The height of the display */
protected int height;
/** The game being managed */
protected Game game;
/** The default font to use in the graphics context */
private Font defaultFont;
/** The graphics context to be passed to the game */
private Graphics graphics;
/** The input system to pass to the game */
protected Input input;
/** The FPS we want to lock to */
protected int targetFPS = -1;
/** True if we should show the fps */
private boolean showFPS = true;
/** The minimum logic update interval */
protected long minimumLogicInterval = 1;
/** The stored delta */
protected long storedDelta;
/** The maximum logic update interval */
protected long maximumLogicInterval = 0;
/** The last game started */
protected Game lastGame;
/** True if we should clear the screen each frame */
protected boolean clearEachFrame = true;
/** True if the game is paused */
protected boolean paused;
/** True if we should force exit */
protected boolean forceExit = true;
/** True if vsync has been requested */
protected boolean vsync;
/** Smoothed deltas requested */
protected boolean smoothDeltas;
/** The number of samples we'll attempt through hardware */
protected int samples;
/** True if this context supports multisample */
protected boolean supportsMultiSample;
/** True if we should render when not focused */
protected boolean alwaysRender;
/** True if we require stencil bits */
protected static boolean stencil;
/**
* Create a new game container wrapping a given game
*
* @param game The game to be wrapped
*/
protected GameContainer(Game game) {
this.game = game;
lastFrame = getTime();
getBuildVersion();
Log.checkVerboseLogSetting();
}
public static void enableStencil() {
stencil = true;
}
/**
* Set the default font that will be intialised in the graphics held in this container
*
* @param font The font to use as default
*/
public void setDefaultFont(Font font) {
if (font != null) {
this.defaultFont = font;
} else {
Log.warn("Please provide a non null font");
}
}
/**
* Indicate whether we want to try to use fullscreen multisampling. This will
* give antialiasing across the whole scene using a hardware feature.
*
* @param samples The number of samples to attempt (2 is safe)
*/
public void setMultiSample(int samples) {
this.samples = samples;
}
/**
* Check if this hardware can support multi-sampling
*
* @return True if the hardware supports multi-sampling
*/
public boolean supportsMultiSample() {
return supportsMultiSample;
}
/**
* The number of samples we're attempting to performing using
* hardware multisampling
*
* @return The number of samples requested
*/
public int getSamples() {
return samples;
}
/**
* Indicate if we should force exitting the VM at the end
* of the game (default = true)
*
* @param forceExit True if we should force the VM exit
*/
public void setForceExit(boolean forceExit) {
this.forceExit = forceExit;
}
/**
* Indicate if we want to smooth deltas. This feature will report
* a delta based on the FPS not the time passed. This works well with
* vsync.
*
* @param smoothDeltas True if we should report smooth deltas
*/
public void setSmoothDeltas(boolean smoothDeltas) {
this.smoothDeltas = smoothDeltas;
}
/**
* Check if the display is in fullscreen mode
*
* @return True if the display is in fullscreen mode
*/
public boolean isFullscreen() {
return false;
}
/**
* Get the aspect ratio of the screen
*
* @return The aspect ratio of the display
*/
public float getAspectRatio() {
return getWidth() / getHeight();
}
/**
* Indicate whether we want to be in fullscreen mode. Note that the current
* display mode must be valid as a fullscreen mode for this to work
*
* @param fullscreen True if we want to be in fullscreen mode
* @throws SlickException Indicates we failed to change the display mode
*/
public void setFullscreen(boolean fullscreen) throws SlickException {
}
/**
* Enable shared OpenGL context. After calling this all containers created
* will shared a single parent context
*
* @throws SlickException Indicates a failure to create the shared drawable
*/
public static void enableSharedContext() throws SlickException {
try {
SHARED_DRAWABLE = new Pbuffer(64, 64, new PixelFormat(8, 0, 0), null);
} catch (LWJGLException e) {
throw new SlickException("Unable to create the pbuffer used for shard context, buffers not supported", e);
}
}
/**
* Get the context shared by all containers
*
* @return The context shared by all the containers or null if shared context isn't enabled
*/
public static Drawable getSharedContext() {
return SHARED_DRAWABLE;
}
/**
* Indicate if we should clear the screen at the beginning of each frame. If you're
* rendering to the whole screen each frame then setting this to false can give
* some performance improvements
*
* @param clear True if the the screen should be cleared each frame
*/
public void setClearEachFrame(boolean clear) {
this.clearEachFrame = clear;
}
/**
* Renitialise the game and the context in which it's being rendered
*
* @throws SlickException Indicates a failure rerun initialisation routines
*/
public void reinit() throws SlickException {
}
/**
* Pause the game - i.e. suspend updates
*/
public void pause()
{
setPaused(true);
}
/**
* Resumt the game - i.e. continue updates
*/
public void resume()
{
setPaused(false);
}
/**
* Check if the container is currently paused.
*
* @return True if the container is paused
*/
public boolean isPaused() {
return paused;
}
/**
* Indicates if the game should be paused, i.e. if updates
* should be propogated through to the game.
*
* @param paused True if the game should be paused
*/
public void setPaused(boolean paused)
{
this.paused = paused;
}
/**
* True if this container should render when it has focus
*
* @return True if this container should render when it has focus
*/
public boolean getAlwaysRender () {
return alwaysRender;
}
/**
* Indicate whether we want this container to render when it has focus
*
* @param alwaysRender True if this container should render when it has focus
*/
public void setAlwaysRender (boolean alwaysRender) {
this.alwaysRender = alwaysRender;
}
/**
* Get the build number of slick
*
* @return The build number of slick
*/
public static int getBuildVersion() {
try {
Properties props = new Properties();
props.load(ResourceLoader.getResourceAsStream("version"));
int build = Integer.parseInt(props.getProperty("build"));
Log.info("Slick Build #"+build);
return build;
} catch (Exception e) {
Log.info("Unable to determine Slick build number");
return -1;
}
}
/**
* Get the default system font
*
* @return The default system font
*/
@Override
public Font getDefaultFont() {
return defaultFont;
}
/**
* Check if sound effects are enabled
*
* @return True if sound effects are enabled
*/
public boolean isSoundOn() {
return SoundStore.get().soundsOn();
}
/**
* Check if music is enabled
*
* @return True if music is enabled
*/
public boolean isMusicOn() {
return SoundStore.get().musicOn();
}
/**
* Indicate whether music should be enabled
*
* @param on True if music should be enabled
*/
public void setMusicOn(boolean on) {
SoundStore.get().setMusicOn(on);
}
/**
* Indicate whether sound effects should be enabled
*
* @param on True if sound effects should be enabled
*/
public void setSoundOn(boolean on) {
SoundStore.get().setSoundsOn(on);
}
/**
* Retrieve the current default volume for music
* @return the current default volume for music
*/
public float getMusicVolume() {
return SoundStore.get().getMusicVolume();
}
/**
* Retrieve the current default volume for sound fx
* @return the current default volume for sound fx
*/
public float getSoundVolume() {
return SoundStore.get().getSoundVolume();
}
/**
* Set the default volume for sound fx
* @param volume the new default value for sound fx volume
*/
public void setSoundVolume(float volume) {
SoundStore.get().setSoundVolume(volume);
}
/**
* Set the default volume for music
* @param volume the new default value for music volume
*/
public void setMusicVolume(float volume) {
SoundStore.get().setMusicVolume(volume);
}
/**
* Get the width of the standard screen resolution
*
* @return The screen width
*/
@Override
public abstract int getScreenWidth();
/**
* Get the height of the standard screen resolution
*
* @return The screen height
*/
@Override
public abstract int getScreenHeight();
/**
* Get the width of the game canvas
*
* @return The width of the game canvas
*/
@Override
public int getWidth() {
return width;
}
/**
* Get the height of the game canvas
*
* @return The height of the game canvas
*/
@Override
public int getHeight() {
return height;
}
/**
* Set the icon to be displayed if possible in this type of
* container
*
* @param ref The reference to the icon to be displayed
* @throws SlickException Indicates a failure to load the icon
*/
public abstract void setIcon(String ref) throws SlickException;
/**
* Set the icons to be used for this application. Note that the size of the icon
* defines how it will be used. Important ones to note
*
* Windows window icon must be 16x16
* Windows alt-tab icon must be 24x24 or 32x32 depending on Windows version (XP=32)
*
* @param refs The reference to the icon to be displayed
* @throws SlickException Indicates a failure to load the icon
*/
public abstract void setIcons(String[] refs) throws SlickException;
/**
* Get the accurate system time
*
* @return The system time in milliseconds
*/
@Override
public long getTime() {
return (Sys.getTime() * 1000) / Sys.getTimerResolution();
}
/**
* Sleep for a given period
*
* @param milliseconds The period to sleep for in milliseconds
*/
public void sleep(int milliseconds) {
long target = getTime()+milliseconds;
while (getTime() < target) {
try { Thread.sleep(1); } catch (Exception e) {}
}
}
/**
* Set the mouse cursor to be displayed - this is a hardware cursor and hence
* shouldn't have any impact on FPS.
*
* @param ref The location of the image to be loaded for the cursor
* @param hotSpotX The x coordinate of the hotspot within the cursor image
* @param hotSpotY The y coordinate of the hotspot within the cursor image
* @throws SlickException Indicates a failure to load the cursor image or create the hardware cursor
*/
@Override
public abstract void setMouseCursor(String ref, int hotSpotX, int hotSpotY) throws SlickException;
/**
* Set the mouse cursor to be displayed - this is a hardware cursor and hence
* shouldn't have any impact on FPS.
*
* @param data The image data from which the cursor can be construted
* @param hotSpotX The x coordinate of the hotspot within the cursor image
* @param hotSpotY The y coordinate of the hotspot within the cursor image
* @throws SlickException Indicates a failure to load the cursor image or create the hardware cursor
*/
@Override
public abstract void setMouseCursor(ImageData data, int hotSpotX, int hotSpotY) throws SlickException;
/**
* Set the mouse cursor based on the contents of the image. Note that this will not take
* account of render state type changes to images (rotation and such). If these effects
* are required it is recommended that an offscreen buffer be used to produce an appropriate
* image. An offscreen buffer will always be used to produce the new cursor and as such
* this operation an be very expensive
*
* @param image The image to use as the cursor
* @param hotSpotX The x coordinate of the hotspot within the cursor image
* @param hotSpotY The y coordinate of the hotspot within the cursor image
* @throws SlickException Indicates a failure to load the cursor image or create the hardware cursor
*/
public abstract void setMouseCursor(Image image, int hotSpotX, int hotSpotY) throws SlickException;
/**
* Set the mouse cursor to be displayed - this is a hardware cursor and hence
* shouldn't have any impact on FPS.
*
* @param cursor The cursor to use
* @param hotSpotX The x coordinate of the hotspot within the cursor image
* @param hotSpotY The y coordinate of the hotspot within the cursor image
* @throws SlickException Indicates a failure to load the cursor image or create the hardware cursor
*/
@Override
public abstract void setMouseCursor(Cursor cursor, int hotSpotX, int hotSpotY) throws SlickException;
/**
* Get a cursor based on a image reference on the classpath. The image
* is assumed to be a set/strip of cursor animation frames running from top to
* bottom.
*
* @param ref The reference to the image to be loaded
* @param x The x-coordinate of the cursor hotspot (left {@literal ->} right)
* @param y The y-coordinate of the cursor hotspot (bottom {@literal ->} top)
* @param width The x width of the cursor
* @param height The y height of the cursor
* @param cursorDelays image delays between changing frames in animation
*
* @throws SlickException Indicates a failure to load the image or a failure to create the hardware cursor
*/
public void setAnimatedMouseCursor(String ref, int x, int y, int width, int height, int[] cursorDelays) throws SlickException
{
try {
Cursor cursor;
cursor = CursorLoader.get().getAnimatedCursor(ref, x, y, width, height, cursorDelays);
setMouseCursor(cursor, x, y);
} catch (IOException e) {
throw new SlickException("Failed to set mouse cursor", e);
} catch (LWJGLException e) {
throw new SlickException("Failed to set mouse cursor", e);
}
}
/**
* Set the default mouse cursor - i.e. the original cursor before any native
* cursor was set
*/
@Override
public abstract void setDefaultMouseCursor();
/**
* Get the input system
*
* @return The input system available to this game container
*/
@Override
public Input getInput() {
return input;
}
/**
* Get the current recorded FPS (frames per second)
*
* @return The current FPS
*/
public int getFPS() {
return recordedFPS;
}
/**
* Indicate whether mouse cursor should be grabbed or not
*
* @param grabbed True if mouse cursor should be grabbed
*/
public abstract void setMouseGrabbed(boolean grabbed);
/**
* Check if the mouse cursor is current grabbed. This will cause it not
* to be seen.
*
* @return True if the mouse is currently grabbed
*/
public abstract boolean isMouseGrabbed();
/**
* Retrieve the time taken to render the last frame, i.e. the change in time - delta.
*
* @return The time taken to render the last frame
*/
protected int getDelta() {
long time = getTime();
int delta = (int) (time - lastFrame);
lastFrame = time;
return delta;
}
/**
* Updated the FPS counter
*/
protected void updateFPS() {
if (getTime() - lastFPS > 1000) {
lastFPS = getTime();
recordedFPS = fps;
fps = 0;
}
fps++;
}
/**
* Set the minimum amount of time in milliseonds that has to
* pass before update() is called on the container game. This gives
* a way to limit logic updates compared to renders.
*
* @param interval The minimum interval between logic updates
*/
public void setMinimumLogicUpdateInterval(int interval) {
minimumLogicInterval = interval;
}
/**
* Set the maximum amount of time in milliseconds that can passed
* into the update method. Useful for collision detection without
* sweeping.
*
* @param interval The maximum interval between logic updates
*/
public void setMaximumLogicUpdateInterval(int interval) {
maximumLogicInterval = interval;
}
/**
* Update and render the game
*
* @param delta The change in time since last update and render
* @throws SlickException Indicates an internal fault to the game.
*/
protected void updateAndRender(int delta) throws SlickException {
if (smoothDeltas) {
if (getFPS() != 0) {
delta = 1000 / getFPS();
}
}
input.poll(width, height);
Music.poll(delta);
if (!paused) {
storedDelta += delta;
if (storedDelta >= minimumLogicInterval) {
try {
if (maximumLogicInterval != 0) {
long cycles = storedDelta / maximumLogicInterval;
for (int i=0;i<cycles;i++) {
game.update(this, (int) maximumLogicInterval);
}
int remainder = (int) (storedDelta % maximumLogicInterval);
if (remainder > minimumLogicInterval) {
game.update(this, (int) (remainder % maximumLogicInterval));
storedDelta = 0;
} else {
storedDelta = remainder;
}
} else {
game.update(this, (int) storedDelta);
storedDelta = 0;
}
} catch (Throwable e) {
// Log.error(e);
throw new SlickException("Game.update() failure.", e);
}
}
} else {
game.update(this, 0);
}
if (hasFocus() || getAlwaysRender()) {
if (clearEachFrame) {
GL.glClear(SGL.GL_COLOR_BUFFER_BIT | SGL.GL_DEPTH_BUFFER_BIT);
}
GL.glLoadIdentity();
graphics.resetTransform();
graphics.resetFont();
graphics.resetLineWidth();
graphics.setAntiAlias(false);
try {
game.render(this, graphics);
} catch (Throwable e) {
// Log.error(e);
throw new SlickException("Game.render() failure.", e);
}
graphics.resetTransform();
if (showFPS) {
defaultFont.drawString(10, 10, "FPS: "+recordedFPS);
}
GL.flush();
}
if (targetFPS != -1) {
Display.sync(targetFPS);
}
}
/**
* Indicate if the display should update only when the game is visible
* (the default is true)
*
* @param updateOnlyWhenVisible True if we should updated only when the display is visible
*/
public void setUpdateOnlyWhenVisible(boolean updateOnlyWhenVisible) {
}
/**
* Check if this game is only updating when visible to the user (default = true)
*
* @return True if the game is only updated when the display is visible
*/
public boolean isUpdatingOnlyWhenVisible() {
return true;
}
/**
* Initialise the GL context
*/
protected void initGL() {
Log.info("Starting display "+width+"x"+height);
GL.initDisplay(width, height);
if (input == null) {
input = new Input(height);
}
input.init(height);
// no need to remove listeners?
//input.removeAllListeners();
if (game instanceof InputListener) {
input.removeListener((InputListener) game);
input.addListener((InputListener) game);
}
if (graphics != null) {
graphics.setDimensions(getWidth(), getHeight());
}
lastGame = game;
}
/**
* Initialise the system components, OpenGL and OpenAL.
*
* @throws SlickException Indicates a failure to create a native handler
*/
protected void initSystem() throws SlickException {
initGL();
setMusicVolume(1.0f);
setSoundVolume(1.0f);
graphics = new Graphics(width, height);
defaultFont = graphics.getFont();
}
/**
* Enter the orthographic mode
*/
protected void enterOrtho() {
enterOrtho(width, height);
}
/**
* Indicate whether the container should show the FPS
*
* @param show True if the container should show the FPS
*/
public void setShowFPS(boolean show) {
showFPS = show;
}
/**
* Check if the FPS is currently showing
*
* @return True if the FPS is showing
*/
public boolean isShowingFPS() {
return showFPS;
}
/**
* Set the target fps we're hoping to get
*
* @param fps The target fps we're hoping to get
*/
public void setTargetFrameRate(int fps) {
targetFPS = fps;
}
/**
* Indicate whether the display should be synced to the
* vertical refresh (stops tearing)
*
* @param vsync True if we want to sync to vertical refresh
*/
public void setVSync(boolean vsync) {
this.vsync = vsync;
Display.setVSyncEnabled(vsync);
}
/**
* True if vsync is requested
*
* @return True if vsync is requested
*/
public boolean isVSyncRequested() {
return vsync;
}
/**
* True if the game is running
*
* @return True if the game is running
*/
protected boolean running() {
return running;
}
/**
* Inidcate we want verbose logging
*
* @param verbose True if we want verbose logging (INFO and DEBUG)
*/
public void setVerbose(boolean verbose) {
Log.setVerbose(verbose);
}
/**
* Cause the game to exit and shutdown cleanly
*/
public void exit() {
running = false;
}
/**
* Check if the game currently has focus
*
* @return True if the game currently has focus
*/
public abstract boolean hasFocus();
/**
* Get the graphics context used by this container. Note that this
* value may vary over the life time of the game.
*
* @return The graphics context used by this container
*/
public Graphics getGraphics() {
return graphics;
}
/**
* Enter the orthographic mode
*
* @param xsize The size of the panel being used
* @param ysize The size of the panel being used
*/
protected void enterOrtho(int xsize, int ysize) {
GL.enterOrtho(xsize, ysize);
}
}

View File

@ -34,232 +34,70 @@ import org.newdawn.slick.Font;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.geom.Rectangle;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.components.ActionListener;
import yugecin.opsudance.core.components.Component;
/**
* A single text field supporting text entry
*
* @author kevin
*/
@SuppressWarnings("unused")
public class TextField extends AbstractComponent {
/** The key repeat interval */
public class TextField extends Component {
private static final int INITIAL_KEY_REPEAT_INTERVAL = 400;
/** The key repeat interval */
private static final int KEY_REPEAT_INTERVAL = 50;
private final DisplayContainer displayContainer;
/** The width of the field */
private int width;
/** The height of the field */
private int height;
/** The location in the X coordinate */
protected int x;
/** The location in the Y coordinate */
protected int y;
/** The maximum number of characters allowed to be input */
private String value = "";
private Font font;
private int maxCharacter = 10000;
/** The value stored in the text field */
private String value = "";
private Color borderCol = Color.white;
private Color textCol = Color.white;
private Color backgroundCol = new Color(0, 0, 0, 0.5f);
/** The font used to render text in the field */
private Font font;
/** The border color - null if no border */
private Color border = Color.white;
/** The text color */
private Color text = Color.white;
/** The background color - null if no background */
private Color background = new Color(0, 0, 0, 0.5f);
/** The current cursor position */
private int cursorPos;
/** True if the cursor should be visible */
private boolean visibleCursor = true;
/** The last key pressed */
private int lastKey = -1;
/** The last character pressed */
private char lastChar = 0;
/** The time since last key repeat */
private long repeatTimer;
/** The text before the paste in */
private String oldText;
/** The cursor position before the paste */
private int oldCursorPos;
/** True if events should be consumed by the field */
private boolean consume = true;
/**
* Create a new text field
*
* @param container
* The container rendering this field
* @param font
* The font to use in the text field
* @param x
* The x coordinate of the top left corner of the text field
* @param y
* The y coordinate of the top left corner of the text field
* @param width
* The width of the text field
* @param height
* The height of the text field
* @param listener
* The listener to add to the text field
*/
public TextField(GUIContext container, Font font, int x, int y, int width,
int height, ComponentListener listener) {
this(container,font,x,y,width,height);
addListener(listener);
}
/**
* Create a new text field
*
* @param container
* The container rendering this field
* @param font
* The font to use in the text field
* @param x
* The x coordinate of the top left corner of the text field
* @param y
* The y coordinate of the top left corner of the text field
* @param width
* The width of the text field
* @param height
* The height of the text field
*/
public TextField(GUIContext container, Font font, int x, int y, int width,
int height) {
super(container);
private ActionListener listener;
public TextField(DisplayContainer displayContainer, Font font, int x, int y, int width, int height) {
this.displayContainer = displayContainer;
this.font = font;
setLocation(x, y);
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
/**
* Indicate if the input events should be consumed by this field
*
* @param consume True if events should be consumed by this field
*/
public void setConsumeEvents(boolean consume) {
this.consume = consume;
public void setListener(ActionListener listener) {
this.listener = listener;
}
/**
* Deactivate the key input handling for this field
*/
public void deactivate() {
setFocus(false);
public void setBorderColor(Color border) {
this.borderCol = border;
}
/**
* Moves the component.
*
* @param x
* X coordinate
* @param y
* Y coordinate
*/
public void setTextColor(Color text) {
this.textCol = text;
}
public void setBackgroundColor(Color background) {
this.backgroundCol = background;
}
@Override
public void setLocation(int x, int y) {
this.x = x;
this.y = y;
public boolean isFocusable() {
return true;
}
/**
* Returns the position in the X coordinate
*
* @return x
*/
@Override
public int getX() {
return x;
}
/**
* Returns the position in the Y coordinate
*
* @return y
*/
@Override
public int getY() {
return y;
}
/**
* Get the width of the component
*
* @return The width of the component
*/
@Override
public int getWidth() {
return width;
}
/**
* Get the height of the component
*
* @return The height of the component
*/
@Override
public int getHeight() {
return height;
}
/**
* Set the background color. Set to null to disable the background
*
* @param color
* The color to use for the background
*/
public void setBackgroundColor(Color color) {
background = color;
}
/**
* Set the border color. Set to null to disable the border
*
* @param color
* The color to use for the border
*/
public void setBorderColor(Color color) {
border = color;
}
/**
* Set the text color.
*
* @param color
* The color to use for the text
*/
public void setTextColor(Color color) {
text = color;
}
/**
* @see org.newdawn.slick.gui.AbstractComponent#render(org.newdawn.slick.gui.GUIContext,
* org.newdawn.slick.Graphics)
*/
@Override
public void render(GUIContext container, Graphics g) {
public void render(Graphics g) {
if (lastKey != -1) {
if (input.isKeyDown(lastKey)) {
if (displayContainer.input.isKeyDown(lastKey)) {
if (repeatTimer < System.currentTimeMillis()) {
repeatTimer = System.currentTimeMillis() + KEY_REPEAT_INTERVAL;
keyPressed(lastKey, lastChar);
@ -274,11 +112,11 @@ public class TextField extends AbstractComponent {
// Someone could have set a color for me to blend...
Color clr = g.getColor();
if (background != null) {
g.setColor(background.multiply(clr));
if (backgroundCol != null) {
g.setColor(backgroundCol.multiply(clr));
g.fillRect(x, y, width, height);
}
g.setColor(text.multiply(clr));
g.setColor(textCol.multiply(clr));
Font temp = g.getFont();
int cpos = font.getWidth(value.substring(0, cursorPos));
@ -291,14 +129,14 @@ public class TextField extends AbstractComponent {
g.setFont(font);
g.drawString(value, x + 1, y + 1);
if (hasFocus() && visibleCursor) {
g.drawString("_", x + 1 + cpos + 2, y + 1);
if (focused) {
g.drawString("|", x + 1 + cpos + 2, y + 1);
}
g.translate(-tx - 2, 0);
if (border != null) {
g.setColor(border.multiply(clr));
if (borderCol != null) {
g.setColor(borderCol.multiply(clr));
g.drawRect(x, y, width, height);
}
g.setColor(clr);
@ -307,21 +145,10 @@ public class TextField extends AbstractComponent {
g.setClip(oldClip);
}
/**
* Get the value in the text field
*
* @return The value in the text field
*/
public String getText() {
return value;
}
/**
* Set the value to be displayed in the text field
*
* @param value
* The value to be displayed in the text field
*/
public void setText(String value) {
this.value = value;
if (cursorPos > value.length()) {
@ -329,35 +156,6 @@ public class TextField extends AbstractComponent {
}
}
/**
* Set the position of the cursor
*
* @param pos
* The new position of the cursor
*/
public void setCursorPos(int pos) {
cursorPos = pos;
if (cursorPos > value.length()) {
cursorPos = value.length();
}
}
/**
* Indicate whether the mouse cursor should be visible or not
*
* @param visibleCursor
* True if the mouse cursor should be visible
*/
public void setCursorVisible(boolean visibleCursor) {
this.visibleCursor = visibleCursor;
}
/**
* Set the length of the allowed input
*
* @param length
* The length of the allowed input
*/
public void setMaxLength(int length) {
maxCharacter = length;
if (value.length() > maxCharacter) {
@ -365,173 +163,101 @@ public class TextField extends AbstractComponent {
}
}
/**
* Do the paste into the field, overrideable for custom behaviour
*
* @param text The text to be pasted in
*/
protected void doPaste(String text) {
recordOldPosition();
for (int i=0;i<text.length();i++) {
keyPressed(-1, text.charAt(i));
}
}
/**
* Record the old position and content
*/
protected void recordOldPosition() {
oldText = getText();
oldCursorPos = cursorPos;
}
/**
* Do the undo of the paste, overrideable for custom behaviour
*
* @param oldCursorPos before the paste
* @param oldText The text before the last paste
*/
protected void doUndo(int oldCursorPos, String oldText) {
if (oldText != null) {
setText(oldText);
setCursorPos(oldCursorPos);
}
}
/**
* @see org.newdawn.slick.gui.AbstractComponent#keyPressed(int, char)
*/
@Override
public void keyPressed(int key, char c) {
if (hasFocus()) {
if (key != -1)
{
if ((key == Input.KEY_V) &&
((input.isKeyDown(Input.KEY_LCONTROL)) || (input.isKeyDown(Input.KEY_RCONTROL)))) {
String text = Sys.getClipboard();
if (text != null) {
doPaste(text);
}
return;
if (key != -1)
{
if ((key == Input.KEY_V) &&
((displayContainer.input.isKeyDown(Input.KEY_LCONTROL)) || (displayContainer.input.isKeyDown(Input.KEY_RCONTROL)))) {
String text = Sys.getClipboard();
if (text != null) {
doPaste(text);
}
/* if ((key == Input.KEY_Z) &&
((input.isKeyDown(Input.KEY_LCONTROL)) || (input.isKeyDown(Input.KEY_RCONTROL)))) {
if (oldText != null) {
doUndo(oldCursorPos, oldText);
return;
}
}
if (lastKey != key) {
lastKey = key;
repeatTimer = System.currentTimeMillis() + INITIAL_KEY_REPEAT_INTERVAL;
} else {
repeatTimer = System.currentTimeMillis() + KEY_REPEAT_INTERVAL;
}
lastChar = c;
if (key == Input.KEY_LEFT) { /*
if (cursorPos > 0) {
cursorPos--;
}
// Nobody more will be notified
if (consume) {
container.getInput().consumeEvent();
}
*/ } else if (key == Input.KEY_RIGHT) { /*
if (cursorPos < value.length()) {
cursorPos++;
}
// Nobody more will be notified
if (consume) {
container.getInput().consumeEvent();
}
*/ } else if (key == Input.KEY_BACK) {
if ((cursorPos > 0) && (value.length() > 0)) {
if (displayContainer.input.isKeyDown(Input.KEY_LCONTROL) || displayContainer.input.isKeyDown(Input.KEY_RCONTROL)) {
int sp = 0;
boolean startSpace = Character.isWhitespace(value.charAt(cursorPos - 1));
boolean charSeen = false;
for (int i = cursorPos - 1; i >= 0; i--) {
boolean isSpace = Character.isWhitespace(value.charAt(i));
if (!startSpace && isSpace) {
sp = i;
break;
} else if (startSpace) {
if (charSeen && isSpace) {
sp = i + 1;
break;
} else if (!charSeen && !isSpace)
charSeen = true;
}
}
if (cursorPos < value.length())
value = value.substring(0, sp) + value.substring(cursorPos);
else
value = value.substring(0, sp);
cursorPos = sp;
} else {
if (cursorPos < value.length()) {
value = value.substring(0, cursorPos - 1)
+ value.substring(cursorPos);
} else {
value = value.substring(0, cursorPos - 1);
}
return;
} */
// alt and control keys don't come through here
/* if (input.isKeyDown(Input.KEY_LCONTROL) || input.isKeyDown(Input.KEY_RCONTROL)) {
return;
} */
if (input.isKeyDown(Input.KEY_LALT) || input.isKeyDown(Input.KEY_RALT)) {
return;
}
}
if (lastKey != key) {
lastKey = key;
repeatTimer = System.currentTimeMillis() + INITIAL_KEY_REPEAT_INTERVAL;
} else {
repeatTimer = System.currentTimeMillis() + KEY_REPEAT_INTERVAL;
}
lastChar = c;
if (key == Input.KEY_LEFT) { /*
if (cursorPos > 0) {
cursorPos--;
}
// Nobody more will be notified
if (consume) {
container.getInput().consumeEvent();
}
*/ } else if (key == Input.KEY_RIGHT) { /*
if (cursorPos < value.length()) {
cursorPos++;
}
// Nobody more will be notified
if (consume) {
container.getInput().consumeEvent();
}
*/ } else if (key == Input.KEY_BACK) {
if ((cursorPos > 0) && (value.length() > 0)) {
if (input.isKeyDown(Input.KEY_LCONTROL) || input.isKeyDown(Input.KEY_RCONTROL)) {
int sp = 0;
boolean startSpace = Character.isWhitespace(value.charAt(cursorPos - 1));
boolean charSeen = false;
for (int i = cursorPos - 1; i >= 0; i--) {
boolean isSpace = Character.isWhitespace(value.charAt(i));
if (!startSpace && isSpace) {
sp = i;
break;
} else if (startSpace) {
if (charSeen && isSpace) {
sp = i + 1;
break;
} else if (!charSeen && !isSpace)
charSeen = true;
}
}
if (cursorPos < value.length())
value = value.substring(0, sp) + value.substring(cursorPos);
else
value = value.substring(0, sp);
cursorPos = sp;
} else {
if (cursorPos < value.length()) {
value = value.substring(0, cursorPos - 1)
+ value.substring(cursorPos);
} else {
value = value.substring(0, cursorPos - 1);
}
cursorPos--;
}
}
// Nobody more will be notified
if (consume) {
container.getInput().consumeEvent();
}
} else if (key == Input.KEY_DELETE) {
if (value.length() > cursorPos) {
value = value.substring(0,cursorPos) + value.substring(cursorPos+1);
}
// Nobody more will be notified
if (consume) {
container.getInput().consumeEvent();
}
} else if ((c < 127) && (c > 31) && (value.length() < maxCharacter)) {
if (cursorPos < value.length()) {
value = value.substring(0, cursorPos) + c
+ value.substring(cursorPos);
} else {
value = value.substring(0, cursorPos) + c;
}
cursorPos++;
// Nobody more will be notified
if (consume) {
container.getInput().consumeEvent();
}
} else if (key == Input.KEY_RETURN) {
notifyListeners();
// Nobody more will be notified
if (consume) {
container.getInput().consumeEvent();
}
}
} else if (key == Input.KEY_DELETE) {
if (value.length() > cursorPos) {
value = value.substring(0,cursorPos) + value.substring(cursorPos+1);
}
} else if ((c < 127) && (c > 31) && (value.length() < maxCharacter)) {
if (cursorPos < value.length()) {
value = value.substring(0, cursorPos) + c
+ value.substring(cursorPos);
} else {
value = value.substring(0, cursorPos) + c;
}
cursorPos++;
} else if (key == Input.KEY_RETURN) {
if (listener != null) {
listener.onAction();
}
}
}
/**
* @see org.newdawn.slick.gui.AbstractComponent#setFocus(boolean)
*/
@Override
public void setFocus(boolean focus) {
lastKey = -1;
super.setFocus(focus);
}
}

View File

@ -19,9 +19,11 @@ package yugecin.opsudance;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.beatmap.BeatmapWatchService;
import itdelatrisu.opsu.db.DBController;
import itdelatrisu.opsu.downloads.DownloadList;
import itdelatrisu.opsu.downloads.Updater;
import itdelatrisu.opsu.states.Splash;
import org.newdawn.slick.util.Log;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
@ -61,23 +63,32 @@ public class OpsuDance {
initUpdater(args);
sout("database & updater initialized");
container.init(EmptyState.class);
//container.init(EmptyState.class);
container.init(Splash.class);
} catch (Exception e) {
errorAndExit("startup failure", e);
}
while (rungame());
container.teardownAL();
Options.saveOptions();
closeSingleInstanceSocket();
DBController.closeConnections();
DownloadList.get().cancelAllDownloads();
Utils.deleteDirectory(Options.TEMP_DIR);
if (!Options.isWatchServiceEnabled()) {
BeatmapWatchService.destroy();
}
}
private boolean rungame() {
try {
container.setup();
container.resume();
} catch (Exception e) {
errorAndExit("could not initialize GL", e);
ErrorHandler.error("could not initialize GL", e).allowTerminate().preventContinue().show();
return false;
}
Exception caughtException = null;
try {
@ -86,7 +97,8 @@ public class OpsuDance {
caughtException = e;
}
container.teardown();
return caughtException != null && ErrorHandler.error("update/render error", caughtException).show().shouldIgnoreAndContinue();
container.pause();
return caughtException != null && ErrorHandler.error("update/render error", caughtException).allowTerminate().show().shouldIgnoreAndContinue();
}
private void initDatabase() {
@ -160,7 +172,7 @@ public class OpsuDance {
}
private void errorAndExit(String errstr) {
ErrorHandler.error(errstr, new Throwable()).preventContinue().show();
ErrorHandler.error(errstr, new Throwable()).allowTerminate().preventContinue().show();
System.exit(1);
}

View File

@ -17,18 +17,23 @@
*/
package yugecin.opsudance.core;
import itdelatrisu.opsu.GameData;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.audio.MusicController;
import itdelatrisu.opsu.beatmap.Beatmap;
import itdelatrisu.opsu.downloads.DownloadList;
import itdelatrisu.opsu.downloads.Updater;
import itdelatrisu.opsu.render.CurveRenderState;
import itdelatrisu.opsu.ui.Cursor;
import itdelatrisu.opsu.ui.Fonts;
import org.lwjgl.LWJGLException;
import itdelatrisu.opsu.ui.UI;
import org.lwjgl.Sys;
import org.lwjgl.openal.AL;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.KeyListener;
import org.newdawn.slick.MouseListener;
import org.newdawn.slick.*;
import org.newdawn.slick.opengl.InternalTextureLoader;
import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.opengl.renderer.SGL;
@ -41,6 +46,7 @@ import yugecin.opsudance.core.state.specialstates.BarNotificationState;
import yugecin.opsudance.core.state.specialstates.BubbleNotificationState;
import yugecin.opsudance.core.state.specialstates.FpsRenderState;
import yugecin.opsudance.core.state.transitions.*;
import yugecin.opsudance.events.BubbleNotificationEvent;
import yugecin.opsudance.events.ResolutionChangedEvent;
import yugecin.opsudance.utils.GLHelper;
@ -70,10 +76,10 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
private OpsuState state;
private final DisplayMode nativeDisplayMode;
public final DisplayMode nativeDisplayMode;
private Graphics graphics;
private Input input;
public Input input;
public int width;
public int height;
@ -90,16 +96,27 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
public int renderDelta;
public int delta;
public boolean exitRequested;
public int timeSinceLastRender;
private long lastFrame;
private boolean wasMusicPlaying;
private String glVersion;
private String glVendor;
private long exitconfirmation;
public final Cursor cursor;
public boolean drawCursor;
public DisplayContainer(InstanceContainer instanceContainer, EventBus eventBus) {
this.instanceContainer = instanceContainer;
this.eventBus = eventBus;
this.cursor = new Cursor();
drawCursor = true;
outTransitionListener = new TransitionFinishedListener() {
@Override
@ -149,16 +166,21 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
}
public void run() throws LWJGLException {
while(!(Display.isCloseRequested() && state.onCloseRequest())) {
public void run() throws Exception {
while(!exitRequested && !(Display.isCloseRequested() && state.onCloseRequest()) || !confirmExit()) {
delta = getDelta();
timeSinceLastRender += delta;
input.poll(width, height);
Music.poll(delta);
mouseX = input.getMouseX();
mouseY = input.getMouseY();
state.update();
if (drawCursor) {
cursor.setCursorPosition(delta, mouseX, mouseY);
}
int maxRenderInterval;
if (Display.isVisible() && Display.isActive()) {
@ -185,6 +207,12 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
bubNotifState.render(graphics);
barNotifState.render(graphics);
cursor.updateAngle(renderDelta);
if (drawCursor) {
cursor.draw(input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON) || input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON));
}
UI.drawTooltip(graphics);
timeSinceLastRender = 0;
Display.update(false);
@ -193,28 +221,66 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
Display.processMessages();
Display.sync(targetUpdatesPerSecond);
}
teardown();
}
public void setup() throws Exception {
width = height = -1;
Input.disableControllers();
Display.setTitle("opsu!dance");
// temp displaymode to not flash the screen with a 1ms black window
Display.setDisplayMode(new DisplayMode(100, 100));
Options.setDisplayMode(this);
Display.create();
GLHelper.setIcons(new String[] { "icon16.png", "icon32.png" });
setDisplayMode(800, 600, false);
sout("GL ready");
initGL();
glVersion = GL11.glGetString(GL11.GL_VERSION);
glVendor = GL11.glGetString(GL11.GL_VENDOR);
GLHelper.hideNativeCursor();
}
public void teardown() {
InternalTextureLoader.get().clear();
GameImage.destroyImages();
GameData.Grade.destroyImages();
Beatmap.destroyBackgroundImageCache();
CurveRenderState.shutdown();
Display.destroy();
}
public void teardownAL() {
AL.destroy();
}
public void pause() {
wasMusicPlaying = MusicController.isPlaying();
if (wasMusicPlaying) {
MusicController.pause();
}
}
public void resume() {
if (wasMusicPlaying) {
MusicController.resume();
}
}
private boolean confirmExit() {
if (System.currentTimeMillis() - exitconfirmation < 10000) {
return true;
}
if (DownloadList.get().hasActiveDownloads()) {
eventBus.post(new BubbleNotificationEvent(DownloadList.EXIT_CONFIRMATION, BubbleNotificationEvent.COMMONCOLOR_PURPLE));
exitRequested = false;
exitconfirmation = System.currentTimeMillis();
return false;
}
if (Updater.get().getStatus() == Updater.Status.UPDATE_DOWNLOADING) {
eventBus.post(new BubbleNotificationEvent(Updater.EXIT_CONFIRMATION, BubbleNotificationEvent.COMMONCOLOR_PURPLE));
exitRequested = false;
exitconfirmation = System.currentTimeMillis();
return false;
}
return true;
}
public void setDisplayMode(int width, int height, boolean fullscreen) throws Exception {
if (this.width == width && this.height == height) {
Display.setFullscreen(fullscreen);
@ -231,6 +297,7 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
if (fullscreen) {
fullscreen = false;
Log.warn("could not find fullscreen displaymode for " + width + "x" + height);
eventBus.post(new BubbleNotificationEvent("Fullscreen mode is not supported for " + width + "x" + height, BubbleNotificationEvent.COLOR_ORANGE));
}
}
@ -240,9 +307,9 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
Display.setDisplayMode(displayMode);
Display.setFullscreen(fullscreen);
initGL();
eventBus.post(new ResolutionChangedEvent(this.width, this.height));
if (Display.isCreated()) {
initGL();
}
if (displayMode.getBitsPerPixel() == 16) {
InternalTextureLoader.get().set16BitMode();
@ -257,11 +324,20 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
graphics.setAntiAlias(false);
input = new Input(height);
input.enableKeyRepeat();
input.addKeyListener(this);
input.addMouseListener(this);
sout("GL ready");
GameImage.init(width, height);
Fonts.init();
eventBus.post(new ResolutionChangedEvent(this.width, this.height));
}
public void resetCursor() {
cursor.reset(mouseX, mouseY);
}
private int getDelta() {
@ -292,6 +368,10 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
state.writeErrorDump(dump);
}
public boolean isInState(Class<? extends OpsuState> state) {
return state.isInstance(state);
}
public boolean isTransitioning() {
return state instanceof TransitionState;
}
@ -301,7 +381,13 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
}
public void switchStateNow(Class<? extends OpsuState> newState) {
switchState(newState, EmptyTransitionState.class, 0, EmptyTransitionState.class, 0);
switchState(newState, EmptyTransitionState.class, 0, FadeInTransitionState.class, 300);
}
public void switchStateInstantly(Class<? extends OpsuState> newState) {
state.leave();
state = instanceContainer.provide(newState);
state.enter();
}
public void switchState(Class<? extends OpsuState> newState, Class<? extends TransitionState> outTransition, int outTime, Class<? extends TransitionState> inTransition, int inTime) {
@ -353,7 +439,9 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
public void mouseMoved(int oldx, int oldy, int newx, int newy) { }
@Override
public void mouseDragged(int oldx, int oldy, int newx, int newy) { }
public void mouseDragged(int oldx, int oldy, int newx, int newy) {
state.mouseDragged(oldx, oldy, newx, newy);
}
@Override
public void setInput(Input input) { }

View File

@ -17,6 +17,7 @@
*/
package yugecin.opsudance.core;
import itdelatrisu.opsu.downloads.Updater;
import yugecin.opsudance.OpsuDance;
import yugecin.opsudance.core.inject.OpsuDanceInjector;
@ -27,6 +28,10 @@ public class Entrypoint {
public static void main(String[] args) {
sout("launched");
(new OpsuDanceInjector()).provide(OpsuDance.class).start(args);
if (Updater.get().getStatus() == Updater.Status.UPDATE_FINAL) {
Updater.get().runUpdate();
}
}
public static long runtime() {

View File

@ -0,0 +1,24 @@
/*
* opsu!dance - fork of opsu! with cursordance auto
* Copyright (C) 2017 yugecin
*
* opsu!dance 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!dance 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!dance. If not, see <http://www.gnu.org/licenses/>.
*/
package yugecin.opsudance.core.components;
public interface ActionListener {
void onAction();
}

View File

@ -0,0 +1,60 @@
/*
* opsu!dance - fork of opsu! with cursordance auto
* Copyright (C) 2017 yugecin
*
* opsu!dance 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!dance 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!dance. If not, see <http://www.gnu.org/licenses/>.
*/
package yugecin.opsudance.core.components;
import org.newdawn.slick.Graphics;
public abstract class Component {
public int width;
public int height;
public int x;
public int y;
protected boolean focused;
protected boolean hovered;
public abstract boolean isFocusable();
public boolean isHovered() {
return hovered;
}
public void updateHover(int x, int y) {
this.hovered = this.x <= x && x <= this.x + width && this.y <= y && y <= this.y + height;
}
public void mouseReleased(int button) {
}
public void preRenderUpdate() {
}
public abstract void render(Graphics g);
public void keyPressed(int key, char c) {
}
public void keyReleased(int key, char c) {
}
public void setFocused(boolean focused) {
this.focused = focused;
}
}

View File

@ -52,6 +52,7 @@ public class ErrorHandler {
private boolean preventContinue;
private boolean preventReport;
private boolean ignoreAndContinue;
private boolean allowTerminate;
public ErrorHandler(DisplayContainer displayContainer) {
this.displayContainer = displayContainer;
@ -95,6 +96,11 @@ public class ErrorHandler {
return this;
}
public ErrorHandler allowTerminate() {
allowTerminate = true;
return this;
}
public ErrorHandler preventContinue() {
preventContinue = true;
return this;
@ -127,7 +133,9 @@ public class ErrorHandler {
Object[] messageComponents = new Object[] { message, new JScrollPane(textArea), createViewLogButton(), createReportButton() };
String[] buttons;
if (preventContinue) {
if (!allowTerminate && !preventContinue) {
buttons = new String[] { "Ignore & continue" };
} else if (preventContinue) {
buttons = new String[] { "Terminate" };
} else {
buttons = new String[] { "Terminate", "Ignore & continue" };
@ -145,7 +153,7 @@ public class ErrorHandler {
null,
buttons,
buttons[buttons.length - 1]);
ignoreAndContinue = result == 1;
ignoreAndContinue = !allowTerminate || result == 1;
frame.dispose();
return this;

View File

@ -22,10 +22,14 @@ import java.util.*;
@SuppressWarnings("unchecked")
public class EventBus {
@Deprecated
public static EventBus instance; // TODO get rid of this
private final List<Subscriber> subscribers;
public EventBus() {
subscribers = new LinkedList<>();
instance = this;
}
public <T> void subscribe(Class<T> eventType, EventListener<T> eventListener) {

View File

@ -17,6 +17,7 @@
*/
package yugecin.opsudance.core.inject;
import itdelatrisu.opsu.states.*;
import yugecin.opsudance.PreStartupInitializer;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.events.EventBus;
@ -50,6 +51,15 @@ public class OpsuDanceInjector extends Injector {
bind(EmptyRedState.class).asEagerSingleton();
bind(EmptyState.class).asEagerSingleton();
bind(Splash.class).asEagerSingleton();
bind(MainMenu.class).asEagerSingleton();
bind(ButtonMenu.class).asEagerSingleton();
bind(SongMenu.class).asEagerSingleton();
bind(DownloadsMenu.class).asEagerSingleton();
bind(Game.class).asEagerSingleton();
bind(GameRanking.class).asEagerSingleton();
bind(GamePauseMenu.class).asEagerSingleton();
}
}

View File

@ -17,6 +17,10 @@
*/
package yugecin.opsudance.core.state;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.events.EventListener;
import yugecin.opsudance.events.ResolutionChangedEvent;
@ -41,6 +45,18 @@ public abstract class BaseOpsuState implements OpsuState, EventListener<Resoluti
protected void revalidate() {
}
@Override
public void update() {
}
@Override
public void preRenderUpdate() {
}
@Override
public void render(Graphics g) {
}
@Override
public void onEvent(ResolutionChangedEvent event) {
if (isCurrentState) {
@ -76,6 +92,18 @@ public abstract class BaseOpsuState implements OpsuState, EventListener<Resoluti
@Override
public boolean keyReleased(int key, char c) {
if (key == Input.KEY_F7) {
Options.setNextFPS(displayContainer);
return true;
}
if (key == Input.KEY_F10) {
Options.toggleMouseDisabled();
return true;
}
if (key == Input.KEY_F12) {
Utils.takeScreenShot();
return true;
}
return false;
}
@ -94,6 +122,11 @@ public abstract class BaseOpsuState implements OpsuState, EventListener<Resoluti
return false;
}
@Override
public boolean mouseDragged(int oldx, int oldy, int newx, int newy) {
return false;
}
@Override
public void writeErrorDump(StringWriter dump) {
dump.append("> BaseOpsuState dump\n");

View File

@ -0,0 +1,194 @@
/*
* opsu!dance - fork of opsu! with cursordance auto
* Copyright (C) 2017 yugecin
*
* opsu!dance 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!dance 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!dance. If not, see <http://www.gnu.org/licenses/>.
*/
package yugecin.opsudance.core.state;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.components.Component;
import java.util.LinkedList;
public abstract class ComplexOpsuState extends BaseOpsuState {
protected final LinkedList<Component> components;
protected final LinkedList<OverlayOpsuState> overlays;
private Component focusedComponent;
public ComplexOpsuState(DisplayContainer displayContainer) {
super(displayContainer);
this.components = new LinkedList<>();
this.overlays = new LinkedList<>();
}
public final void focusComponent(Component component) {
if (!component.isFocusable()) {
return;
}
if (focusedComponent != null) {
focusedComponent.setFocused(false);
}
focusedComponent = component;
component.setFocused(true);
}
public boolean isAnyComponentFocused() {
return focusedComponent != null || isAnyOverlayActive();
}
public boolean isAnyOverlayActive() {
for (OverlayOpsuState overlay : overlays) {
if (overlay.active) {
return true;
}
}
return false;
}
@Override
public boolean mouseWheelMoved(int delta) {
for (OverlayOpsuState overlay : overlays) {
if (overlay.mouseWheelMoved(delta)) {
return true;
}
}
return false;
}
@Override
public boolean mousePressed(int button, int x, int y) {
for (OverlayOpsuState overlay : overlays) {
if (overlay.mousePressed(button, x, y)) {
return true;
}
}
return false;
}
@Override
public boolean mouseDragged(int oldx, int oldy, int newx, int newy) {
for (OverlayOpsuState overlay : overlays) {
if (overlay.mouseDragged(oldx, oldy, newx, newy)) {
return true;
}
}
return false;
}
@Override
public boolean mouseReleased(int button, int x, int y) {
for (OverlayOpsuState overlay : overlays) {
if (overlay.mouseReleased(button, x, y)) {
return true;
}
}
if (focusedComponent == null) {
for (Component component : components) {
if (!component.isFocusable()) {
continue;
}
component.updateHover(x, y);
if (component.isHovered()) {
focusedComponent = component;
focusedComponent.setFocused(true);
return true;
}
}
return false;
}
focusedComponent.updateHover(x, y);
if (focusedComponent.isHovered()) {
focusedComponent.mouseReleased(button);
return true;
}
focusedComponent.setFocused(false);
focusedComponent = null;
return true;
}
@Override
public void preRenderUpdate() {
super.preRenderUpdate();
for (Component component : components) {
component.updateHover(displayContainer.mouseX, displayContainer.mouseY);
component.preRenderUpdate();
}
for (OverlayOpsuState overlay : overlays) {
overlay.preRenderUpdate();
}
}
@Override
protected void revalidate() {
super.revalidate();
for (OverlayOpsuState overlay : overlays) {
overlay.revalidate();
}
}
@Override
public void render(Graphics g) {
for (OverlayOpsuState overlay : overlays) {
overlay.render(g);
}
super.render(g);
}
@Override
public boolean keyReleased(int key, char c) {
if (super.keyReleased(key, c)) {
return true;
}
for (OverlayOpsuState overlay : overlays) {
if (overlay.keyReleased(key, c)) {
return true;
}
}
if (focusedComponent != null) {
if (key == Input.KEY_ESCAPE) {
focusedComponent.setFocused(false);
focusedComponent = null;
return true;
}
focusedComponent.keyReleased(key, c);
return true;
}
return false;
}
@Override
public boolean keyPressed(int key, char c) {
for (OverlayOpsuState overlay : overlays) {
if (overlay.keyPressed(key, c)) {
return true;
}
}
if (focusedComponent != null) {
if (key == Input.KEY_ESCAPE) {
focusedComponent.setFocused(false);
focusedComponent = null;
return true;
}
focusedComponent.keyPressed(key, c);
return true;
}
return false;
}
}

View File

@ -58,4 +58,9 @@ public interface OpsuState extends ErrorDumpable {
*/
boolean mouseReleased(int button, int x, int y);
/**
* @return false to stop event bubbling
*/
boolean mouseDragged(int oldx, int oldy, int newx, int newy);
}

View File

@ -0,0 +1,122 @@
/*
* opsu!dance - fork of opsu! with cursordance auto
* Copyright (C) 2017 yugecin
*
* opsu!dance 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!dance 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!dance. If not, see <http://www.gnu.org/licenses/>.
*/
package yugecin.opsudance.core.state;
import org.newdawn.slick.Graphics;
import java.io.StringWriter;
public abstract class OverlayOpsuState implements OpsuState {
protected boolean active;
protected boolean acceptInput;
public void hide() {
acceptInput = active = false;
}
public void show() {
acceptInput = active = true;
}
@Override
public final void update() {
}
public void revalidate() {
}
protected abstract void onPreRenderUpdate();
@Override
public final void preRenderUpdate() {
if (active) {
onPreRenderUpdate();
}
}
protected abstract void onRender(Graphics g);
@Override
public final void render(Graphics g) {
if (active) {
onRender(g);
}
}
@Override
public final void enter() {
}
@Override
public final void leave() {
}
@Override
public final boolean onCloseRequest() {
return true;
}
protected abstract boolean onKeyPressed(int key, char c);
@Override
public final boolean keyPressed(int key, char c) {
return acceptInput && onKeyPressed(key, c);
}
protected abstract boolean onKeyReleased(int key, char c);
@Override
public final boolean keyReleased(int key, char c) {
return acceptInput && onKeyReleased(key, c);
}
protected abstract boolean onMouseWheelMoved(int delta);
@Override
public final boolean mouseWheelMoved(int delta) {
return acceptInput && onMouseWheelMoved(delta);
}
protected abstract boolean onMousePressed(int button, int x, int y);
@Override
public final boolean mousePressed(int button, int x, int y) {
return acceptInput && onMousePressed(button, x, y);
}
protected abstract boolean onMouseReleased(int button, int x, int y);
@Override
public final boolean mouseReleased(int button, int x, int y) {
return acceptInput && onMouseReleased(button, x, y);
}
protected abstract boolean onMouseDragged(int oldx, int oldy, int newx, int newy);
@Override
public final boolean mouseDragged(int oldx, int oldy, int newx, int newy) {
return acceptInput && onMouseDragged(oldx, oldy, newx, newy);
}
@Override
public void writeErrorDump(StringWriter dump) {
dump.append("> OverlayOpsuState dump\n");
dump.append("accepts input: ").append(String.valueOf(acceptInput)).append(" is active: ").append(String.valueOf(active));
}
}

View File

@ -43,16 +43,16 @@ public class FpsRenderState implements EventListener<ResolutionChangedEvent> {
public void render(Graphics g) {
int x = this.x;
int target = displayContainer.targetRenderInterval - (displayContainer.targetUpdateInterval % displayContainer.targetRenderInterval);
int target = displayContainer.targetRenderInterval + (displayContainer.targetUpdateInterval % displayContainer.targetRenderInterval);
x = drawText(g, getColor(target, displayContainer.renderDelta), (1000 / displayContainer.renderDelta) + " fps", x, this.y);
drawText(g, getColor(displayContainer.targetUpdateInterval, displayContainer.delta), (1000 / displayContainer.delta) + " ups", x, this.y);
}
private Color getColor(int targetValue, int realValue) {
if (realValue >= targetValue) {
if (realValue <= targetValue) {
return GREEN;
}
if (realValue >= targetValue * 0.9f) {
if (realValue <= targetValue * 1.15f) {
return ORANGE;
}
return DARKORANGE;

View File

@ -21,10 +21,11 @@ import org.newdawn.slick.Color;
public class BubbleNotificationEvent {
public static final Color COMMONCOLOR_RED = new Color(138, 72, 51);
public static final Color COMMONCOLOR_GREEN = new Color(98, 131, 59);
public static final Color COMMONCOLOR_WHITE = new Color(220, 220, 220);
public static final Color COMMONCOLOR_PURPLE = new Color(94, 46, 149);
public static final Color COMMONCOLOR_RED = new Color(141, 49, 16);
public static final Color COLOR_ORANGE = new Color(138, 72, 51);
public final String message;
public final Color borderColor;

View File

@ -25,6 +25,8 @@ import itdelatrisu.opsu.ui.animations.AnimationEquation;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.state.OverlayOpsuState;
import yugecin.opsudance.sbv2.movers.CubicStoryboardMover;
import yugecin.opsudance.sbv2.movers.LinearStoryboardMover;
import yugecin.opsudance.sbv2.movers.QuadraticStoryboardMover;
@ -34,20 +36,20 @@ import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MoveStoryboard {
public class MoveStoryboard extends OverlayOpsuState{
private final SimpleButton btnAddLinear;
private final SimpleButton btnAddQuadratic;
private final SimpleButton btnAddCubic;
private final DisplayContainer displayContainer;
private final SimpleButton btnAnimLin;
private final SimpleButton btnAnimMid;
private final SimpleButton btnAnimCub;
private SimpleButton btnAddLinear;
private SimpleButton btnAddQuadratic;
private SimpleButton btnAddCubic;
private SimpleButton btnAnimLin;
private SimpleButton btnAnimMid;
private SimpleButton btnAnimCub;
private final StoryboardMove dummyMove;
private int width;
private StoryboardMove[] moves;
private GameObject[] gameObjects;
@ -55,14 +57,8 @@ public class MoveStoryboard {
private int trackPosition;
public MoveStoryboard(GameContainer container) {
this.width = container.getWidth();
btnAddLinear = new SimpleButton(width - 205, 50, 200, 25, Fonts.SMALL, "add linear", Colors.BLUE_BUTTON, Colors.WHITE_FADE, Colors.WHITE_FADE, Colors.ORANGE_BUTTON);
btnAddQuadratic = new SimpleButton(width - 205, 80, 200, 25, Fonts.SMALL, "add quadratic", Colors.BLUE_BUTTON, Colors.WHITE_FADE, Colors.WHITE_FADE, Colors.ORANGE_BUTTON);
btnAddCubic = new SimpleButton(width - 205, 110, 200, 25, Fonts.SMALL, "add cubic", Colors.BLUE_BUTTON, Colors.WHITE_FADE, Colors.WHITE_FADE, Colors.ORANGE_BUTTON);
btnAnimLin = new SimpleButton(width - 250, 50, 40, 25, Fonts.SMALL, "lin", Color.blue, Color.white, Color.white, Color.orange);
btnAnimMid = new SimpleButton(width - 250, 80, 40, 25, Fonts.SMALL, "mid", Color.blue, Color.white, Color.white, Color.orange);
btnAnimCub = new SimpleButton(width - 250, 110, 40, 25, Fonts.SMALL, "cub", Color.blue, Color.white, Color.white, Color.orange);
public MoveStoryboard(DisplayContainer displayContainer) {
this.displayContainer = displayContainer;
dummyMove = (StoryboardMove) Proxy.newProxyInstance(StoryboardMove.class.getClassLoader(), new Class<?>[]{StoryboardMove.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
@ -71,6 +67,16 @@ public class MoveStoryboard {
});
}
@Override
public void revalidate() {
btnAddLinear = new SimpleButton(displayContainer.width - 205, 50, 200, 25, Fonts.SMALL, "add linear", Colors.BLUE_BUTTON, Colors.WHITE_FADE, Colors.WHITE_FADE, Colors.ORANGE_BUTTON);
btnAddQuadratic = new SimpleButton(displayContainer.width - 205, 80, 200, 25, Fonts.SMALL, "add quadratic", Colors.BLUE_BUTTON, Colors.WHITE_FADE, Colors.WHITE_FADE, Colors.ORANGE_BUTTON);
btnAddCubic = new SimpleButton(displayContainer.width - 205, 110, 200, 25, Fonts.SMALL, "add cubic", Colors.BLUE_BUTTON, Colors.WHITE_FADE, Colors.WHITE_FADE, Colors.ORANGE_BUTTON);
btnAnimLin = new SimpleButton(displayContainer.width - 250, 50, 40, 25, Fonts.SMALL, "lin", Color.blue, Color.white, Color.white, Color.orange);
btnAnimMid = new SimpleButton(displayContainer.width - 250, 80, 40, 25, Fonts.SMALL, "mid", Color.blue, Color.white, Color.white, Color.orange);
btnAnimCub = new SimpleButton(displayContainer.width - 250, 110, 40, 25, Fonts.SMALL, "cub", Color.blue, Color.white, Color.white, Color.orange);
}
/**
* Get the point at the current time
* @param trackPosition current time in ms
@ -88,7 +94,33 @@ public class MoveStoryboard {
return moves[objectIndex].getPointAt(t);
}
public void render(Graphics g) {
@Override
public void hide() {
}
@Override
public void show() {
}
@Override
protected void onPreRenderUpdate() {
int x = displayContainer.mouseX;
int y = displayContainer.mouseY;
btnAddLinear.update(x, y);
btnAddQuadratic.update(x, y);
btnAddCubic.update(x, y);
btnAnimLin.update(x, y);
btnAnimMid.update(x, y);
btnAnimCub.update(x, y);
if (moves[objectIndex] != null) {
moves[objectIndex].update(displayContainer.renderDelta, x, y);
}
}
@Override
protected void onRender(Graphics g) {
btnAddLinear.render(g);
btnAddQuadratic.render(g);
btnAddCubic.render(g);
@ -100,13 +132,31 @@ public class MoveStoryboard {
}
}
public void mousePressed(int x, int y) {
@Override
protected boolean onKeyPressed(int key, char c) {
return false;
}
@Override
protected boolean onKeyReleased(int key, char c) {
return false;
}
@Override
protected boolean onMouseWheelMoved(int delta) {
return false;
}
@Override
protected boolean onMousePressed(int button, int x, int y) {
if (moves[objectIndex] != null) {
moves[objectIndex].mousePressed(x, y);
}
return true;
}
public void mouseReleased(int x, int y) {
@Override
protected boolean onMouseReleased(int button, int x, int y) {
if (moves[objectIndex] != null) {
moves[objectIndex].mouseReleased(x, y);
if (moves[objectIndex].getAmountOfMovers() == 0) {
@ -114,7 +164,7 @@ public class MoveStoryboard {
}
}
if (objectIndex == 0) {
return;
return true;
}
if (btnAddLinear.isHovered()) {
getCurrentMoveOrCreateNew().add(new LinearStoryboardMover());
@ -134,6 +184,12 @@ public class MoveStoryboard {
if (btnAnimCub.isHovered()) {
getCurrentMoveOrDummy().setAnimationEquation(AnimationEquation.IN_OUT_EASE_MIDDLE);
}
return true;
}
@Override
protected boolean onMouseDragged(int oldx, int oldy, int newx, int newy) {
return false;
}
private StoryboardMove getCurrentMoveOrCreateNew() {
@ -142,7 +198,7 @@ public class MoveStoryboard {
return dummyMove;
}
if (moves[objectIndex] == null) {
return moves[objectIndex] = new StoryboardMoveImpl(gameObjects[objectIndex - 1].end, gameObjects[objectIndex].start, width);
return moves[objectIndex] = new StoryboardMoveImpl(gameObjects[objectIndex - 1].end, gameObjects[objectIndex].start, displayContainer.width);
}
return moves[objectIndex];
}
@ -154,18 +210,6 @@ public class MoveStoryboard {
return moves[objectIndex];
}
public void update(int delta, int x, int y) {
btnAddLinear.update(x, y);
btnAddQuadratic.update(x, y);
btnAddCubic.update(x, y);
btnAnimLin.update(x, y);
btnAnimMid.update(x, y);
btnAnimCub.update(x, y);
if (moves[objectIndex] != null) {
moves[objectIndex].update(delta, x, y);
}
}
public void setGameObjects(GameObject[] gameObjects) {
this.gameObjects = gameObjects;
this.moves = new StoryboardMove[gameObjects.length];

View File

@ -100,6 +100,11 @@ public class EmptyRedState implements OpsuState {
return false;
}
@Override
public boolean mouseDragged(int oldx, int oldy, int newx, int newy) {
return false;
}
@Override
public void writeErrorDump(StringWriter dump) {
dump.append("> EmptyRedState dump\n");

View File

@ -92,6 +92,11 @@ public class EmptyState implements OpsuState {
return false;
}
@Override
public boolean mouseDragged(int oldx, int oldy, int newx, int newy) {
return false;
}
@Override
public void writeErrorDump(StringWriter dump) {
dump.append("> EmptyState dump\n");

View File

@ -29,16 +29,18 @@ import itdelatrisu.opsu.ui.Fonts;
import itdelatrisu.opsu.ui.MenuButton;
import itdelatrisu.opsu.ui.UI;
import org.newdawn.slick.*;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.state.OverlayOpsuState;
@SuppressWarnings("UnusedParameters")
public class OptionsOverlay {
public class OptionsOverlay extends OverlayOpsuState {
private Parent parent;
private GameContainer container;
private final DisplayContainer displayContainer;
private final Image sliderBallImg;
private final Image checkOnImg;
private final Image checkOffImg;
private Listener listener;
private Image sliderBallImg;
private Image checkOnImg;
private Image checkOffImg;
private OptionTab[] tabs;
private int selectedTab;
@ -79,21 +81,29 @@ public class OptionsOverlay {
private int sliderSoundDelay;
public OptionsOverlay(Parent parent, OptionTab[] tabs, int defaultSelectedTabIndex, GameContainer container) {
this.parent = parent;
this.container = container;
public OptionsOverlay(DisplayContainer displayContainer, OptionTab[] tabs, int defaultSelectedTabIndex) {
this.displayContainer = displayContainer;
this.tabs = tabs;
selectedTab = defaultSelectedTabIndex;
listHoverIndex = -1;
}
public void setListener(Listener listener) {
this.listener = listener;
}
@Override
public void revalidate() {
super.revalidate();
sliderBallImg = GameImage.CONTROL_SLIDER_BALL.getImage().getScaledCopy(20, 20);
checkOnImg = GameImage.CONTROL_CHECK_ON.getImage().getScaledCopy(20, 20);
checkOffImg = GameImage.CONTROL_CHECK_OFF.getImage().getScaledCopy(20, 20);
width = container.getWidth();
height = container.getHeight();
width = displayContainer.width;
height = displayContainer.height;
// calculate positions
optionWidth = width / 2;
@ -109,10 +119,12 @@ public class OptionsOverlay {
maxScrollOffset = Fonts.MEDIUM.getLineHeight() * 2 * tabs.length;
scrollOffset = 0;
for (OptionTab tab : tabs) {
/*
if (defaultSelectedTabIndex-- > 0) {
scrollOffset += Fonts.MEDIUM.getLineHeight() * 2;
scrollOffset += tab.options.length * optionHeight;
}
*/
maxScrollOffset += tab.options.length * optionHeight;
tab.button = new MenuButton(tabImage, tabX, tabY);
tabX += tabOffset;
@ -127,7 +139,8 @@ public class OptionsOverlay {
optionStartY = (int) (tabY + tabImage.getHeight() / 2 + 2); // +2 for the separator line
}
public void render(Graphics g, int mouseX, int mouseY) {
@Override
public void onRender(Graphics g) {
// bg
g.setColor(Colors.BLACK_ALPHA_75);
g.fillRect(0, 0, width, height);
@ -136,7 +149,7 @@ public class OptionsOverlay {
renderTitle();
// option tabs
renderTabs(mouseX, mouseY);
renderTabs();
// line separator
g.setColor(Color.white);
@ -159,7 +172,7 @@ public class OptionsOverlay {
UI.getBackButton().draw();
// tooltip
renderTooltip(g, mouseX, mouseY);
renderTooltip(g);
// key input options
if (keyEntryLeft || keyEntryRight) {
@ -175,15 +188,10 @@ public class OptionsOverlay {
Fonts.LARGE.drawString((width - Fonts.LARGE.getWidth(prompt)) / 2, (height - Fonts.LARGE.getLineHeight()) / 2, prompt);
}
private void renderTooltip(Graphics g, int mouseX, int mouseY) {
private void renderTooltip(Graphics g) {
if (hoverOption != null) {
String optionDescription = hoverOption.getDescription();
float textWidth = Fonts.SMALL.getWidth(optionDescription);
Color.black.a = 0.7f;
g.setColor(Color.black);
g.fillRoundRect(mouseX + 10, mouseY + 10, 10 + textWidth, 10 + Fonts.SMALL.getLineHeight(), 4);
Fonts.SMALL.drawString(mouseX + 15, mouseY + 15, optionDescription, Color.white);
Color.black.a = 1f;
UI.updateTooltip(displayContainer.renderDelta, hoverOption.getDescription(), false);
UI.drawTooltip(g);
}
}
@ -322,10 +330,10 @@ public class OptionsOverlay {
Fonts.MEDIUM.drawString(optionStartX + optionWidth - valueLen, y, value, Colors.BLUE_BACKGROUND);
}
public void renderTabs(int mouseX, int mouseY) {
public void renderTabs() {
for (int i = 0; i < tabs.length; i++) {
OptionTab tab = tabs[i];
boolean hovering = tab.button.contains(mouseX, mouseY);
boolean hovering = tab.button.contains(displayContainer.mouseX, displayContainer.mouseY);
UI.drawTab(tab.button.getX(), tab.button.getY(), tab.name, i == selectedTab, hovering);
}
}
@ -339,7 +347,25 @@ public class OptionsOverlay {
Fonts.DEFAULT.drawString(marginX, marginY, "Change the way opsu! behaves", Color.white);
}
public void update(int delta, int mouseX, int mouseY) {
@Override
public void hide() {
acceptInput = false;
SoundController.playSound(SoundEffect.MENUBACK);
active = false;
}
@Override
public void show() {
acceptInput = true;
active = true;
}
@Override
public void onPreRenderUpdate() {
int mouseX = displayContainer.mouseX;
int mouseY = displayContainer.mouseY;
int delta = displayContainer.renderDelta;
if (sliderSoundDelay > 0) {
sliderSoundDelay -= delta;
}
@ -352,7 +378,7 @@ public class OptionsOverlay {
UI.getBackButton().hoverUpdate(delta, mouseX, mouseY);
if (isAdjustingSlider) {
int sliderValue = hoverOption.getIntegerValue();
updateSliderOption(mouseX, mouseY);
updateSliderOption();
if (hoverOption.getIntegerValue() - sliderValue != 0 && sliderSoundDelay <= 0) {
sliderSoundDelay = 90;
SoundController.playSound(SoundEffect.MENUHIT);
@ -366,22 +392,27 @@ public class OptionsOverlay {
}
}
public void mousePressed(int button, int x, int y) {
@Override
public boolean onMousePressed(int button, int x, int y) {
if (keyEntryLeft || keyEntryRight) {
keyEntryLeft = keyEntryRight = false;
return;
return true;
}
if (isListOptionOpen) {
if (y > optionStartY && listStartX <= x && x < listStartX + listWidth && listStartY <= y && y < listStartY + listHeight) {
hoverOption.clickListItem(listHoverIndex);
parent.onSaveOption(hoverOption);
if (0 <= listHoverIndex && listHoverIndex < hoverOption.getListItems().length) {
hoverOption.clickListItem(listHoverIndex);
if (listener != null) {
listener.onSaveOption(hoverOption);
}
}
SoundController.playSound(SoundEffect.MENUCLICK);
}
isListOptionOpen = false;
listHoverIndex = -1;
updateHoverOption(x, y);
return;
return true;
}
mousePressY = y;
@ -393,35 +424,39 @@ public class OptionsOverlay {
} else if (hoverOption.getType() == OptionType.NUMERIC) {
isAdjustingSlider = sliderOptionStartX <= x && x < sliderOptionStartX + sliderOptionLength;
if (isAdjustingSlider) {
updateSliderOption(x, y);
updateSliderOption();
}
}
}
if (UI.getBackButton().contains(x, y)) {
parent.onLeave();
hide();
}
return true;
}
public void mouseReleased(int button, int x, int y) {
@Override
public boolean onMouseReleased(int button, int x, int y) {
selectedOption = null;
if (isAdjustingSlider) {
parent.onSaveOption(hoverOption);
if (isAdjustingSlider && listener != null) {
listener.onSaveOption(hoverOption);
}
isAdjustingSlider = false;
sliderOptionLength = 0;
// check if clicked, not dragged
if (Math.abs(y - mousePressY) >= 5) {
return;
return true;
}
if (hoverOption != null) {
if (hoverOption.getType() == OptionType.BOOLEAN) {
hoverOption.click(container);
parent.onSaveOption(hoverOption);
hoverOption.click();
if (listener != null) {
listener.onSaveOption(hoverOption);
}
SoundController.playSound(SoundEffect.MENUHIT);
return;
return true;
} else if (hoverOption == GameOption.KEY_LEFT) {
keyEntryLeft = true;
} else if (hoverOption == GameOption.KEY_RIGHT) {
@ -435,27 +470,38 @@ public class OptionsOverlay {
if (tab.button.contains(x, y)) {
scrollOffset = tScrollOffset;
SoundController.playSound(SoundEffect.MENUCLICK);
return;
return true;
}
tScrollOffset += Fonts.MEDIUM.getLineHeight() * 2;
tScrollOffset += tab.options.length * optionHeight;
}
if (UI.getBackButton().contains(x, y) && listener != null) {
listener.onLeaveOptionsMenu();
}
return true;
}
public void mouseDragged(int oldx, int oldy, int newx, int newy) {
@Override
public boolean onMouseDragged(int oldx, int oldy, int newx, int newy) {
if (!isAdjustingSlider) {
scrollOffset = Utils.clamp(scrollOffset + oldy - newy, 0, maxScrollOffset);
}
return true;
}
public void mouseWheelMoved(int delta) {
@Override
public boolean onMouseWheelMoved(int delta) {
if (!isAdjustingSlider) {
scrollOffset = Utils.clamp(scrollOffset - delta, 0, maxScrollOffset);
}
updateHoverOption(prevMouseX, prevMouseY);
return true;
}
public boolean keyPressed(int key, char c) {
@Override
public boolean onKeyPressed(int key, char c) {
if (keyEntryRight) {
Options.setGameKeyRight(key);
keyEntryRight = false;
@ -468,23 +514,31 @@ public class OptionsOverlay {
return true;
}
switch (key) {
case Input.KEY_ESCAPE:
if (isListOptionOpen) {
isListOptionOpen = false;
listHoverIndex = -1;
return true;
}
parent.onLeave();
if (key == Input.KEY_ESCAPE) {
if (isListOptionOpen) {
isListOptionOpen = false;
listHoverIndex = -1;
return true;
}
hide();
if (listener != null) {
listener.onLeaveOptionsMenu();
}
return true;
}
return false;
}
private void updateSliderOption(int mouseX, int mouseY) {
@Override
public boolean onKeyReleased(int key, char c) {
return false;
}
private void updateSliderOption() {
int min = hoverOption.getMinValue();
int max = hoverOption.getMaxValue();
int value = min + Math.round((float) (max - min) * (mouseX - sliderOptionStartX) / (sliderOptionLength));
int value = min + Math.round((float) (max - min) * (displayContainer.mouseX - sliderOptionStartX) / (sliderOptionLength));
hoverOption.setValue(Utils.clamp(value, min, max));
}
@ -533,10 +587,9 @@ public class OptionsOverlay {
}
public interface Parent {
void onLeave();
public interface Listener {
void onLeaveOptionsMenu();
void onSaveOption(GameOption option);
}

View File

@ -22,86 +22,27 @@ import itdelatrisu.opsu.Options.GameOption;
import itdelatrisu.opsu.audio.MusicController;
import itdelatrisu.opsu.objects.GameObject;
import itdelatrisu.opsu.states.Game;
import itdelatrisu.opsu.states.OptionsMenu;
import itdelatrisu.opsu.ui.Fonts;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import yugecin.opsudance.ObjectColorOverrides;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.state.OverlayOpsuState;
import yugecin.opsudance.sbv2.MoveStoryboard;
import yugecin.opsudance.ui.OptionsOverlay.OptionTab;
import java.util.*;
@SuppressWarnings("unchecked")
public class SBOverlay implements OptionsOverlay.Parent {
private static final OptionTab[] options = new OptionsOverlay.OptionTab[]{
new OptionTab("Gameplay", new GameOption[] {
GameOption.BACKGROUND_DIM,
GameOption.DANCE_REMOVE_BG,
GameOption.SNAKING_SLIDERS,
GameOption.SHRINKING_SLIDERS,
GameOption.SHOW_HIT_LIGHTING,
GameOption.SHOW_HIT_ANIMATIONS,
GameOption.SHOW_COMBO_BURSTS,
GameOption.SHOW_PERFECT_HIT,
GameOption.SHOW_FOLLOW_POINTS,
}),
new OptionTab("Input", new GameOption[] {
GameOption.CURSOR_SIZE,
GameOption.NEW_CURSOR,
GameOption.DISABLE_CURSOR
}),
new OptionTab("Dance", new GameOption[] {
GameOption.DANCE_MOVER,
GameOption.DANCE_EXGON_DELAY,
GameOption.DANCE_QUAD_BEZ_AGGRESSIVENESS,
GameOption.DANCE_QUAD_BEZ_SLIDER_AGGRESSIVENESS_FACTOR,
GameOption.DANCE_QUAD_BEZ_USE_CUBIC_ON_SLIDERS,
GameOption.DANCE_QUAD_BEZ_CUBIC_AGGRESSIVENESS_FACTOR,
GameOption.DANCE_MOVER_DIRECTION,
GameOption.DANCE_SLIDER_MOVER_TYPE,
GameOption.DANCE_SPINNER,
GameOption.DANCE_SPINNER_DELAY,
GameOption.DANCE_LAZY_SLIDERS,
GameOption.DANCE_CIRCLE_STREAMS,
GameOption.DANCE_ONLY_CIRCLE_STACKS,
GameOption.DANCE_CIRLCE_IN_SLOW_SLIDERS,
GameOption.DANCE_CIRLCE_IN_LAZY_SLIDERS,
GameOption.DANCE_MIRROR,
}),
new OptionTab("Dance display", new GameOption[] {
GameOption.DANCE_DRAW_APPROACH,
GameOption.DANCE_OBJECT_COLOR_OVERRIDE,
GameOption.DANCE_OBJECT_COLOR_OVERRIDE_MIRRORED,
GameOption.DANCE_RGB_OBJECT_INC,
GameOption.DANCE_CURSOR_COLOR_OVERRIDE,
GameOption.DANCE_CURSOR_MIRROR_COLOR_OVERRIDE,
GameOption.DANCE_CURSOR_ONLY_COLOR_TRAIL,
GameOption.DANCE_RGB_CURSOR_INC,
GameOption.DANCE_CURSOR_TRAIL_OVERRIDE,
GameOption.DANCE_HIDE_OBJECTS,
GameOption.DANCE_HIDE_UI,
GameOption.DANCE_HIDE_WATERMARK,
}),
new OptionTab ("Pippi", new GameOption[] {
GameOption.PIPPI_ENABLE,
GameOption.PIPPI_RADIUS_PERCENT,
GameOption.PIPPI_ANGLE_INC_MUL,
GameOption.PIPPI_ANGLE_INC_MUL_SLIDER,
GameOption.PIPPI_SLIDER_FOLLOW_EXPAND,
GameOption.PIPPI_PREVENT_WOBBLY_STREAMS,
})
};
public class StoryboardOverlay extends OverlayOpsuState implements OptionsOverlay.Listener {
private final static List<GameOption> optionList = new ArrayList<>();
private boolean hide;
private boolean menu;
private final DisplayContainer displayContainer;
private int width;
private int height;
private boolean hide;
private int speed;
private GameObject[] gameObjects;
@ -112,43 +53,42 @@ public class SBOverlay implements OptionsOverlay.Parent {
private final Game game;
private final MoveStoryboard msb;
private final OptionsOverlay overlay;
private final OptionsOverlay optionsOverlay;
static {
for (OptionTab tab : options) {
for (OptionTab tab : OptionsMenu.storyboardOptions) {
optionList.addAll(Arrays.asList(tab.options));
}
}
public SBOverlay(Game game, MoveStoryboard msb, GameContainer container) {
this.game = game;
public StoryboardOverlay(DisplayContainer displayContainer, MoveStoryboard msb, OptionsOverlay optionsOverlay, Game game) {
this.displayContainer = displayContainer;
this.msb = msb;
this.optionsOverlay = optionsOverlay;
this.game = game;
initialOptions = new HashMap<>();
overlay = new OptionsOverlay(this, options, 2, container);
this.width = container.getWidth();
this.height = container.getHeight();
speed = 10;
gameObjects = new GameObject[0];
}
public void render(GameContainer container, Graphics g) {
@Override
public void onRender(Graphics g) {
if (!Options.isEnableSB() || hide) {
return;
}
msb.render(g);
int lh = Fonts.SMALL.getLineHeight();
Fonts.SMALL.drawString(10, height - 50 + lh, "save position: ctrl+s, load position: ctrl+l", Color.cyan);
Fonts.SMALL.drawString(10, height - 50, "speed: C " + (speed / 10f) + " V", Color.cyan);
Fonts.SMALL.drawString(10, height - 50 - lh, "Menu: N", Color.cyan);
Fonts.SMALL.drawString(10, height - 50 - lh * 2, "HIDE: H", Color.cyan);
Fonts.SMALL.drawString(10, height - 50 - lh * 3, "obj: J " + index + " K", Color.cyan);
Fonts.SMALL.drawString(10, displayContainer.height - 50 + lh, "save position: ctrl+s, load position: ctrl+l", Color.cyan);
Fonts.SMALL.drawString(10, displayContainer.height - 50, "speed: C " + (speed / 10f) + " V", Color.cyan);
Fonts.SMALL.drawString(10, displayContainer.height - 50 - lh, "Menu: N", Color.cyan);
Fonts.SMALL.drawString(10, displayContainer.height - 50 - lh * 2, "HIDE: H", Color.cyan);
Fonts.SMALL.drawString(10, displayContainer.height - 50 - lh * 3, "obj: J " + index + " K", Color.cyan);
g.setColor(Color.red);
if (index < optionsMap.length && optionsMap[index] != null) {
int i = 0;
for (Object o : optionsMap[index].entrySet()) {
Map.Entry<Options.GameOption, String> option = (Map.Entry<Options.GameOption, String>) o;
Fonts.SMALL.drawString(10, 50 + i * lh, option.getKey().getName(), Color.cyan);
Fonts.SMALL.drawString(width / 5, 50 + i * lh, option.getKey().getValueString(), Color.cyan);
Fonts.SMALL.drawString(displayContainer.width / 5, 50 + i * lh, option.getKey().getValueString(), Color.cyan);
g.fillRect(0, 50 + i * lh + lh / 4, 10, 10);
i++;
}
@ -157,27 +97,16 @@ public class SBOverlay implements OptionsOverlay.Parent {
int start = gameObjects[0].getTime();
int end = gameObjects[gameObjects.length - 1].getEndTime();
float curtime = (float) (MusicController.getPosition() - start) / (end - start);
g.fillRect(curtime * width, height - 10f, 10f, 10f);
}
if (menu) {
overlay.render(g, container.getInput().getMouseX(), container.getInput().getMouseY());
g.fillRect(curtime * displayContainer.width, displayContainer.height - 10f, 10f, 10f);
}
}
public void update(int delta, int mouseX, int mouseY) {
if (Options.isEnableSB() && menu) {
overlay.update(delta, mouseX, mouseY);
}
msb.update(delta, mouseX, mouseY);
@Override
public void onPreRenderUpdate() {
}
public boolean keyPressed(int key, char c) {
if (!Options.isEnableSB()) {
return false;
}
if (menu && overlay.keyPressed(key, c)) {
return true;
}
@Override
public boolean onKeyPressed(int key, char c) {
if (key == Input.KEY_C) {
if (speed > 0) {
speed -= 1;
@ -196,11 +125,9 @@ public class SBOverlay implements OptionsOverlay.Parent {
} else if (key == Input.KEY_H) {
hide = !hide;
} else if (key == Input.KEY_N) {
menu = !menu;
if (menu && speed != 0) {
optionsOverlay.show();
if (speed != 0) {
MusicController.pause();
} else if (!menu && speed != 0) {
MusicController.resume();
}
} else if (key == Input.KEY_J && index > 0) {
index--;
@ -214,6 +141,11 @@ public class SBOverlay implements OptionsOverlay.Parent {
return false;
}
@Override
protected boolean onKeyReleased(int key, char c) {
return false;
}
private void goBackOneSBIndex() {
if (index + 1 < optionsMap.length) {
// new options on previous index, so to revert then we have to reload them all to this point..
@ -252,20 +184,13 @@ public class SBOverlay implements OptionsOverlay.Parent {
this.gameObjects = gameObjects;
}
public boolean mousePressed(int button, int x, int y) {
msb.mousePressed(x, y);
if (!menu) {
return false;
}
overlay.mousePressed(button, x, y);
@Override
public boolean onMousePressed(int button, int x, int y) {
return true;
}
public boolean mouseDragged(int oldx, int oldy, int newx, int newy) {
if (!menu) {
return false;
}
overlay.mouseDragged(oldx, oldy, newx, newy);
@Override
public boolean onMouseDragged(int oldx, int oldy, int newx, int newy) {
return true;
}
@ -293,12 +218,8 @@ public class SBOverlay implements OptionsOverlay.Parent {
this.index--;
}
public boolean mouseReleased(int button, int x, int y) {
if (menu) {
overlay.mouseReleased(button, x, y);
return true;
}
msb.mouseReleased(x, y);
@Override
public boolean onMouseReleased(int button, int x, int y) {
if (x > 10 || index >= optionsMap.length || optionsMap[index] == null) {
return false;
}
@ -318,15 +239,12 @@ public class SBOverlay implements OptionsOverlay.Parent {
return true;
}
public boolean mouseWheelMoved(int delta) {
if (!menu) {
return false;
}
overlay.mouseWheelMoved(delta);
@Override
public boolean onMouseWheelMoved(int delta) {
return true;
}
public void enter() {
public void onEnter() {
// enter, save current settings
for (Options.GameOption o : optionList) {
initialOptions.put(o, o.write());
@ -334,7 +252,7 @@ public class SBOverlay implements OptionsOverlay.Parent {
speed = 10;
}
public void leave() {
public void onLeave() {
// leave, revert the settings saved before entering
for (Options.GameOption o : optionList) {
if (initialOptions.containsKey(o)) {
@ -358,13 +276,8 @@ public class SBOverlay implements OptionsOverlay.Parent {
}
}
public float[] getPoint(int trackPosition) {
return msb.getPoint(trackPosition);
}
@Override
public void onLeave() {
menu = false;
public void onLeaveOptionsMenu() {
if (speed != 0) {
MusicController.resume();
}

View File

@ -17,7 +17,10 @@
*/
package yugecin.opsudance.utils;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Cursor;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.newdawn.slick.opengl.ImageIOImageData;
@ -25,8 +28,10 @@ import org.newdawn.slick.opengl.LoadableImageData;
import org.newdawn.slick.opengl.TGAImageData;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
public class GLHelper {
@ -85,4 +90,22 @@ public class GLHelper {
Display.setIcon(bufs);
}
public static void hideNativeCursor() {
try {
int min = Cursor.getMinCursorSize();
IntBuffer tmp = BufferUtils.createIntBuffer(min * min);
Mouse.setNativeCursor(new Cursor(min, min, min / 2, min / 2, 1, tmp, null));
} catch (LWJGLException e) {
ErrorHandler.error("Cannot hide native cursor", e).show();
}
}
public static void showNativeCursor() {
try {
Mouse.setNativeCursor(null);
} catch (LWJGLException e) {
ErrorHandler.error("Cannot show native cursor", e).show();
}
}
}

View File

@ -0,0 +1,44 @@
/*
* opsu!dance - fork of opsu! with cursordance auto
* Copyright (C) 2017 yugecin
*
* opsu!dance 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!dance 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!dance. If not, see <http://www.gnu.org/licenses/>.
*/
package yugecin.opsudance.utils;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
public class SlickUtil {
public static void destroyImages(Image[] imgs) {
if (imgs == null) {
return;
}
for (Image i : imgs) {
destroyImage(i);
}
}
public static void destroyImage(Image image) {
if (image == null) {
return;
}
try {
image.destroy();
} catch (SlickException ignored) {
}
}
}