Merge branch 'reorganise'

This commit is contained in:
yugecin 2017-01-21 13:30:38 +01:00
commit d311f8bb95
97 changed files with 5266 additions and 4695 deletions

View File

@ -1,23 +1,22 @@
#opsu!dance
[example video](https://www.youtube.com/watch?v=QvXWRMg1gwE)
[example video](https://www.youtube.com/watch?v=tqZqn7nx8N0)
fork of [opsu!](https://github.com/itdelatrisu/opsu) with cursordancing auto.
I make cursordancing video's -[example](https://www.youtube.com/watch?v=1oFH58X_lTY)-, but I have too many requests and this way I can give people the opportunity to just run it and see the result instead of waiting for me to make a video. Original bot is written in C# and videos are made on osu!, but I don't want to distribute that program, because I don't want to endorse cheating in any way. The sources found in this repo are very representative of the ones of my bot (most files are the exact same, except for C#/java changes).
Originally started as a fork of [opsu!](https://github.com/itdelatrisu/opsu) with cursordance stuff. I made a cursordancing bot in C# for osu!, and by adding it into this clone, it allows me to do even more stuff with it. This way I can also provide this client to other players so they can play with it too, as I will not give my bot to people because I don't want to endorse cheating in any way.
As of 2017 some major changes were made in this fork which changed the inner workings of opsu. This was done in an attempt to make the code more organised (for me, my subjective opinion) and allowed a.o. changing resolution and skin at runtime without the need to restart the whole system. This fork was pretty even to opsu! before this change, but now there are way more differences.
My goal is to to add cool cursordancing things to this fork, but also make it possible to play the normal way.
###Downloads
Click on the releases link (scroll up) to go to the downloadpage with prebuilt jars.
###Building
You can find general (run/build) instructions in the original [opsu! README](README-OPSU.md).
###Making videos
You can make videos under following conditions:
* You will wait a reasonable amount of time between making videos, making too much too fast means it won't be entertaining anymore.
* You will not deny you used opsu!dance to make your video
* You may provide a link to this repository. This is always very appreciated. Please do keep in mind I only made some adjustements/fixes and added the dancing stuff. opsu! was made by [@itdelatrisu](https://github.com/itdelatrisu) (see credits below)
* Asking for beatmap requests is discouraged. After all, everyone can download this and see it for themselves.
* __YOU WILL NOT PRETEND LIKE YOU MADE/OWN THIS SOFTWARE__
Please note that I am only using maven and gradle scripts are not being updated.
###Credits
opsu! was made by Jeffrey Han ([@itdelatrisu](https://github.com/itdelatrisu)). All game concepts and designs are based on work by osu! developer Dean Herbert. Other opsu! credits can be found [here](CREDITS.md).
opsu!dance (everything in the src package yugecin.opsudance) was made by me ([@yugecin](https://github.com/yugecin)). Originally in C#, now ported to java. Some edits were made in the opsu! sources.
opsu!dance (everything in the src package yugecin.opsudance) was made by me ([@yugecin](https://github.com/yugecin)). Eits were made in the opsu! sources, too.
###License
**This software is licensed under GNU GPL version 3.**

View File

@ -1,109 +0,0 @@
apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'application'
import org.apache.tools.ant.filters.*
group = 'itdelatrisu'
version = '0.12.1'
mainClassName = 'itdelatrisu.opsu.Opsu'
buildDir = new File(rootProject.projectDir, "build/")
def useXDG = 'false'
if (hasProperty('XDG')) {
useXDG = XDG
}
sourceCompatibility = 1.7
targetCompatibility = 1.7
sourceSets {
main {
java {
srcDir 'src'
}
}
}
repositories {
mavenCentral()
}
dependencies {
compile('org.lwjgl.lwjgl:lwjgl:2.9.3') {
exclude group: 'net.java.jinput', module: 'jinput'
}
compile('org.slick2d:slick2d-core:1.0.1') {
exclude group: 'org.lwjgl.lwjgl', module: 'lwjgl'
}
compile 'org.jcraft:jorbis:0.0.17'
compile 'net.lingala.zip4j:zip4j:1.3.2'
compile 'com.googlecode.soundlibs:jlayer:1.0.1-1'
compile 'com.googlecode.soundlibs:mp3spi:1.9.5-1'
compile 'com.googlecode.soundlibs:tritonus-share:0.3.7-2'
compile 'org.xerial:sqlite-jdbc:3.8.10.2'
compile 'org.json:json:20140107'
compile 'net.java.dev.jna:jna:4.1.0'
compile 'net.java.dev.jna:jna-platform:4.1.0'
compile 'org.apache.maven:maven-artifact:3.3.3'
compile 'org.apache.commons:commons-compress:1.9'
compile 'org.tukaani:xz:1.5'
compile 'com.github.jponge:lzma-java:1.3'
}
def nativePlatforms = ['windows', 'linux', 'osx']
nativePlatforms.each { platform -> //noinspection GroovyAssignabilityCheck
task "${platform}Natives" {
def outputDir = "${buildDir}/natives/"
inputs.files(configurations.compile)
outputs.dir(outputDir)
doLast {
copy {
def artifacts = configurations.compile.resolvedConfiguration.resolvedArtifacts
.findAll { it.classifier == "natives-$platform" }
artifacts.each {
from zipTree(it.file)
}
into outputDir
}
}
}
}
processResources {
from 'res'
exclude '**/Thumbs.db'
filesMatching('version') {
expand(version: project.version, timestamp: new Date().format("yyyy-MM-dd HH:mm"))
}
}
task unpackNatives {
description "Copies native libraries to the build directory."
dependsOn nativePlatforms.collect { "${it}Natives" }.findAll { tasks[it] }
}
jar {
manifest {
attributes 'Implementation-Title': 'opsu!',
'Implementation-Version': version,
'Main-Class': mainClassName,
'Use-XDG': useXDG
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
baseName = "opsu"
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
exclude '**/Thumbs.db'
outputs.upToDateWhen { false }
}
run {
dependsOn 'unpackNatives'
}

Binary file not shown.

View File

@ -1,6 +0,0 @@
#Tue Aug 25 19:45:21 BST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-all.zip

164
gradlew vendored
View File

@ -1,164 +0,0 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
gradlew.bat vendored
View File

@ -1,90 +0,0 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -3,12 +3,12 @@
<modelVersion>4.0.0</modelVersion>
<groupId>yugecin</groupId>
<artifactId>opsu-dance</artifactId>
<version>0.4.2</version>
<version>0.5.0-SNAPSHOT</version>
<properties>
<version>${project.version}</version>
<timestamp>${maven.build.timestamp}</timestamp>
<maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
<mainClassName>itdelatrisu.opsu.Opsu</mainClassName>
<mainClassName>yugecin.opsudance.core.Entrypoint</mainClassName>
<XDG>false</XDG>
</properties>
<build>

View File

@ -1 +0,0 @@
rootProject.name = 'opsu'

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,9 +101,17 @@ 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;
}
}
/**
* Constructor.

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;
@ -590,6 +577,8 @@ public class Utils {
if (isJarRunning())
return null;
File f = new File(".git/refs/remotes/origin/master");
if (!f.isFile())
f = new File("../.git/refs/remotes/origin/master");
if (!f.isFile())
return null;
try (BufferedReader in = new BufferedReader(new FileReader(f))) {

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);
}
@Override
public void draw(GameContainer container, StateBasedGame game, Graphics g) {
int width = container.getWidth();
int height = container.getHeight();
public void leave() {
Button.CLOSE.click();
}
@Override
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);
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);
for (MenuState ms : MenuState.values()) {
ms.revalidate(button, buttonL, buttonR);
}
}
@Override
public void render(GameContainer container, StateBasedGame game, Graphics g)
throws SlickException {
public void render(Graphics g) {
super.render(g);
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)
if (menuState == null) {
return;
if (menuState != null)
menuState.click(container, game, x, y);
}
menuState.render(g);
}
@Override
public void mouseWheelMoved(int newValue) {
if (menuState != null)
menuState.scroll(container, game, newValue);
public void preRenderUpdate() {
super.preRenderUpdate();
UI.update(displayContainer.renderDelta);
MusicController.loopTrackIfEnded(false);
menuState.preRenderUpdate();
}
@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;
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 void enter(GameContainer container, StateBasedGame game)
throws SlickException {
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:
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;
}
break;
}
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; }
public boolean keyPressed(int key, char c) {
if (super.keyPressed(key, c)) {
return true;
}
@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());
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);
}
}
switch (key) {
case Input.KEY_ESCAPE:
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)) {
return true;
}
if (key == Input.KEY_R && (displayContainer.input.isKeyDown(Input.KEY_RCONTROL) || displayContainer.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;
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();
MusicController.setPitch(1.0f);
game.enterState(Opsu.STATE_SONGMENU, new EasedFadeOutTransition(), new FadeInTransition());
if (displayContainer.cursor.isBeatmapSkinned()) {
displayContainer.resetCursor();
}
MusicController.setPitch(1.0f);
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);
}
UI.getBackButton().hoverUpdate(delta, displayContainer.mouseX, displayContainer.mouseY);
}
@Override
public int getID() { return state; }
@Override
public void mouseWheelMoved(int newValue) {
if (input.isKeyDown(Input.KEY_LALT) || input.isKeyDown(Input.KEY_RALT))
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 void keyPressed(int key, char c) {
switch (key) {
case Input.KEY_ESCAPE:
public boolean keyPressed(int key, char c) {
if (super.keyPressed(key, c)) {
return true;
}
if (key == 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;
}
return true;
}
@Override
public void mousePressed(int button, int x, int y) {
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,12 +212,25 @@ 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;
}
/**
* Returns to the song menu.
@ -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,9 +544,10 @@ 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
g.setColor(Colors.BLACK_ALPHA);
@ -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();
if (isInputBlocked()) {
return true;
}
@Override
public void mouseClicked(int button, int x, int y, int clickCount) {
// check mouse button
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
// 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,24 @@ 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;
case Input.KEY_O:
if (input.isKeyDown(Input.KEY_LCONTROL) || input.isKeyDown(Input.KEY_RCONTROL)) {
game.enterState(Opsu.STATE_OPTIONSMENU, new EmptyTransition(), new FadeInTransition());
return true;
}
if (key == Input.KEY_O && (input.isKeyDown(Input.KEY_LCONTROL) || input.isKeyDown(Input.KEY_RCONTROL))) {
optionsOverlay.show();
return true;
}
break;
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 +1191,58 @@ public class SongMenu extends BasicGameState {
lastSearchTextLength = textLength;
}
}
break;
}
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 +1254,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 +1279,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 +1310,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 +1442,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 +1564,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 +1699,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 +1780,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);
} else {
instanceContainer.provide(SongMenu.class).setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
}
// play the theme song
else
} 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)
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,15 +275,13 @@ public class Cursor {
/**
* Resets all cursor location data.
*/
public void resetLocations() {
public void resetLocations(int mouseX, int mouseY) {
trail.clear();
if (lastPosition != null) {
lastPosition = new Point(mouseX, mouseY);
for (int i = 0; i < 50; i++) {
trail.add(new Point(lastPosition));
}
}
lastPosition = new Point(0, 0);
}
/**
* Returns whether or not the cursor is skinned.
@ -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;
// 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

@ -40,7 +40,7 @@ import org.newdawn.slick.util.ResourceLoader;
* Fonts used for drawing.
*/
public class Fonts {
public static UnicodeFont DEFAULT, BOLD, XLARGE, LARGE, MEDIUM, MEDIUMBOLD, SMALL;
public static UnicodeFont DEFAULT, BOLD, XLARGE, LARGE, MEDIUM, MEDIUMBOLD, SMALL, SMALLBOLD;
/** Set of all Unicode strings already loaded per font. */
private static HashMap<UnicodeFont, HashSet<String>> loadedGlyphs = new HashMap<UnicodeFont, HashSet<String>>();
@ -65,6 +65,7 @@ public class Fonts {
MEDIUM = new UnicodeFont(font.deriveFont(fontBase * 3 / 2));
MEDIUMBOLD = new UnicodeFont(font.deriveFont(Font.BOLD, fontBase * 3 / 2));
SMALL = new UnicodeFont(font.deriveFont(fontBase));
SMALLBOLD = new UnicodeFont(font.deriveFont(Font.BOLD, fontBase));
ColorEffect colorEffect = new ColorEffect();
loadFont(DEFAULT, colorEffect);
loadFont(BOLD, colorEffect);
@ -73,6 +74,7 @@ public class Fonts {
loadFont(MEDIUM, colorEffect);
loadFont(MEDIUMBOLD, colorEffect);
loadFont(SMALL, colorEffect);
loadFont(SMALLBOLD, colorEffect);
}
/**
@ -123,7 +125,7 @@ public class Fonts {
* @return the list of split strings
* @author davedes (http://slick.ninjacave.com/forum/viewtopic.php?t=3778)
*/
public static List<String> wrap(org.newdawn.slick.Font font, String text, int width) {
public static List<String> wrap(org.newdawn.slick.Font font, String text, int width, boolean newlines) {
List<String> list = new ArrayList<String>();
String str = text;
String line = "";
@ -134,7 +136,7 @@ public class Fonts {
if (Character.isWhitespace(c))
lastSpace = i;
String append = line + c;
if (font.getWidth(append) > width) {
if (font.getWidth(append) > width || (newlines && c == '\n')) {
int split = (lastSpace != -1) ? lastSpace : i;
int splitTrimmed = split;
if (lastSpace != -1 && split < str.length() - 1)
@ -153,4 +155,5 @@ public class Fonts {
list.add(str);
return list;
}
}

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;
/** The width of the field */
private int width;
private final DisplayContainer displayContainer;
/** 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;
}
public void setTextColor(Color text) {
this.textCol = text;
}
public void setBackgroundColor(Color background) {
this.backgroundCol = background;
}
/**
* Moves the component.
*
* @param x
* X coordinate
* @param y
* Y coordinate
*/
@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,71 +163,23 @@ 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)))) {
((displayContainer.input.isKeyDown(Input.KEY_LCONTROL)) || (displayContainer.input.isKeyDown(Input.KEY_RCONTROL)))) {
String text = Sys.getClipboard();
if (text != null) {
doPaste(text);
}
return;
}
/* if ((key == Input.KEY_Z) &&
((input.isKeyDown(Input.KEY_LCONTROL)) || (input.isKeyDown(Input.KEY_RCONTROL)))) {
if (oldText != null) {
doUndo(oldCursorPos, oldText);
}
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) {
@ -458,7 +208,7 @@ public class TextField extends AbstractComponent {
}
*/ } else if (key == Input.KEY_BACK) {
if ((cursorPos > 0) && (value.length() > 0)) {
if (input.isKeyDown(Input.KEY_LCONTROL) || input.isKeyDown(Input.KEY_RCONTROL)) {
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;
@ -490,18 +240,10 @@ public class TextField extends AbstractComponent {
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
@ -510,28 +252,12 @@ public class TextField extends AbstractComponent {
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();
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

@ -0,0 +1,184 @@
/*
* 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;
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;
import yugecin.opsudance.states.EmptyState;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import static yugecin.opsudance.core.Entrypoint.sout;
/*
* loosely based on itdelatrisu.opsu.Opsu
*/
public class OpsuDance {
private final DisplayContainer container;
private ServerSocket singleInstanceSocket;
public OpsuDance(DisplayContainer container) {
this.container = container;
}
public void start(String[] args) {
try {
sout("initialized");
checkRunningDirectory();
Options.parseOptions();
ensureSingleInstance();
sout("prechecks done and options parsed");
initDatabase();
initUpdater(args);
sout("database & updater initialized");
//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) {
ErrorHandler.error("could not initialize GL", e).allowTerminate().preventContinue().show();
return false;
}
Exception caughtException = null;
try {
container.run();
} catch (Exception e) {
caughtException = e;
}
container.teardown();
container.pause();
return caughtException != null && ErrorHandler.error("update/render error", caughtException).allowTerminate().show().shouldIgnoreAndContinue();
}
private void initDatabase() {
try {
DBController.init();
} catch (UnsatisfiedLinkError e) {
errorAndExit("Could not initialize database.", e);
}
}
private void initUpdater(String[] args) {
// check if just updated
if (args.length >= 2)
Updater.get().setUpdateInfo(args[0], args[1]);
// check for updates
if (Options.isUpdaterDisabled()) {
return;
}
new Thread() {
@Override
public void run() {
try {
Updater.get().checkForUpdates();
} catch (IOException e) {
Log.warn("updatecheck failed.", e);
}
}
}.start();
}
private void checkRunningDirectory() {
if (!Utils.isJarRunning()) {
return;
}
File runningDir = Utils.getRunningDirectory();
if (runningDir == null) {
return;
}
if (runningDir.getAbsolutePath().indexOf('!') == -1) {
return;
}
errorAndExit("Cannot run from a path that contains a '!'. Please move or rename the jar and try again.");
}
private void ensureSingleInstance() {
if (Options.noSingleInstance()) {
return;
}
try {
singleInstanceSocket = new ServerSocket(Options.getPort(), 1, InetAddress.getLocalHost());
} catch (UnknownHostException e) {
// shouldn't happen
} catch (IOException e) {
errorAndExit(String.format(
"Could not launch. Either opsu! is already running or a different program uses port %d.\n" +
"You can change the port opsu! uses by editing the 'Port' field in the .opsu.cfg configuration file.\n" +
"If that still does not resolve the problem, you can set 'NoSingleInstance' to 'true', but this is not recommended.", Options.getPort()), e);
}
}
private void closeSingleInstanceSocket() {
if (singleInstanceSocket == null) {
return;
}
try {
singleInstanceSocket.close();
} catch (IOException e) {
Log.error("Single instance socket was not closed!", e);
}
}
private void errorAndExit(String errstr) {
ErrorHandler.error(errstr, new Throwable()).allowTerminate().preventContinue().show();
System.exit(1);
}
private void errorAndExit(String errstr, Throwable cause) {
ErrorHandler.error(errstr, cause).preventContinue().show();
System.exit(1);
}
}

View File

@ -0,0 +1,68 @@
/*
* 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;
import itdelatrisu.opsu.NativeLoader;
import itdelatrisu.opsu.Options;
import org.newdawn.slick.util.FileSystemLocation;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
public class PreStartupInitializer {
public PreStartupInitializer() {
loadNatives();
setResourcePath();
}
private void loadNatives() {
File nativeDir = loadNativesUsingOptionsPath();
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);
}
}
private File loadNativesUsingOptionsPath() {
File nativeDir = Options.NATIVE_DIR;
try {
new NativeLoader(nativeDir).loadNatives();
} catch (IOException e) {
Log.error("Error loading natives.", e);
}
return nativeDir;
}
private void setResourcePath() {
ResourceLoader.addResourceLocation(new FileSystemLocation(new File("./res/")));
}
}

View File

@ -0,0 +1,460 @@
/*
* 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;
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 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.*;
import org.newdawn.slick.opengl.InternalTextureLoader;
import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.opengl.renderer.SGL;
import org.newdawn.slick.util.Log;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.core.errorhandling.ErrorDumpable;
import yugecin.opsudance.core.inject.InstanceContainer;
import yugecin.opsudance.core.state.OpsuState;
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;
import java.io.StringWriter;
import static yugecin.opsudance.core.Entrypoint.sout;
/**
* based on org.newdawn.slick.AppGameContainer
*/
public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListener {
private static SGL GL = Renderer.get();
public final EventBus eventBus;
private final InstanceContainer instanceContainer;
private FpsRenderState fpsState;
private BarNotificationState barNotifState;
private BubbleNotificationState bubNotifState;
private TransitionState outTransitionState;
private TransitionState inTransitionState;
private final TransitionFinishedListener outTransitionListener;
private final TransitionFinishedListener inTransitionListener;
private OpsuState state;
public final DisplayMode nativeDisplayMode;
private Graphics graphics;
public Input input;
public int width;
public int height;
public int mouseX;
public int mouseY;
private int targetUpdatesPerSecond;
public int targetUpdateInterval;
private int targetRendersPerSecond;
public int targetRenderInterval;
public int targetBackgroundRenderInterval;
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
public void onFinish() {
state.leave();
outTransitionState.getApplicableState().leave();
state = inTransitionState;
state.enter();
inTransitionState.getApplicableState().enter();
}
};
inTransitionListener = new TransitionFinishedListener() {
@Override
public void onFinish() {
state.leave();
state = inTransitionState.getApplicableState();
}
};
this.nativeDisplayMode = Display.getDisplayMode();
setUPS(1000);
setFPS(60);
targetBackgroundRenderInterval = 41; // ~24 fps
lastFrame = getTime();
delta = 1;
renderDelta = 1;
}
public void setUPS(int ups) {
targetUpdatesPerSecond = ups;
targetUpdateInterval = 1000 / targetUpdatesPerSecond;
}
public void setFPS(int fps) {
targetRendersPerSecond = fps;
targetRenderInterval = 1000 / targetRendersPerSecond;
}
public void init(Class<? extends OpsuState> startingState) {
state = instanceContainer.provide(startingState);
state.enter();
fpsState = instanceContainer.provide(FpsRenderState.class);
bubNotifState = instanceContainer.provide(BubbleNotificationState.class);
barNotifState = instanceContainer.provide(BarNotificationState.class);
}
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()) {
maxRenderInterval = targetRenderInterval;
} else {
maxRenderInterval = targetBackgroundRenderInterval;
}
if (timeSinceLastRender >= maxRenderInterval) {
GL.glClear(SGL.GL_COLOR_BUFFER_BIT);
/*
graphics.resetTransform();
graphics.resetFont();
graphics.resetLineWidth();
graphics.resetTransform();
*/
renderDelta = timeSinceLastRender;
state.preRenderUpdate();
state.render(graphics);
fpsState.render(graphics);
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);
}
Display.processMessages();
Display.sync(targetUpdatesPerSecond);
}
}
public void setup() throws Exception {
width = height = -1;
Input.disableControllers();
Display.setTitle("opsu!dance");
Options.setDisplayMode(this);
Display.create();
GLHelper.setIcons(new String[] { "icon16.png", "icon32.png" });
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);
return;
}
DisplayMode displayMode = null;
if (fullscreen) {
displayMode = GLHelper.findFullscreenDisplayMode(nativeDisplayMode.getBitsPerPixel(), nativeDisplayMode.getFrequency(), width, height);
}
if (displayMode == null) {
displayMode = new DisplayMode(width, height);
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));
}
}
this.width = displayMode.getWidth();
this.height = displayMode.getHeight();
Display.setDisplayMode(displayMode);
Display.setFullscreen(fullscreen);
if (Display.isCreated()) {
initGL();
}
if (displayMode.getBitsPerPixel() == 16) {
InternalTextureLoader.get().set16BitMode();
}
}
private void initGL() throws Exception {
GL.initDisplay(width, height);
GL.enterOrtho(width, height);
graphics = new Graphics(width, height);
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() {
long time = getTime();
int delta = (int) (time - lastFrame);
lastFrame = time;
return delta;
}
public long getTime() {
return (Sys.getTime() * 1000) / Sys.getTimerResolution();
}
@Override
public void writeErrorDump(StringWriter dump) {
dump.append("> DisplayContainer dump\n");
dump.append("OpenGL version: ").append(glVersion).append( "(").append(glVendor).append(")\n");
if (isTransitioning()) {
dump.append("doing a transition\n");
dump.append("using out transition ").append(outTransitionState.getClass().getSimpleName()).append('\n');
dump.append("using in transition ").append(inTransitionState.getClass().getSimpleName()).append('\n');
if (state == inTransitionState) {
dump.append("currently doing the in transition\n");
} else {
dump.append("currently doing the out transition\n");
}
}
state.writeErrorDump(dump);
}
public boolean isInState(Class<? extends OpsuState> state) {
return state.isInstance(state);
}
public boolean isTransitioning() {
return state instanceof TransitionState;
}
public void switchState(Class<? extends OpsuState> newState) {
switchState(newState, FadeOutTransitionState.class, 200, FadeInTransitionState.class, 300);
}
public void switchStateNow(Class<? extends OpsuState> newState) {
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) {
if (isTransitioning()) {
return;
}
outTransitionState = instanceContainer.provide(outTransition).set(state, outTime, outTransitionListener);
inTransitionState = instanceContainer.provide(inTransition).set(instanceContainer.provide(newState), inTime, inTransitionListener);
state = outTransitionState;
state.enter();
}
/*
* input events below, see org.newdawn.slick.KeyListener & org.newdawn.slick.MouseListener
*/
@Override
public void keyPressed(int key, char c) {
state.keyPressed(key, c);
}
@Override
public void keyReleased(int key, char c) {
state.keyReleased(key, c);
}
@Override
public void mouseWheelMoved(int change) {
state.mouseWheelMoved(change);
}
@Override
public void mouseClicked(int button, int x, int y, int clickCount) { }
@Override
public void mousePressed(int button, int x, int y) {
state.mousePressed(button, x, y);
}
@Override
public void mouseReleased(int button, int x, int y) {
if (bubNotifState.mouseReleased(x, y)) {
return;
}
state.mouseReleased(button, x, y);
}
@Override
public void mouseMoved(int oldx, int oldy, int newx, int newy) { }
@Override
public void mouseDragged(int oldx, int oldy, int newx, int newy) {
state.mouseDragged(oldx, oldy, newx, newy);
}
@Override
public void setInput(Input input) { }
@Override
public boolean isAcceptingInput() {
return true;
}
@Override
public void inputEnded() { }
@Override
public void inputStarted() { }
}

View File

@ -0,0 +1,45 @@
/*
* 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;
import itdelatrisu.opsu.downloads.Updater;
import yugecin.opsudance.OpsuDance;
import yugecin.opsudance.core.inject.OpsuDanceInjector;
public class Entrypoint {
public static final long startTime = System.currentTimeMillis();
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() {
return System.currentTimeMillis() - startTime;
}
public static void sout(String message) {
System.out.println(String.format("[%7d] %s", runtime(), message));
}
}

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

@ -0,0 +1,26 @@
/*
* 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.errorhandling;
import java.io.StringWriter;
public interface ErrorDumpable {
void writeErrorDump(StringWriter dump);
}

View File

@ -0,0 +1,248 @@
/*
* 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.errorhandling;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils;
import org.newdawn.slick.util.Log;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.utils.MiscUtils;
import javax.swing.*;
import java.awt.Desktop;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
/**
* based on itdelatrisu.opsu.ErrorHandler
*/
public class ErrorHandler {
private static ErrorHandler instance;
private final DisplayContainer displayContainer;
private String customMessage;
private Throwable cause;
private String errorDump;
private String messageBody;
private boolean preventContinue;
private boolean preventReport;
private boolean ignoreAndContinue;
private boolean allowTerminate;
public ErrorHandler(DisplayContainer displayContainer) {
this.displayContainer = displayContainer;
instance = this;
}
private ErrorHandler init(String customMessage, Throwable cause) {
this.customMessage = customMessage;
this.cause = cause;
StringWriter dump = new StringWriter();
try {
displayContainer.writeErrorDump(dump);
} catch (Exception e) {
dump
.append("### ")
.append(e.getClass().getSimpleName())
.append(" while creating errordump");
e.printStackTrace(new PrintWriter(dump));
}
errorDump = dump.toString();
dump = new StringWriter();
dump.append(customMessage).append("\n");
cause.printStackTrace(new PrintWriter(dump));
dump.append("\n").append(errorDump);
messageBody = dump.toString();
Log.error("====== start unhandled exception dump");
Log.error(messageBody);
Log.error("====== end unhandled exception dump");
return this;
}
public static ErrorHandler error(String message, Throwable cause) {
return instance.init(message, cause);
}
public ErrorHandler preventReport() {
preventReport = true;
return this;
}
public ErrorHandler allowTerminate() {
allowTerminate = true;
return this;
}
public ErrorHandler preventContinue() {
preventContinue = true;
return this;
}
public ErrorHandler show() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
Log.warn("Unable to set look and feel for error dialog");
}
String title = "opsu!dance error - " + customMessage;
String messageText = "opsu!dance has encountered an error.";
if (!preventReport) {
messageText += " Please report this!";
}
JLabel message = new JLabel(messageText);
JTextArea textArea = new JTextArea(15, 100);
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);
textArea.setText(messageBody);
Object[] messageComponents = new Object[] { message, new JScrollPane(textArea), createViewLogButton(), createReportButton() };
String[] buttons;
if (!allowTerminate && !preventContinue) {
buttons = new String[] { "Ignore & continue" };
} else if (preventContinue) {
buttons = new String[] { "Terminate" };
} else {
buttons = new String[] { "Terminate", "Ignore & continue" };
}
JFrame frame = new JFrame(title);
frame.setUndecorated(true);
frame.setVisible(true);
frame.setLocationRelativeTo(null);
int result = JOptionPane.showOptionDialog(frame,
messageComponents,
title,
JOptionPane.DEFAULT_OPTION,
JOptionPane.ERROR_MESSAGE,
null,
buttons,
buttons[buttons.length - 1]);
ignoreAndContinue = !allowTerminate || result == 1;
frame.dispose();
return this;
}
private JComponent createViewLogButton() {
return createButton("View log", Desktop.Action.OPEN, new ActionListener() {
@Override
public void actionPerformed(ActionEvent event) {
try {
Desktop.getDesktop().open(Options.LOG_FILE);
} catch (IOException e) {
Log.warn("Could not open log file", e);
JOptionPane.showMessageDialog(null, "whoops could not open log file", "errorception", JOptionPane.ERROR_MESSAGE);
}
}
});
}
private JComponent createReportButton() {
if (preventReport) {
return new JLabel();
}
return createButton("Report error", Desktop.Action.BROWSE, new ActionListener() {
@Override
public void actionPerformed(ActionEvent event) {
try {
Desktop.getDesktop().browse(createGithubIssueUrl());
} catch (IOException e) {
Log.warn("Could not open browser to report issue", e);
JOptionPane.showMessageDialog(null, "whoops could not launch a browser", "errorception", JOptionPane.ERROR_MESSAGE);
}
}
});
}
private JButton createButton(String buttonText, Desktop.Action action, ActionListener listener) {
JButton button = new JButton(buttonText);
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(action)) {
button.addActionListener(listener);
return button;
}
button.setEnabled(false);
return button;
}
private URI createGithubIssueUrl() {
StringWriter dump = new StringWriter();
dump.append(customMessage).append("\n");
dump.append("**ver** ").append(MiscUtils.buildProperties.get().getProperty("version")).append('\n');
String gitHash = Utils.getGitHash();
if (gitHash != null) {
dump.append("**git hash** ").append(gitHash.substring(0, 12)).append('\n');
}
dump.append("**os** ").append(System.getProperty("os.name"))
.append(" (").append(System.getProperty("os.arch")).append(")\n");
dump.append("**jre** ").append(System.getProperty("java.version")).append('\n');
dump.append("**trace**").append("\n```\n");
cause.printStackTrace(new PrintWriter(dump));
dump.append("\n```\n");
dump.append("**info dump**").append('\n');
dump.append("```\n").append(errorDump).append("\n```\n\n");
String issueTitle = "";
String issueBody = "";
try {
issueTitle = URLEncoder.encode("*** Unhandled " + cause.getClass().getSimpleName() + " " + customMessage, "UTF-8");
issueBody = URLEncoder.encode(truncateGithubIssueBody(dump.toString()), "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.warn("URLEncoder failed to encode the auto-filled issue report URL.", e);
}
return URI.create(String.format(Options.ISSUES_URL, issueTitle, issueBody));
}
private String truncateGithubIssueBody(String body) {
if (body.replaceAll("[^a-zA-Z+-]", "").length() < 1750) {
return body;
}
Log.warn("error dump too long to fit into github issue url, truncating");
return body.substring(0, 1640) + "** TRUNCATED **\n```";
}
public boolean shouldIgnoreAndContinue() {
return ignoreAndContinue;
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.events;
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) {
subscribers.add(new Subscriber<>(eventType, eventListener));
}
public void post(Object event) {
for (Subscriber s : subscribers) {
if (s.eventType.isInstance(event)) {
s.listener.onEvent(event);
}
}
}
private class Subscriber<T> {
private final Class<T> eventType;
private final EventListener<T> listener;
private Subscriber(Class<T> eventType, EventListener<T> listener) {
this.eventType = eventType;
this.listener = listener;
}
}
}

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.events;
public interface EventListener<T> {
void onEvent(T event);
}

View File

@ -0,0 +1,25 @@
/*
* 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.inject;
public interface Binder<T> {
void asEagerSingleton();
void asLazySingleton();
void to(Class<? extends T> type);
}

View File

@ -0,0 +1,99 @@
/*
* 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.inject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.ListIterator;
@SuppressWarnings("unchecked")
public abstract class Injector implements InstanceContainer, Binder {
private final HashMap<Class<?>, Object> instances;
private final LinkedList<Class<?>> lazyInstances;
private Class<?> lastType;
public Injector() {
instances = new HashMap<>();
lazyInstances = new LinkedList<>();
instances.put(InstanceContainer.class, this);
configure();
}
protected abstract void configure();
public final <T> T provide(Class<T> type) {
Object instance = instances.get(type);
if (instance != null) {
return (T) instance;
}
ListIterator<Class<?>> iter = lazyInstances.listIterator();
while (iter.hasNext()) {
Class<?> l = iter.next();
if (l == type) {
iter.remove();
instance = createInstance(type);
instances.put(type, instance);
return (T) instance;
}
}
return createInstance(type);
}
private <T> T createInstance(Class<T> type) {
Constructor<?>[] constructors = type.getDeclaredConstructors();
if (constructors.length == 0) {
throw new RuntimeException("Cannot provide " + type.getSimpleName());
}
Constructor constructor = constructors[0];
Class<?>[] parameterTypes = constructor.getParameterTypes();
Object[] params = new Object[parameterTypes.length];
for (int i = parameterTypes.length - 1; i >= 0; i--) {
params[i] = provide(parameterTypes[i]);
}
try {
return (T) constructor.newInstance(params);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public final <T> Binder<T> bind(Class<T> type) {
lastType = type;
return this;
}
@Override
public final void asEagerSingleton() {
instances.put(lastType, createInstance(lastType));
}
@Override
public final void asLazySingleton() {
lazyInstances.add(lastType);
}
@Override
public final void to(Class type) {
instances.put(lastType, createInstance(type));
}
}

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.inject;
public interface InstanceContainer {
<T> T provide(Class<T> type);
}

View File

@ -0,0 +1,65 @@
/*
* 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.inject;
import itdelatrisu.opsu.states.*;
import yugecin.opsudance.PreStartupInitializer;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.events.EventBus;
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.EmptyTransitionState;
import yugecin.opsudance.core.state.transitions.FadeInTransitionState;
import yugecin.opsudance.core.state.transitions.FadeOutTransitionState;
import yugecin.opsudance.core.errorhandling.ErrorHandler;
import yugecin.opsudance.states.EmptyRedState;
import yugecin.opsudance.states.EmptyState;
public class OpsuDanceInjector extends Injector {
protected void configure() {
bind(EventBus.class).asEagerSingleton();
bind(PreStartupInitializer.class).asEagerSingleton();
bind(DisplayContainer.class).asEagerSingleton();
bind(ErrorHandler.class).asEagerSingleton();
bind(FpsRenderState.class).asEagerSingleton();
bind(BarNotificationState.class).asEagerSingleton();
bind(BubbleNotificationState.class).asEagerSingleton();
bind(EmptyTransitionState.class).asEagerSingleton();
bind(FadeInTransitionState.class).asEagerSingleton();
bind(FadeOutTransitionState.class).asEagerSingleton();
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

@ -0,0 +1,136 @@
/*
* 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 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;
import java.io.StringWriter;
public abstract class BaseOpsuState implements OpsuState, EventListener<ResolutionChangedEvent> {
protected final DisplayContainer displayContainer;
/**
* state is dirty when resolution or skin changed but hasn't rendered yet
*/
private boolean isDirty;
private boolean isCurrentState;
public BaseOpsuState(DisplayContainer displayContainer) {
this.displayContainer = displayContainer;
displayContainer.eventBus.subscribe(ResolutionChangedEvent.class, this);
}
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) {
revalidate();
return;
}
isDirty = true;
}
@Override
public void enter() {
isCurrentState = true;
if (isDirty) {
revalidate();
isDirty = false;
}
}
@Override
public void leave() {
isCurrentState = false;
}
@Override
public boolean onCloseRequest() {
return true;
}
@Override
public boolean keyPressed(int key, char c) {
return false;
}
@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;
}
@Override
public boolean mouseWheelMoved(int delta) {
return false;
}
@Override
public boolean mousePressed(int button, int x, int y) {
return false;
}
@Override
public boolean mouseReleased(int button, int x, int y) {
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");
dump.append("isDirty: ").append(String.valueOf(isDirty)).append('\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

@ -0,0 +1,66 @@
/*
* 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 yugecin.opsudance.core.errorhandling.ErrorDumpable;
public interface OpsuState extends ErrorDumpable {
void update();
void preRenderUpdate();
void render(Graphics g);
void enter();
void leave();
/**
* @return true if closing is allowed
*/
boolean onCloseRequest();
/**
* @return false to stop event bubbling
*/
boolean keyPressed(int key, char c);
/**
* @return false to stop event bubbling
*/
boolean keyReleased(int key, char c);
/**
* @return false to stop event bubbling
*/
boolean mouseWheelMoved(int delta);
/**
* @return false to stop event bubbling
*/
boolean mousePressed(int button, int x, int y);
/**
* @return false to stop event bubbling
*/
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

@ -0,0 +1,117 @@
/*
* 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.specialstates;
import itdelatrisu.opsu.ui.Fonts;
import itdelatrisu.opsu.ui.animations.AnimationEquation;
import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.core.events.EventListener;
import yugecin.opsudance.events.BarNotificationEvent;
import yugecin.opsudance.events.ResolutionChangedEvent;
import java.util.List;
public class BarNotificationState implements EventListener<BarNotificationEvent> {
private final int IN_TIME = 200;
private final int DISPLAY_TIME = 5700 + IN_TIME;
private final int OUT_TIME = 200;
private final int TOTAL_TIME = DISPLAY_TIME + OUT_TIME;
private final DisplayContainer displayContainer;
private final Color bgcol;
private final Color textCol;
private int timeShown;
private String message;
private List<String> lines;
private int textY;
private int barHalfTargetHeight;
private int barHalfHeight;
public BarNotificationState(DisplayContainer displayContainer, EventBus eventBus) {
this.displayContainer = displayContainer;
this.bgcol = new Color(Color.black);
this.textCol = new Color(Color.white);
this.timeShown = TOTAL_TIME;
eventBus.subscribe(BarNotificationEvent.class, this);
eventBus.subscribe(ResolutionChangedEvent.class, new EventListener<ResolutionChangedEvent>() {
@Override
public void onEvent(ResolutionChangedEvent event) {
if (timeShown >= TOTAL_TIME) {
return;
}
calculatePosition();
}
});
}
public void render(Graphics g) {
if (timeShown >= TOTAL_TIME) {
return;
}
timeShown += displayContainer.renderDelta;
processAnimations();
g.setColor(bgcol);
g.fillRect(0, displayContainer.height / 2 - barHalfHeight, displayContainer.width, barHalfHeight * 2);
int y = textY;
for (String line : lines) {
Fonts.LARGE.drawString((displayContainer.width - Fonts.LARGE.getWidth(line)) / 2, y, line, textCol);
y += Fonts.LARGE.getLineHeight();
}
}
private void processAnimations() {
if (timeShown < IN_TIME) {
float progress = (float) timeShown / IN_TIME;
barHalfHeight = (int) (barHalfTargetHeight * AnimationEquation.OUT_BACK.calc(progress));
textCol.a = progress;
bgcol.a = 0.4f * progress;
return;
}
if (timeShown > DISPLAY_TIME) {
float progress = 1f - (float) (timeShown - DISPLAY_TIME) / OUT_TIME;
textCol.a = progress;
bgcol.a = 0.4f * progress;
return;
}
barHalfHeight = barHalfTargetHeight;
textCol.a = 1f;
bgcol.a = 0.4f;
}
private void calculatePosition() {
this.lines = Fonts.wrap(Fonts.LARGE, message, (int) (displayContainer.width * 0.96f), true);
int textHeight = (int) (Fonts.LARGE.getLineHeight() * (lines.size() + 0.5f));
textY = (displayContainer.height - textHeight) / 2 + Fonts.LARGE.getLineHeight() / 8;
barHalfTargetHeight = textHeight / 2 + Fonts.LARGE.getLineHeight() / 8;
}
@Override
public void onEvent(BarNotificationEvent event) {
this.message = event.message;
calculatePosition();
timeShown = 0;
}
}

View File

@ -0,0 +1,249 @@
/*
* 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.specialstates;
import itdelatrisu.opsu.ui.Fonts;
import itdelatrisu.opsu.ui.animations.AnimationEquation;
import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.core.events.EventListener;
import yugecin.opsudance.events.BubbleNotificationEvent;
import yugecin.opsudance.events.ResolutionChangedEvent;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
public class BubbleNotificationState implements EventListener<BubbleNotificationEvent> {
public static final int IN_TIME = 633;
public static final int DISPLAY_TIME = 7000 + IN_TIME;
public static final int OUT_TIME = 433;
public static final int TOTAL_TIME = DISPLAY_TIME + OUT_TIME;
private final DisplayContainer displayContainer;
private final LinkedList<Notification> bubbles;
private int addAnimationTime;
private int addAnimationHeight;
public BubbleNotificationState(DisplayContainer displayContainer, EventBus eventBus) {
this.displayContainer = displayContainer;
this.bubbles = new LinkedList<>();
this.addAnimationTime = IN_TIME;
eventBus.subscribe(BubbleNotificationEvent.class, this);
eventBus.subscribe(ResolutionChangedEvent.class, new EventListener<ResolutionChangedEvent>() {
@Override
public void onEvent(ResolutionChangedEvent event) {
calculatePositions();
}
});
}
public void render(Graphics g) {
ListIterator<Notification> iter = bubbles.listIterator();
if (!iter.hasNext()) {
return;
}
addAnimationTime += displayContainer.renderDelta;
if (addAnimationTime > IN_TIME) {
finishAddAnimation();
}
boolean animateUp = false;
do {
Notification next = iter.next();
if (animateUp && addAnimationTime < IN_TIME) {
next.y = next.baseY - (int) (addAnimationHeight * AnimationEquation.OUT_QUINT.calc((float) addAnimationTime / IN_TIME));
}
if (next.render(g, displayContainer.mouseX, displayContainer.mouseY, displayContainer.renderDelta)) {
iter.remove();
}
animateUp = true;
} while (iter.hasNext());
}
public boolean mouseReleased(int x, int y) {
if (x < Notification.finalX) {
return false;
}
for (Notification bubble : bubbles) {
if (bubble.mouseReleased(x, y)) {
return true;
}
}
return false;
}
private void calculatePositions() {
Notification.width = (int) (displayContainer.width * 0.1703125f);
Notification.baseLine = (int) (displayContainer.height * 0.9645f);
Notification.paddingY = (int) (displayContainer.height * 0.0144f);
Notification.finalX = displayContainer.width - Notification.width - (int) (displayContainer.width * 0.01);
Notification.fontPaddingX = (int) (Notification.width * 0.02f);
Notification.fontPaddingY = (int) (Fonts.SMALLBOLD.getLineHeight() / 4f);
Notification.lineHeight = Fonts.SMALLBOLD.getLineHeight();
if (bubbles.isEmpty()) {
return;
}
finishAddAnimation();
int y = Notification.baseLine;
for (Notification bubble : bubbles) {
bubble.recalculateDimensions();
y -= bubble.height;
bubble.baseY = bubble.y = y;
y -= Notification.paddingY;
}
}
private void finishAddAnimation() {
if (bubbles.isEmpty()) {
addAnimationHeight = 0;
addAnimationTime = IN_TIME;
return;
}
ListIterator<Notification> iter = bubbles.listIterator();
iter.next();
while (iter.hasNext()) {
Notification bubble = iter.next();
bubble.y = bubble.baseY - addAnimationHeight;
bubble.baseY = bubble.y;
}
addAnimationHeight = 0;
addAnimationTime = IN_TIME;
}
@Override
public void onEvent(BubbleNotificationEvent event) {
finishAddAnimation();
Notification newBubble = new Notification(event.message, event.borderColor);
bubbles.add(0, newBubble);
addAnimationTime = 0;
addAnimationHeight = newBubble.height + Notification.paddingY;
ListIterator<Notification> iter = bubbles.listIterator();
iter.next();
while (iter.hasNext()) {
Notification next = iter.next();
next.baseY = next.y;
}
}
private static class Notification {
private final static int HOVER_ANIM_TIME = 150;
private static int width;
private static int finalX;
private static int baseLine;
private static int fontPaddingX;
private static int fontPaddingY;
private static int lineHeight;
private static int paddingY;
private final Color bgcol;
private final Color textColor;
private final Color borderColor;
private final Color targetBorderColor;
private int timeShown;
private int x;
private int y;
private int baseY;
private int height;
private final String message;
private List<String> lines;
private boolean isFading;
private int hoverTime;
private Notification(String message, Color borderColor) {
this.message = message;
recalculateDimensions();
this.targetBorderColor = borderColor;
this.borderColor = new Color(borderColor);
this.textColor = new Color(Color.white);
this.bgcol = new Color(Color.black);
this.y = baseLine - height;
this.baseY = this.y;
}
private void recalculateDimensions() {
this.lines = Fonts.wrap(Fonts.SMALLBOLD, message, (int) (width * 0.96f), true);
this.height = (int) (Fonts.SMALLBOLD.getLineHeight() * (lines.size() + 0.5f));
}
/**
* @return true if this notification expired
*/
private boolean render(Graphics g, int mouseX, int mouseY, int delta) {
timeShown += delta;
processAnimations(isMouseHovered(mouseX, mouseY), delta);
g.setColor(bgcol);
g.fillRoundRect(x, y, width, height, 6);
g.setLineWidth(2f);
g.setColor(borderColor);
g.drawRoundRect(x, y, width, height, 6);
int y = this.y + fontPaddingY;
for (String line : lines) {
Fonts.SMALLBOLD.drawString(x + fontPaddingX, y, line, textColor);
y += lineHeight;
}
return timeShown > BubbleNotificationState.TOTAL_TIME;
}
private void processAnimations(boolean mouseHovered, int delta) {
if (mouseHovered) {
hoverTime = Math.min(HOVER_ANIM_TIME, hoverTime + delta);
} else {
hoverTime = Math.max(0, hoverTime - delta);
}
float hoverProgress = (float) hoverTime / HOVER_ANIM_TIME;
borderColor.r = targetBorderColor.r + (0.977f - targetBorderColor.r) * hoverProgress;
borderColor.g = targetBorderColor.g + (0.977f - targetBorderColor.g) * hoverProgress;
borderColor.b = targetBorderColor.b + (0.977f - targetBorderColor.b) * hoverProgress;
if (timeShown < BubbleNotificationState.IN_TIME) {
float progress = (float) timeShown / BubbleNotificationState.IN_TIME;
this.x = finalX + (int) ((1 - AnimationEquation.OUT_BACK.calc(progress)) * width / 2);
textColor.a = borderColor.a = bgcol.a = progress;
return;
}
x = Notification.finalX;
if (timeShown > BubbleNotificationState.DISPLAY_TIME) {
isFading = true;
float progress = (float) (timeShown - BubbleNotificationState.DISPLAY_TIME) / BubbleNotificationState.OUT_TIME;
textColor.a = borderColor.a = bgcol.a = 1f - progress;
}
}
private boolean mouseReleased(int x, int y) {
if (!isFading && isMouseHovered(x, y)) {
timeShown = BubbleNotificationState.DISPLAY_TIME;
return true;
}
return false;
}
private boolean isMouseHovered(int x, int y) {
return this.x <= x && x < this.x + width && this.y <= y && y <= this.y + this.height;
}
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.specialstates;
import itdelatrisu.opsu.ui.Fonts;
import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.events.EventListener;
import yugecin.opsudance.events.ResolutionChangedEvent;
public class FpsRenderState implements EventListener<ResolutionChangedEvent> {
private final DisplayContainer displayContainer;
private final static Color GREEN = new Color(171, 218, 25);
private final static Color ORANGE = new Color(255, 204, 34);
private final static Color DARKORANGE = new Color(255, 149, 24);
private int x;
private int y;
private int singleHeight;
public FpsRenderState(DisplayContainer displayContainer) {
this.displayContainer = displayContainer;
displayContainer.eventBus.subscribe(ResolutionChangedEvent.class, this);
}
public void render(Graphics g) {
int x = this.x;
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) {
return GREEN;
}
if (realValue <= targetValue * 1.15f) {
return ORANGE;
}
return DARKORANGE;
}
/**
* @return x position where the next block can be drawn (right aligned)
*/
private int drawText(Graphics g, Color color, String text, int x, int y) {
int width = Fonts.SMALL.getWidth(text) + 10;
g.setColor(color);
g.fillRoundRect(x - width, y, width, singleHeight + 6, 2);
Fonts.SMALL.drawString(x - width + 3, y + 3, text, Color.black);
return x - width - 6;
}
@Override
public void onEvent(ResolutionChangedEvent event) {
singleHeight = Fonts.SMALL.getLineHeight();
x = event.width - 3;
y = event.height - 3 - singleHeight - 10;
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.transitions;
import yugecin.opsudance.core.DisplayContainer;
public class EmptyTransitionState extends TransitionState {
public EmptyTransitionState(DisplayContainer displayContainer) {
super(displayContainer);
}
@Override
public void enter() {
finish();
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.transitions;
import yugecin.opsudance.core.DisplayContainer;
public class FadeInTransitionState extends FadeTransitionState {
public FadeInTransitionState(DisplayContainer container) {
super(container);
}
@Override
protected float getMaskAlphaLevel(float fadeProgress) {
return 1f - fadeProgress;
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.transitions;
import yugecin.opsudance.core.DisplayContainer;
public class FadeOutTransitionState extends FadeTransitionState {
public FadeOutTransitionState(DisplayContainer container) {
super(container);
}
@Override
protected float getMaskAlphaLevel(float fadeProgress) {
return fadeProgress;
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.transitions;
import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import yugecin.opsudance.core.DisplayContainer;
public abstract class FadeTransitionState extends TransitionState {
private final Color black;
public FadeTransitionState(DisplayContainer displayContainer) {
super(displayContainer);
black = new Color(Color.black);
}
@Override
public void render(Graphics g) {
applicableState.render(g);
black.a = getMaskAlphaLevel((float) transitionTime / transitionTargetTime);
g.setColor(black);
g.fillRect(0, 0, displayContainer.width, displayContainer.height);
}
protected abstract float getMaskAlphaLevel(float fadeProgress);
}

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.state.transitions;
public interface TransitionFinishedListener {
void onFinish();
}

View File

@ -0,0 +1,98 @@
/*
* 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.transitions;
import org.newdawn.slick.Graphics;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.state.BaseOpsuState;
import yugecin.opsudance.core.state.OpsuState;
import java.io.StringWriter;
public abstract class TransitionState extends BaseOpsuState {
protected OpsuState applicableState;
protected int transitionTargetTime;
protected int transitionTime;
private TransitionFinishedListener listener;
public TransitionState(DisplayContainer displayContainer) {
super(displayContainer);
}
public final TransitionState set(OpsuState applicableState, int targetTime, TransitionFinishedListener listener) {
this.applicableState = applicableState;
this.transitionTargetTime = targetTime;
this.listener = listener;
return this;
}
public final OpsuState getApplicableState() {
return applicableState;
}
@Override
public void update() {
applicableState.update();
transitionTime += displayContainer.delta;
if (transitionTime >= transitionTargetTime) {
finish();
}
}
@Override
public void preRenderUpdate() {
applicableState.preRenderUpdate();
}
@Override
public void render(Graphics g) {
applicableState.render(g);
}
@Override
public void enter() {
super.enter();
transitionTime = 0;
}
protected final void finish() {
listener.onFinish();
}
@Override
public boolean onCloseRequest() {
return false;
}
@Override
public void writeErrorDump(StringWriter dump) {
dump.append("> TransitionState dump\n");
dump.append("progress: ").append(String.valueOf(transitionTime)).append("/").append(String.valueOf(transitionTargetTime)).append('\n');
dump.append("applicable state: ");
if (applicableState == null) {
dump.append("IS NULL");
return;
}
dump.append(applicableState.getClass().getSimpleName()).append('\n');
applicableState.writeErrorDump(dump);
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.events;
public class BarNotificationEvent {
public final String message;
public BarNotificationEvent(String message) {
this.message = message;
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.events;
import org.newdawn.slick.Color;
public class BubbleNotificationEvent {
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;
public BubbleNotificationEvent(String message, Color borderColor) {
this.message = message;
this.borderColor = borderColor;
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.events;
public class ResolutionChangedEvent {
public final int width;
public final int height;
public ResolutionChangedEvent(int width, int height) {
this.width = width;
this.height = height;
}
}

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,18 @@ public class MoveStoryboard {
});
}
@Override
public void revalidate() {
super.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 +96,23 @@ public class MoveStoryboard {
return moves[objectIndex].getPointAt(t);
}
public void render(Graphics g) {
@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 +124,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 +156,7 @@ public class MoveStoryboard {
}
}
if (objectIndex == 0) {
return;
return true;
}
if (btnAddLinear.isHovered()) {
getCurrentMoveOrCreateNew().add(new LinearStoryboardMover());
@ -134,6 +176,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 +190,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 +202,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

@ -0,0 +1,114 @@
/*
* 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.states;
import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.state.OpsuState;
import yugecin.opsudance.events.BarNotificationEvent;
import yugecin.opsudance.events.BubbleNotificationEvent;
import java.io.StringWriter;
public class EmptyRedState implements OpsuState {
private int counter;
private long start;
private final DisplayContainer displayContainer;
public EmptyRedState(DisplayContainer displayContainer) {
this.displayContainer = displayContainer;
}
@Override
public void update() {
counter -= displayContainer.delta;
if (counter < 0) {
counter = 10000; // to prevent more calls to switch, as this will keep rendering until state transitioned
System.out.println(System.currentTimeMillis() - start);
displayContainer.switchState(EmptyState.class);
}
}
@Override
public void preRenderUpdate() {
}
@Override
public void render(Graphics g) {
g.setColor(Color.red);
g.fillRect(0, 0, 100, 100);
}
@Override
public void enter() {
counter = 5000;
start = System.currentTimeMillis();
}
@Override
public void leave() {
}
@Override
public boolean onCloseRequest() {
return true;
}
@Override
public boolean keyPressed(int key, char c) {
displayContainer.eventBus.post(new BubbleNotificationEvent("this is a bubble notification... bubbly bubbly bubbly linewraaaaaaaaaap", BubbleNotificationEvent.COMMONCOLOR_RED));
return false;
}
@Override
public boolean keyReleased(int key, char c) {
return false;
}
@Override
public boolean mouseWheelMoved(int delta) {
displayContainer.eventBus.post(new BubbleNotificationEvent("Life is like a box of chocolates. It's all going to melt by the end of the day.\n-Emily", BubbleNotificationEvent.COMMONCOLOR_PURPLE));
return false;
}
@Override
public boolean mousePressed(int button, int x, int y) {
return false;
}
@Override
public boolean mouseReleased(int button, int x, int y) {
displayContainer.eventBus.post(new BarNotificationEvent("this is a\nbar notification"));
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");
dump.append("its red\n");
}
}

View File

@ -0,0 +1,106 @@
/*
* 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.states;
import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.state.OpsuState;
import java.io.StringWriter;
public class EmptyState implements OpsuState {
private int counter;
private final DisplayContainer displayContainer;
public EmptyState(DisplayContainer displayContainer) {
this.displayContainer = displayContainer;
}
@Override
public void update() {
counter -= displayContainer.delta;
if (counter < 0) {
counter = 10000; // to prevent more calls to switch, as this will keep rending until state transitioned
displayContainer.switchState(EmptyRedState.class);
}
}
@Override
public void preRenderUpdate() {
}
@Override
public void render(Graphics g) {
g.setColor(Color.green);
g.fillRect(0, 0, 100, 100);
}
@Override
public void enter() {
counter = 2000;
}
@Override
public void leave() {
}
@Override
public boolean onCloseRequest() {
return true;
}
@Override
public boolean keyPressed(int key, char c) {
return false;
}
@Override
public boolean keyReleased(int key, char c) {
return false;
}
@Override
public boolean mouseWheelMoved(int delta) {
return false;
}
@Override
public boolean mousePressed(int button, int x, int y) {
return false;
}
@Override
public boolean mouseReleased(int button, int x, int y) {
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");
dump.append("its green\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) {
if (0 <= listHoverIndex && listHoverIndex < hoverOption.getListItems().length) {
hoverOption.clickListItem(listHoverIndex);
parent.onSaveOption(hoverOption);
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();
}
public void mouseDragged(int oldx, int oldy, int newx, int newy) {
return true;
}
@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 (key == Input.KEY_ESCAPE) {
if (isListOptionOpen) {
isListOptionOpen = false;
listHoverIndex = -1;
return true;
}
parent.onLeave();
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

@ -0,0 +1,41 @@
/*
* 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;
public class CachedVariable<T> {
private T value;
private Getter<T> getter;
public CachedVariable(Getter<T> getter) {
this.getter = getter;
}
public T get() {
if (getter != null) {
value = getter.get();
getter = null;
}
return value;
}
public interface Getter<T> {
T get();
}
}

View File

@ -0,0 +1,111 @@
/*
* 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.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;
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 {
/**
* from org.newdawn.slick.AppGameContainer#setDisplayMode
*/
public static DisplayMode findFullscreenDisplayMode(int targetBPP, int targetFrequency, int width, int height) throws LWJGLException {
DisplayMode[] modes = Display.getAvailableDisplayModes();
DisplayMode foundMode = null;
int freq = 0;
int bpp = 0;
for (DisplayMode current : modes) {
if (current.getWidth() != width || current.getHeight() != height) {
continue;
}
if (current.getBitsPerPixel() == targetBPP && current.getFrequency() == targetFrequency) {
return current;
}
if (current.getFrequency() >= freq && (foundMode == null || current.getBitsPerPixel() >= bpp)) {
foundMode = current;
freq = foundMode.getFrequency();
bpp = foundMode.getBitsPerPixel();
}
}
return foundMode;
}
/**
* from org.newdawn.slick.AppGameContainer#setDisplayMode
*/
public static void setIcons(String[] refs) {
ByteBuffer[] bufs = new ByteBuffer[refs.length];
for (int i = 0; i < refs.length; i++) {
LoadableImageData data;
boolean flip = true;
if (refs[i].endsWith(".tga")) {
data = new TGAImageData();
} else {
flip = false;
data = new ImageIOImageData();
}
try {
bufs[i] = data.loadImage(ResourceLoader.getResourceAsStream(refs[i]), flip, false, null);
} catch (Exception e) {
Log.error("failed to set the icon", e);
return;
}
}
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,42 @@
/*
* 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 itdelatrisu.opsu.Options;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
import java.io.IOException;
import java.util.Properties;
public class MiscUtils {
public static final CachedVariable<Properties> buildProperties = new CachedVariable<>(new CachedVariable.Getter<Properties>() {
@Override
public Properties get() {
Properties props = new Properties();
try {
props.load(ResourceLoader.getResourceAsStream(Options.VERSION_FILE));
} catch (IOException e) {
Log.error("Could not read version file", e);
}
return props;
}
});
}

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) {
}
}
}