/*
* 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.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.ByteBuffer;
import java.rmi.Naming;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import javax.imageio.ImageIO;
import net.puppygames.applet.effects.SFX;
import net.puppygames.applet.screens.BindingsScreen;
import net.puppygames.applet.screens.TitleScreen;
import net.puppygames.gamecommerce.shared.GameInfo;
import net.puppygames.gamecommerce.shared.GameInfoServerRemote;
import net.puppygames.gamecommerce.shared.RegistrationDetails;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.LWJGLUtil;
import org.lwjgl.Sys;
import org.lwjgl.input.Controllers;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.openal.AL;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.util.Rectangle;
import org.lwjgl.util.Timer;
import com.shavenpuppy.jglib.IResource;
import com.shavenpuppy.jglib.Image;
import com.shavenpuppy.jglib.Image.JPEGDecompressor;
import com.shavenpuppy.jglib.Resource;
import com.shavenpuppy.jglib.Resources;
import com.shavenpuppy.jglib.Wave;
import com.shavenpuppy.jglib.jpeg.JPEGDecoder;
import com.shavenpuppy.jglib.openal.ALBuffer;
import com.shavenpuppy.jglib.openal.ALStream;
import com.shavenpuppy.jglib.resources.ClassLoaderResource;
import com.shavenpuppy.jglib.resources.Data;
import com.shavenpuppy.jglib.resources.DynamicResource;
import com.shavenpuppy.jglib.resources.Feature;
import com.shavenpuppy.jglib.resources.ResourceConverter;
import com.shavenpuppy.jglib.resources.ResourceLoadedListener;
import com.shavenpuppy.jglib.resources.StringArray;
import com.shavenpuppy.jglib.resources.TextResource;
import com.shavenpuppy.jglib.resources.TextWrapper;
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.*;
import static org.lwjgl.openal.AL10.*;
/**
* The main Game class
*/
public abstract class Game extends Feature {
/*
* Static game data
*/
private static final long serialVersionUID = 1L;
private static final String DEFAULT_GAME_RESOURCE_NAME = "game.puppygames";
private static final String DEFAULT_ROAMING_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];
/** Mouse events */
private static final List<MouseEvent> MOUSEEVENTS = new ArrayList<MouseEvent>();
/** Restore filename */
protected static final String RESTORE_FILE = "restore.dat";
/** Force run even when not focused */
private static boolean alwaysRun;
/** 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 */
protected static long installation;
/** Global Puppy Games preferences */
private static Preferences GLOBALPREFS;
/** Local Puppy Games preferences */
private static Preferences LOCALPREFS;
/** Roaming game preferences */
private static Preferences ROAMINGPREFS;
/** Configuration */
protected static Configuration configuration;
/** 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;
/** Roaming files directory prefix */
private static String roamingDirPrefix;
/** Local files directory prefix */
private static String localDirPrefix;
/** Stash the local log here */
private static String GAMEINFO_FILE;
/** Music volume 0..100 */
private static int musicVolume;
/** Effects volume 0..100 */
private static int sfxVolume;
/** Display bounds */
private static Rectangle viewPort;
/** Game title */
private static String title;
/** Version */
private static String version;
/** Internal version, used for local settings */
private static String internalVersion;
/** Steam App ID */
private static int appID;
/** Current player slot */
private static PlayerSlot playerSlot;
/** 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;
/** Frame mouse visibility */
private static boolean mouseVisible = true;
/** Current FPS */
private static final int[] FPS = new int[60];
private static int fps = 0, currentFPS = 60;
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
*/
/** Display title */
private String displayTitle;
/** 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";
/** Splash screen name (NOT mirrored by a transient Splash) */
@Data
private String splash = "splash.screen";
/** Window Icon */
@Data
private String icon;
/** Default to fullscreen */
private int defaultFullscreen = 0;
/** Don't check for messages? */
private boolean dontCheckMessages;
/** Slot management */
private boolean useSlotManagement;
/** Sound voices */
private int soundVoices = 32;
/** GUI scale in pixels */
private int scale;
/** Don't scale the game */
private boolean dontScale;
/** 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;
/** Language 2-char code (en, de, etc) */
@Data
private String language = "en"; // Defaults to English
/** Default input mode */
private String defaultInputMode = InputDeviceType.DESKTOP.name();
/*
* Transient data
*/
private transient int panic;
private transient boolean wasGrabbed;
private transient int logicalWidth, logicalHeight;
private transient boolean catchUp;
private transient float masterGain, targetMasterGain;
/** Current input mode */
private transient InputDeviceType inputMode;
/** Prefs saver */
private 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 display title.
*/
public static String getDisplayTitle() {
return game.displayTitle;
}
/**
* @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 URL for more games
*/
public static String getMoreGamesURL() {
return game.moreGamesURL;
}
/**
* @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 URL from where you can buy this game
*/
public static String getBuyURL() {
return game.buyURL;
}
/**
* @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 local Puppygames preferences.
*/
public static Preferences getLocalPreferences() {
return LOCALPREFS;
}
/**
* @return Returns the roaming game preferences.
*/
public static Preferences getRoamingPreferences() {
return ROAMINGPREFS;
}
/**
* @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(localDirPrefix + File.separator + fileName);
if (outFile.exists()) {
if (outFile.length() > 65535) {
outFile.renameTo(new File(localDirPrefix + 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);
}
private static void badDrivers() {
String message = ((TextWrapper) Resources.get("lwjglapplets.game.baddrivers")).getText().replace("[title]", getDisplayTitle());
Game.alert(message);
Support.doSupport("opengl");
exit();
}
/**
* 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 void init(Properties properties, InputStream resourcesStream) throws Exception {
if (initialised) {
return;
}
initialised = true;
finished = false;
// Timer hack
@SuppressWarnings("unused")
Thread timerHack = new Thread() {
{
this.setDaemon(true);
this.start();
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException ex) {
}
}
}
};
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;
}
TextResource steamID = ((TextResource) Resources.peek("steam_app_id"));
if (steamID != null) {
Game.appID = Integer.parseInt(steamID.getText().trim());
} else {
Game.appID = 0;
}
// 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);
if (game.displayTitle == null) {
game.displayTitle = title;
}
System.out.println(new Date() + " Game: " + title + " " + version + " ["+internalVersion+"]");
// Maybe init Steam
if (isUsingSteam()) {
try {
initSteam();
} catch (Exception e) {
e.printStackTrace(System.err);
String message = ((TextWrapper) Resources.get("lwjglapplets.game.steamerror")).getText().replace("[title]", getTitle()); // Can't use display title yet
Game.alert(message);
exit();
}
}
// 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);
}
}
}
// 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);
// Local prefs are just that: local
LOCALPREFS = Preferences.userNodeForPackage(Game.class).node(title+" Local");
// Load roaming prefs from backup file
ROAMINGPREFS = Preferences.userNodeForPackage(Game.class).node(title);
GameInputStream gis = null;
RoamingFile prefsFile = new RoamingFile(getRoamingPrefsFileName());
boolean zapPrefs = false, backupDone = false;
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024 * 1024);
if (prefsFile.exists()) {
// Back up the prefs before we attempt to import it
writePrefs(baos);
backupDone = true;
try {
gis = new GameInputStream(getRoamingPrefsFileName());
Preferences.importPreferences(gis);
if (DEBUG) {
System.out.println("Loaded preferences file "+getRoamingPrefsFileName());
}
} catch (Exception e) {
e.printStackTrace(System.err);
zapPrefs = true;
} finally {
if (gis != null) {
try {
gis.close();
} catch (IOException e) {
}
}
}
} else {
System.out.println("Preferences file "+getRoamingPrefsFileName()+" does not exist.");
zapPrefs = true;
}
if (zapPrefs) {
try {
ROAMINGPREFS.removeNode();
ROAMINGPREFS.flush();
ROAMINGPREFS = null;
ROAMINGPREFS = Preferences.userNodeForPackage(Game.class).node(title);
// Restore backup
if (backupDone) {
Preferences.importPreferences(new ByteArrayInputStream(baos.toByteArray()));
}
doFlushPrefs();
} catch (Exception e) {
e.printStackTrace(System.err);
// Carry on anyway
}
}
prefsSaver = new PrefsSaverThread();
prefsSaver.start();
// Load or create configuration
byte[] cfg = LOCALPREFS.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 = LOCALPREFS.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()+" (language: "+game.language+")");
// If there was a bad exit, let's put up a message
if (wasBadExit && !DEBUG) {
String shutdownMessage;
if ("".equals(getSupportEmail())) {
shutdownMessage = ((TextWrapper) Resources.get("lwjglapplets.game.badexit.url.message")).getText().replace("[url]", getSupportURL());
} else {
shutdownMessage = ((TextWrapper) Resources.get("lwjglapplets.game.badexit.email.message")).getText().replace("[email]", getSupportEmail());
}
shutdownMessage = shutdownMessage.replace("[title]", getTitle());
Game.alert(shutdownMessage);
Support.doSupport("crash");
}
// Set the bad exit flag. The only way to clear it is to exit the game
// cleanly via the exit() method.
LOCALPREFS.putBoolean("badexit", true);
flushPrefs();
// Load DLC
loadDLC();
// 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) {
if (DEBUG) {
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);
badDrivers();
}
// Update gameinfo
try {
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);
System.out.println("GL Vendor "+glvendor+", GL Renderer "+glrenderer+", GL Version "+glversion+", "+(gldriver != null ? "GL Driver "+gldriver : ""));
} catch (Exception e) {
e.printStackTrace(System.err);
badDrivers();
return;
}
// Show the splash screen
//Display.setVSyncEnabled(false);
Splash splashInstance = Resources.peek(game.splash);
if (splashInstance != null) {
try {
splashInstance.create();
Resources.setCreatingCallback(splashInstance);
} catch (Exception e) {
e.printStackTrace(System.err);
splashInstance = null;
badDrivers();
return;
}
}
// Autocreate features
SFX.createSFX();
try {
Res.createResources();
Feature.autoCreate();
} catch (Exception e) {
e.printStackTrace(System.err);
gameInfo.setException(e);
badDrivers();
return;
}
// We're in Run Mode now
Resources.setRunMode(true);
// Force sleep?
forceSleep = properties.getProperty("sleep", Runtime.getRuntime().availableProcessors() > 1 ? "false" : "true").equalsIgnoreCase("true");
// Finally, create the game
game.create();
// Init input mode
initInput();
// Load keyboard bindings
loadBindings();
// That's enough of the loading screen...
if (splashInstance != null) {
Resources.setCreatingCallback(null);
splashInstance.destroy();
splashInstance = null;
}
initVsync();
// If slot managed, read current slot:
if (isSlotManaged()) {
String slotName = ROAMINGPREFS.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();
game.onInit();
// Show title screen or registration screen as appropriate
if (!isRegistered() || TESTREGISTER) {
unregisteredStartup();
} else if (REGISTERED || game.preregistered) {
preRegisteredStartup();
} else {
registeredStartup();
}
// 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);
}
exit();
}
/**
* Called by {@link #init(Properties, InputStream)} just before we show any screens and start running the game
*/
protected void onInit() {
}
/**
* Called instead of opening the title screen
*/
private static void preRegisteredStartup() {
game.onPreRegisteredStartup();
}
protected void onPreRegisteredStartup() {
}
private static void unregisteredStartup() {
game.onUnregisteredStartup();
}
protected void onUnregisteredStartup() {
}
private static void registeredStartup() {
game.onRegisteredStartup();
}
protected void onRegisteredStartup() {
}
/**
* Get the roaming app settings dir
* @return String
*/
private static String getRoamingSettingsDir() {
if (isUsingSteamCloud()) {
return "";
} else {
return getLocalSettingsDir();
}
}
/**
* Get the local settings dir
* @return String
*/
private static String getLocalSettingsDir() {
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 for roaming files (includes trailing slash); these may be stored locally
* @return String
*/
public static String getRoamingDirectoryPrefix() {
return roamingDirPrefix;
}
/**
* Get the directory prefix for local files (includes trailing slash)
* @return String
*/
public static String getLocalDirPrefix() {
return localDirPrefix;
}
/**
* 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 = getRoamingDirectoryPrefix() + "slots_" + getInternalVersion() + File.separator + playerSlot.getName();
// Lazy creation
if (!isUsingSteamCloud()) {
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 = getRoamingDirectoryPrefix() + "slots_" + getInternalVersion();
// Lazy creation
File f = new File(ret);
if (!f.exists()) {
f.mkdirs();
}
return ret + File.separator;
}
/**
* Initialise all the file paths we need (local and roaming directories and prefixes)
*/
private static void initFiles() {
boolean remoteDirPrefixExists;
String remoteSettingsDirName = getRoamingSettingsDir() + File.separator + "." + getTitle().replace(' ', '_').toLowerCase() + '_' + getInternalVersion();
System.out.println("Roaming settings dir name="+remoteSettingsDirName);
if (!isUsingSteamCloud()) {
File settingsDir = new File(remoteSettingsDirName);
if (!settingsDir.exists()) {
System.out.println("Creating roaming settings dir: "+remoteSettingsDirName);
settingsDir.mkdirs();
remoteDirPrefixExists = settingsDir.exists();
} else {
remoteDirPrefixExists = true;
}
} else {
remoteDirPrefixExists = true;
}
boolean localDirPrefixExists;
String localSettingsDirName = getLocalSettingsDir() + File.separator + "." + getTitle().replace(' ', '_').toLowerCase() + '_' + getInternalVersion();
System.out.println("Local settings dir name="+localSettingsDirName);
if (!isUsingSteamCloud()) {
File localSettingsDir = new File(localSettingsDirName);
if (!localSettingsDir.exists()) {
System.out.println("Creating local settings dir: "+localSettingsDirName);
localSettingsDir.mkdirs();
localDirPrefixExists = localSettingsDir.exists();
} else {
localDirPrefixExists = true;
}
} else {
localDirPrefixExists = true;
}
localDirPrefix = localDirPrefixExists ? localSettingsDirName + File.separator : getRoamingSettingsDir() + File.separator;
roamingDirPrefix = remoteDirPrefixExists ? remoteSettingsDirName + File.separator : getRoamingSettingsDir() + File.separator;
System.out.println("Local dir prefix="+localDirPrefix);
System.out.println("Roaming dir prefix="+roamingDirPrefix);
GAMEINFO_FILE = localDirPrefix + "log.dat";
}
/**
* 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.
*/
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(getLocalSettingsDir() + 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
game.updateLog();
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) {
}
}
}
}
}
/**
* Update the log with any information before writing it
*/
protected void updateLog() {
}
public static void requestExit() {
game.doRequestExit();
}
protected void doRequestExit() {
// Don't ask just exit by default
exit();
}
/**
* Exit the program
*/
public static 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 (LOCALPREFS != null) {
LOCALPREFS.putBoolean("badexit", false);
}
if (prefsSaver != null) {
prefsSaver.finish();
prefsSaver = null;
}
AL.destroy();
Display.destroy();
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.getStream() != 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 = LOCALPREFS.getInt("musicvolume", 70);
sfxVolume = LOCALPREFS.getInt("sfxvolume", 70);
music = null;
} catch (Exception e) {
sfxEnabled = false;
musicEnabled = false;
e.printStackTrace();
String message = ((TextWrapper) Resources.get("lwjglapplets.game.badsound")).getText().replace("[title]", getDisplayTitle());
message = message.replace("[email]", getSupportEmail());
Game.alert(message);
Support.doSupport("openal");
if (org.lwjgl.openal.AL.isCreated()) {
org.lwjgl.openal.AL.destroy();
}
}
}
private static ByteBuffer imageToBuffer(BufferedImage src, int size) {
ColorModel ccm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
ByteBuffer ret = BufferUtils.createByteBuffer(size * size * 4);
BufferedImage scaledImage = new BufferedImage(ccm, ccm.createCompatibleWritableRaster(size, size), false, null);
Graphics2D g2d = (Graphics2D) scaledImage.getGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2d.drawImage(src, 0, 0, size, size, null);
g2d.dispose();
byte[] data = (byte[]) scaledImage.getData().getDataElements(0, 0, size, size, new byte[size * size * 4]);
ret.put(data);
ret.rewind();
return ret;
}
/**
* Creates the AWT display
* @throws LWJGLException
*/
private static void createDisplay() throws LWJGLException {
// Load window size from prefs, or choose something sensible based on initial desktop display resolution
setWindowSizeFromPreferences();
Display.setResizable(true);
try {
BufferedImage iconImage = ImageIO.read(Thread.currentThread().getContextClassLoader().getResource(game.icon));
ByteBuffer[] imageBuffer = null;
switch (LWJGLUtil.getPlatform()) {
case LWJGLUtil.PLATFORM_WINDOWS:
// Create 16x16 and 32x32 icons, as well as the original one
imageBuffer = new ByteBuffer[3];
imageBuffer[0] = imageToBuffer(iconImage, iconImage.getWidth());
imageBuffer[1] = imageToBuffer(iconImage, 16);
imageBuffer[2] = imageToBuffer(iconImage, 32);
break;
case LWJGLUtil.PLATFORM_MACOSX:
// One 128x128 icon
imageBuffer = new ByteBuffer[1];
imageBuffer[0] = imageToBuffer(iconImage, 128);
break;
case LWJGLUtil.PLATFORM_LINUX:
// One 32x32 icon
imageBuffer = new ByteBuffer[1];
imageBuffer[0] = imageToBuffer(iconImage, 32);
break;
}
if (imageBuffer != null) {
Display.setIcon(imageBuffer);
}
} catch (IOException e) {
e.printStackTrace(System.err);
}
Display.setTitle(getDisplayTitle());
Display.create();
}
private static void setWindowSizeFromPreferences() throws LWJGLException {
int desktopWidth = Display.getDesktopDisplayMode().getWidth();
int desktopHeight = Display.getDesktopDisplayMode().getHeight();
int width = Math.max(game.scale, Math.min(desktopWidth, getLocalPreferences().getInt("window.width", (desktopWidth * 4) / 5)));
int height = Math.max(game.scale, Math.min(desktopHeight, getLocalPreferences().getInt("window.height", (desktopHeight * 4) / 5)));
game.width = width;
game.height = height;
Display.setDisplayMode(new DisplayMode(width, height));
}
/**
* 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 {
if (properties.containsKey("vx")) {
viewportXoffset = Integer.parseInt(properties.getProperty("vx", "0"));
} else {
viewportXoffset = 0;
}
if (properties.containsKey("vy")) {
viewportYoffset = Integer.parseInt(properties.getProperty("vy", "0"));
} else {
viewportYoffset = 0;
}
if (properties.containsKey("vw")) {
viewportWidth = Integer.parseInt(properties.getProperty("vw", "0"));
} else {
viewportWidth = Display.getDesktopDisplayMode().getWidth();
}
if (properties.containsKey("vh")) {
viewportHeight = Integer.parseInt(properties.getProperty("vh", "0"));
} else {
viewportHeight = Display.getDesktopDisplayMode().getHeight();
}
if ("!".equals(LOCALPREFS.get("fullscreen2", "!"))) {
LOCALPREFS.putInt("fullscreen2", game.defaultFullscreen);
}
createDisplay();
// Determine fullscreenness
if (Boolean.getBoolean("net.puppygames.applet.Game.windowed")) {
setFullscreen(false);
} else {
int fs = LOCALPREFS.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 {
try {
if (Mouse.isCreated()) {
Mouse.destroy();
}
} catch (Exception e) {
e.printStackTrace(System.err);
}
try {
if (Keyboard.isCreated()) {
Keyboard.destroy();
}
} catch (Exception e) {
e.printStackTrace(System.err);
}
// Use the desktop display mode
if (fullscreen) {
Display.setDisplayModeAndFullscreen(Display.getDesktopDisplayMode());
} else {
Display.setFullscreen(false);
setWindowSizeFromPreferences();
}
game.width = Display.getWidth();
game.height = Display.getHeight();
if (Display.isCreated()) {
Display.update();
}
initVsync();
if (!Display.isCreated()) {
Display.create();
}
try {
Mouse.create();
} catch (Exception e) {
e.printStackTrace(System.err);
}
try {
Keyboard.create();
} catch (Exception e) {
e.printStackTrace(System.err);
}
// Properly clear the entire display
viewPort = new Rectangle(0, 0, Display.getWidth(), Display.getHeight());
resetViewport();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
Display.update();
Display.setResizable(false);
if (!fullscreen) {
Display.setResizable(true);
}
doResize();
LOCALPREFS.putInt("fullscreen2", fullscreen ? 2 : 1);
try {
LOCALPREFS.sync();
} catch (BackingStoreException e) {
}
} 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 void doResize() {
if (!game.dontScale) {
// 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;
}
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glFrustum(-game.logicalWidth / 64.0, game.logicalWidth / 64.0, -game.logicalHeight / 64.0, game.logicalHeight / 64.0, 8, 65536);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
if (Display.isFullscreen()) {
viewPort = new Rectangle(viewportXoffset, viewportYoffset, viewportWidth, viewportHeight);
} else {
viewPort = new Rectangle(0, 0, Display.getWidth(), Display.getHeight());
}
resetViewport();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
Screen.onGameResized();
}
/**
* Reset the game viewport
*/
public static void resetViewport() {
// System.out.println("Set viewport to "+viewPort);
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 (LOCALPREFS.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
LOCALPREFS.putLong("preregistered-installation", generatePregregistrationKey());
registered = true;
flushPrefs();
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();
LOCALPREFS.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() {
// // Linux hack
// if (LWJGLUtil.getPlatform() == LWJGLUtil.PLATFORM_LINUX && !Boolean.getBoolean("net.puppygames.applet.Game.dontAlwaysRun")) {
// setAlwaysRun(true);
// }
int ticksToDo = 1;
long then = Sys.getTime() & 0x7FFFFFFFFFFFFFFFL;
long framesTicked = 0;
long timerResolution = Sys.getTimerResolution();
while (!finished) {
if (!Display.isFullscreen() && (Display.getWidth() != game.width || Display.getHeight() != game.height)) {
game.width = Display.getWidth();
game.height = Display.getHeight();
doResize();
getLocalPreferences().putInt("window.width", game.width);
getLocalPreferences().putInt("window.height", game.height);
flushPrefs();
}
if (Display.isCloseRequested()) {
finished = true;
break;
}
if (Display.isDirty() || 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 > 20) {
// 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();
}
if (ticksToDo > 0) {
fps ++;
if (fps == FPS.length) {
fps = 0;
}
FPS[fps] = getFrameRate() / ticksToDo;
int totalF = 0;
for (int i = 0; i < FPS.length; i ++) {
totalF += FPS[i];
}
currentFPS = totalF / FPS.length;
framesTicked += ticksToDo;
}
render();
Display.update();
}
if (DEBUG || forceSleep) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
} else {
Thread.yield();
}
// Ensure we have sound
if (isPaused()) {
targetMasterGain = 0.0f;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
} else {
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()) {
alListenerf(AL_GAIN, masterGain);
}
}
}
/**
* @return true if this frame is a catch-up frame
*/
public static boolean isCatchUp() {
return game.catchUp;
}
/**
* @return mouse events since last update
*/
public static List<MouseEvent> getMouseEvents() {
return MOUSEEVENTS;
}
/**
* Tick. Called every frame.
*/
private void tick() {
// Process key & mouse bindings
Binding.poll();
// Process mouse events
MOUSEEVENTS.clear();
while (Mouse.next()) {
MouseEvent event = new MouseEvent();
event.fromMouse();
MOUSEEVENTS.add(event);
}
// 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();
// Custom ticking
doTick();
// Tick screens
Screen.tickAllScreens();
// 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(getDisplayTitle() + " [PAUSED]");
game.onPaused();
} else {
//Mouse.setGrabbed(game.wasGrabbed);
Display.setTitle(getDisplayTitle());
game.onResumed();
}
}
}
protected abstract void doTick();
protected void onPaused() {
}
protected void onResumed() {
}
/**
* Render. Called every frame.
*/
private void render() {
Screen.updateAllScreens();
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
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() {
}
/**
* 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() {
LOCALPREFS.putBoolean("online", true);
try {
LOCALPREFS.flush();
} catch (BackingStoreException e) {
}
}
/**
* Are remote calls allowed?
* @return boolean
*/
public static boolean isRemoteCallAllowed() {
return LOCALPREFS.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);
}
}
LOCALPREFS.putInt("musicvolume", musicVolume);
try {
LOCALPREFS.sync();
} catch (BackingStoreException e) {
}
}
/**
* 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));
LOCALPREFS.putInt("sfxvolume", sfxVolume);
try {
LOCALPREFS.sync();
} catch (BackingStoreException e) {
}
}
/**
* 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;
}
/**
* Initialise input
*/
public static void initInput() {
try {
game.inputMode = InputDeviceType.valueOf(LOCALPREFS.get("inputmode", InputDeviceType.DESKTOP.name()));
} catch (Exception e) {
game.inputMode = InputDeviceType.valueOf(game.defaultInputMode);
}
}
/**
* Get the game's input mode
* @return the game's input mode
*/
public static InputDeviceType getInputMode() {
return game.inputMode;
}
/**
* Load the keyboard and mouse bindings, if possible. Fails silently.
*/
public static void loadBindings() {
try {
byte[] b = ROAMINGPREFS.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);
ROAMINGPREFS.putByteArray("bindings", baos.toByteArray());
flushPrefs();
} catch (Exception e) {
if (DEBUG) {
e.printStackTrace(System.err);
}
}
}
/**
* Open the redefine keys screen, if there is one
*/
public static void redefineKeys() {
game.doRedefineKeys();
}
/**
* Flushes all preferences (synchronously)
*/
private static void doFlushPrefs() {
GameOutputStream gos;
try {
gos = new GameOutputStream(getRoamingPrefsFileName());
writePrefs(gos);
if (DEBUG) {
System.out.println("Saved preferences file "+getRoamingPrefsFileName());
}
} catch (IOException e) {
e.printStackTrace(System.err);
}
}
/**
* Synchronously write preferences to an output stream
* @param os
*/
private static void writePrefs(OutputStream os) {
if (GLOBALPREFS != null) {
synchronized (GLOBALPREFS) {
try {
GLOBALPREFS.sync();
LOCALPREFS.sync();
ROAMINGPREFS.sync();
if (playerSlot != null) {
playerSlot.getPreferences().sync();
}
} catch (Exception e) {
e.printStackTrace(System.err);
}
try {
ROAMINGPREFS.exportSubtree(os);
os.flush();
} catch (Exception e) {
e.printStackTrace(System.err);
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace(System.err);
}
}
}
}
}
}
public static String getRoamingPrefsFileName() {
return game.doGetRoamingPrefsFileName();
}
protected String doGetRoamingPrefsFileName() {
return getRoamingDirectoryPrefix() + DEFAULT_ROAMING_PREFS_FILENAME;
}
/**
* 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(getDisplayTitle(), message);
}
/**
* Forces game to run & render even when not focused
* @param alwaysRun
*/
public static void setAlwaysRun(boolean alwaysRun) {
Game.alwaysRun = alwaysRun;
}
public static boolean getDontCheckMessages() {
return game.dontCheckMessages;
}
/**
* 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;
ROAMINGPREFS.put("slot_"+getInternalVersion(), newSlot.getName());
try {
ROAMINGPREFS.sync();
} catch (BackingStoreException e) {
e.printStackTrace(System.err);
}
TitleScreen.updateSlotDetails();
game.onSetPlayerSlot();
}
protected void onSetPlayerSlot() {}
@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();
}
/**
* @return the game's default scale (logical pixels : real pixels)
*/
public static int getScale() {
return game.scale;
}
/**
* 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 (LOCALPREFS.getBoolean("puppygames_exists", false)) {
return;
}
Game.registered = true;
}
public static void onRegistrationRecovery() {
Game.registered = false;
LOCALPREFS.putBoolean("puppygames_exists", true);
try {
LOCALPREFS.flush();
} catch (BackingStoreException e) {
}
}
/**
* 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;
}
loadMod(jarPath, false);
}
}
/**
* Load DLC found under the /dlc directory
*/
private static final void loadDLC() {
File dlcDir = new File("dlc");
if (!dlcDir.exists()) {
return;
}
File[] dlc = dlcDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".jar");
}
});
if (dlc == null) {
return;
}
for (File file : dlc) {
String jarPath;
try {
jarPath = file.getCanonicalPath();
loadMod(jarPath, true);
} catch (IOException e) {
System.err.println("failed to load DLC from jar "+file.getPath());
e.printStackTrace(System.err);
}
}
}
/**
* Load a mod or DLC
* @param jarPath Full path to a .jar file
* @param dlc Whether this is offical DLC or not
*/
private static final void loadMod(String jarPath, boolean dlc) {
File file = new File(jarPath);
if (!file.exists()) {
System.err.println((dlc ? "DLC" : "Mod")+" at "+jarPath+" does not exist.");
return;
}
try {
System.out.println("Loading "+(dlc ? "DLC" : "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(IResource 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());
}
// DLC doesn't affect the modded status of the game
if (!dlc) {
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);
} else {
String n = ((TextResource) Resources.get("modName")).getText().trim();
System.out.println("Successfully loaded DLC "+n+" from "+jarPath);
}
} catch (Exception e) {
System.err.println("Failed to load "+(dlc ? "DLC" : "mod")+" at "+jarPath);
e.printStackTrace(System.err);
}
}
/**
* @return the game's 2 character language
*/
public static String getLanguage() {
return game.language;
}
/**
* Shortcut to get a bit of wrapped text
* @param key Points to a {@link Resource} implementing {@link TextWrapper}
* @return a String
*/
public static String getMessage(String key) {
return ((TextWrapper) Resources.get(key)).getText();
}
/**
* @return true if we're using Steam
*/
public static boolean isUsingSteam() {
return Game.appID != 0;
}
/**
* @return true if we're able to use the Steam cloud
*/
public static boolean isUsingSteamCloud() {
return false;
}
/**
* @return the Steam app ID, if we're using steam
*/
public static int getSteamAppID() {
return Game.appID;
}
/**
* Initialise Steam.
*/
private static void initSteam() {
// Do nothing.
}
/**
* @return the on-the-fly FPS counter
*/
public static int getFPS() {
return currentFPS;
}
}