/*
* 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 worm;
import java.io.*;
import java.util.*;
import net.puppygames.applet.*;
import net.puppygames.applet.effects.LabelEffect;
import net.puppygames.applet.effects.Particle;
import net.puppygames.applet.screens.DialogScreen;
import net.puppygames.applet.screens.NagScreen;
import net.puppygames.gamecommerce.shared.NewsletterIncentive;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.util.Rectangle;
import worm.animation.SimpleThingWithLayers;
import worm.features.LayersFeature;
import worm.features.PrizeFeature;
import worm.screens.*;
import com.shavenpuppy.jglib.Resources;
import com.shavenpuppy.jglib.interpolators.LinearInterpolator;
import com.shavenpuppy.jglib.openal.ALBuffer;
import com.shavenpuppy.jglib.resources.*;
import com.shavenpuppy.jglib.sprites.*;
import com.shavenpuppy.jglib.util.Util;
/**
* $Id: Worm.java,v 1.56 2010/11/06 17:20:03 foo Exp $
* The Puppytron game
* <p>
* @author $Author: foo $
* @version $Revision: 1.56 $
*/
public class Worm extends Game {
private static final long serialVersionUID = 1L;
private static final boolean TESTSIGNUP = false;
public static final float MAX_LOUD_ATTENUATION_DISTANCE = 1280.0f;
public static final float MAX_ATTENUATION_DISTANCE = 720.f;
public static final float MIN_ATTENUATION_GAIN = 0.01f;
private static final int FAST_FORWARD_SPEED = 9;
/** Singleton */
private static Worm instance;
/** Game state mode for when we create a new {@link WormGameState} */
private static int newMode;
/** Game state (shadows Game.gameState) */
private static WormGameState gameState;
/** Sound attenuators */
public static final Attenuator ATTENUATOR = new Attenuator() {
@Override
public float getVolume(float x, float y) {
double ddx = -GameScreen.getSpriteOffset().getX() + Game.getWidth() / 2.0f - x;
double ddy = -GameScreen.getSpriteOffset().getY() + Game.getHeight() / 2.0f - y;
double dist = Math.sqrt(ddx * ddx + ddy * ddy);
return LinearInterpolator.instance.interpolate(1.0f, MIN_ATTENUATION_GAIN, (float) dist / MAX_ATTENUATION_DISTANCE);
}
};
public static final Attenuator LOUD_ATTENUATOR = new Attenuator() {
@Override
public float getVolume(float x, float y) {
double ddx = -GameScreen.getSpriteOffset().getX() + Game.getWidth() / 2.0f - x;
double ddy = -GameScreen.getSpriteOffset().getY() + Game.getHeight() / 2.0f - y;
double dist = Math.sqrt(ddx * ddx + ddy * ddy);
return LinearInterpolator.instance.interpolate(1.0f, MIN_ATTENUATION_GAIN, (float) dist / MAX_LOUD_ATTENUATION_DISTANCE);
}
};
private static final AttenuatorFeature ATTENUATOR_FEATURE = new AttenuatorFeature("default.attenuator") {
@Override
public float getVolume(float x, float y) {
return Game.getSFXVolume() * ATTENUATOR.getVolume(x, y);
}
};
private static final AttenuatorFeature LOUD_ATTENUATOR_FEATURE = new AttenuatorFeature("loud.attenuator") {
@Override
public float getVolume(float x, float y) {
return Game.getSFXVolume() * LOUD_ATTENUATOR.getVolume(x, y);
}
};
private static final AttenuatorFeature DEFAULT_ATTENUATOR_FEATURE = new AttenuatorFeature("none.attenuator") {
@Override
public float getVolume(float x, float y) {
return Game.getSFXVolume();
}
};
static {
Resources.put(ATTENUATOR_FEATURE);
Resources.put(LOUD_ATTENUATOR_FEATURE);
Resources.put(DEFAULT_ATTENUATOR_FEATURE);
}
private static final SpriteAllocator MOUSE_SPRITE_ALLOCATOR = new SpriteAllocator() {
@Override
public Sprite allocateSprite(Serializable owner) {
return instance.mouseSpriteEngine.allocate(owner);
}
};
/** Max mouse speed */
public static final int MOUSE_MAX_SCALE = 10;
/** Mouse speed multiplier */
public static final int MOUSE_SPEED_MULTIPLIER = 3;
/** Mouse speed */
private static int mouseSpeed = MOUSE_MAX_SCALE / MOUSE_SPEED_MULTIPLIER;
/** Global mouse pointers rendered using this very small sprite engine */
private transient SpriteEngine mouseSpriteEngine;
/** Mouse pointer */
private transient SimpleThingWithLayers mouseLayers;
/** Current scaled mouse coordinates */
private transient float mouseDX, mouseDY, mouseX, mouseY;
/*
* Options
*/
private static boolean showTooltips, showInfo, showHints, autoDifficulty;
/**
* C'tor
*/
public Worm(String name) {
super(name);
}
public static void setMouseAppearance(LayersFeature newMouseAppearance) {
instance.doSetMouseAppearance(newMouseAppearance);
}
private void doSetMouseAppearance(LayersFeature newMouseAppearance) {
mouseLayers.requestSetAppearance(newMouseAppearance);
updateMouse();
}
private void updateMouse() {
if (mouseLayers.getSprites() != null) {
float lx = physicalXtoLogicalX(getMouseX());
float ly = physicalYtoLogicalY(getMouseY());
for (int i = 0; i < mouseLayers.getSprites().length; i++) {
mouseLayers.getSprite(i).setLocation(lx, ly, 0.0f);
}
}
mouseSpriteEngine.tick();
}
/* (non-Javadoc)
* @see net.puppygames.applet.Game#setGameState(net.puppygames.applet.GameState)
*/
@Override
protected void setGameState(GameState newGameState) {
super.setGameState(newGameState);
Worm.gameState = (WormGameState) newGameState;
}
/**
* @return the game state
*/
public static WormGameState getGameState() {
return gameState;
}
@Override
protected void doGameOver() {
gameState.onGameOver();
GameScreen.gameOver();
}
@Override
protected void doShowOptions() {
net.puppygames.applet.screens.OptionsScreen.show();
}
@Override
protected void doShowHelp() {
Sys.openURL("http://www.puppygames.net/revenge-of-the-titans/help");
}
@Override
protected void doCreate() {
setInstance(this);
super.doCreate();
Sprite.setMissingImage((SpriteImage) Resources.get("spriteimage.missing"));
mouseSpriteEngine = new StaticSpriteEngine(false, 0, false, 1);
mouseSpriteEngine.create();
mouseLayers = new SimpleThingWithLayers(MOUSE_SPRITE_ALLOCATOR);
setMouseAppearance(Res.getMousePointer());
mouseSpeed = getPreferences().getInt("mouseSpeed", mouseSpeed);
prefsSaver = new PrefsSaverThread();
prefsSaver.start();
Particle.setMaxParticles(4096);
SoundCommand.setDefaultAttenuator(DEFAULT_ATTENUATOR_FEATURE);
}
public static void setInstance(Worm instance) {
Worm.instance = instance;
}
@Override
protected void postRender() {
mouseSpriteEngine.render();
}
@Override
protected void doEndGame() {
if (Game.isDemoExpired()) {
NagScreen.show("You know you want to!", true);
} else {
net.puppygames.applet.screens.TitleScreen.show();
}
}
@Override
protected void doGameTick() {
int newMouseDX = Mouse.getDX() * MOUSE_SPEED_MULTIPLIER * getMouseSpeed();
int newMouseDY = Mouse.getDY() * MOUSE_SPEED_MULTIPLIER * getMouseSpeed();
float newMouseX = mouseX + newMouseDX;
float newMouseY = mouseY + newMouseDY;
Rectangle viewPort = getViewPort();
int minX = viewPort.getX() * Worm.MOUSE_MAX_SCALE;
int maxX = (viewPort.getWidth() + viewPort.getX()) * Worm.MOUSE_MAX_SCALE - 1;
mouseX = Math.max(minX, Math.min(maxX, newMouseX));
int minY = viewPort.getY() * Worm.MOUSE_MAX_SCALE;
int maxY = (viewPort.getHeight() + viewPort.getY()) * Worm.MOUSE_MAX_SCALE - 1;
mouseY = Math.max(minY, Math.min(maxY, newMouseY));
mouseDX = 0.0f;
mouseDY = 0.0f;
if (!isCatchUp()) {
if (newMouseX < minX) {
mouseDX = (newMouseX - minX) * getWidth() / (Worm.MOUSE_MAX_SCALE * viewPort.getWidth());
} else if (newMouseX >= maxX) {
mouseDX = (newMouseX - maxX) * getWidth() / (Worm.MOUSE_MAX_SCALE * viewPort.getWidth());
}
if (newMouseY < minY) {
mouseDY = (newMouseY - minY) * getHeight() / (Worm.MOUSE_MAX_SCALE * viewPort.getHeight());
} else if (newMouseY >= maxY) {
mouseDY = (newMouseY - maxY) * getHeight() / (Worm.MOUSE_MAX_SCALE * viewPort.getHeight());
}
}
updateMouse();
GameScreen gameScreen = GameScreen.getInstance();
if (gameScreen.isFastForward()) {
for (int i = 0; i < FAST_FORWARD_SPEED; i ++) {
gameScreen.tick();
}
gameScreen.clearFastForward();
}
if (DEBUG) {
if (wasKeyPressed(Keyboard.KEY_F2)) {
//setSize(Util.random(640, 960), 640);
//setSize(1138,640); //16:9 widescreen
setSize(569,320); //16:9 widescreen
}
if (wasKeyPressed(Keyboard.KEY_F3)) {
setSize(640,Util.random(640, 960) );
}
if (wasKeyPressed(Keyboard.KEY_F4)) {
setSize(640,640);
}
if (wasKeyPressed(Keyboard.KEY_F5)) {
setSize(816, 640);
}
}
}
@Override
protected int doGetMouseX() {
return (int) (mouseX / Worm.MOUSE_MAX_SCALE);
}
@Override
protected int doGetMouseY() {
return (int) (mouseY / Worm.MOUSE_MAX_SCALE);
}
public static float getMouseDX() {
return instance.mouseDX;
}
public static float getMouseDY() {
return instance.mouseDY;
}
/**
* @return the mouse speed (0..{@link #MOUSE_MAX_SCALE})
*/
public static int getMouseSpeed() {
return mouseSpeed;
}
/**
* @param newSpeed
*/
public static void setMouseSpeed(int newSpeed) {
mouseSpeed = newSpeed;
getPreferences().putInt("mouseSpeed", newSpeed);
flushPrefs();
}
/**
* @return the number of worlds unlocked the current slot has, past Earth
*/
public static int getMaxWorld() {
return getPlayerSlot().getPreferences().getInt("maxlevel_"+WormGameState.GAME_MODE_CAMPAIGN, 0) / WormGameState.LEVELS_IN_WORLD;
}
public static int getMaxLevelUnlockedInWorld(int world) {
if (world < getMaxWorld()) {
return WormGameState.LEVELS_IN_WORLD - 1;
} else {
return getMaxLevel(WormGameState.GAME_MODE_CAMPAIGN) % WormGameState.LEVELS_IN_WORLD;
}
}
public static void setMaxLevel(int level, int gameMode) {
getPlayerSlot().getPreferences().putInt("maxlevel_"+gameMode, level);
flushPrefs();
}
public static int getMaxLevel(int gameMode) {
return getMaxLevel(getPlayerSlot(), gameMode);
}
public static int getMaxLevel(PlayerSlot slot, int gameMode) {
return slot.getPreferences().getInt("maxlevel_"+gameMode, 0);
}
/**
* Sets some extra level data
* @param level
* @param gameMode TODO
* @param key
* @param value
*/
public static void setExtraLevelData(int level, int gameMode, String key, String value) {
getPlayerSlot().getPreferences().put(key+"."+gameMode+"."+level, value);
flushPrefs();
}
/**
* Sets some extra level data
* @param level
* @param gameMode TODO
* @param key
* @param value
*/
public static void setExtraLevelData(int level, int gameMode, String key, int value) {
getPlayerSlot().getPreferences().putInt(key+"."+gameMode+"."+level, value);
flushPrefs();
}
/**
* Sets some extra level data
* @param level
* @param gameMode TODO
* @param key
* @param value
*/
public static void setExtraLevelData(int level, int gameMode, String key, float value) {
getPlayerSlot().getPreferences().putFloat(key+"."+gameMode+"."+level, value);
flushPrefs();
}
/**
* Sets some extra level data
* @param level
* @param gameMode TODO
* @param key
* @param value
*/
public static void setExtraLevelData(int level, int gameMode, String key, long value) {
getPlayerSlot().getPreferences().putLong(key+"."+gameMode+"."+level, value);
flushPrefs();
}
/**
* Gets extra level data
* @param slot TODO
* @param level
* @param gameMode TODO
* @param key
* @param default_
* @return
*/
public static String getExtraLevelData(PlayerSlot slot, int level, int gameMode, String key, String default_) {
return slot.getPreferences().get(key+"."+gameMode+"."+level, default_);
}
/**
* Gets extra level data
* @param slot TODO
* @param level
* @param gameMode TODO
* @param key
* @param default_
* @return
*/
public static float getExtraLevelData(PlayerSlot slot, int level, int gameMode, String key, float default_) {
return slot.getPreferences().getFloat(key+"."+gameMode+"."+level, default_);
}
/**
* Gets extra level data
* @param slot TODO
* @param level
* @param gameMode TODO
* @param key
* @param default_
* @return
*/
public static long getExtraLevelData(PlayerSlot slot, int level, int gameMode, String key, long default_) {
return slot.getPreferences().getLong(key+"."+gameMode+"."+level, default_);
}
public static int getExtraLevelData(PlayerSlot slot, int level, int gameMode, String key, int default_) {
return slot.getPreferences().getInt(key+"."+gameMode+"."+level, default_);
}
/**
* Utility to calculate gain
*/
public static float calcGain(float x, float y) {
return ATTENUATOR.getVolume(x, y);
}
/**
* Utility to calculate gain for loud noises
*/
public static float calcLoudGain(float x, float y) {
return LOUD_ATTENUATOR.getVolume(x, y);
}
@Override
protected String getSaveGameRegistryMagicLocation() {
return "tox"+gameState.getGameMode();
}
@Override
protected void onGameSaved() {
LabelEffect le = new LabelEffect(net.puppygames.applet.Res.getBigFont(), "GAME SAVED", new MappedColor("titles.colormap:text-bold"), new MappedColor("titles.colormap:text-dark"), 180, 60);
le.setLayer(100);
le.setLocation(Game.getWidth() / 2, Game.getHeight() / 2);
le.setSound((ALBuffer) Resources.get("gamesaved.buffer"));
le.spawn(net.puppygames.applet.screens.TitleScreen.getInstance());
}
@Override
protected void doRequestExit() {
final DialogScreen reallyDialog = (DialogScreen) Resources.get("yescancel.dialog");
reallyDialog.doModal("EXIT GAME", "REALLY EXIT THE GAME?", new Runnable() {
@Override
public void run() {
if (reallyDialog.getOption() == DialogScreen.OK_OPTION) {
Game.exit();
}
}
});
}
@Override
protected boolean doIsDemoExpired() {
// Never expire the demo
return false;
}
@Override
protected boolean doMaybeShowHelp() {
// Don't show help
return false;
}
/**
* @return autodifficulty setting
*/
public static boolean getAutoDifficulty() {
return autoDifficulty;
}
/**
* @return the showHints
*/
public static boolean getShowHints() {
return showHints;
}
/**
* @return the showInfo
*/
public static boolean getShowInfo() {
return showInfo;
}
/**
* @return the showTooltips
*/
public static boolean getShowTooltips() {
return showTooltips;
}
/**
* @param showHints the showHints to set
*/
public static void setShowHints(boolean showHints) {
Worm.showHints = showHints;
saveSlotOptions();
}
/**
* @param showInfo the showInfo to set
*/
public static void setShowInfo(boolean showInfo) {
Worm.showInfo = showInfo;
saveSlotOptions();
}
/**
* @param showTooltips the showTooltips to set
*/
public static void setShowTooltips(boolean showTooltips) {
Worm.showTooltips = showTooltips;
saveSlotOptions();
}
public static void setAutoDifficulty(boolean autoDifficulty) {
Worm.autoDifficulty = autoDifficulty;
saveSlotOptions();
}
private static void saveSlotOptions() {
getPlayerSlot().getPreferences().putBoolean("showHints", showHints);
getPlayerSlot().getPreferences().putBoolean("showTooltips", showTooltips);
getPlayerSlot().getPreferences().putBoolean("showInfo", showInfo);
getPlayerSlot().getPreferences().putBoolean("autoDifficulty", autoDifficulty);
flushPrefs();
}
private static void loadSlotOptions() {
showHints = getPlayerSlot().getPreferences().getBoolean("showHints", true);
showTooltips = getPlayerSlot().getPreferences().getBoolean("showTooltips", true);
showInfo = getPlayerSlot().getPreferences().getBoolean("showInfo", true);
autoDifficulty = getPlayerSlot().getPreferences().getBoolean("autoDifficulty", true);
}
@Override
protected void onSetPlayerSlot() {
loadSlotOptions();
}
@Override
protected void onBeginNewGame() {
// Open the game mode choice dialog
ChooseGameModeScreen.show();
}
/**
* Choose a random valid prize
* @return a {@link PrizeFeature}, or null
*/
private PrizeFeature choosePrize() {
List<PrizeFeature> prizes = new ArrayList<PrizeFeature>(PrizeFeature.getPrizes());
Collections.shuffle(prizes);
for (PrizeFeature pf : prizes) {
if (pf.isValid()) {
return pf;
}
}
return null;
}
public static NagState getNagState() {
return NagState.valueOf(getPreferences().get("nagstate", NagState.NOT_YET_SHOWN.name()));
}
public static void setNagState(NagState newNagState) {
getPreferences().put("nagstate", newNagState.name());
flushPrefs();
}
@SuppressWarnings("unused")
@Override
protected void onPreRegisteredStartup() {
if (getPlayerSlot() == null) {
showTitleScreen();
return;
}
NagState nagState = getNagState();
if (TESTSIGNUP && DEBUG) {
nagState = NagState.NOT_YET_SHOWN;
}
switch (nagState) {
case NOT_YET_SHOWN:
PrizeFeature prize = choosePrize();
if (prize != null) {
SignUpScreen.show(prize);
} else {
showTitleScreen();
}
break;
case PRIZE_AWAITS:
// Restore the incentive file
FileInputStream fis = null;
BufferedInputStream bis = null;
ObjectInputStream ois = null;
try {
fis = new FileInputStream(getIncentiveFile());
bis = new BufferedInputStream(fis);
ois = new ObjectInputStream(bis);
NewsletterIncentive ni = (NewsletterIncentive) ois.readObject();
if (!ni.validate()) {
throw new Exception("Existing incentive file is invalid.");
}
UnlockBonusScreen.show(ni);
} catch (Exception e) {
e.printStackTrace(System.err);
showTitleScreen();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
}
}
}
break;
case DONT_NAG:
case REDEEMED:
showTitleScreen();
break;
default:
assert false : "Unknown nag state "+nagState;
}
}
public static File getIncentiveFile() {
return new File(Game.getDirectoryPrefix() + "incentive.dat");
}
@Override
protected File getRestoreFile() {
return new File(getPlayerDirectoryPrefix() + "savedGame_"+newMode+".dat");
}
/**
* Start a new game from scratch
* @param mode A game mode (0, 1, 2)
*/
public static void newGame(int mode) {
newMode = mode;
// Is there a saved game file?
if (isRestoreAvailable()) {
restoreGame();
} else {
cleanGame();
}
}
@Override
protected GameState createGameState() {
return new WormGameState(newMode);
}
/**
* Reset game state (used by survival level select)
*/
public static void resetGameState() {
instance.setGameState(instance.createGameState());
}
@Override
protected void onRestoreGameFailed(Exception e) {
// Clean up the GameScreen
GameScreen.getInstance().panic();
}
}