diff --git a/src/itdelatrisu/opsu/Container.java b/src/itdelatrisu/opsu/Container.java deleted file mode 100644 index 63880381..00000000 --- a/src/itdelatrisu/opsu/Container.java +++ /dev/null @@ -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 . - */ - -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(); - } -} diff --git a/src/itdelatrisu/opsu/ErrorHandler.java b/src/itdelatrisu/opsu/ErrorHandler.java deleted file mode 100644 index 1ccead20..00000000 --- a/src/itdelatrisu/opsu/ErrorHandler.java +++ /dev/null @@ -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 . - */ - -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, "", "")); - } - } -} diff --git a/src/itdelatrisu/opsu/Opsu.java b/src/itdelatrisu/opsu/Opsu.java deleted file mode 100644 index b2c6d410..00000000 --- a/src/itdelatrisu/opsu/Opsu.java +++ /dev/null @@ -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 . - */ - -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. - *

- * 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); - } -} diff --git a/src/org/newdawn/slick/GameContainer.java b/src/org/newdawn/slick/GameContainer.java deleted file mode 100644 index 01c7604f..00000000 --- a/src/org/newdawn/slick/GameContainer.java +++ /dev/null @@ -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 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); - } -}