/* * Copyright (c) 2003-onwards Shaven Puppy Ltd * 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 'Shaven Puppy' 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 net.puppygames.applet; import java.io.*; import java.net.*; import java.nio.ByteBuffer; import java.rmi.Naming; import java.text.*; import java.util.*; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import net.puppygames.applet.effects.EffectFeature; import net.puppygames.applet.effects.SFX; import net.puppygames.applet.screens.*; import net.puppygames.gamecommerce.shared.*; import org.lwjgl.LWJGLException; import org.lwjgl.Sys; import org.lwjgl.input.*; import org.lwjgl.openal.AL10; import org.lwjgl.opengl.*; import org.lwjgl.util.Rectangle; import org.lwjgl.util.Timer; import com.shavenpuppy.jglib.*; import com.shavenpuppy.jglib.Image.JPEGDecompressor; import com.shavenpuppy.jglib.jpeg.JPEGDecoder; import com.shavenpuppy.jglib.openal.ALBuffer; import com.shavenpuppy.jglib.openal.ALStream; import com.shavenpuppy.jglib.resources.*; import com.shavenpuppy.jglib.sound.SoundEffect; import com.shavenpuppy.jglib.sound.SoundPlayer; import com.shavenpuppy.jglib.sprites.SoundCommand; import com.shavenpuppy.jglib.util.CheckOnline; import com.shavenpuppy.jglib.util.Util; import static org.lwjgl.opengl.GL11.*; /** * $Id: Game.java,v 1.56 2010/10/17 21:04:01 foo Exp $ Abstract base class for a "game" * * @author $Author: foo $ * @version $Revision: 1.56 $ */ public abstract class Game extends Feature { /* * Static game data */ private static final long serialVersionUID = 1L; private static final String RESTORE_GAME_DIALOG_FEATURE = "restore_game.dialog"; private static final String SAVE_GAME_EFFECT_FEATURE = "save_game.effect"; private static final String DEFAULT_GAME_RESOURCE_NAME = "game.puppygames"; private static final String DEFAULT_USER_PREFS_FILENAME = "prefs.xml"; public static final boolean DEBUG = false; private static final boolean REGISTERED = false; private static final boolean FORCEUSELOG = false; private static final boolean TESTREGISTER = false; /** Keydown state tracking */ private static final boolean[] KEYDOWN = new boolean[Keyboard.KEYBOARD_SIZE]; private static final boolean[] KEYWASDOWN = new boolean[Keyboard.KEYBOARD_SIZE]; /** Restore filename */ protected static final String RESTORE_FILE = "restore.dat"; /** Force run even when not focused */ private static boolean alwaysRun; /** Do the Buy page */ private static boolean doBuy; /** Prevent the Buy page appearing */ private static boolean preventBuy; /** Pause enabled */ private static boolean pauseEnabled = true; /** Sound effects enabled */ private static boolean sfxEnabled = true; /** Music enabled */ private static boolean musicEnabled = true; /** Paused mode */ private static boolean paused; /** Finished flag */ private static boolean finished; /** Initialised flag */ private static boolean initialised; /** Arguments passed in main() */ private static Properties properties; /** Registration details */ private static RegistrationDetails registrationDetails; /** Registered flag */ private static boolean registered; /** Unique installation number */ private static long installation; /** Score group */ private static String scoreGroup; /** Game preferences */ private static Preferences PREFS, GLOBALPREFS; /** Configuration */ private static Configuration configuration; /** Games this session */ private static int playedThisSession; /** Instructions shown? */ private static boolean shownInstructions; /** Singleton */ private static Game game; /** Game info, for logging */ private static GameInfo gameInfo; /** Sound player */ private static SoundPlayer soundPlayer; /** All sound players */ private static List<SoundPlayer> soundPlayers; /** Music */ private static SoundEffect music; /** Local files directory prefix */ private static String dirPrefix; /** Stash the local log here */ private static String GAMEINFO_FILE; /** Ticks played so far */ private static int playedTicks; /** Music volume 0..100 */ private static int musicVolume; /** Effects volume 0..100 */ private static int sfxVolume; /** Initial display mode */ private static DisplayMode initialMode; /** Display bounds */ private static Rectangle viewPort; /** Game state */ private static GameState gameState; /** Allow saving the game */ private static boolean allowSave = true; /** Top screen */ public static Screen topScreen; /** Game title */ private static String title; /** Internal title, used for local prefs */ private static String internalTitle; /** Version */ private static String version; /** Internal version, used for local settings */ private static String internalVersion; /** Restore game dialog */ private static DialogScreen restoreGameDialog; /** Current player slot */ private static PlayerSlot playerSlot; /** Custom display mode */ private static boolean customDisplayMode; /** Viewport offset */ private static int viewportXoffset, viewportYoffset, viewportWidth, viewportHeight; /** Force sleep instead of yield */ private static boolean forceSleep; /** Modded? */ private static boolean modded; /** Mod name */ private static String modName; static { Image.setDecompressor(new JPEGDecompressor() { @Override public void decompress(ByteBuffer src, ByteBuffer dest) throws Exception { byte[] data = new byte[src.capacity()]; src.get(data); ByteArrayInputStream bais = new ByteArrayInputStream(data); Image img = JPEGDecoder.loadFromByteStream(bais); dest.put(img.getData()); img.dispose(); dest.flip(); } }); } /* * Feature data */ /** Game dimensions */ private int width, height; /** Framerate */ private int frameRate; /** Message sequence number */ private int messageSequence; /** Support email */ @Data private String supportEmail = "support@puppygames.net"; /** Support url */ @Data private String supportURL = "www.puppygames.net/support/support.php"; /** Contact url */ @Data private String contactURL = "www.puppygames.net/contact.php"; /** Website */ @Data private String website = "www.puppygames.net"; /** Download URL */ @Data private String download = "www.puppygames.net"; /** Buy url */ @Data private String buyURL = ""; /** More Games url */ @Data private String moreGamesURL = "www.puppygames.net"; /** Default to fullscreen */ private int defaultFullscreen = 0; /** Don't allow remote hiscores */ private boolean dontUseRemoteHiscores; /** Don't check for messages? */ private boolean dontCheckMessages; /** Slot management */ private boolean useSlotManagement; /** Sound voices */ private int soundVoices = 64; /** Use variable window sizing */ private boolean useWindowSizing; /** GUI scale in pixels */ private int scale; /** Preregistered? */ private boolean preregistered; /** Preregistered to (optional) */ @Data private String preregisteredTo; /** Preregistered until this date, then it turns back into a demo (optional). Format is yyyy-mm-dd */ @Data private String preregisteredUntil; /** Locale locked: game only preregistered in this locale (optional) */ @Data private String preregisteredLocale; /** Keyboard locked: game only preregistered in this locale (optional) */ @Data private String preregisteredLanguage; /* * Transient data */ /** Window size */ private transient float windowSize; private transient int panic; private transient boolean wasGrabbed; private transient boolean submitRemoteHiscores; private transient int logicalWidth; private transient int logicalHeight; private transient boolean catchUp; private transient float masterGain, targetMasterGain; /** Prefs saver */ public static PrefsSaverThread prefsSaver; public static class PrefsSaverThread extends Thread { boolean threadFinished; boolean triggered; /** * C'tor */ public PrefsSaverThread() { super("Prefs Saver Thread"); setPriority(NORM_PRIORITY - 1); } public synchronized void save() { triggered = true; notifyAll(); } synchronized void finish() { threadFinished = true; save(); try { join(); } catch (InterruptedException e) { e.printStackTrace(System.err); } } @Override public void run() { while (!threadFinished) { synchronized (this) { // Wait for first notification... try { wait(); } catch (InterruptedException e) { } // Now wait for 200ms to elapse before finally flushing while (triggered) { triggered = false; try { wait(200L); if (!triggered) { doFlushPrefs(); } } catch (InterruptedException e) { } } } } } } /** * C'tor */ public Game(String name) { super(name); } /** * Enable / disable pausing */ public static void setPauseEnabled(boolean pauseEnabled) { Game.pauseEnabled = pauseEnabled; if (paused && !pauseEnabled) { paused = false; } } /** * @return Returns the game. */ public static Game getGame() { return game; } /** * @return Returns the game width (logical units). */ public static int getWidth() { return game.logicalWidth; } public static void setSize(int width, int height) { int oldWidth = game.width; int oldHeight = game.height; game.width = width; game.height = height; try { setFullscreen(false); } catch (Exception e) { e.printStackTrace(System.err); game.width = oldWidth; game.height = oldHeight; } } /** * @return Returns the game height (logical units). */ public static int getHeight() { return game.logicalHeight; } /** * @return Returns the frameRate. */ public static int getFrameRate() { return game.frameRate; } /** * @return Returns the title. */ public static String getTitle() { return title; } /** * @return Returns the version. */ public static String getVersion() { return version; } /** * @return the internal version number */ public static String getInternalVersion() { return internalVersion; } /** * @return Returns the website URL, minus the schema (default www.puppygames.net) */ public static String getWebsite() { return game.website; } /** * @return the download URL */ public static String getDownload() { return game.download; } /** * @return Returns the support website URL, minus the schema (default www.puppygames.net/support.php) */ public static String getSupportURL() { return game.supportURL; } /** * Returns the support email address (default support@puppygames.net) Will return "", if the contact URL is to be used instead * @return String */ public static String getSupportEmail() { return game.supportEmail; } /** * @return Returns the support contact URL (default null) */ public static String getContactURL() { return game.contactURL; } /** * @return Returns the configuration. */ public static Configuration getConfiguration() { return configuration; } /** * @return Returns the gameInfo */ public static GameInfo getGameInfo() { return gameInfo; } /** * @return Returns the installation. */ public static long getInstallation() { return installation; } /** * @return Returns the global Puppygames preferences. */ public static Preferences getGlobalPreferences() { return GLOBALPREFS; } /** * @return Returns the game preferences. */ public static Preferences getPreferences() { return PREFS; } /** * @return Returns the registrationDetails. */ public static RegistrationDetails getRegistrationDetails() { return registrationDetails; } /** * @return Returns the finished. */ public static boolean isFinished() { return finished; } /** * @return Returns true if the game is paused. */ public static boolean isPaused() { return paused; } /** * @return Returns true if the game is registered */ public static boolean isRegistered() { return REGISTERED || registered || game.preregistered; } /** * Redirect a PrintStream to a file */ private static PrintStream redirectOutput(final PrintStream output, String fileName) throws FileNotFoundException { if (fileName == null || fileName.equals("")) { return output; } boolean append = true; File outFile = new File(dirPrefix + File.separator + fileName); if (outFile.exists()) { if (outFile.length() > 65535) { outFile.renameTo(new File(dirPrefix + File.separator + fileName + ".old")); append = false; } } final FileOutputStream fos = new FileOutputStream(outFile, append); OutputStream os = new OutputStream() { boolean wroteDate; @Override public void write(int b) throws IOException { if (!wroteDate) { wroteDate = true; write(new Date().toString()); write("\t"); } output.write(b); fos.write(b); if (b == '\n') { flush(); output.flush(); wroteDate = false; } } private void write(String s) throws IOException { int len = s.length(); for (int i = 0; i < len; i ++) { write(s.charAt(i)); } } }; return new PrintStream(os); } /** * Initialise the game. This must be called <strong>outside</strong> of the AWT thread! * @param resourcesStream An InputStream for reading in a compiled resource data file, created by the JGLIB ResourceConverter * tool. * @throws Exception if the game fails to initialise correctly */ public static synchronized void init(Properties properties, InputStream resourcesStream) throws Exception { if (initialised) { return; } initialised = true; finished = false; Game.properties = properties; // Load game resource metadata. Resources.load(resourcesStream); // Get the game's title & version directly from a text resource Game.title = ((TextResource) Resources.get("title")).getText().trim(); Game.version = ((TextResource) Resources.get("version")).getText().trim(); TextResource iv = (TextResource) Resources.peek("internalVersion"); if (iv != null) { Game.internalVersion = iv.getText().trim(); } else { Game.internalVersion = Game.version; } // Initialise local filesystem initFiles(); if (FORCEUSELOG || !DEBUG) { try { System.setOut(redirectOutput(System.out, properties.getProperty("out", "out.log"))); System.setErr(redirectOutput(System.err, properties.getProperty("err", "err.log"))); } catch (FileNotFoundException e) { // Ignore if (DEBUG) { e.printStackTrace(System.err); } } } System.out.println(new Date() + " Game: " + title + " " + version + " ["+internalVersion+"]"); // Create preferences and determine / generate installation number GLOBALPREFS = Preferences.userNodeForPackage(Game.class); installation = GLOBALPREFS.getLong("installation", 0); if (installation == 0L) { installation = (long) (Math.random() * Long.MAX_VALUE); GLOBALPREFS.putLong("installation", installation); } System.out.println("Serial " + installation); // Find the game we want String gameResource; try { gameResource = System.getProperty("net.puppygames.applet.Game.gameResource", properties.getProperty("gameresource", DEFAULT_GAME_RESOURCE_NAME)); } catch (SecurityException e) { e.printStackTrace(System.err); gameResource = DEFAULT_GAME_RESOURCE_NAME; } System.out.println("Game resource: "+gameResource); game = (Game) Resources.peek(gameResource); // Load prefs from backup file FileInputStream fis = null; BufferedInputStream bis = null; File prefsFile = new File(getUserPrefsFileName()); boolean zapPrefs = false; if (prefsFile.exists()) { try { fis = new FileInputStream(prefsFile); bis = new BufferedInputStream(fis); Preferences.importPreferences(bis); System.out.println("Loaded preferences file "+prefsFile); } catch (Exception e) { e.printStackTrace(System.err); zapPrefs = true; } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { } } } } else { System.out.println("Preferences file "+prefsFile+" does not exist."); zapPrefs = true; } PREFS = Preferences.userNodeForPackage(Game.class).node(title); if (zapPrefs) { PREFS.removeNode(); PREFS.flush(); PREFS = null; PREFS = Preferences.userNodeForPackage(Game.class).node(title); doFlushPrefs(); } prefsSaver = new PrefsSaverThread(); prefsSaver.start(); // Load or create configuration byte[] cfg = PREFS.getByteArray("configuration", null); if (cfg == null) { // No configuration present so create a new one createConfiguration(); } else { ByteArrayInputStream bais = new ByteArrayInputStream(cfg); ObjectInputStream ois = new ObjectInputStream(bais); try { configuration = (Configuration) ois.readObject(); } catch (Exception e) { // Corrupted, so create a new one createConfiguration(); } finally { ois.close(); } } if (DEBUG) { System.out.println("Proxy host: " + System.getProperty("http.proxyHost")); } // Check registration details checkRegistration(); // Check for bad exit last time (only caused by VM crashes or machine crashes) boolean wasBadExit = PREFS.getBoolean("badexit", false); // Initialise a gameinfo object so we can log things gameInfo = new GameInfo(getTitle(), getVersion(), getInstallation(), wasBadExit, org.lwjgl.opengl.Display.getAdapter(), org.lwjgl.opengl.Display.getVersion(), configuration.encode()); System.out.println("Starting " + getTitle() + " " + getVersion()); // If there was a bad exit, let's put up a message if (wasBadExit && !DEBUG) { Game.alert(getTitle() + " did not shut down correctly last time you tried to play.\n\nIf you are experiencing problems or bugs please " + ("".equals(getSupportEmail()) ? "visit " + getContactURL() : "contact " + getSupportEmail()) + "\nand tell us!"); Support.doSupport("crash"); } // Set the bad exit flag. The only way to clear it is to exit the game // cleanly via the exit() method. PREFS.putBoolean("badexit", true); flushPrefs(); // Load mod if any is specified loadMods(); DynamicResource.createAll(); // Initialise sound initSound(); // Gamepads... have to init first for LWJGL bug. try { Controllers.create(); } catch (Exception e) { System.err.println("No gamepads or joysticks enabled due to " + e); } // Initialise display try { initDisplay(); } catch (Exception e) { e.printStackTrace(System.err); gameInfo.setException(e); Game.alert("You need to get new graphics card drivers in order to play " + getTitle() + ".\nPlease contact your system vendor for assistance."); Support.doSupport("opengl"); exit(); } // Show the splash screen Display.setVSyncEnabled(false); Splash splash = Splash.getInstance(); if (splash != null) { try { splash.create(); } catch (Exception e) { e.printStackTrace(System.err); splash = null; } } // Autocreate features SFX.createSFX(); Res.createResources(); Feature.autoCreate(); // We're in Run Mode now Resources.setRunMode(true); // Update gameinfo String glvendor = glGetString(GL_VENDOR); String glrenderer = glGetString(GL_RENDERER); String glversion = glGetString(GL_VERSION); String gldriver = null; int i = glversion.indexOf(' '); if (i != -1) { gldriver = glversion.substring(i + 1); glversion = glversion.substring(0, i); } gameInfo.update(glvendor, glrenderer, glversion, gldriver, registrationDetails); // Force sleep? forceSleep = properties.getProperty("sleep", Runtime.getRuntime().availableProcessors() > 1 ? "false" : "true").equalsIgnoreCase("true"); // Finally, create the game game.create(); // Load keyboard bindings loadBindings(); // That's enough of the loading screen... if (splash != null) { splash.destroy(); splash = null; } initVsync(); // If slot managed, read current slot: if (isSlotManaged()) { String slotName = PREFS.get("slot_"+getInternalVersion(), null); if (slotName != null) { PlayerSlot slot = new PlayerSlot(slotName); if (slot.exists()) { setPlayerSlot(slot); } } } // Now go into 3D... glMatrixMode(GL_PROJECTION); glLoadIdentity(); glFrustum(-Game.getWidth() / 64.0, Game.getWidth() / 64.0, -Game.getHeight() / 64.0, Game.getHeight() / 64.0, 8, 65536); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); // Show title screen or registration screen as appropriate if (!isRegistered() || TESTREGISTER) { showRegisterScreen(); } else if (REGISTERED || game.preregistered) { preRegisteredStartup(); } else { showTitleScreen(); } // Remote hiscores? game.submitRemoteHiscores = PREFS.getBoolean("submitremotehiscores", !game.dontUseRemoteHiscores); // Now run! resourcesStream.close(); resourcesStream = null; try { game.run(); } catch (Throwable t) { System.err.println("Set exception to " + t + " : " + t.getMessage()+" Stack trace follows:"); t.printStackTrace(System.err); gameInfo.setException(t); } finally { exit(); } } /** * Called instead of opening the title screen */ private static void preRegisteredStartup() { game.onPreRegisteredStartup(); } protected void onPreRegisteredStartup() { // Default behaviour, just open the title screen showTitleScreen(); } /** * Get the local app settings dir * @return String */ private static String getSettingsDir() { return properties.getProperty ( "home", System.getProperty("os.name").startsWith("Mac OS") ? System.getProperty("user.home", "") + "/Library/Application Support" : System.getProperty("user.home", "") ); } /** * Get the directory prefix * @return the directory prefix for "global" local settings, for all slots (includes trailing slash) */ public static String getDirectoryPrefix() { return dirPrefix; } /** * Get the player directory prefix * @return the directory prefix for the current player's "slot" local settings (includes trailing slash) */ public static String getPlayerDirectoryPrefix() { String ret = dirPrefix + "slots_" + getInternalVersion() + File.separator + playerSlot.getName(); // Lazy creation File f = new File(ret); if (!f.exists()) { f.mkdirs(); } return ret + File.separator; } /** * Get the slot directory prefix * @return the directory prefix for all the player slots (includes trailing slash) */ public static String getSlotDirectoryPrefix() { String ret = dirPrefix + "slots_" + getInternalVersion(); // Lazy creation File f = new File(ret); if (!f.exists()) { f.mkdirs(); } return ret + File.separator; } private static void initFiles() { boolean dirPrefixExists; String settingsDirName = getSettingsDir() + File.separator + "." + getTitle().replace(' ', '_').toLowerCase() + '_' + getInternalVersion(); System.out.println("settingsDirName="+settingsDirName); File settingsDir = new File(settingsDirName); if (!settingsDir.exists()) { System.out.println("Creating settingsDir: "+settingsDirName); settingsDir.mkdirs(); dirPrefixExists = settingsDir.exists(); } else { dirPrefixExists = true; } dirPrefix = dirPrefixExists ? settingsDirName + File.separator : getSettingsDir() + File.separator; System.out.println("dirPrefix="+dirPrefix); GAMEINFO_FILE = dirPrefix + "log.dat"; } /** * Write tix out so we can count how long the player has played for */ private static void writeTix() { // Write tix int tix = PREFS.getInt("tix", 0); int newTix = playedTicks / getFrameRate(); tix += newTix; playedTicks = 0; gameInfo.addTime(newTix); PREFS.putInt("tix", tix); } /** * Call this every frame that the game is being played. */ public static void onTicked() { playedTicks++; } /** * Write out the execution log. If we're online, then we'll read in the existing execution log (if any), append the current * GameInfo, and send it to the server. If we're offline or unsuccessful we'll write it to disk for a rainy day. The log is * deleted once it's sent successfully. */ @SuppressWarnings("unchecked") private static void writeLog() { if (gameInfo == null) { System.out.println("No game info log"); } if (GAMEINFO_FILE == null) { System.out.println("Couldn't write log - no filename"); return; } // Find and load any existing log File log = new File(getSettingsDir() + File.separator + GAMEINFO_FILE); List<GameInfo> logList = null; if (log.exists()) { ObjectInputStream ois = null; BufferedInputStream bis = null; FileInputStream fis = null; try { fis = new FileInputStream(log); bis = new BufferedInputStream(fis); ois = new ObjectInputStream(bis); logList = (List<GameInfo>) ois.readObject(); // Warning suppressed } catch (Exception e) { e.printStackTrace(System.err); logList = new ArrayList<GameInfo>(1); } finally { if (ois != null) { try { ois.close(); } catch (Exception e) { } } if (bis != null) { try { bis.close(); } catch (Exception e) { } } if (fis != null) { try { fis.close(); } catch (Exception e) { } } ois = null; bis = null; fis = null; } } else { // Don't bother with logging if the game is registered - unless // we've got a support call pending if (isRegistered() && !Support.isSupportQueued()) { return; } logList = new ArrayList<GameInfo>(1); } if (gameInfo != null) { // Update gameInfo writeTix(); logList.add(gameInfo); } // Attempt submission boolean isOnline = isRemoteCallAllowed(); boolean submitted = false; if (CheckOnline.isOnline() && (isOnline || Support.isSupportQueued() || gameInfo.isCrashRecovery() || gameInfo.getException() != null)) { try { // System.out.print("Submitting log... "); GameInfoServerRemote server = (GameInfoServerRemote) Naming.lookup("//puppygames.net/" + GameInfoServerRemote.REMOTE_NAME); server.submit(logList); onRemoteCallSuccess(); submitted = true; log.delete(); // System.out.println(" Log submitted."); } catch (Throwable e) { System.err.println(" Failed to write log: "+e); e.printStackTrace(System.err); } } if (!submitted) { // Write out log for a rainy day FileOutputStream fos = null; BufferedOutputStream bos = null; ObjectOutputStream oos = null; try { fos = new FileOutputStream(log); bos = new BufferedOutputStream(fos); oos = new ObjectOutputStream(bos); oos.writeObject(logList); oos.flush(); bos.flush(); fos.flush(); } catch (IOException e) { //e.printStackTrace(System.err); } finally { if (oos != null) { try { oos.close(); } catch (Exception e) { } } if (bos != null) { try { bos.close(); } catch (Exception e) { } } if (fos != null) { try { fos.close(); } catch (Exception e) { } } } } } /** * Clear the buy flag */ public static void clearBuy() { preventBuy = true; } /** * Show the "More Games" URL */ public static void showMoreGames() { preventBuy = true; Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { try { String page; PREFS.putBoolean("showregister", true); if (Resources.exists("moregames_url")) { TextResource tr = (TextResource) Resources.get("moregames_url"); page = tr.getText(); } else if (game.moreGamesURL != null && !"".equals(game.moreGamesURL)) { page = game.moreGamesURL; } else if (!System.getProperty("moregames_url", "!").equals("!")) { page = System.getProperty("moregames_url"); } else { page = "http://" + getWebsite(); } if (!Sys.openURL(page)) { throw new Exception("Failed to open URL "+page); } } catch (Exception e) { e.printStackTrace(System.err); Game.alert("Please open your web browser on the page http://" + getWebsite()); } } }); exit(); } /** * Buy the game */ public static void buy(boolean doExit) { if (!doBuy) { doBuy = true; Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { if (isRegistered() || preventBuy) { return; } PREFS.putBoolean("showregister", true); try { String page; if (Resources.exists("buy_url")) { TextResource tr = (TextResource) Resources.get("buy_url"); page = tr.getText(); } else if (game.buyURL != null && !"".equals(game.buyURL)) { page = game.buyURL; } else if (!System.getProperty("buy_url", "!").equals("!")) { page = System.getProperty("buy_url"); } else { page = "http://" + getWebsite() + "/purchase/buy.php?game=" + URLEncoder.encode(getTitle(), "utf-8") + "&configuration=" + URLEncoder.encode(configuration.encode(), "utf-8") + "@installation@"; } String replacement = "&installation=" + URLEncoder.encode(String.valueOf(installation), "utf-8"); int idx = page.indexOf("@installation@"); if (idx != -1) { StringBuilder sb = new StringBuilder(page); sb.replace(idx, idx + 14, replacement); page = sb.toString(); } if (!Sys.openURL(page)) { throw new Exception("Failed to open URL "+page); } } catch (Exception e) { e.printStackTrace(System.err); Game.alert("Please open your web browser on the page http://" + getWebsite()); } } }); } if (doExit) { exit(); } } public static void requestExit() { game.doRequestExit(); } protected void doRequestExit() { // Don't ask just exit by default exit(); } /** * Exit the program */ public static synchronized void exit() { if (!initialised) { return; } if (game != null) { game.onExit(); } initialised = false; finished = true; // Destroy the sound player if (soundPlayer != null && soundPlayer.isCreated()) { soundPlayer.destroy(); } // Do any other cleanup if (game != null) { game.cleanup(); } // Write log writeLog(); // If we got here, it's because of a nice clean exit. We'll clear the // bad exit flag. if (PREFS != null) { PREFS.putBoolean("badexit", false); } if (prefsSaver != null) { prefsSaver.finish(); prefsSaver = null; } // And nag :) if (game != null && !preventBuy) { buy(false); } System.exit(0); } /** * Called on exit */ protected void onExit() { } /** * Play a sound effect. The sound effect is always owned by the game, for simplicity * @param buffer The sound to play * @return a SoundEffect, or null, if a sound effect could not be created */ public static SoundEffect allocateSound(ALBuffer buffer) { return allocateSound(buffer, 1.0f, 1.0f); } /** * Play a sound effect. The sound effect is always owned by the game, for simplicity * @param buffer The sound to play * @return a SoundEffect, or null, if a sound effect could not be created */ public static SoundEffect allocateSound(ALBuffer buffer, float gain, float pitch) { return allocateSound(buffer, gain, pitch, Game.class); } /** * Play a sound effect. The sound effect is always owned by the game, for simplicity * @param buffer The sound to play * @return a SoundEffect, or null, if a sound effect could not be created */ public static SoundEffect allocateSound(ALBuffer buffer, float gain, float pitch, Object owner) { if (!org.lwjgl.openal.AL.isCreated() || !isSFXEnabled() || buffer == null || buffer.getWave() == null || soundPlayer == null) { return null; } SoundEffect effect = soundPlayer.allocate(buffer, (int) (buffer.getPriority() * gain), owner); if (effect != null && effect.getBuffer() != null) { effect.setAttenuated(false, owner); effect.setGain(effect.getBuffer().getGain() * sfxVolume * gain / 100.0f, owner); effect.setPitch(effect.getBuffer().getPitch() * pitch, owner); if (buffer.getWave().getType() == Wave.MONO_16BIT) { effect.setPosition(0.0f, 0.0f, 0.0f, owner); } } return effect; } /** * Play a sound effect. The sound effect is always owned by the game, for simplicity * @param buffer The sound to play * @return a SoundEffect, or null, if a sound effect could not be created */ public static SoundEffect allocateSound(ALStream buffer) { if (!org.lwjgl.openal.AL.isCreated() || !isSFXEnabled() || buffer == null || soundPlayer == null) { return null; } SoundEffect effect = soundPlayer.allocate(buffer, Game.class); if (effect != null) { effect.setAttenuated(false, Game.class); effect.setGain(effect.getStream().getSourceStream().getGain() * sfxVolume / 100.0f, Game.class); if (effect.getStream().getType() == Wave.MONO_16BIT) { effect.setPosition(0.0f, 0.0f, 0.0f, Game.class); } } return effect; } /** * Are sound effects enabled? * @return boolean */ public static boolean isSFXEnabled() { return sfxEnabled; } /** * Is music enabled? * @return boolean */ public static boolean isMusicEnabled() { return musicEnabled; } /** * Play some music. The existing music is nicely crossfaded. * @param buf The buffer containing the music * @param fade The fade length, in ticks * @param gain Extra gain multiplier */ public static void playMusic(ALStream buf, int fade, float gain) { // Maybe don't do anything if (music == null && buf == null || music != null && (music.getStream() == null || buf == music.getStream().getSourceStream())) { if (music != null) { music.setFade(fade + 1, gain * music.getStream().getSourceStream().getGain() * musicVolume / 100.0f, false, Game.class); } return; } // Fade out existing music boolean xfade = music != null; if (xfade) { music.setFade(fade + 1, 0.0f, true, Game.class); } if (buf == null || !isMusicEnabled()) { music = null; } else { music = allocateSound(buf); } if (music != null) { if (xfade) { music.setGain(0.0f, Game.class); music.setFade(fade + 1, buf == null ? 0.0f : gain * buf.getGain() * musicVolume / 100.0f, false, Game.class); } else { music.setGain(buf == null ? 0.0f : gain * buf.getGain() * musicVolume / 100.0f, Game.class); } } } /** * Play some music. The existing music is nicely crossfaded. * @param buf The buffer containing the music * @param fade The fade length, in ticks */ public static void playMusic(ALStream buf, int fade) { playMusic(buf, fade, 1.0f); } /** * Play some music. The existing music is nicely crossfaded. * @param buf The buffer containing the music * @param fade The fade length, in ticks * @param gain Extra gain */ public static void playMusic(ALBuffer buf, int fade, float gain) { // Maybe don't do anything if (music == null && buf == null || music != null && buf == music.getBuffer()) { return; } // Fade out existing music boolean xfade = music != null; if (xfade) { music.setFade(fade, 0.0f, true, Game.class); } if (buf == null || !isMusicEnabled()) { music = null; } else { music = allocateSound(buf); } if (music != null) { if (xfade) { music.setGain(0.0f, Game.class); music.setFade(fade, buf == null ? 0.0f : gain * buf.getGain() * musicVolume / 100.0f, false, Game.class); } else { music.setGain(buf == null ? 0.0f : gain * buf.getGain() * musicVolume / 100.0f, Game.class); } } } /** * Play some music. The existing music is nicely crossfaded. * @param buf The buffer containing the music * @param fade The fade length, in ticks */ public static void playMusic(ALBuffer buf, int fade) { playMusic(buf, fade, 1.0f); } /** * Get the current music * @return SoundEffect, or null */ public static SoundEffect getMusic() { return music; } /** * Initialise the sound system */ private static void initSound() { // Create sound try { System.out.println("Initing sound"); org.lwjgl.openal.AL.create(); soundPlayer = new SoundPlayer(game.soundVoices); soundPlayer.create(); SoundCommand.setDefaultSoundPlayer(soundPlayer); musicVolume = PREFS.getInt("musicvolume", 70); sfxVolume = PREFS.getInt("sfxvolume", 70); music = null; } catch (Exception e) { sfxEnabled = false; musicEnabled = false; e.printStackTrace(); Game.alert("You need a sound card to to hear the sound effects and music in " + getTitle() + ".\n\nYou may have a suitable card but not have appropriate drivers.\n\nPlease contact " + getSupportEmail() + " for assistance or visit our website if you need help finding drivers for your sound card."); Support.doSupport("openal"); if (org.lwjgl.openal.AL.isCreated()) { org.lwjgl.openal.AL.destroy(); } } } /** * Initialise the display * @throws Exception if the display fails to initialise, probably because the graphics card has no OpenGL drivers */ private static void initDisplay() throws Exception { Display.setTitle(getTitle()); if (Display.getParent() != null) { initialMode = new DisplayMode(Display.getParent().getWidth(), Display.getParent().getHeight()); customDisplayMode = false; } else { if (properties.containsKey("width") && properties.containsKey("height")) { initialMode = new DisplayMode(Integer.parseInt(properties.getProperty("width", "800")), Integer.parseInt(properties.getProperty("height", "600"))); customDisplayMode = true; } else { initialMode = Display.getDesktopDisplayMode(); customDisplayMode = false; } if (properties.containsKey("vx")) { viewportXoffset = Integer.parseInt(properties.getProperty("vx", "0")); } if (properties.containsKey("vy")) { viewportYoffset = Integer.parseInt(properties.getProperty("vy", "0")); } if (properties.containsKey("vw")) { viewportWidth = Integer.parseInt(properties.getProperty("vw", "0")); } if (properties.containsKey("vh")) { viewportHeight = Integer.parseInt(properties.getProperty("vh", "0")); } } if ("!".equals(PREFS.get("fullscreen2", "!"))) { PREFS.putInt("fullscreen2", game.defaultFullscreen); } // Determine fullscreenness if (Boolean.getBoolean("net.puppygames.applet.Game.windowed")) { setFullscreen(false); } else { int fs = PREFS.getInt("fullscreen2", 0); switch (fs) { case 0: setFullscreen(configuration.isFullscreen()); break; case 1: setFullscreen(false); break; case 2: setFullscreen(true); break; } } } /** * Set/unset fullscreen mode * @param fullscreen */ public static void setFullscreen(boolean fullscreen) throws Exception { try { if (fullscreen || customDisplayMode) { try { initFullscreen(); } catch (LWJGLException e) { fullscreen = false; initWindow(); } } else { initWindow(); } PREFS.putInt("fullscreen2", fullscreen ? 2 : 1); } catch (Exception e) { System.err.println("Failed to set fullscreen=" + fullscreen + " due to " + e); throw e; } } /** * Initialise vsync */ private static void initVsync() { int freq = Display.getDisplayMode().getFrequency(); if (freq != getFrameRate()) { Display.setVSyncEnabled(false); } else { Display.setVSyncEnabled(true); } } private static int getRecommendedBPP() { return PREFS.getInt("recommendedbpp", initialMode.getBitsPerPixel()); } /** * Initialise fullscreen display (or a custom sized window) */ private static void initFullscreen() throws Exception { System.out.println("Initialising full screen display"); // Use the desktop display mode if (customDisplayMode) { Display.setDisplayMode(initialMode); } else { Display.setDisplayModeAndFullscreen(Display.getDesktopDisplayMode()); } if (Display.isCreated()) { Display.update(); } System.out.println("Set fullscreen displaymode to " + Display.getDisplayMode()); initVsync(); if (!Display.isCreated()) { Display.create(); } // Properly clear the entire display viewPort = new Rectangle(0, 0, Display.getDisplayMode().getWidth(), Display.getDisplayMode().getHeight()); resetViewport(); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); Display.update(); glMatrixMode(GL_PROJECTION); glLoadIdentity(); int displayWidth = viewportWidth == 0 ? Display.getDisplayMode().getWidth() : viewportWidth; int displayHeight = viewportHeight == 0 ? Display.getDisplayMode().getHeight() : viewportHeight; if (customDisplayMode || properties.containsKey("scale")) { int smallest = Math.min(displayWidth, displayHeight); int fits; if (game.useWindowSizing) { fits = smallest / game.scale; } else { fits = 1; } System.out.println("Scale "+properties.getProperty("scale", "!")); game.logicalWidth = (int) (displayWidth / Math.max(1.0, Float.parseFloat(properties.getProperty("scale", String.valueOf(fits))))); game.logicalHeight = (int) (displayHeight / Math.max(1.0, Float.parseFloat(properties.getProperty("scale", String.valueOf(fits))))); viewPort = new Rectangle(viewportXoffset, viewportYoffset, displayWidth, displayHeight); } else if (game.useWindowSizing) { // Now we want to scale the game up to be the biggest it can be in the display's smallest dimension, so that it is an // even multiple of the scale, and centre this viewport on the screen // Fit the scale into the smaller dimension, and scale the other dimension int ratio; if (displayWidth < displayHeight) { // Weirdy potrait orientation // Height is smallest. ratio = displayWidth / game.scale; } else { // Height is smallest. ratio = displayHeight / game.scale; } game.logicalWidth = displayWidth / ratio; game.logicalHeight = displayHeight / ratio; viewPort = new Rectangle(viewportXoffset, viewportYoffset, displayWidth, displayHeight); } else { // TODO: FIX ALL THIS SO IT USES MULTIPLES OF THE WIDTH OR HEIGHT game.logicalWidth = game.width; game.logicalHeight = game.height; int x, y, gw, gh; if ((float) displayWidth / (float) displayHeight > (float) getWidth() / (float) getHeight()) { // Fix height to the display, scale width accordingly gh = displayHeight; gw = (int) (game.width * (float) displayHeight / game.height); } else { // Fix width to the display, scale height accordingly gw = displayWidth; gh = (int) (game.height * (float) displayWidth / game.width); } System.out.println("Game scaled to " + gw + " x " + gh); x = (displayWidth - gw) / 2 + viewportXoffset; y = (displayHeight - gh) / 2 + viewportYoffset; viewPort = new Rectangle(x, y, gw, gh); } glFrustum(-game.logicalWidth / 64.0, game.logicalWidth / 64.0, -game.logicalHeight / 64.0, game.logicalHeight / 64.0, 8, 65536); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); resetViewport(); Display.update(); Screen.onGameResized(); } /** * Initialise windowed display */ private static void initWindow() throws Exception { System.out.println("Initialising window"); Display.setFullscreen(false); Display.setVSyncEnabled(false); if (game.useWindowSizing) { // Initialise width if we've not yet got window size if (game.windowSize == 0.0f) { setWindowSize(2.0f); return; } Display.setDisplayMode(new DisplayMode(game.width, game.height)); } else if (initialMode.getWidth() > getWidth() * 3.125 && initialMode.getHeight() > getHeight() * 3.125) { Display.setDisplayMode(new DisplayMode(game.width * 3, game.height * 3)); } else if (initialMode.getWidth() > getWidth() * 2.25 && initialMode.getHeight() > getHeight() * 2.25) { Display.setDisplayMode(new DisplayMode(game.width * 2, game.height * 2)); } else { Display.setDisplayMode(new DisplayMode(game.width, game.height)); } if (!Display.isCreated()) { Display.create(); } glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (game.useWindowSizing) { // Fit the scale into the smaller dimension, and scale the other dimension if (game.width < game.height) { // Weirdy potrait orientation double ratio = (double) game.width / (double) game.scale; game.logicalWidth = game.scale; game.logicalHeight = (int) (game.height / ratio); } else { // Normal landscape orientation double ratio = (double) game.height / (double) game.scale; game.logicalHeight = game.scale; game.logicalWidth = (int) (game.width / ratio); } } else { game.logicalWidth = game.width; game.logicalHeight = game.height; } glFrustum(-game.logicalWidth / 64.0, game.logicalWidth / 64.0, -game.logicalHeight / 64.0, game.logicalHeight / 64.0, 8, 65536); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); viewPort = new Rectangle(0, 0, Display.getDisplayMode().getWidth(), Display.getDisplayMode().getHeight()); resetViewport(); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); Screen.onGameResized(); System.out.println("Window sized to "+Display.getDisplayMode()); } /** * Reset the game viewport */ public static void resetViewport() { System.out.println("Set viewport to "+viewPort); GL11.glViewport(viewPort.getX(), viewPort.getY(), viewPort.getWidth(), viewPort.getHeight()); } /** * Do custom cleanup */ protected void cleanup() { } private static long generatePregregistrationKey() { return getInstallation() ^ System.getProperty("user.country").hashCode() ^ (long) System.getProperty("user.language").hashCode() << 16L ^ (long)System.getProperty("user.home").hashCode() << 32L; } /** * Validate the user's registration details. The outcome of this method is that the game will either be tagged as "registered" * or not. */ private static void checkRegistration() { if (game.preregistered) { // This game is completely DRM free. return; } // Check for preregistration-until date if (game.preregisteredUntil != null) { // Is game already installed? if (PREFS.getLong("preregistered-installation", 0L) == generatePregregistrationKey()) { boolean ok = true; if (game.preregisteredLocale != null) { String country = Locale.getDefault().getCountry(); System.out.println("Country check: "+country+" vs "+game.preregisteredLocale); if (!game.preregisteredLocale.equals(country)) { ok = false; } } if (game.preregisteredLanguage != null) { String language = Locale.getDefault().getLanguage(); System.out.println("Language check: "+language+" vs "+game.preregisteredLanguage); if (!game.preregisteredLanguage.equals(language)) { ok = false; } } if (ok) { registered = true; return; } } else { // No. Are we after expiry date? DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); Date until; try { until = df.parse(game.preregisteredUntil); if (new Date().before(until)) { // No. So this game is preregistered, provided locale checks pass. boolean ok = true; if (game.preregisteredLocale != null) { String country = Locale.getDefault().getCountry(); if (!game.preregisteredLocale.equals(country)) { ok = false; } } if (game.preregisteredLanguage != null) { String language = Locale.getDefault().getLanguage(); if (!game.preregisteredLanguage.equals(language)) { ok = false; } } if (ok) { // Ok, we are now permanently registered PREFS.putLong("preregistered-installation", generatePregregistrationKey()); registered = true; return; } } } catch (ParseException e) { e.printStackTrace(System.err); } } } try { RegistrationDetails checkedDetails = RegistrationDetails.checkRegistration(getTitle()); setRegistrationDetails(checkedDetails); } catch (Exception e) { setRegistrationDetails(null); } } /** * Stash the customer's registration details. * @param registrationDetails The new registration details */ public static void setRegistrationDetails(RegistrationDetails registrationDetails) { if (Game.registrationDetails == registrationDetails) { return; } Game.registrationDetails = registrationDetails; if (registrationDetails != null) { registered = true; System.out.println("Game is registered: " + registrationDetails); registrationDetails.toPreferences(); } else { System.out.println("Game is unregistered."); registered = false; RegistrationDetails.clearRegistration(getTitle()); } } private static void createConfiguration() throws Exception { configuration = new Configuration(); // See if there's a configuration list if (Resources.exists("configurations")) { StringArray configurations = (StringArray) Resources.peek("configurations"); String config = configurations.getString(Util.random(0, configurations.getNumStrings() - 1)); configuration.decode(config); } else { configuration.init(); } writeConfiguration(); } private static void writeConfiguration() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(configuration); oos.flush(); byte[] cfg = baos.toByteArray(); PREFS.putByteArray("configuration", cfg); oos.close(); } /** * Set a new configuration * @param newConfiguration */ public static void setConfiguration(Configuration newConfiguration) throws Exception { configuration = newConfiguration; writeConfiguration(); } /** * Run the game. */ @SuppressWarnings("unused") private void run() { int ticksToDo = 1; long then = Sys.getTime() & 0x7FFFFFFFFFFFFFFFL; long framesTicked = 0; long timerResolution = Sys.getTimerResolution(); while (!finished) { if (Display.isCloseRequested()) { // Check for O/S close requests exit(); } else if (Display.isActive() || alwaysRun) { // The window is in the foreground, so we should play the game long now = Sys.getTime() & 0x7FFFFFFFFFFFFFFFL; long currentTimerResolution = Sys.getTimerResolution(); if (currentTimerResolution != timerResolution) { // Timer resolution change -- all bets off if (DEBUG) { System.out.println("Timer resolution change from "+timerResolution+" to "+currentTimerResolution); } timerResolution = currentTimerResolution; then = now; } if (now > then) { long ticksElapsed = now - then; double shouldHaveTickedThisMany = (double) (getFrameRate() * ticksElapsed) / (double) timerResolution; ticksToDo = (int) Math.max(0.0, shouldHaveTickedThisMany - framesTicked); if (ticksToDo > 5) { // We're overrunning! if (DEBUG) { System.out.println("Frame overrun! "+ticksToDo); } ticksToDo = 1; then = now; framesTicked = 0; } } else if (now < then) { if (DEBUG) { System.out.println("Clock reset: "+Long.toHexString(now)+" vs "+Long.toHexString(then)); } ticksToDo = 0; then = now; framesTicked = 0; } else { ticksToDo = 0; } if (ticksToDo > 0) { if (DEBUG && ticksToDo > 1) { // Warning suppressed System.out.println("Do "+ticksToDo+" ticks"); } for (int i = 0; i < ticksToDo; i ++) { if (i > 0) { Display.processMessages(); catchUp = true; } else { catchUp = false; } // If any tick takes longer than the allotted time, reset counters and stop ticking. We're basically running flat out. long tickThen = Sys.getTime() & 0x7FFFFFFFFFFFFFFFL; tick(); long tickNow = Sys.getTime() & 0x7FFFFFFFFFFFFFFFL; long tickElapsed = tickNow - tickThen; if (tickElapsed < 0 || tickElapsed > (double) timerResolution / getFrameRate()) { ticksToDo = 0; then = tickThen; framesTicked = 1; catchUp = false; if (DEBUG) { System.out.println("Running flat out! "+tickElapsed+" vs "+(double) timerResolution / getFrameRate()); } } Thread.yield(); } framesTicked += ticksToDo; render(); Display.update(); } if (DEBUG || forceSleep) { try { Thread.sleep(1); } catch (InterruptedException e) { } } else { Thread.yield(); } // Ensure we have sound targetMasterGain = 1.0f; } else { // The window is not in the foreground, so we can allow other stuff to run and // infrequently update // Silence the game targetMasterGain = 0.0f; try { Thread.sleep(100); } catch (InterruptedException e) { } if (Display.isVisible() || Display.isDirty()) { // Only bother rendering if the window is visible or dirty render(); } Display.update(); if (Mouse.isGrabbed()) { Mouse.setGrabbed(false); } } // Master gain if (Math.abs(targetMasterGain - masterGain) < 0.1f) { masterGain = targetMasterGain; } else if (targetMasterGain > masterGain) { masterGain += 0.1f; } else { masterGain -= 0.1f; } if (isSFXEnabled()) { AL10.alListenerf(AL10.AL_GAIN, masterGain); } } } /** * @return true if this frame is a catch-up frame */ public static boolean isCatchUp() { return game.catchUp; } /** * @return true if the demo expired */ public static boolean isDemoExpired() { return game.doIsDemoExpired(); } protected boolean doIsDemoExpired() { if (isRegistered()) { return false; } int played = PREFS.getInt("played" + getVersion(), 0); played ^= 0xAF6AD755; played = played >> 16 & 0xFFFF | played << 16; played ^= 0xCCCCCABE; if (played == 0x1B9965D4) { played = 0; } int tix = PREFS.getInt("tix", 0); return played < 0 || (played > configuration.getMaxGames() && configuration.getMaxGames() > 0 || configuration.getMaxGames() == 0) && (tix > configuration.getMaxTime() && configuration.getMaxTime() > 0 || configuration.getMaxTime() == 0); } public static boolean maybeShowHelp() { return game.doMaybeShowHelp(); } /** * @return true if help is shown; false if you just want to start the game */ protected boolean doMaybeShowHelp() { showHelp(); return true; } /** * Begin a new game */ public static void beginNewGame() { System.out.println("Begin new game"); // If demo version, count down the number of plays if (!isRegistered()) { // If not played before and not registered, open help first if (!shownInstructions && maybeShowHelp()) { return; } int played = PREFS.getInt("played" + getVersion(), 0); played ^= 0xAF6AD755; played = played >> 16 & 0xFFFF | played << 16; played ^= 0xCCCCCABE; if (played == 0x1B9965D4) { played = 0; } int tix = PREFS.getInt("tix", 0); System.out.println("You have played " + getTitle() + " " + played + " times for " + tix / 60 + " minutes"); System.out.println("Max games " + configuration.getMaxGames() + " / max time " + configuration.getMaxTime() / 60 + " / max level " + configuration.getMaxLevel()); if (isDemoExpired() && (configuration.isCrippled() || playedThisSession > 0)) { // Nag and quit on second game this session NagScreen.show("Your demo has expired!", true); return; } else { played++; played ^= 0xCCCCCABE; played = played >> 16 & 0xFFFF | played << 16; played ^= 0xAF6AD755; PREFS.putInt("played" + getVersion(), played); gameInfo.onNewGame(); playedThisSession++; } } SFX.newGame(); game.onBeginNewGame(); } /** * Called to begin a new game. */ protected void onBeginNewGame() { if (isRestoreAvailable()) { // Ask to resume restoreGame(); } else { cleanGame(); } } /** * Game over */ public static void gameOver() { SFX.gameOver(); game.doGameOver(); } /** * Game over. This should put the game in a "dormant" state and display the "game over" message to the player. After a while * someone will call endGame() and put us back on the title screen. */ protected abstract void doGameOver(); /** * End the game and return to the title screen (or the hiscore entry screen) */ public static void endGame() { writeTix(); game.doEndGame(); } /** * End the game and return to the title screen (or the hiscore entry screen). The default is simply to return to the title * screen. */ protected void doEndGame() { showTitleScreen(); } /** * Show the options screen (if any) */ public static void showOptions() { game.doShowOptions(); } /** * Show the credits screen (if any) */ public static void showCredits() { game.doShowCredits(); } /** * Show credits screen (if any). */ protected void doShowCredits() { CreditsScreen.show(); } /** * Show the help screen (if any) */ public static void showHelp() { shownInstructions = true; game.doShowHelp(); } /** * Show help screen (if any). */ protected void doShowHelp() { InstructionsScreen.show(); } /** * Show the title screen (if any) */ public static void showTitleScreen() { game.doShowTitleScreen(); } /** * Show the registration screen (if any) */ public static void showRegisterScreen() { game.doShowRegisterScreen(); } /** * Show the registration screen (if any) */ protected void doShowRegisterScreen() { RegisterScreen.show(); } /** * Show the title screen (if any) */ protected void doShowTitleScreen() { TitleScreen.show(); } /** * Show options screen (if any). Default is do nothing. */ protected void doShowOptions() { } /** * Show the hiscores screen (if any) */ public static void showHiscores() { game.doShowHiscores(); } /** * Show hiscores screen (if any). */ protected void doShowHiscores() { HiscoresScreen.show(null); } /** * Tick. Called every frame. */ private void tick() { // Process key & mouse bindings Binding.poll(); // Process keydowns for (int i = 0; i < Keyboard.KEYBOARD_SIZE; i++) { if (Keyboard.isKeyDown(i)) { if (!KEYWASDOWN[i]) { KEYDOWN[i] = true; } else { KEYDOWN[i] = false; } KEYWASDOWN[i] = true; } else { KEYWASDOWN[i] = false; KEYDOWN[i] = false; } } // Check for escape if (Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) { panic++; if (panic == 60) { exit(); } } else { panic = 0; } // Cheese for the camera if (wasKeyPressed(Keyboard.KEY_P) && pauseEnabled) { setPaused(!paused); } if (!paused) { Timer.tick(); // Tick screens Screen.tickAllScreens(); // Custom ticking doTick(); // Now tick the sound engine if (org.lwjgl.openal.AL.isCreated()) { int n = soundPlayers.size(); for (int i = 0; i < n; i ++) { SoundPlayer sp = soundPlayers.get(i); sp.play(); } } } } public static void setPaused(boolean newPaused) { paused = newPaused; if (game != null) { if (paused) { game.wasGrabbed = Mouse.isGrabbed(); Mouse.setGrabbed(false); Display.setTitle(getTitle() + " [PAUSED]"); game.onPaused(); } else { Mouse.setGrabbed(game.wasGrabbed); Display.setTitle(getTitle()); game.onResumed(); } } } private final void doTick() { doGameTick(); } protected abstract void doGameTick(); protected void onPaused() { } protected void onResumed() { } /** * Render. Called every frame. */ private void render() { Screen.updateAllScreens(); glClear(GL_COLOR_BUFFER_BIT); glPushMatrix(); glTranslatef((float)(-logicalWidth / 2.0), (float)(-logicalHeight / 2.0), -256); preRender(); Screen.renderAllScreens(); postRender(); glPopMatrix(); } /** * Called before any screens are rendered */ protected void preRender() { } /** * Called after all screens are rendered */ protected void postRender() { } /** * @return Returns the scoreGroup. */ public static String getScoreGroup() { return scoreGroup; } /** * @param scoreGroup The scoreGroup to set. */ public static void setScoreGroup(String scoreGroup) { Game.scoreGroup = scoreGroup; } /** * Check for keyboard presses. After checking, the key down state is cleared. * @param key The keyboard key * @return true if the key has just been pressed */ public static boolean wasKeyPressed(int key) { boolean ret = KEYDOWN[key]; KEYDOWN[key] = false; return ret; } /** * Called whenever a successful remote call is made, so we know that this app is allowed to contact teh intarweb. */ public static void onRemoteCallSuccess() { PREFS.putBoolean("online", true); } /** * Are remote calls allowed? * @return boolean */ public static boolean isRemoteCallAllowed() { return PREFS.getBoolean("online", false); } /** * Set music volume * @param volume 0.0f...1.0f */ public static void setMusicVolume(float vol) { musicVolume = (int) Math.max(0.0f, Math.min(100.0f, vol * 100.0f)); if (music != null) { if (music.getStream() != null) { if (music.getStream().getSourceStream() != null) { music.setGain(music.getStream().getSourceStream().getGain() * vol, Game.class); } } else if (music.getBuffer() != null) { music.setGain(music.getBuffer().getGain() * vol, Game.class); } } PREFS.putInt("musicvolume", musicVolume); } /** * Set sfx volume * @param volume 0.0f...1.0f */ public static void setSFXVolume(float vol) { sfxVolume = (int) Math.max(0.0f, Math.min(100.0f, vol * 100.0f)); PREFS.putInt("sfxvolume", sfxVolume); } /** * Get music volume * @return volume 0.0f...1.0f */ public static float getMusicVolume() { return musicVolume / 100.0f; } /** * Get sfx volume * @return volume 0.0f...1.0f */ public static float getSFXVolume() { return sfxVolume / 100.0f; } /** * Get the game viewport * @return Rectangle */ public static Rectangle getViewPort() { return viewPort; } /** * Load the keyboard and mouse bindings, if possible. Fails silently. */ public static void loadBindings() { try { byte[] b = PREFS.getByteArray("bindings", null); if (b == null) { if (DEBUG) { System.out.println("No bindings found, so using defaults."); } Binding.resetToDefaults(); } else { Binding.load(new ByteArrayInputStream(b)); } } catch (Exception e) { if (DEBUG) { e.printStackTrace(System.err); } } } /** * Save the keyboard and mouse bindings. Fails silently. */ public static void saveBindings() { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); Binding.save(baos); PREFS.putByteArray("bindings", baos.toByteArray()); } catch (Exception e) { if (DEBUG) { e.printStackTrace(System.err); } } } /** * Open the redefine keys screen, if there is one */ public static void redefineKeys() { game.doRedefineKeys(); } /** * Save the game */ public static void saveGame() { game.doSaveGame(); } protected String getSaveGameRegistryMagicLocation() { return "tox"; } protected void doSaveGame() { if (!allowSave) { return; } File file = game.getRestoreFile(); try { FileOutputStream fos = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fos); ObjectOutputStream oos = new ObjectOutputStream(bos); // Set current magic number gameState.setMagic(new Random().nextLong()); if (playerSlot != null) { playerSlot.getPreferences().putLong(getSaveGameRegistryMagicLocation(), gameState.getMagic()); } else { PREFS.putLong(getSaveGameRegistryMagicLocation(), gameState.getMagic()); } // This restore file is now only valid when tox in prefs is the // same as that in the file. As soon as we do a restore, we zap // the tox value :) oos.writeObject(gameState); oos.flush(); fos.close(); allowSave = false; onGameSaved(); flushPrefs(); } catch (Exception e) { e.printStackTrace(System.err); } TitleScreen.show(); } /** * Called when the game has been saved. Use it to pop up some sort of confirmation on the title screen */ protected void onGameSaved() { ((EffectFeature) Resources.get(SAVE_GAME_EFFECT_FEATURE)).spawn(TitleScreen.getInstance()); } /** * Is there a game to restore? * @return boolean */ public static boolean isRestoreAvailable() { return game.getRestoreFile().exists(); } /** * Flushes all preferences (synchronously) */ private static void doFlushPrefs() { if (GLOBALPREFS != null) { synchronized (GLOBALPREFS) { try { GLOBALPREFS.sync(); PREFS.sync(); if (playerSlot != null) { playerSlot.getPreferences().sync(); } } catch (Exception e) { e.printStackTrace(System.err); } // Now write backup file FileOutputStream fos = null; BufferedOutputStream bos = null; File prefsFile = new File(getUserPrefsFileName()); try { fos = new FileOutputStream(prefsFile); bos = new BufferedOutputStream(fos); PREFS.exportSubtree(bos); bos.flush(); System.out.println("Saved preferences file "+prefsFile); } catch (Exception e) { e.printStackTrace(System.err); } finally { if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(System.err); } } } } } } public static String getUserPrefsFileName() { return game.doGetUserPrefsFileName(); } protected String doGetUserPrefsFileName() { return getDirectoryPrefix() + DEFAULT_USER_PREFS_FILENAME; } /** * Start a new game from scratch */ public static void cleanGame() { File file = game.getRestoreFile(); if (file.exists() && !file.delete()) { System.err.println("Failed to delete save file "+file); } allowSave = true; game.onCleanGame(); } /** * Called to start a new game from scratch after deleting the restore file */ protected void onCleanGame() { setGameState(createGameState()); gameState.init(); } /** * @return the File which we use to save games to */ protected File getRestoreFile() { if (playerSlot != null) { return new File(getPlayerDirectoryPrefix() + RESTORE_FILE); } else { return new File(getDirectoryPrefix() + RESTORE_FILE); } } /** * Factory method to create a GameState * @return a GameState */ protected abstract GameState createGameState(); /** * Restore the game */ public static void restoreGame() { game.onRestoreGame(); } /** * Asks the user if they want to restore their game, and then either restores it, starts a clean game, or does nothing. */ protected void onRestoreGame() { if (!isRestoreAvailable()) { return; } topScreen = Screen.getTopScreen(); if (topScreen == null) { return; } try { restoreGameDialog = (DialogScreen) Resources.get(RESTORE_GAME_DIALOG_FEATURE); restoreGameDialog.doModal("RESTORE GAME", "WOULD YOU LIKE TO RESTORE YOUR SAVED GAME?", new Runnable() { @Override public void run() { topScreen.setEnabled(true); switch (restoreGameDialog.getOption()) { case DialogScreen.YES_OPTION: doRestoreGame(); break; case DialogScreen.NO_OPTION: cleanGame(); break; default: // Do nothing break; } restoreGameDialog = null; } }); topScreen.setEnabled(false); } catch (Exception e) { e.printStackTrace(System.err); game.onRestoreGameFailed(e); } } /** * Called when a restore game failed */ protected void onRestoreGameFailed(Exception e) { } /** * Restore the game */ protected void doRestoreGame() { allowSave = true; File file = getRestoreFile(); FileInputStream fis = null; BufferedInputStream bis = null; ObjectInputStream ois = null; boolean exceptionOccurred = false; try { fis = new FileInputStream(file); bis = new BufferedInputStream(fis); ois = new ObjectInputStream(bis); setGameState((GameState) ois.readObject()); // Check tox value in prefs... long tox; if (playerSlot != null) { tox = playerSlot.getPreferences().getLong(getSaveGameRegistryMagicLocation(), 0L); } else { tox = PREFS.getLong(getSaveGameRegistryMagicLocation(), 0L); } if (!DEBUG && tox != gameState.getMagic()) { throw new Exception("Invalid game state"); } Resources.dequeue(); gameState.reinit(); } catch (Exception e) { exceptionOccurred = true; e.printStackTrace(System.err); onRestoreGameFailed(e); } finally { if (ois != null) { try { ois.close(); } catch (Exception e) { } } if (bis != null) { try { bis.close(); } catch (Exception e) { } } if (fis != null) { try { fis.close(); } catch (Exception e) { } } if (!Game.DEBUG) { // Vape tox value if (playerSlot != null) { playerSlot.getPreferences().putLong("tox", new Random().nextLong()); } else { PREFS.putLong("tox", new Random().nextLong()); } flushPrefs(); // Now delete the file whatever happens. If an exception occurs, rename it instead. if (exceptionOccurred) { File newFile = new File(file.getPath()+".broken"); if (!file.renameTo(newFile)) { System.err.println("Failed to rename "+file+" to "+newFile); } } else if (!file.delete()) { System.err.println("Failed to delete save file"); } } } } /** * Set game state * @param newgameState */ protected void setGameState(GameState newGameState) { Game.gameState = newGameState; } /** * Open the redefine keys screen, if there is one. */ protected void doRedefineKeys() { BindingsScreen.show(); } /** * @return Returns the messageSequence. */ public int getMessageSequence() { return messageSequence; } /** * Wraps alert messages */ public static void alert(String message) { Sys.alert(getTitle(), message); } /** * Forces game to run & render even when not focused * @param alwaysRun */ public static void setAlwaysRun(boolean alwaysRun) { Game.alwaysRun = alwaysRun; } /** * @return the dontUseRemoteHiscores */ public static boolean getDontUseRemoteHiscores() { return game.dontUseRemoteHiscores; } public static boolean getDontCheckMessages() { return game.dontCheckMessages; } public static boolean getSubmitRemoteHiscores() { return !game.dontUseRemoteHiscores && game.submitRemoteHiscores; } public static void setSubmitRemoteHiscores(boolean set) { game.submitRemoteHiscores = set; synchronized (PREFS) { PREFS.putBoolean("submitremotehiscores", !game.dontUseRemoteHiscores && game.submitRemoteHiscores); } new Thread() { @Override public void run() { try { synchronized (PREFS) { PREFS.flush(); } } catch (BackingStoreException e) { e.printStackTrace(System.err); } } }.start(); } /** * Is this a slot-managed game? * @return true if we're using player slots */ public static boolean isSlotManaged() { return game.useSlotManagement; } /** * Get the current player slot. If we're not slot managed, we return null. Also, if we're a slot * managed game but there is no currently selected slot, we return null (and should probably prompt * for a slot name) * @return a PlayerSlot, or null */ public static PlayerSlot getPlayerSlot() { return playerSlot; } /** * Sets the current player slot that we're using * @param newSlot; may not be null */ public static void setPlayerSlot(PlayerSlot newSlot) { if (newSlot == null) { throw new IllegalArgumentException("newSlot may not be null"); } Game.playerSlot = newSlot; PREFS.put("slot_"+getInternalVersion(), newSlot.getName()); try { PREFS.flush(); } catch (BackingStoreException e) { e.printStackTrace(System.err); } TitleScreen.updateSlotDetails(); game.onSetPlayerSlot(); } protected void onSetPlayerSlot() {} /* (non-Javadoc) * @see com.shavenpuppy.jglib.resources.Feature#doCreate() */ @Override protected void doCreate() { super.doCreate(); // Make a note of all the sound players soundPlayers = Resources.list(SoundPlayer.class); if (soundPlayer != null) { soundPlayers.add(soundPlayer); } } public static int getMouseX() { return game.doGetMouseX(); } public static int getMouseY() { return game.doGetMouseY(); } protected int doGetMouseX() { return Mouse.getX(); } protected int doGetMouseY() { return Mouse.getY(); } /** * Convert a logical (game coordinate) into a physical screen coordinate * @param logicalX * @return */ public static float logicalXtoPhysicalX(float logicalX) { return logicalX * viewPort.getWidth() / getWidth() + viewPort.getX(); } /** * Convert a logical (game coordinate) into a physical screen coordinate * @param logicalY * @return */ public static float logicalYtoPhysicalY(float logicalY) { return logicalY * viewPort.getHeight() / getHeight() + viewPort.getY(); } /** * Convert a physical screen coordinate into a game coordinate * @param physicalX * @return */ public static float physicalXtoLogicalX(float physicalX) { return getWidth() * (physicalX - viewPort.getX()) / viewPort.getWidth(); } /** * Convert a physical screen coordinate into a game coordinate * @param physicalY * @return */ public static float physicalYtoLogicalY(float physicalY) { return getHeight() * (physicalY - viewPort.getY()) / viewPort.getHeight(); } public static void setWindowSize(float windowSize) { float oldSize = game.windowSize; try { if (game.windowSize != windowSize || Display.isFullscreen() || customDisplayMode) { game.windowSize = windowSize; if (initialMode.getWidth() > initialMode.getHeight()) { // Normal landscape mode setSize((int) (initialMode.getWidth() * game.scale * windowSize / initialMode.getHeight()), (int) (game.scale * windowSize)); } else { // Wierdy portrait mode setSize((int) (game.scale * windowSize), (int) (initialMode.getHeight() * game.scale * windowSize / initialMode.getWidth())); } } } catch (Exception e) { game.windowSize = oldSize; } } public static float getWindowSize() { return game.windowSize; } public static int getScale() { return game.scale; } public static boolean getUseWindowSizing() { return game.useWindowSizing; } /** * Called by the registration screen when it completely fails to connect to Puppygames, or the Puppygames server throws an unexpected * Throwable. The game becomes temporarily registered. */ public static void onRegistrationDisaster() { if (PREFS.getBoolean("puppygames_exists", false)) { return; } Game.registered = true; } public static void onRegistrationRecovery() { Game.registered = false; PREFS.putBoolean("puppygames_exists", true); try { PREFS.flush(); } catch (BackingStoreException e) { } } /** * @return true if we passed in width and height on commandline for a custom display resolution */ public static boolean isCustomDisplayMode() { return customDisplayMode; } /** * Flushes preferences (asynchronously) */ public static void flushPrefs() { if (prefsSaver != null) { prefsSaver.save(); } else { doFlushPrefs(); } } public static boolean isModded() { return modded; } /** * @return the current mod name; or "", if none. */ public static String getModName() { return modName; } /** * Load mod specified on commandline */ private static final void loadMods() { modName = ""; String modPath = properties.getProperty("mods", ""); if (modPath.equals("")) { return; } // "mods" points to the full paths of jar files with mods in them, loaded in order. StringTokenizer st = new StringTokenizer(modPath, File.pathSeparator, false); while (st.hasMoreTokens()) { String jarPath = st.nextToken().trim(); if (jarPath.equals("")) { continue; } File file = new File(jarPath); if (!file.exists()) { System.err.println("Mod at "+jarPath+" does not exist."); continue; } try { System.out.println("Loading mod at "+jarPath); URLClassLoader cl = new URLClassLoader(new URL[] {file.toURI().toURL()}); ClassLoaderResource clr = new ClassLoaderResource("mod.loader"); clr.setClassLoader(cl); Resources.put(clr); if (cl.findResource("resources.dat") == null) { // Load from xml instead URL url = cl.getResource("resources.xml"); ResourceConverter rc = new ResourceConverter(new ResourceLoadedListener() { @Override public void resourceLoaded(Resource loadedResource) { System.out.println(" Loaded "+loadedResource); } }, cl); rc.setOverwrite(true); rc.include(url.openStream()); } else { URL url = cl.getResource("resources.dat"); Resources.load(url.openStream()); } modded = true; if (modName.length() > 0) { modName += ","; } String n; if (Resources.exists("modName")) { n = ((TextResource) Resources.get("modName")).getText().trim(); if (n.equals("")) { n = "unknown_mod"; } } else { n = "unknown_mod"; } modName += n; System.out.println("Successfully loaded mod "+n+" from "+jarPath); } catch (Exception e) { System.err.println("Failed to load mod at "+jarPath); e.printStackTrace(System.err); } } } }