/* * 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.FileNotFoundException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import net.puppygames.applet.Area; import net.puppygames.applet.Game; import net.puppygames.applet.GameInputStream; import net.puppygames.applet.GameOutputStream; import net.puppygames.applet.GameState; import net.puppygames.applet.MiniGame; import net.puppygames.applet.RoamingFile; import net.puppygames.applet.effects.LabelEffect; import net.puppygames.applet.screens.DialogScreen; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; import org.lwjgl.util.Color; import org.lwjgl.util.Point; import org.lwjgl.util.ReadableColor; import org.lwjgl.util.ReadablePoint; import org.lwjgl.util.ReadableRectangle; import org.lwjgl.util.Rectangle; import worm.buildings.BuildingFeature; import worm.effects.ArrowEffect; import worm.entities.Building; import worm.entities.Capacitor; import worm.entities.Factory; import worm.entities.Gidrah; import worm.entities.Saucer; import worm.entities.Smartbomb; import worm.entities.Turret; import worm.entities.Unit; import worm.features.GidrahFeature; import worm.features.HintFeature; import worm.features.LevelFeature; import worm.features.MedalFeature; import worm.features.RankFeature; import worm.features.ResearchFeature; import worm.features.StoryFeature; import worm.features.WorldFeature; import worm.generator.BaseMapGenerator; import worm.powerups.BatteryPowerupFeature; import worm.powerups.BezerkPowerupFeature; import worm.powerups.CapacitorPowerupFeature; import worm.powerups.CoolingTowerPowerupFeature; import worm.powerups.FreezePowerupFeature; import worm.powerups.MoneyPowerupFeature; import worm.powerups.PowerupFeature; import worm.powerups.ReactorPowerupFeature; import worm.powerups.RepairPowerupFeature; import worm.powerups.ResourcePowerupFeature; import worm.powerups.ScannerPowerupFeature; import worm.powerups.ShieldGeneratorPowerupFeature; import worm.powerups.ShieldPowerupFeature; import worm.powerups.SmartbombPowerupFeature; import worm.screens.CompleteGameScreen; import worm.screens.CompleteXmasScreen; import worm.screens.GameScreen; import worm.screens.IntermissionScreen; import worm.screens.MenuScreen; import worm.screens.NewWorldScreen; import worm.screens.ResearchScreen; import worm.screens.SelectEndlessLevelScreen; import worm.screens.SelectLevelScreen; import worm.screens.SelectSandboxLevelScreen; import worm.screens.SelectSurvivalLevelScreen; import worm.screens.SelectWorldScreen; import worm.screens.StoryScreen; import worm.screens.SurvivalMenuScreen; import worm.screens.XmasMenuScreen; import worm.tiles.Crystal; import worm.tiles.Exclude; import com.shavenpuppy.jglib.Resources; import com.shavenpuppy.jglib.interpolators.CosineInterpolator; import com.shavenpuppy.jglib.interpolators.LinearInterpolator; import com.shavenpuppy.jglib.interpolators.SineInterpolator; import com.shavenpuppy.jglib.resources.MappedColor; import com.shavenpuppy.jglib.resources.ResourceArray; import com.shavenpuppy.jglib.util.Util; /** * The entire game state during play */ public class WormGameState extends GameState { private static final long serialVersionUID = 3L; private static final Rectangle TEMPBOUNDS = new Rectangle(); private static final ArrayList<Entity> BUILD_COLLISIONS = new ArrayList<Entity>(); public static final int GAME_MODE_CAMPAIGN = 0; public static final int GAME_MODE_ENDLESS = 1; public static final int GAME_MODE_SURVIVAL = 2; public static final int GAME_MODE_SANDBOX = 3; public static final int GAME_MODE_XMAS = 4; /** Mouse dragging speed */ private static final float MOUSE_DRAG_SPEED = 2.0f; /** Absolute max map size in tiles */ public static final int ABS_MAX_SIZE = 96; /** Absolute max map width in pixels */ public static final int MAP_WIDTH = ABS_MAX_SIZE * MapRenderer.TILE_SIZE; /** Absolute max map height in pixels */ public static final int MAP_HEIGHT = MAP_WIDTH; /** Map resolution - defines the grid size upon which we place buildings */ public static final int MAP_RESOLUTION = MapRenderer.TILE_SIZE / 4; /** Maximum number of gidrahs allowed */ public static final int MAX_GIDRAHS = 160; /** Number of levels in a world */ public static final int LEVELS_IN_WORLD = 10; /** Number of worlds */ public static final int NUM_WORLDS = 5; /** Number of levels */ public static final int MAX_LEVELS = LEVELS_IN_WORLD * NUM_WORLDS; /** Duration in ticks that we wait after save is initiated */ private static final int SAVE_DURATION = 45; /** End of level duration, in ticks */ private static final int END_OF_LEVEL_DURATION = 30 * 60; /** Wait a few seconds delay, after a boss is killed */ private static final int WAIT_A_FEW_SECONDS_DELAY = 5 * 60; /** Endgame duration, ticks */ private static final int END_OF_GAME_DURATION = 420; /** After this interval (ticks), we warn about crystals that aren't being mined */ private static final int UNMINED_WARNING_TIME = 600; /** After this interval (ticks) of nothing happening, we mention the fast forward button */ private static final int INTERESTING_INTERVAL = 500; /** Right mouse button drag sensitivity */ private static final int RMB_DRAG_SENSITIVITY = 3; /** Game configuration */ private final GameConfiguration config; private static final ArrayList<Capacitor> TEMP_CAPACITORS = new ArrayList<Capacitor>(); /* * Transient data */ /** Save game stuff */ private transient int saveTick; private transient boolean saving; /** Xmas reset */ private transient boolean xmasReset; /** * Meta state - holds all the information that should be saved on a level-by-level basis, not the actual in-play state, in * a campaign game */ public static class MetaState implements Serializable { private static final long serialVersionUID = 3L; private static final int MAX_RANDOM = 30; private static final int MAX_DEFAULT_RANDOM = 24; private static final int MAX_EXOTIC_RANDOM = 6; private static final int MAX_CRAPPY_RANDOM = 10; /** Game mode */ private final int gameMode; /** A map of stats (Strings to Integers) */ private final Map<String, Integer> stats = new HashMap<String, Integer>(); /** Powerups: a Map of PowerupFeatures to Integers, which is the number of each of that sort of powerup the player has. Or null for 0. */ private final Map<PowerupFeature, Integer> powerups = new HashMap<PowerupFeature, Integer>(); /** Medals: a Map of MedalFeatures to Integers */ private final Map<MedalFeature, Integer> medals = new HashMap<MedalFeature, Integer>(); /** Total score for all medals */ private int score; /** Current rank */ private RankFeature rank; /** Which BuildingFeatures have been researched (Strings - from BuildingFeature.getID()) */ private final HashSet<String> researched = new HashSet<String>(); /** Money */ private int money; /** Number of attempts at current level so far */ private int attempts; /** Level index */ private int level = 0; /** World */ private WorldFeature world; /** Level */ private LevelFeature levelFeature; /** Base difficulty, usually negative as player starts to lose */ private float difficulty = 0.0f; /** Survival params */ private SurvivalParams survivalParams; /** Sandbox params */ private SandboxParams sandboxParams; /** Powerups shuffle */ private ArrayList<Integer> shuffle; private ArrayList<Integer> crappyShuffle; private ArrayList<Integer> randomShuffle; private ArrayList<Integer> exoticShuffle; /** Resources */ private Map<BuildingFeature, Integer> availableStock; public MetaState(int gameMode) { this.gameMode = gameMode; reset(); } public void bonusMoney(int amount) { money += amount; } public int getAvailableStock(BuildingFeature bf) { if (availableStock == null) { availableStock = new HashMap<BuildingFeature, Integer>(); } Integer ret = availableStock.get(bf); if (ret == null) { return 0; } else { return ret.intValue(); } } public void addAvailableStock(BuildingFeature bf, int n) { int current = getAvailableStock(bf); if (current == -1) { // Hmm. assert false; return; } current = Math.min(bf.getMaxAvailable(), Math.max(0, current + n)); availableStock.put(bf, new Integer(current)); } void reset() { stats.clear(); powerups.clear(); medals.clear(); researched.clear(); if (availableStock == null) { availableStock = new HashMap<BuildingFeature, Integer>(); } else { availableStock.clear(); } switch (gameMode) { case GAME_MODE_SURVIVAL: if (survivalParams != null) { money = GameConfiguration.getInstance().getSurvivalInitialMoney()[survivalParams.getWorld().getIndex()]; } break; case GAME_MODE_XMAS: money = GameConfiguration.getInstance().getXmasInitialMoney(); break; default: money = GameConfiguration.getInstance().getNormalInitialMoney(); break; } score = 0; rank = RankFeature.getRank(0); shuffle = new ArrayList<Integer>(MAX_RANDOM); randomShuffle = new ArrayList<Integer>(MAX_DEFAULT_RANDOM); crappyShuffle = new ArrayList<Integer>(MAX_CRAPPY_RANDOM); exoticShuffle = new ArrayList<Integer>(MAX_EXOTIC_RANDOM); } private int addStat(String stat, int n) { Integer current = stats.get(stat); if (current == null) { current = Integer.valueOf(n); } else { current = Integer.valueOf(current.intValue() + n); } stats.put(stat, current); return current.intValue(); } private static String getPath(int level, int gameMode) { return Game.getPlayerDirectoryPrefix() + "metastate_" + level + "_"+gameMode+".ser"; } public static MetaState load(int level, int gameMode) throws Exception { GameInputStream gis = null; ObjectInputStream ois = null; try { String path = getPath(level, gameMode); RoamingFile file = new RoamingFile(path); if (!file.exists()) { throw new FileNotFoundException("The save game file "+path+" was not found"); } gis = new GameInputStream(path); ois = new ObjectInputStream(gis); MetaState ret = (MetaState) ois.readObject(); Resources.dequeue(); if (ret.shuffle == null || ret.exoticShuffle == null || ret.randomShuffle == null || ret.crappyShuffle == null) { ret.shuffle = new ArrayList<Integer>(MAX_RANDOM); ret.randomShuffle = new ArrayList<Integer>(MAX_DEFAULT_RANDOM); ret.crappyShuffle = new ArrayList<Integer>(MAX_CRAPPY_RANDOM); ret.exoticShuffle = new ArrayList<Integer>(MAX_EXOTIC_RANDOM); } if (ret.availableStock == null) { ret.availableStock = new HashMap<BuildingFeature, Integer>(); } return ret; } finally { try { if (gis != null) { gis.close(); } } catch (IOException e) { } } } public void save() throws Exception { GameOutputStream gos = null; ObjectOutputStream oos = null; try { gos = new GameOutputStream(getPath(level, gameMode)); oos = new ObjectOutputStream(gos); oos.writeObject(this); oos.flush(); gos.flush(); } finally { try { gos.close(); } catch (IOException e) { } } } public int getMoney() { return money; } public void bonusPowerup(PowerupFeature powerup, int amount) { Integer current = powerups.get(powerup); if (current == null) { current = Integer.valueOf(amount); } else { current = Integer.valueOf(current.intValue() + amount); } powerups.put(powerup, current); } private PowerupFeature getCrappyPowerup() { if (crappyShuffle.size() == 0) { for (int i = 0; i < MAX_CRAPPY_RANDOM; i ++) { crappyShuffle.add(new Integer(i)); } Collections.shuffle(crappyShuffle); } Integer i = crappyShuffle.remove(crappyShuffle.size() - 1); switch (i.intValue()) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: return MoneyPowerupFeature.getInstance(50); default: return getPowerup(); } } private PowerupFeature getRandomPowerup() { if (randomShuffle.size() == 0) { for (int i = 0; i < MAX_DEFAULT_RANDOM; i ++) { randomShuffle.add(new Integer(i)); } Collections.shuffle(randomShuffle); } Integer i = randomShuffle.remove(randomShuffle.size() - 1); switch (i.intValue()) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: return MoneyPowerupFeature.getInstance(50); case 7: case 8: case 9: case 10: case 11: return MoneyPowerupFeature.getInstance(100); case 12: case 13: case 14: case 15: return MoneyPowerupFeature.getInstance(250); case 16: case 17: case 18: return MoneyPowerupFeature.getInstance(500); case 19: case 20: case 21: return getPowerup(); default: return getExoticPowerup(); } } /** * @return an exotic powerup */ private PowerupFeature getExoticPowerup() { if (gameMode == GAME_MODE_SURVIVAL || gameMode == GAME_MODE_XMAS || level < 4) { return getPowerup(); } PowerupFeature ret = null; do { if (exoticShuffle.size() == 0) { for (int i = 0; i < MAX_EXOTIC_RANDOM; i ++) { exoticShuffle.add(new Integer(i)); } Collections.shuffle(exoticShuffle); } Integer i = exoticShuffle.remove(exoticShuffle.size() - 1); WormGameState gameState = Worm.getGameState(); switch (i.intValue()) { case 0: if (gameState.isResearched(ResearchFeature.BATTERY)) { ret = BatteryPowerupFeature.getInstance(); } else { ret = getCrappyPowerup(); } break; case 1: if (gameState.isResearched(ResearchFeature.CAPACITOR)) { ret = CapacitorPowerupFeature.getInstance(); } else { ret = getCrappyPowerup(); } break; case 2: if (gameState.isResearched(ResearchFeature.COOLINGTOWER)) { ret = CoolingTowerPowerupFeature.getInstance(); } else { ret = getCrappyPowerup(); } break; case 3: if (gameState.isResearched(ResearchFeature.REACTOR)) { ret = ReactorPowerupFeature.getInstance(); } else { ret = getCrappyPowerup(); } break; case 4: if (gameState.isResearched(ResearchFeature.SHIELDGENERATOR)) { ret = ShieldGeneratorPowerupFeature.getInstance(); } else { ret = getCrappyPowerup(); } break; case 5: if (gameState.isResearched(ResearchFeature.SCANNER)) { ret = ScannerPowerupFeature.getInstance(); } else { ret = getCrappyPowerup(); } break; default: assert false; return null; } } while (!ret.isAvailable()); return ret; } /** * @return a non-exotic powerup */ private PowerupFeature getPowerup() { if (shuffle.size() == 0) { for (int i = 0; i < MAX_RANDOM; i ++) { shuffle.add(new Integer(i)); } Collections.shuffle(shuffle); } Integer i = shuffle.remove(shuffle.size() - 1); WormGameState gameState = Worm.getGameState(); switch (i.intValue()) { case 0: case 1: case 2: return BezerkPowerupFeature.getInstance(); case 3: case 4: case 5: return RepairPowerupFeature.getInstance(); case 6: case 7: case 8: return ShieldPowerupFeature.getInstance(); case 9: case 10: case 11: return SmartbombPowerupFeature.getInstance(); case 12: case 13: case 14: return FreezePowerupFeature.getInstance(); case 15: case 16: case 17: if ((gameState.getGameMode() == WormGameState.GAME_MODE_SURVIVAL || gameState.getGameMode() == WormGameState.GAME_MODE_XMAS) && gameState.isResearched(ResearchFeature.MINES)) { return ResourcePowerupFeature.getInstance("mines.powerup"); } else { return getPowerup(); // Recurse } case 18: case 19: if ((gameState.getGameMode() == WormGameState.GAME_MODE_SURVIVAL || gameState.getGameMode() == WormGameState.GAME_MODE_XMAS) && gameState.isResearched(ResearchFeature.CLUSTERMINES)) { return ResourcePowerupFeature.getInstance("clustermines.powerup"); } else { return getPowerup(); // Recurse } case 20: if ((gameState.getGameMode() == WormGameState.GAME_MODE_SURVIVAL || gameState.getGameMode() == WormGameState.GAME_MODE_XMAS) && gameState.isResearched(ResearchFeature.BLASTMINES)) { return ResourcePowerupFeature.getInstance("blastmines.powerup"); } else { return getPowerup(); // Recurse } case 21: case 22: case 23: if ((gameState.getGameMode() == WormGameState.GAME_MODE_SURVIVAL || gameState.getGameMode() == WormGameState.GAME_MODE_XMAS) && gameState.isResearched(ResearchFeature.CONCRETE)) { return ResourcePowerupFeature.getInstance("concrete.powerup"); } else { return getPowerup(); // Recurse } case 24: case 25: case 26: if ((gameState.getGameMode() == WormGameState.GAME_MODE_SURVIVAL || gameState.getGameMode() == WormGameState.GAME_MODE_XMAS) && gameState.isResearched(ResearchFeature.STEEL)) { return ResourcePowerupFeature.getInstance("steel.powerup"); } else { return getPowerup(); // Recurse } case 27: case 28: if ((gameState.getGameMode() == WormGameState.GAME_MODE_SURVIVAL || gameState.getGameMode() == WormGameState.GAME_MODE_XMAS) && gameState.isResearched(ResearchFeature.TITANIUM)) { return ResourcePowerupFeature.getInstance("titanium.powerup"); } else { return getPowerup(); // Recurse } case 29: if ((gameState.getGameMode() == WormGameState.GAME_MODE_SURVIVAL || gameState.getGameMode() == WormGameState.GAME_MODE_XMAS) && gameState.isResearched(ResearchFeature.NANOMESH)) { return ResourcePowerupFeature.getInstance("nanomesh.powerup"); } else { return getPowerup(); // Recurse } default: assert false; return null; } } } /* * Game state */ /** Current metastate */ private MetaState metaState; /** Current map */ private GameMap map; /** Spawn points */ private final ArrayList<SpawnPoint> spawnPoints = new ArrayList<SpawnPoint>(); /** Debugging aid */ private transient boolean forceDifficulty; /** Current total difficulty */ private float currentDifficulty; /** Powerup difficulty */ private float powerupDifficulty; /** Whether to begin the level */ private boolean beginLevel; /** Money at start of level */ private int startingMoney; /** Factories */ private int factories, totalFactories; /** Warehouses */ private int warehouses; /** Shield generators */ private int shieldGenerators; /** Autoloaders */ private int autoloaders; /** Spawners */ private int spawners; /** Are we alive? */ private boolean alive = true; /** Freeze tick */ private int freezeTick; /** Shield tick */ private int shieldTick; /** Capacitor boost */ private int capacitorBoost; /** Cooling boost */ private int coolingBoost; /** Battery boost */ private int batteryBoost; /** Reactor boost */ private int reactorBoost; /** Scanner boost */ private int scannerBoost; /** Shield boost: extra hitpoints for all buildings */ private int shieldBoost; /** Level duration */ private int levelDuration; /** Aliens spawned */ private int aliensSpawnedValue, aliensSpawnedAtLevelEnd; /** Aliens vanquished value */ private int aliensVanquishedValue, aliensVanquishedSinceEndOfLevel; /** Last story index */ private int currentStoryIndex; /** Something interesting happened this many ticks ago */ private int somethingInterestingHappenedTick; /** Last hovered entity */ private Entity lastHovered; /** Total value of buildings created on this level */ private int valueOfBuiltBuildings; /** Number of buildings made this level */ private int numberOfBuildingsMade; /** Total value of buildings destroyed by gidrahs on this level */ private int valueOfDestroyedBuildings; /** Number of buildings destroyed this level */ private int numberOfBuildingsDestroyed; /** Have any buildings been damaged on this level? (for Pristine medal) */ private boolean anyDamaged; /** Number of crystal buildings */ private int crystals, initialCrystals; /** The base */ private Building base; /** Entities */ private final ArrayList<Entity> entities = new ArrayList<Entity>(); /** All the gidrahs currently on the level */ private final ArrayList<Gidrah> gidrahs = new ArrayList<Gidrah>(); /** All the bosses currently on the level */ private final ArrayList<Gidrah> bosses = new ArrayList<Gidrah>(); /** All the units currently on the level */ private final ArrayList<Unit> units = new ArrayList<Unit>(); /** All the buildings currently on the level */ private final ArrayList<Building> buildings = new ArrayList<Building>(); /** All the saucers currently on the level */ private final ArrayList<Saucer> saucers = new ArrayList<Saucer>(); /** Medals earned during play this level */ private final Set<MedalFeature> medalsThisLevel = new HashSet<MedalFeature>(); /** Armed capacitors */ private final ArrayList<Capacitor> armedCapacitors = new ArrayList<Capacitor>(); /** Event hint seq number */ private int eventHintSeq; /** Unmined crystals warning */ private int unminedTick; /** Game state interface */ private final GameStateInterface gameStateInterface = new GameStateInterface() { private static final long serialVersionUID = 1L; @Override public void addToGidrahs(Gidrah gidrah) { gidrahs.add(gidrah); if (gidrah.getFeature().isBoss()) { bosses.add(gidrah); addStat(Stats.BOSSES_SPAWNED, 1); } if (gidrah.getFeature().isAngry()) { addStat(Stats.ANGRY_SPAWNED, 1); } if (gidrah.getFeature().isGidlet()) { addStat(Stats.GIDLETS_SPAWNED, 1); } aliensSpawnedValue += gidrah.getFeature().getValue(); addStat(Stats.ALIENS_SPAWNED, 1); } @Override public void addToUnits(Unit unit) { units.add(unit); } @Override public void addToBuildings(Building building) { buildings.add(building); if (building.isCity()) { base = building; } onSomethingInterestingHappened(); } @Override public void addToSaucers(Saucer saucer) { saucers.add(saucer); onSomethingInterestingHappened(); } @Override public void removeFromBuildings(Building building) { buildings.remove(building); onSomethingInterestingHappened(); } @Override public void removeFromGidrahs(Gidrah gidrah) { gidrahs.remove(gidrah); aliensVanquishedValue += gidrah.getFeature().getValue(); if (!isLevelActive()) { aliensVanquishedSinceEndOfLevel += gidrah.getFeature().getValue(); } onSomethingInterestingHappened(); } @Override public void removeFromUnits(Unit unit) { units.remove(unit); } @Override public void removeFromSaucers(Saucer saucer) { saucers.remove(saucer); } @Override public void buffBatteries() { batteryBoost ++; onSomethingInterestingHappened(); } @Override public void buffScanners() { scannerBoost ++; onSomethingInterestingHappened(); } @Override public void buffCapacitors() { capacitorBoost ++; onSomethingInterestingHappened(); } @Override public void buffCoolingTowers() { coolingBoost ++; onSomethingInterestingHappened(); } @Override public void buffReactors() { reactorBoost ++; onSomethingInterestingHappened(); } @Override public void buffShieldGenerators() { shieldBoost ++; for (Building building : buildings) { building.addShieldBoost(); } onSomethingInterestingHappened(); } @Override public void repairFully() { for (Building building : buildings) { building.repairFully(); } if (metaState.addStat(Stats.REPAIRS_USED, 1) == 5) { awardMedal(Medals.JIMLL_FIX_IT); } onSomethingInterestingHappened(); } @Override public void setSmartbombMode() { setBuilding(null); mode = Mode.MODE_SMARTBOMB; onSomethingInterestingHappened(); } @Override public void freeze(int duration) { // Freeze all the gidrahs! freezeTick += duration; GameScreen.instance.onFreezeTimerIncreased(freezeTick); if (metaState.addStat(Stats.FREEZES_USED, 1) == 5) { awardMedal(Medals.TOILET_BREAK); } onSomethingInterestingHappened(); } @Override public void bezerk(int duration) { bezerkTick += duration; GameScreen.getInstance().onBezerkTimerIncreased(bezerkTick); // Bezerk turrets reload instantly for (Building building : buildings) { building.onBezerk(); } if (metaState.addStat(Stats.BEZERKS_USED, 1) == 5) { awardMedal(Medals.SHORT_TEMPERED); } onSomethingInterestingHappened(); } @Override public void invulnerable(int duration) { shieldTick += duration; GameScreen.getInstance().onShieldTimerIncreased(shieldTick); if (metaState.addStat(Stats.SHIELDS_USED, 1) == 5) { awardMedal(Medals.BATFINK); } onSomethingInterestingHappened(); } @Override public void addMoney(int delta) { WormGameState.this.addMoney(delta); onSomethingInterestingHappened(); } @Override public int getAvailableStock(BuildingFeature bf) { return WormGameState.this.getAvailableStock(bf); } @Override public void addAvailableStock(BuildingFeature bf, int n) { WormGameState.this.addAvailableStock(bf, n); } }; /** Ticks */ private int tick, totalTicks, saucerTick, levelTick, bezerkTick, survivalSpawnPointTick, crystalTick; /** Next saucer at... */ private int nextSaucer; /** Contains a shuffle bag of crystal sizes (Integers), valued between 1 and 3 inclusive */ private final ArrayList<Integer> survivalCrystals = new ArrayList<Integer>(); /** Survival mode: next boss at this tick */ private int survivalNextBossTick; /** Survival mode: next crystal at this tick */ private int survivalNextCrystalTick; /** Survival mode: next survival spawn point count increase at this tick */ private int survivalNextSpawnPointTick; /** Survival mode: survival spawn point interval */ private int survivalSpawnPointInterval; /** Number of survival spawnpoints that there should be */ private int numSurvivalSpawnPoints; /** Survival mode gidrah unlock levels */ private final int[] survivalGidrahUnlock = new int[] {1, 0, 0, 0}; /** Survival mode gidrah unlock levels */ private final int[] survivalGidrahUnlockNext = new int[] {0, 0, 0, 0}; /** Survival mode gidrah kill counts */ private final int[] survivalGidrahKills = new int[] {0, 0, 0, 0}; /** Whether mouse button was down last tick */ private transient boolean leftMouseWasDown, rightMouseWasDown; /** Wait for mouse button to be released */ private transient boolean waitForMouse; /** Building we want to build */ private transient BuildingFeature building; /** Build entity */ private transient Building buildEntity; /** Things we've clicked on */ private transient ArrayList<Entity> clicked; /** Back button runnable */ private transient Runnable backButtonRunnable; /** RMB scroll */ private transient boolean rmbScroll; /** Unmined crystals */ private final ArrayList<Building> unminedCrystalList = new ArrayList<Building>(); /** Game phase */ private int phase; private static final int PHASE_NORMAL = 0; private static final int PHASE_END_OF_GAME = 1; private static final int PHASE_WAIT_FOR_GIDRAHS = 2; private static final int PHASE_WAIT_A_FEW_SECONDS = 3; /** Rush mode */ private boolean rush; /** Whether we've expired the demo */ private boolean demoExpired; /** Survival boss counter (earth, moon, mars, etc) */ private int survivalBoss; /** Survival boss ticker */ private int survivalBossTick; /** Input mode (see {@link Mode} */ private int mode; /** Scroll origin */ private transient int scrollOriginX, scrollOriginY; /** Are we in range of a capacitor? */ private transient boolean capacitorRange; /** RMB drag sensor */ private transient int rmbDragSensor; /** Hint map maps HintFeatures to Integers, which is the number of times that hint has been shown in sequence */ private final Map<HintFeature, Integer> hintMap = new HashMap<HintFeature, Integer>(); /** Stories for this level */ private transient List<StoryFeature> currentStories; /** Survival init params */ private SurvivalParams survivalParams; /** Sandbox init params */ private SandboxParams sandboxParams; /** Suppress medals display on game screen */ private boolean suppressMedals; /** Awesome awarded this level */ private boolean awesome; /** Number of attempts */ private transient int attempts; /** * Gidrah spawn point */ private class SpawnPoint implements Serializable { private static final long serialVersionUID = 1L; int tileX, tileY, type, subType; final boolean edge; /** Gidrah generator tick */ private int gidrahTick; /** Waiting for spawn point to clear */ private int waitingToSpawn; /** Gidrah position indicator */ private int gidrahsSpawned; /** Spawn boss */ private GidrahFeature boss; /** Stop spawning */ private boolean stop; /** More aliens please */ private int moreAliens; /** Effect */ private transient ArrowEffect arrowEffect; SpawnPoint(int tileX, int tileY, int type, boolean edge) { this.tileX = tileX; this.tileY = tileY; this.type = type; this.edge = edge; gidrahTick = Util.random(getInitialLevelDelay(map.getWidth(), map.getHeight()), getInitialLevelDelay(map.getWidth(), map.getHeight()) + 600); waitingToSpawn = 0; if (getGameMode() == GAME_MODE_SURVIVAL) { chooseType(); } reinit(); } void spawnBoss(GidrahFeature boss) { this.boss = boss; waitingToSpawn = 0; } @Override public boolean equals(Object obj) { return obj != null && obj instanceof SpawnPoint && ((SpawnPoint) obj).tileX == tileX && ((SpawnPoint) obj).tileY == tileY && ((SpawnPoint) obj).type == type; } @Override public int hashCode() { return (tileX ^ tileY << 16) * (type + 1); } /** * Tick the spawnpoint. Returns true if still active * @return */ boolean tick() { if (!base.isAlive()) { // No base? return !stop; } if (getGameMode() == GAME_MODE_XMAS) { if (levelTick % Xmas.BOSS_INTERVAL == 0 && levelTick > 0) { // Every 5 minutes spawn a boss type = 2; } else if (levelTick % Xmas.ANGRY_INTERVAL == 0 && levelTick > 0 && type == 0) { type = 1; } } if (gidrahs.size() >= MAX_GIDRAHS) { return !stop; } if (freezeTick > 0) { return !stop; } if (getGameMode() != GAME_MODE_SURVIVAL) { if (crystals == 0 && !isLevelActive()) { return !stop; } } if (waitingToSpawn > 0) { waitingToSpawn --; if (waitingToSpawn == 0) { spawnGidrah(); } } else { if (stop) { return false; } if (boss != null) { spawnGidrah(); stop = true; arrowEffect.remove(); arrowEffect = null; } else if (--gidrahTick <= 0) { // Get next item gidrahsSpawned ++; // xmas mode: continuous stream if (getGameMode() == GAME_MODE_XMAS) { spawnGidrah(); } else { if (gidrahsSpawned >= getWaveLength()) { // We've got to the end. Go into a very long delay and start again at the beginning gidrahTick = getLongGidrahDelay(); gidrahsSpawned = 0; // In Survival we move the spawnpoint around, and choose a new alien to spawn if (getGameMode() == GAME_MODE_SURVIVAL) { moveSpawnPoint(); } else { moreAliens = Math.min(3, moreAliens + 1); } } else { spawnGidrah(); } } } } return true; } void moveSpawnPoint() { int ox = 0, oy = 0; if (tileX == -1) { tileY += Util.random(-2, 2); ox = 1; oy = 0; } else if (tileX == getMap().getWidth()) { tileY += Util.random(-2, 2); ox = 1; oy = 0; } else if (tileY == -1) { tileX += Util.random(-2, -2); ox = 0; oy = 1; } else if (tileY == getMap().getHeight()) { tileX += Util.random(-2, -2); ox = 0; oy = -1; } for (int z = 0; z < GameMap.LAYERS; z ++) { Tile t = getMap().getTile(tileX + ox, tileY + oy, z); if (t == null || t.isImpassable() || t.isSolid()) { // Zap! stop = true; remove(); return; } } arrowEffect.setSpawnLocation(tileX, tileY); // Choose a new type chooseType(); } void chooseType() { assert getGameMode() == GAME_MODE_SURVIVAL; int maxType = 0; for (int i = survivalGidrahUnlock.length; -- i >= 0; ) { if (survivalGidrahUnlock[i] != 0) { maxType = i; break; } } type = Util.random(0, maxType); subType = Util.random(0, Math.min(getNumTypes(type) - 1, survivalGidrahUnlock[type] - 1)); } int getNumTypes(int type) { assert getGameMode() == GAME_MODE_SURVIVAL; return Res.getSurvivalGidrahs(type).getNumResources(); } int getWaveLength() { switch (metaState.gameMode) { case GAME_MODE_CAMPAIGN: return getLevelInWorld() / 3 + 10 + moreAliens - Math.min(9, type * 3); case GAME_MODE_ENDLESS: return metaState.level / LEVELS_IN_WORLD + 10 + moreAliens - Math.min(9, type * 3); case GAME_MODE_SURVIVAL: return config.getSurvivalWaveLength() - Math.max(0, type * 3 - Math.max(0, getLevelTick() - config.getSurvivalWaveLengthTimeOffset()) / config.getSurvivalWaveLengthTimeAdjust()); case GAME_MODE_XMAS: assert false; return -1; // Constant stream case GAME_MODE_SANDBOX: return metaState.level / LEVELS_IN_WORLD + 10 + moreAliens - Math.min(9, type * 3); default: assert false : "Unknown game mode " + metaState.gameMode; return 1; } } void spawnGidrah() { if (map.isOccupied(tileX, tileY)) { if (metaState.gameMode == GAME_MODE_XMAS) { waitingToSpawn = 1; } else { waitingToSpawn = config.getSpawnDelay(); } } else { waitingToSpawn = 0; if (boss != null) { boss.spawn(GameScreen.getInstance(), tileX, tileY, 0); boss = null; gidrahTick = Util.random(getInitialLevelDelay(map.getWidth(), map.getHeight()), getInitialLevelDelay(map.getWidth(), map.getHeight()) + 600); } else { GidrahFeature gf; if (getGameMode() == GAME_MODE_XMAS) { // Choose gidrah based on level duration so far float ratio = levelTick == getLevelDuration() ? 0.99999f : (float) levelTick / (float) getLevelDuration(); int gidIndex = Util.random(0, (int) LinearInterpolator.instance.interpolate(0.0f, Res.getXmasGidrahs().getNumResources(), ratio)); switch (type) { case 0: // Ordinary gid gf = (GidrahFeature) Res.getXmasGidrahs().getResource(gidIndex); break; case 1: // Angry gid gf = (GidrahFeature) Res.getXmasAngryGidrahs().getResource(gidIndex); type = 0; break; case 2: // Boss gf = Res.getXmasBoss(survivalBoss ++); doBossWarning(1); // Stop when last boss made if (survivalBoss == Xmas.MAX_BOSSES) { stop = true; } type = 0; break; default: assert false; return; } } else if ((getGameMode() == GAME_MODE_SURVIVAL || metaState.level > 2) && gidrahsSpawned == 1) { // Maybe an angry gidrah at the head of the column? if (Util.random() < getDifficulty()) { gf = getAngryGidrah(type, subType); } else { gf = getGidrah(type, subType); } } else { gf = getGidrah(type, subType); } gf.spawn(GameScreen.getInstance(), tileX, tileY, type); gidrahTick = 1; } } } int getLongGidrahDelay() { switch (getGameMode()) { case GAME_MODE_SURVIVAL: return config.getLongDelay() + (int) LinearInterpolator.instance.interpolate(config.getSpawnpointDelayAdjust() * 5.0f, 0.0f, getDifficulty()); case GAME_MODE_XMAS: return 0; // No delay default: return getShortGidrahDelay() * 2; } } int getShortGidrahDelay() { return config.getLongDelay() + Math.max(0, spawnPoints.size() - metaState.level / LEVELS_IN_WORLD) * config.getSpawnpointDelayAdjust(); } void reinit() { arrowEffect = new ArrowEffect(tileX, tileY); arrowEffect.spawn(GameScreen.getInstance()); arrowEffect.setOffset(null); } void remove() { if (arrowEffect != null) { arrowEffect.remove(); arrowEffect = null; } map.clearItem(tileX, tileY); stop = true; } } /** * C'tor */ public WormGameState(int mode) { this.config = GameConfiguration.getInstance(); this.metaState = new MetaState(mode); reset(); } private GidrahFeature getAngryGidrah(int type, int subType) { assert getGameMode() != GAME_MODE_XMAS; switch (getGameMode()) { case GAME_MODE_SURVIVAL: return (GidrahFeature) Res.getSurvivalAngryGidrahs(type).getResource(subType); default: return getLevelFeature().getAngryGidrah(type); } } private GidrahFeature getGidrah(int type, int subType) { assert getGameMode() != GAME_MODE_XMAS; switch (getGameMode()) { case GAME_MODE_SURVIVAL: return (GidrahFeature) Res.getSurvivalGidrahs(type).getResource(subType); default: return getLevelFeature().getGidrah(type); } } /** * Repair all the buildings by one shield */ public void repair() { for (Iterator<Building> i = buildings.iterator(); i.hasNext(); ) { i.next().repair(); } } /** * Get the player's money * @return int */ public int getMoney() { return metaState.money; } /** * Adjust the player's money * @param moneyDelta The amount of money to adjust the player's purse by */ public void addMoney(int moneyDelta) { metaState.money += moneyDelta; if (metaState.money < 0) { metaState.money = 0; } } /** * Add an entity to the game * @param entity */ public void addEntity(Entity entity) { entities.add(entity); // Now get the entity to add itself to the appropriate list entity.addToGameState(gameStateInterface); } /** * Add a powerup * @param powerup * @param doSound TODO */ public void addPowerup(PowerupFeature powerup, boolean doSound) { Integer num = metaState.powerups.get(powerup); int n; if (num == null) { n = 1; } else { n = num.intValue() + 1; } metaState.powerups.put(powerup, new Integer(n)); if (doSound && powerup != null) { Game.allocateSound(powerup.getCollectSound()); } calcPowerupDifficulty(); GameScreen.onPowerupsUpdated(); } private void calcPowerupDifficulty() { powerupDifficulty = 0.0f; for (PowerupFeature pf : metaState.powerups.keySet()) { powerupDifficulty += pf.getDifficulty(); } } /** * Remove a powerup * @param powerup */ public void removePowerup(PowerupFeature powerup) { Integer num = metaState.powerups.get(powerup); int n; if (num == null) { assert false; return; } else { n = num.intValue() - 1; } if (n == 0) { metaState.powerups.remove(powerup); } else { metaState.powerups.put(powerup, new Integer(n)); } calcPowerupDifficulty(); GameScreen.onPowerupsUpdated(); } /** * Return the number of powerups of this type the player has * @param powerup * @return int */ public int getNumPowerups(PowerupFeature powerup) { Integer num = metaState.powerups.get(powerup); if (num == null) { return 0; } else { return num.intValue(); } } /** * Use a powerup * @param powerup */ public void usePowerup(PowerupFeature powerup) { if (getNumPowerups(powerup) == 0) { return; } removePowerup(powerup); Game.allocateSound(powerup.getCollectSound()); powerup.activate(gameStateInterface); } /** * Remove an entity from the game * @param entity */ public void removeEntity(Entity entity) { // We don't actually remove the entity from entities list here; that occurs // for us in checkCollisions(). // Now get the entity to remove itself from the appropriate list entity.removeFromGameState(gameStateInterface); } /** * Cleanup */ public void cleanup() { for (int i = 0; i < entities.size(); i ++) { Entity e = entities.get(i); if (e.isActive()) { e.remove(); } } Entity.reset(); System.gc(); } /** * Adjust the number of factories in play * @param n */ public void addFactories(int n) { factories += n; if (n > 0) { totalFactories += n; } beginLevel = true; assert factories >= 0; } public void addCrystals(int n) { crystals += n; if (crystals == 0 && getGameMode() != GAME_MODE_SURVIVAL && getGameMode() != GAME_MODE_XMAS) { // Mined all crystals before timer expires? if (levelTick < getLevelDuration() && beginLevel) { awardMedal(Medals.EFFICIENT); } } } public void addInitialCrystals(int n) { initialCrystals += n; } /** * @return the number of factories in play */ public int getFactories() { return factories; } public int getAutoloaders() { return autoloaders; } public int getWarehouses() { return warehouses; } public void addWarehouses(int n) { warehouses += n; } public int getShieldGenerators() { return shieldGenerators; } public void addShieldGenerators(int n) { shieldGenerators += n; } public void addAutoloaders(int n) { autoloaders += n; } /** * Adjust the number of spawners in play * @param n */ public void addSpawners(int n) { spawners += n; beginLevel = true; assert spawners >= 0; } /** * @return the number of spawners in play */ public int getSpawners() { return spawners; } /** * @return Returns the tick. */ public int getTick() { return tick; } /** * @return Returns the totalTicks. */ public int getTotalTicks() { return totalTicks; } /** * Reset the total tick count */ public void resetTotalTicks() { totalTicks = 0; } /** * Is the player still "alive"? * @return true if the player hasn't lost a base */ public boolean isAlive() { return alive; } /** * Called when a base is destroyed or built */ public void addBases(int n) { if (n == -1) { kill(); } } /** * Are we playing the game? * @return true if we're in PHASE_NORMAL or WAIT_FOR_GIDRAHS */ public boolean isPlaying() { return phase == PHASE_NORMAL || phase == PHASE_WAIT_FOR_GIDRAHS; } /** * @return true if we're in rush mode */ public boolean isRushActive() { return rush; } /** * End the level */ private void endLevel() { GameScreen.onEndLevel(); // Cancel build mode setBuilding(null); // Shut down etc. shutdownFactories(); if (metaState.level == 19 && !Game.isRegistered()) { expireDemo(); } else { // And open the intermission screen suppressMedals = true; IntermissionScreen.show(); } } public static int getDifficultyAdjust(int level, int gameMode) { return Worm.getExtraLevelData(Game.getPlayerSlot(), level, gameMode, "diff", 0); } public static void setDifficultyAdjust(int level, int gameMode, int diff) { Worm.setExtraLevelData(level, gameMode, "diff", diff); } /** * Checks for new medals at the end of the level. Right now, can create up to *9* medals, which will have to be listed on the * {@link IntermissionScreen}. Check what got created with {@link #getMedalsEarnedThisLevel()}, which could have a whole bunch * more medals in it! */ public void checkForNewMedals() { if (base.isAlive() && base.getHitPoints() <= 4) { awardMedal(Medals.TAPE_AND_STRING); } if (metaState.gameMode == GAME_MODE_CAMPAIGN) { // Award various medals int diff = getDifficultyAdjust(metaState.level, GAME_MODE_CAMPAIGN); if (diff == 0) { // Gold! awardMedal(Medals.GOLD); } else if (diff == 1) { // Silver! awardMedal(Medals.SILVER); } else if (diff == 2) { // Bronze! awardMedal(Medals.BRONZE); } boolean good = false; if (getLevel() == 9) { good = true; awardMedal(Medals.EARTH_COMPLETE); for (int i = 0; i < 10; i ++) { if (getDifficultyAdjust(i, metaState.gameMode) > 0) { good = false; break; } } } else if (getLevel() == 19) { good = true; awardMedal(Medals.MOON_COMPLETE); for (int i = 10; i < 20; i ++) { if (getDifficultyAdjust(i, metaState.gameMode) > 0) { good = false; break; } } } else if (getLevel() == 29) { good = true; awardMedal(Medals.MARS_COMPLETE); for (int i = 20; i < 30; i ++) { if (getDifficultyAdjust(i, metaState.gameMode) > 0) { good = false; break; } } } else if (getLevel() == 39) { good = true; awardMedal(Medals.SATURN_COMPLETE); for (int i = 30; i < 40; i ++) { if (getDifficultyAdjust(i, metaState.gameMode) > 0) { good = false; break; } } } else if (getLevel() == 49) { good = true; awardMedal(Medals.TITAN_COMPLETE); for (int i = 40; i < 50; i ++) { if (getDifficultyAdjust(i, metaState.gameMode) > 0) { good = false; break; } } if (good) { boolean reallyGood = true; for (int i = 0; i < 50; i ++) { if (getDifficultyAdjust(i, metaState.gameMode) > 0) { reallyGood = false; break; } } if (reallyGood) { awardMedal(Medals.PERFECT_GAME); } } } if (good) { awardMedal(Medals.PERFECT_WORLD); } } if (!anyDamaged) { awardMedal(Medals.PRISTINE); } else if (valueOfDestroyedBuildings == 0) { awardMedal(Medals.CAREFUL); } else if ((float) numberOfBuildingsDestroyed / (float) numberOfBuildingsMade >= 0.9f) { awardMedal(Medals.SKIN_OF_YOUR_TEETH); } if (getMoney() - startingMoney >= 15000) { awardMedal(Medals.SHORT_ARMS_DEEP_POCKETS); } else if (getMoney() - startingMoney >= 10000) { awardMedal(Medals.TIGHT_FISTED); } else if (getMoney() - startingMoney >= 5000) { awardMedal(Medals.THRIFTY); } if (getMoney() >= 50000) { awardMedal(Medals.HOARDED_$50000); } else if (getMoney() >= 25000) { awardMedal(Medals.HOARDED_$25000); } else if (getMoney() >= 10000) { awardMedal(Medals.HOARDED_$10000); } else if (getMoney() >= 5000) { awardMedal(Medals.HOARDED_$5000); } } /** * Store the state of play at the end of a level so it's ready to be used by the next level */ public void checkPoint() { metaState.level ++; if (Worm.getMaxLevel(metaState.gameMode) < metaState.level) { Worm.setMaxLevel(metaState.level, metaState.gameMode); } Worm.setExtraLevelData(metaState.level, metaState.gameMode, "money", metaState.money); int diff = getDifficultyAdjust(metaState.level, metaState.gameMode); if (diff > 0) { setDifficultyAdjust(metaState.level, metaState.gameMode, diff - 1); } try { metaState.save(); } catch (Exception e) { e.printStackTrace(System.err); } // Just be sure... Game.flushPrefs(); metaState.level --; } /** * End the game */ private void endGame() { setBuilding(null); phase = PHASE_END_OF_GAME; tick = 0; } /** * Sell a building * @param building */ public void sell(Building building) { building.destroy(true); SFX.sold(); LabelEffect le = new LabelEffect(net.puppygames.applet.Res.getTinyFont(), "$"+building.getSalePrice(), ReadableColor.WHITE, new Color(255, 255, 100), 30, 10); le.setAcceleration(0.0f, -0.025f); le.setVelocity(0.0f, 1.0f); le.setLocation(building.getMapX() + building.getCollisionX(), building.getMapY() + building.getFeature().getBounds().getHeight()); le.spawn(GameScreen.getInstance()); addMoney(building.getSalePrice()); Worm.getGameState().flagHint(Hints.DRAGSELL); if (building.isWorthAttacking()) { addStat(Stats.SOLD, 1); } addAvailableStock(building.getFeature(), 1); // If selling stuff when there are no gidrahs and level ended, there's no point... if (beginLevel && gidrahs.size() == 0) { flagHint(Hints.DONTSELL); } } /** * Get the level duration, in ticks * @return int */ public int getLevelDuration() { return levelDuration; } private void calcLevelDuration(int w, int h) { switch (metaState.gameMode) { case GAME_MODE_CAMPAIGN: float min, max; // In seconds switch (getLevel() / LEVELS_IN_WORLD) { case 0: // Earth min = 30.0f; max = 90.0f; break; case 1: // Moon min = 90.0f; max = 150.0f; break; case 2: // Mars min = 150.0f; max = 210.0f; break; case 3: // Saturn min = 210.0f; max = 270.0f; break; case 4: // Titan min = 270.0f; max = 300.0f; break; default: assert false : "Bad world for campaign: "+metaState.level; min = max = 300.0f; } levelDuration = (int) LinearInterpolator.instance.interpolate(min, max, getLevelInWorld() / (LEVELS_IN_WORLD - 1.0f)) * 60 + getInitialLevelDelay(w, h); // *60 converts seconds to ticks return; case GAME_MODE_ENDLESS: levelDuration = getInitialLevelDelay(w, h) + getLevel() * 300 + 1800; // 5 seconds per level return; case GAME_MODE_SURVIVAL: case GAME_MODE_SANDBOX: levelDuration = -1; return; case GAME_MODE_XMAS: levelDuration = Xmas.DURATION; return; default: assert false : "Unknown game mode "+metaState.gameMode; return; } } /** * @return time in ticks to delay before starting enemy spawning */ private int getInitialLevelDelay(int w, int h) { if (metaState.level < 4 && getGameMode() != GAME_MODE_SURVIVAL && getGameMode() != GAME_MODE_XMAS) { return 0; } return (int) (Math.sqrt(w * h) * config.getInitialLevelDelayFactor()); } /** * Get the current level tick * @return int */ public int getLevelTick() { return levelTick; } /** * Quantize a coordinate to the map resolution * @param coord * @return a quantized coordinate */ public static float quantize(float coord) { return (float) ((int) coord / MAP_RESOLUTION) * MAP_RESOLUTION; } private void tickSurvivalBosses() { survivalBossTick ++; if (survivalBossTick > survivalNextBossTick) { survivalBossTick = 0; survivalNextBossTick += config.getSurvivalBossIntervalAdjust(); createSurvivalBosses(); } } /** * Ticks all the spawnpoints. * @return true if any spawnpoint is still active */ private boolean tickSpawnPoints() { // Survival mode: occasionally create a new spawnpoint if (getGameMode() == GAME_MODE_SURVIVAL) { survivalSpawnPointTick ++; if (survivalSpawnPointTick >= survivalNextSpawnPointTick) { survivalSpawnPointInterval += config.getSurvivalSpawnpointSpawnIntervalAdjust(); survivalNextSpawnPointTick += survivalSpawnPointInterval; numSurvivalSpawnPoints = Math.min(config.getBaseMaxSurvivalSpawnpoints() + getMap().getWidth() / config.getSurvivalSpawnpointsPerMapSize(), numSurvivalSpawnPoints + 1); } while (spawnPoints.size() < numSurvivalSpawnPoints) { createSurvivalSpawnPoint(); } } boolean active = false; for (int i = 0; i < spawnPoints.size(); ) { SpawnPoint sp = spawnPoints.get(i); boolean wasActive = sp.tick(); active |= wasActive; if (!wasActive) { sp.remove(); spawnPoints.remove(i); } else { i ++; } } return active; } /** * Is the mouse pointer in range of a capacitor? * @return boolean */ public boolean inRangeOfCapacitor() { return capacitorRange; } /** * Check for clicking and set the mouse pointer to the right animation for whatever's going on */ private void checkMouse() { if (GameScreen.getInstance().isBlocked()) { return; } if (GameScreen.isSomethingElseGrabbingMouse()) { return; } // Ignore mouse on active areas, unless grabbed int mouseX = GameScreen.getInstance().getMouseX(); int mouseY = GameScreen.getInstance().getMouseY(); List<Area> areasUnderMouse = GameScreen.getInstance().getAreasUnder(mouseX, mouseY); if (GameScreen.getInstance().getGrabbed() == null) { for (Area area : areasUnderMouse) { if (area.isFocusable()) { if (buildEntity != null) { buildEntity.setVisible(false); } Worm.setMouseAppearance(Res.getMousePointer()); return; } } } if (buildEntity != null && !buildEntity.isVisible()) { buildEntity.setVisible(true); } float x = mouseX - GameScreen.getSpriteOffset().getX(); float y = mouseY - GameScreen.getSpriteOffset().getY(); int n = entities.size(); Entity hovered = null; if (clicked == null) { clicked = new ArrayList<Entity>(8); } else { clicked.clear(); } for (int i = 0; i < n; i ++) { Entity entity = entities.get(i); if (entity.isTouching(x, y)) { if (entity.isClickable()) { clicked.add(entity); hovered = entity; } else if (hovered == null && entity.isHoverable()) { hovered = entity; } } } // Check range of capacitors capacitorRange = false; TEMP_CAPACITORS.clear(); for (int i = 0; i < buildings.size(); i ++) { Building b = buildings.get(i); if (b.isAlive() && b instanceof Capacitor) { Capacitor capacitor = (Capacitor) b; if (b.getDistanceTo(x, y) <= capacitor.getZapRadius()) { capacitorRange = true; TEMP_CAPACITORS.add(capacitor); } } } if (mode == Mode.MODE_BUILD) { buildEntity.setLocation(quantize(x - building.getBounds().getX() - building.getBounds().getWidth() / 2 + MAP_RESOLUTION / 2), quantize(y - building.getBounds().getY() - building.getBounds().getHeight() / 2 + MAP_RESOLUTION / 2)); } boolean auxDown = false; int count = Mouse.getButtonCount(); for (int i = 2; i < count; i ++) { if (Mouse.isButtonDown(i)) { auxDown = true; break; } } if (Mouse.isButtonDown(0)) { if (waitForMouse) { return; } GameScreen.onMapGrabbedMouse(true); switch (mode) { case Mode.MODE_SELL: case Mode.MODE_NORMAL: case Mode.MODE_BUILD: case Mode.MODE_DRAG: // Left mouse down in Normal mode. If we're over something, we click on it unless in "no click" mode. If we're not over something, and in capacitor range, // we enter zap mode. If we're outside capacitor range, we set "no click" mode. if (clicked.size() > 0) { if (leftMouseWasDown) { // But we're in "no click" mode... return; } // Ok, enter no click mode if (mode != Mode.MODE_DRAG) { leftMouseWasDown = true; } // Hovering over something. Pick the first one and let that decide what mouse pointer to use. Worm.setMouseAppearance(clicked.get(0).getMousePointer(true)); boolean fire = false, maybeSelect = false; for (Iterator<Entity> i = clicked.iterator(); i.hasNext(); ) { Entity clickable = i.next(); switch (clickable.onClicked(mode)) { case ClickAction.IGNORE: maybeSelect = true; continue; case ClickAction.DRAG: // Allow dragging over factories & turrets. Unless in build mode. if (mode == Mode.MODE_BUILD) { waitForMouse = true; leftMouseWasDown = true; } else { mode = Mode.MODE_DRAG; leftMouseWasDown = false; } return; case ClickAction.CONSUME: if (mode == Mode.MODE_SELL) { // Allow drag leftMouseWasDown = false; waitForMouse = false; } else { waitForMouse = true; leftMouseWasDown = true; } return; case ClickAction.FIRE: if (mode == Mode.MODE_NORMAL) { fire = true; } else { // Build or drag mode; don't zap } break; } } if (!fire && mode == Mode.MODE_NORMAL) { if (maybeSelect) { mode = Mode.MODE_SCROLL; Worm.setMouseAppearance(Res.getMousePointerGrab()); scrollOriginX = mouseX; scrollOriginY = mouseY; } return; } } else { if (mode == Mode.MODE_DRAG) { Worm.setMouseAppearance(Res.getMousePointer()); } } if (mode == Mode.MODE_DRAG || mode == Mode.MODE_SELL || mode == Mode.MODE_SCROLL || mode == Mode.MODE_SELECT) { return; } if (mode == Mode.MODE_BUILD) { build(); break; } if (!capacitorRange) { leftMouseWasDown = true; Worm.setMouseAppearance(Res.getMousePointer()); if (mode == Mode.MODE_NORMAL) { // Go into scroll mode mode = Mode.MODE_SCROLL; scrollOriginX = mouseX; scrollOriginY = mouseY; leftMouseWasDown = true; Worm.setMouseAppearance(Res.getMousePointerGrab()); } return; } else { leftMouseWasDown = true; mode = Mode.MODE_ZAP; armedCapacitors.clear(); armedCapacitors.addAll(TEMP_CAPACITORS); } // Intentional fall-through case Mode.MODE_ZAP: // LMB down in ZAP mode. If we are inside capacitor range, // fire. Otherwise do nothing. for (int i = 0; i < armedCapacitors.size(); i ++) { Capacitor capacitor = armedCapacitors.get(i); if (capacitor.isAlive()) { capacitor.zap(x, y); } } if (armedCapacitors.size() == 0) { mode = Mode.MODE_NORMAL; } if (clicked.size() > 0) { Worm.setMouseAppearance(clicked.get(0).getMousePointer(true)); } else if (isBezerk()) { Worm.setMouseAppearance(capacitorRange ? Res.getMousePointerBezerkOffTarget() : Res.getMousePointerBezerkOutOfRange()); } else { Worm.setMouseAppearance(capacitorRange ? Res.getMousePointerOffTarget() : Res.getMousePointerOutOfRange()); } break; case Mode.MODE_SMARTBOMB: // Boooooooooom! Smartbomb bomb = new Smartbomb(x, y); bomb.spawn(GameScreen.getInstance()); mode = Mode.MODE_NORMAL; Worm.setMouseAppearance(Res.getMousePointerOffTarget()); // That'll probably change immediately next frame waitForMouse = true; if (metaState.addStat(Stats.SMARTBOMBS_USED, 1) == 5) { awardMedal(Medals.THE_ONLY_WAY_TO_BE_SURE); } break; case Mode.MODE_SCROLL: // Scroll the screen around under the mouse int dx = scrollOriginX - mouseX; int dy = scrollOriginY - mouseY; scrollOriginX = mouseX; scrollOriginY = mouseY; GameScreen.getInstance().scroll(dx * MOUSE_DRAG_SPEED, dy * MOUSE_DRAG_SPEED); break; case Mode.MODE_SELECT: // Do nothing return; default: assert false; } } else if (auxDown) { // Sell! GameScreen.onMapGrabbedMouse(true); for (Iterator<Entity> i = clicked.iterator(); i.hasNext(); ) { Entity entity = i.next(); if (entity instanceof Building) { Building building = (Building) entity; if (building.canSell()) { sell(building); } } } } else { GameScreen.onMapGrabbedMouse(false); boolean rmbDown = Mouse.getButtonCount() > 1 && Mouse.isButtonDown(1) || Game.wasKeyPressed(Keyboard.KEY_ESCAPE); if (rmbDown) { if (waitForMouse) { return; } if (!rightMouseWasDown) { scrollOriginX = mouseX; scrollOriginY = mouseY; rmbDragSensor = 0; } leftMouseWasDown = false; rightMouseWasDown = true; int dx = scrollOriginX - mouseX; int dy = scrollOriginY - mouseY; scrollOriginX = mouseX; scrollOriginY = mouseY; if (dx != 0 || dy != 0) { rmbDragSensor ++; if (rmbDragSensor > RMB_DRAG_SENSITIVITY) { GameScreen.getInstance().scroll(dx * MOUSE_DRAG_SPEED, dy * MOUSE_DRAG_SPEED); rmbScroll = true; Worm.setMouseAppearance(Res.getMousePointerGrab()); } } } else { if (rightMouseWasDown && !rmbScroll) { // It was a RMB click. Cancel building / selling / blowing stuff up. Or pick up whatever building is under the mouse to build. switch (mode) { case Mode.MODE_BUILD: setBuilding(null); rightMouseWasDown = false; return; case Mode.MODE_SELL: setSellMode(false); rightMouseWasDown = false; return; case Mode.MODE_DRAG: case Mode.MODE_NORMAL: case Mode.MODE_SELECT: case Mode.MODE_ZAP: for (Iterator<Entity> i = clicked.iterator(); i.hasNext(); ) { Entity clickable = i.next(); if (clickable instanceof Building) { BuildingFeature bf = ((Building) clickable).getFeature(); if (bf.isAvailable()) { clickable.onLeave(mode); lastHovered = null; setBuilding(bf); rightMouseWasDown = false; return; } } } break; case Mode.MODE_SMARTBOMB: // Cancel the smartbomb and return it to the stores addPowerup(SmartbombPowerupFeature.getInstance(), false); mode = Mode.MODE_NORMAL; rightMouseWasDown = false; return; case Mode.MODE_SCROLL: break; default: assert false; } } rmbScroll = false; rightMouseWasDown = false; leftMouseWasDown = false; waitForMouse = false; if (mode == Mode.MODE_BUILD) { if (canBuild(false)) { Worm.setMouseAppearance(Res.getMousePointer()); } else { Worm.setMouseAppearance(Res.getMousePointerCantBuild()); } } else if (hovered != null) { Worm.setMouseAppearance(hovered.getMousePointer(false)); if (lastHovered != hovered) { if (lastHovered != null) { lastHovered.onLeave(mode); } lastHovered = hovered; hovered.onHovered(mode); } } else { if (lastHovered != null) { lastHovered.onLeave(mode); lastHovered = null; } if (mode == Mode.MODE_SMARTBOMB) { Worm.setMouseAppearance(Res.getMousePointerSmartbomb()); } else if (mode == Mode.MODE_SELL) { Worm.setMouseAppearance(Res.getMousePointerSellOff()); } else if (isBezerk()) { Worm.setMouseAppearance(capacitorRange ? Res.getMousePointerBezerkOffTarget() : Res.getMousePointerBezerkOutOfRange()); } else { Worm.setMouseAppearance(capacitorRange ? Res.getMousePointerOffTarget() : Res.getMousePointerOutOfRange()); } } if (mode == Mode.MODE_ZAP || mode == Mode.MODE_DRAG || mode == Mode.MODE_SCROLL || mode == Mode.MODE_SELECT) { mode = Mode.MODE_NORMAL; } } } } /** * Can the build entity be drawn where it is? * @param doingBuild If we're actually attempting to build * @return true if we can build here */ private boolean canBuild(boolean doingBuild) { // Can we build? if (!buildEntity.canBuild()) { return false; } // Don't allow us to overlap with any solid entities buildEntity.getBounds(TEMPBOUNDS); // Ensure no part of the building is on a tile that can't be built upon or off the map if (TEMPBOUNDS.getX() < 0 || TEMPBOUNDS.getY() < 0 || TEMPBOUNDS.getX() + TEMPBOUNDS.getWidth() >= map.getWidth() * MapRenderer.TILE_SIZE || TEMPBOUNDS.getY() + TEMPBOUNDS.getHeight() >= map.getHeight() * MapRenderer.TILE_SIZE) { return false; } for (int y = TEMPBOUNDS.getY() / MapRenderer.TILE_SIZE; y <= (TEMPBOUNDS.getY() + TEMPBOUNDS.getHeight() - 1) / MapRenderer.TILE_SIZE; y ++) { for (int x = TEMPBOUNDS.getX() / MapRenderer.TILE_SIZE; x <= (TEMPBOUNDS.getX() + TEMPBOUNDS.getWidth() - 1) / MapRenderer.TILE_SIZE; x ++) { for (int z = 0; z < GameMap.LAYERS; z ++) { Tile tile = map.getTile(x, y, z); if (tile != null && (tile.isSolid() || tile.isImpassable())) { return false; } } } } Entity.getCollisions(TEMPBOUNDS, BUILD_COLLISIONS); int n = BUILD_COLLISIONS.size(); for (int i = 0; i < n; i ++) { Entity entity = BUILD_COLLISIONS.get(i); if (entity.isActive() && entity.canCollide() && entity.isTouching(buildEntity) && !buildEntity.canBuildOnTopOf(entity)) { return false; } } return true; } /** * Build a building at the specified location. * @param x * @param y */ private void build() { // Make all buildings paintable //waitForMouse = !building.isPaintable(); if (!canBuild(true)) { return; } // Unfortunately need to do these nice cheap checks after canBuild check, to prevent double labels being drawn if (!isBuildingAvailable()) { SFX.insufficientFunds(); waitForMouse = true; LabelEffect le = new LabelEffect(net.puppygames.applet.Res.getTinyFont(), Game.getMessage("ultraworm.wormgamestate.no_longer_available"), ReadableColor.WHITE, ReadableColor.CYAN, 120, 60); le.setAcceleration(0.0f, -0.003125f); le.setVelocity(0.0f, 0.5f); le.setLocation(buildEntity.getMapX() + buildEntity.getCollisionX(), buildEntity.getMapY() + buildEntity.getFeature().getBounds().getHeight()); le.spawn(GameScreen.getInstance()); setBuilding(null); waitForMouse = true; GameScreen.getInstance().enableBuildings(); return; } int cost = building.getShopValue(); if (getMoney() < cost) { SFX.insufficientFunds(); waitForMouse = true; LabelEffect le = new LabelEffect(net.puppygames.applet.Res.getTinyFont(), Game.getMessage("ultraworm.wormgamestate.insufficient_funds")+" $"+(cost - getMoney()), ReadableColor.WHITE, ReadableColor.CYAN, 30, 10); le.setAcceleration(0.0f, -0.025f); le.setVelocity(0.0f, 1.0f); le.setLocation(buildEntity.getMapX() + buildEntity.getCollisionX(), buildEntity.getMapY() + buildEntity.getFeature().getBounds().getHeight()); le.spawn(GameScreen.getInstance()); waitForMouse = true; return; } // Demolish any entities we're building on top of int damage = 0; ArrayList<Entity> ents = new ArrayList<Entity>(entities); for (Iterator<Entity> i = ents.iterator(); i.hasNext(); ) { Entity entity = i.next(); if (entity != buildEntity && entity.canBeCrushed() && entity.isTouching(buildEntity) && buildEntity.canBuildOnTopOf(entity)) { damage += entity.crush(); } } // Create the building Building b = building.build((int) buildEntity.getMapX(), (int) buildEntity.getMapY()); if (damage > 0) { b.damage(damage); } // Record the cost b.setCost(cost); // Pay the price addMoney(-cost); if (!(b.isBarricade() || b.isMineField())) { valueOfBuiltBuildings += cost; numberOfBuildingsMade ++; addStat(Stats.BUILDINGS_BUILT, 1); addStat(Stats.VALUE_OF_BUILDINGS_BUILT, cost); } LabelEffect le = new LabelEffect(net.puppygames.applet.Res.getTinyFont(), "$"+cost, ReadableColor.WHITE, ReadableColor.CYAN, 30, 10); le.setAcceleration(0.0f, -0.025f); le.setVelocity(0.0f, 1.0f); le.setLocation(buildEntity.getMapX() + buildEntity.getCollisionX(), buildEntity.getMapY() + buildEntity.getFeature().getBounds().getHeight()); le.spawn(GameScreen.getInstance()); Worm.getGameState().flagHint(Hints.DRAGBUILD); SFX.build(); if (building.getNumAvailable() != 0) { GameScreen.getInstance().enableBuildings(); } } /** * Puts us in Build Mode and sets the building that we wish to build, or cancels build mode if building is null * @param building */ public void setBuilding(BuildingFeature building) { if (buildEntity != null) { buildEntity.remove(); buildEntity = null; } if (building == null) { // Leave "build mode" mode = Mode.MODE_NORMAL; this.building = null; } else { if (mode == Mode.MODE_SMARTBOMB) { // Return the active smartbomb to the stash addPowerup(SmartbombPowerupFeature.getInstance(), false); } // Go into "build mode" mode = Mode.MODE_BUILD; this.building = building; // Create the build entity float x = quantize(GameScreen.getInstance().getMouseX() - GameScreen.getSpriteOffset().getX()), y = quantize(GameScreen.getInstance().getMouseY() - GameScreen.getSpriteOffset().getY()); buildEntity = building.ghost(quantize(x - building.getBounds().getX() - building.getBounds().getWidth() / 2 + MAP_RESOLUTION / 2), quantize(y - building.getBounds().getY() - building.getBounds().getHeight() / 2 + MAP_RESOLUTION / 2)); } waitForMouse = true; } public void setSellMode(boolean sellMode) { if (sellMode) { if (mode == Mode.MODE_SMARTBOMB) { // Cancel the smartbomb and return it to the stores addPowerup(SmartbombPowerupFeature.getInstance(), true); } else if (mode == Mode.MODE_BUILD) { setBuilding(null); } mode = Mode.MODE_SELL; } else { mode = building == null ? Mode.MODE_NORMAL : Mode.MODE_BUILD; } } public boolean isSelling() { return mode == Mode.MODE_SELL; } private void tickEndOfGame() { if (tick == END_OF_GAME_DURATION) { // Game over! This puts up teh Game Over dialog. Or goes to the title screen if we've expired the demo. if (demoExpired) { MiniGame.endGame(); } else { MiniGame.gameOver(); } } } /** * Expire the demo */ private void expireDemo() { if (demoExpired) { return; } String msg = getGameMode() == GAME_MODE_CAMPAIGN ? Game.getMessage("ultraworm.wormgamestate.completed_campaign_demo") : Game.getMessage("ultraworm.wormgamestate.completed_endless_demo"); Res.getResearchNagDialog().doModal("): DEMO EXPIRED :(", msg, new Runnable() { @Override public void run() { if (Res.getResearchNagDialog().getOption() == DialogScreen.OK_OPTION) { MiniGame.buy(true); } else { LabelEffect nagEffect = new LabelEffect(net.puppygames.applet.Res.getBigFont(), "): ): "+Game.getMessage("ultraworm.wormgamestate.demo_expired")+" :( :(", ReadableColor.YELLOW, ReadableColor.RED, 160, 240); nagEffect.setLocation(Game.getWidth() / 2, Game.getHeight() / 2 + 32); nagEffect.setVisible(true); nagEffect.spawn(GameScreen.getInstance()); nagEffect.setOffset(null); Game.getLocalPreferences().putBoolean("showregister", true); phase = PHASE_END_OF_GAME; tick = 0; demoExpired = true; } } }); } /** * @return true if player is using bezerk weapon */ public boolean isBezerk() { return bezerkTick > 0; } /** * @return true if we've got a smartbomb loaded */ public boolean isSmartbombMode() { return mode == Mode.MODE_SMARTBOMB; } public void handleESC() { if (isBuilding()) { setBuilding(null); } else if (isSelling()) { setSellMode(false); } else if (!saving && !GameScreen.getInstance().isBlocked()) { // Open ingame menu switch (getGameMode()) { case GAME_MODE_SURVIVAL: SurvivalMenuScreen.show(SurvivalMenuScreen.MENU_GAME_MODE); break; case GAME_MODE_XMAS: XmasMenuScreen.show(SurvivalMenuScreen.MENU_GAME_MODE); break; default: MenuScreen.show(MenuScreen.MENU_GAME_MODE); break; } } } /** * Tick, called every frame that the game is playing */ public void tick() { // // Check for escape to save the game and quit, or cancel building // if (Game.wasKeyPressed(Keyboard.KEY_ESCAPE)) { // } if (saving && isAlive()) { saveTick ++; if (saveTick >= SAVE_DURATION) { MiniGame.saveGame(); return; } } // Do colours & animation syncs LevelFeature lf = getLevelFeature(); if (lf == null) { return; } getLevelFeature().getColors().tick(); // If the shops open do nothing else if (GameScreen.getInstance().isBlocked() || GameScreen.getInstance().isShowingPausedHint()) { return; } tick ++; totalTicks ++; calcCurrentDifficulty(); // Tick all entities, cull dead ones tickEntities(); if (freezeTick > 0) { freezeTick --; } if (bezerkTick > 0) { bezerkTick --; } if (shieldTick > 0) { shieldTick --; } if (isAlive()) { checkMouse(); } if (Game.DEBUG) { if (Keyboard.isKeyDown(Keyboard.KEY_RSHIFT)) { try { Thread.sleep(20); } catch (Exception e) { } } if (Game.wasKeyPressed(Keyboard.KEY_M)) { metaState.money = Util.random(1, 100) * 100; // addMoney(10000); } if (Game.wasKeyPressed(Keyboard.KEY_R)) { Map<String, ResearchFeature> research = ResearchFeature.getResearch(); for (String id : research.keySet()) { setResearched(id); } GameScreen.getInstance().enableBuildings(); for (PowerupFeature pf : PowerupFeature.getPowerups()) { addPowerup(pf, false); } for (Iterator<BuildingFeature> i = BuildingFeature.getBuildings().iterator(); i.hasNext(); ) { BuildingFeature bf = i.next(); if (bf.isAvailable()) { int num = bf.getNumAvailable(); if (num > 0) { addAvailableStock(bf, num); } } } // chaz hack! powerup topup for testing //for (int i = 0; i < 5; i ++) { // addPowerup(PowerupFeature.getPowerup()); //} } if (Game.wasKeyPressed(Keyboard.KEY_PERIOD)) { Saucer saucer = new Saucer(); saucer.spawn(GameScreen.getInstance()); } // if (Worm.wasKeyPressed(Keyboard.KEY_S)) { // gameStateInterface.invulnerable(200); // } if (Game.wasKeyPressed(Keyboard.KEY_L)) { endLevel(); return; } if (Game.wasKeyPressed(Keyboard.KEY_C)) { CompleteGameScreen.show(); return; } if (Game.wasKeyPressed(Keyboard.KEY_X)) { CompleteXmasScreen.show(); return; } } switch (phase) { case PHASE_NORMAL: tickNormal(); break; case PHASE_WAIT_FOR_GIDRAHS: tickWaitForGidrahs(); break; case PHASE_END_OF_GAME: tickEndOfGame(); break; case PHASE_WAIT_A_FEW_SECONDS: tickWaitAFewSeconds(); break; default: assert false; } Entity.checkCollisions(); // Now cull inactive entities for (int i = 0; i < entities.size(); ) { Entity src = entities.get(i); if (!src.isActive()) { entities.remove(i); } else { i ++; } } updateEntities(); } /** * Clears the collision quadtree and then ticks each entity in turn, placing it back in the quadtree once it has * been ticked. */ private void tickEntities() { Unit.resetTotalThinkTime(); Gidrah.resetTotalThinkTime(); for (int i = 0; i < entities.size(); ) { Entity e = entities.get(i); if (e.isActive()) { e.tick(); } if (e.isActive()) { i ++; } else { entities.remove(i); } } } /** * Update all the entities */ private void updateEntities() { for (int i = 0; i < entities.size(); i ++ ) { Entity entity = entities.get(i); try { entity.update(); } catch (Exception ex) { System.err.println("Error updating entity "+entity); ex.printStackTrace(System.err); entity.remove(); } } } /** * Normal gameplay: make the gidrahs spawn, and the occasional saucer. */ private void tickNormal() { // Dead? Game over! if (!isAlive()) { endGame(); return; } if (Game.DEBUG) { if (Game.wasKeyPressed(Keyboard.KEY_NUMPAD1)) { forceDifficulty = true; currentDifficulty = 0.0f; } else if (Game.wasKeyPressed(Keyboard.KEY_NUMPAD2)) { forceDifficulty = true; currentDifficulty = 0.5f; } else if (Game.wasKeyPressed(Keyboard.KEY_NUMPAD3)) { forceDifficulty = true; currentDifficulty = 1.0f; } else if (Game.wasKeyPressed(Keyboard.KEY_NUMPAD0)) { forceDifficulty = false; calcCurrentDifficulty(); } else if (Game.wasKeyPressed(Keyboard.KEY_NUMPAD4)) { createSurvivalBosses(); } else if (Game.wasKeyPressed(Keyboard.KEY_NUMPAD5)) { crystalTick = 1; } else if (Game.wasKeyPressed(Keyboard.KEY_NUMPAD6)) { setBuilding((BuildingFeature) Resources.get("building.generic.tangleweb")); } else if (Game.wasKeyPressed(Keyboard.KEY_NUMPAD7)) { CompleteGameScreen.show(); return; } else if (Game.wasKeyPressed(Keyboard.KEY_NUMPAD8)) { levelTick += 3600; } } boolean xmas = getGameMode() == GAME_MODE_XMAS; // Has the level ended? if (getGameMode() == GAME_MODE_SURVIVAL) { // No, it's survival mode, and goes on forever and starts right away levelTick ++; tickSurvivalBosses(); tickSpawnPoints(); doCrystals(); doSaucers(); } else if (levelTick < getLevelDuration()) { if (beginLevel) { levelTick ++; if (xmas) { doCrystals(); } checkUnminedCrystals(); tickSpawnPoints(); doSaucers(); if (levelTick == getLevelDuration()) { aliensSpawnedAtLevelEnd = aliensSpawnedValue; rush = true; if (!xmas) { removeSpawnPoints(); } } checkInterestingThingsHappening(); // Maybe do hints HintFeature[] events = getLevelFeature().getEvents(); if (events != null) { if (events.length > eventHintSeq) { if (events[eventHintSeq].getSeconds() * 60 <= levelTick) { flagHint(events[eventHintSeq ++]); } } } } else if (getMoney() < 250) { beginLevel = true; } } else { waitForGidrahs(); } if (Game.DEBUG && tick % 240 == 0) { System.out.println(gidrahs.size()+" gidrahs, difficulty "+getDifficulty()+", valueSpawned="+aliensSpawnedValue+" vanquished="+aliensVanquishedValue); } } /** * @return true if the gidrahs have started their advance */ public boolean isLevelStarted() { return beginLevel; } private void checkUnminedCrystals() { if (unminedCrystalList.size() == 0) { unminedTick = 0; } else { unminedTick ++; if (unminedTick == UNMINED_WARNING_TIME) { Worm.getGameState().flagHint(Hints.UNMINED); unminedTick = 0; } } } /** * @return the number of unmined crystals */ public int getNumUnminedCrystals() { return unminedCrystalList.size(); } private void checkInterestingThingsHappening() { // Interesting stuff happened? somethingInterestingHappenedTick ++; if (somethingInterestingHappenedTick > INTERESTING_INTERVAL && unminedCrystalList.size() == 0 ) { flagHint(Hints.FASTFORWARD); somethingInterestingHappenedTick = 0; } } private void removeSpawnPoints() { for (Iterator<SpawnPoint> i = spawnPoints.iterator(); i.hasNext(); ) { SpawnPoint s = i.next(); s.remove(); } spawnPoints.clear(); } /** * Wait for all the gidrahs to die at the end of a level. */ private void tickWaitForGidrahs() { // No bases left? Game over! if (!isAlive()) { endGame(); return; } if (gidrahs.size() == 0) { if (getGameMode() == GAME_MODE_XMAS) { // Wait until spawnpoint is gone if (spawnPoints.size() > 0) { tickSpawnPoints(); return; } } // Wait until (all crystals are mined / factory not available) and gidrahs and bosses are dead int n = bosses.size(); for (int i = 0; i < n; i ++) { Gidrah boss = bosses.get(i); if (boss.isActive()) { return; } } bosses.clear(); // Yay! level complete. Wait a few seconds. waitAFewSeconds(); return; } else { if (Game.DEBUG && tick % 240 == 0) { System.out.println(gidrahs.size()+" gidrahs, difficulty "+getDifficulty()); } tickSpawnPoints(); checkInterestingThingsHappening(); } } private void waitAFewSeconds() { tick = 0; rush = false; phase = PHASE_WAIT_A_FEW_SECONDS; // Collect any powerups for (Saucer saucer : new ArrayList<Saucer>(saucers)) { saucer.onClicked(Mode.MODE_NORMAL); } } private void tickWaitAFewSeconds() { if (tick == WAIT_A_FEW_SECONDS_DELAY) { endLevel(); } } /** * Spawns the bosses */ private void spawnBosses() { // Spawn the boss at some of the spawnpoints. int n = 0; ArrayList<SpawnPoint> sp = new ArrayList<SpawnPoint>(spawnPoints); Collections.shuffle(sp); ResourceArray bosses = LevelFeature.getLevel(metaState.level).getBosses(); for (int i = 0; i < bosses.getNumResources() && n < sp.size(); ) { SpawnPoint bossSpawnPoint = sp.get(n ++); if (bossSpawnPoint.edge) { bossSpawnPoint.spawnBoss((GidrahFeature) bosses.getResource(i)); i ++; } } doBossWarning(bosses.getNumResources()); } private void doBossWarning(int numBosses) { // TODO: sfx LabelEffect challengeEffect = new LabelEffect(net.puppygames.applet.Res.getBigFont(), Game.getMessage("ultraworm.wormgamestate.extreme_danger"), ReadableColor.YELLOW, ReadableColor.RED, 60, 240); challengeEffect.setLocation(Game.getWidth() / 2, Game.getHeight() / 2 + 40); challengeEffect.setVisible(true); challengeEffect.spawn(GameScreen.getInstance()); challengeEffect.setOffset(null); LabelEffect challengeEffect2 = new LabelEffect(net.puppygames.applet.Res.getSmallFont(), (numBosses > 1 ? Game.getMessage("ultraworm.wormgamestate.large_enemies_approaching") : Game.getMessage("ultraworm.wormgamestate.large_enemy_approaching")), ReadableColor.YELLOW, ReadableColor.RED, 60, 240); challengeEffect2.setLocation(Game.getWidth() / 2, Game.getHeight() / 2); challengeEffect2.setVisible(true); challengeEffect2.spawn(GameScreen.getInstance()); challengeEffect2.setDelay(120); challengeEffect2.setOffset(null); } private void shutdownFactories() { boolean doSound = false; for (int i = 0; i < buildings.size(); i ++) { Building b = buildings.get(i); if (b.isActive()) { if (b instanceof Factory) { doSound |= ((Factory) b).isMining(); } b.onEndLevel(); } } if (doSound) { SFX.factoryShutdown(); } } private void waitForGidrahs() { phase = PHASE_WAIT_FOR_GIDRAHS; tick = 0; } /** * Activate a powerup * @param powerup */ public void activatePowerup(PowerupFeature powerup) { powerup.activate(gameStateInterface); } /** * Every now and again, launch a saucer. */ private void doSaucers() { saucerTick ++; if (saucerTick > nextSaucer) { saucerTick = 0; if (getGameMode() != GAME_MODE_SURVIVAL && getGameMode() != GAME_MODE_XMAS) { nextSaucer += config.getNextSaucerInterval(); } Saucer saucer = new Saucer(); saucer.spawn(GameScreen.getInstance()); } } /** * Every now and again, spawn a crystal. */ private void doCrystals() { crystalTick --; if (crystalTick <= 0) { if (survivalCrystals.size() == 0) { // Refill the shuffle bag for (int i = 0; i < 3; i ++) { survivalCrystals.add(new Integer(1)); } for (int i = 0; i < 2; i ++) { survivalCrystals.add(new Integer(2)); } for (int i = 0; i < 1; i ++) { survivalCrystals.add(new Integer(3)); } Collections.shuffle(survivalCrystals); } Integer size = survivalCrystals.remove(survivalCrystals.size() - 1); Crystal crystalDefinition = getLevelFeature().getScenery().getCrystal(size.intValue()); int width = crystalDefinition.getWidth(); int height = crystalDefinition.getHeight(); int tileX, tileY; // Choose a spot some way from the base depending on difficulty float minDistance = 10.0f; float maxDistance = getMap().getWidth() * 0.45f; float ratio = (float) Math.random(); float distance = LinearInterpolator.instance.interpolate ( CosineInterpolator.instance.interpolate(minDistance, maxDistance, ratio), SineInterpolator.instance.interpolate(minDistance, maxDistance, ratio), getDifficulty() ); double angle = Math.random() * Math.PI * 2.0; tileX = getBase().getTileX() + (int) (Math.cos(angle) * distance); tileY = getBase().getTileY() + (int) (Math.sin(angle) * distance); for (int x = 0; x < width; x ++) { for (int y = 0; y < height; y ++) { if (getMap().isOccupied(x + tileX, y + tileY)) { return; } if (getMap().isAttacking(x + tileX, y + tileY)) { return; } for (int z = 0; z < GameMap.LAYERS; z ++) { Tile t = getMap().getTile(x + tileX, y + tileY, z); if (t == null || t.isImpassable() || t.isSolid()) { return; } } } } ReadableRectangle bounds = new Rectangle(tileX * MapRenderer.TILE_SIZE, tileY * MapRenderer.TILE_SIZE, width * MapRenderer.TILE_SIZE, height * MapRenderer.TILE_SIZE); for (Iterator<Entity> i = entities.iterator(); i.hasNext(); ) { Entity entity = i.next(); if (entity.isActive() && entity.isSolid() && entity.canCollide() && entity.isTouching(bounds)) { return; } } // Write excludes for (int x = 0; x < width; x ++) { for (int y = 0; y < height; y ++) { Exclude.getInstance().toMap(getMap(), x + tileX, y + tileY, false); } } EntitySpawningFeature esf = crystalDefinition.getCrystalFeature(); esf.spawn(tileX, tileY); crystalTick = survivalNextCrystalTick * size.intValue(); survivalNextCrystalTick += getGameMode() == GAME_MODE_SURVIVAL ? config.getSurvivalCrystalIntervalAdjust() : config.getXmasCrystalIntervalAdjust(); } } /** * @return the game mode */ public int getGameMode() { return metaState.gameMode; } /** * Init for a new game */ @Override public void init() { switch (metaState.gameMode) { case GAME_MODE_CAMPAIGN: if (Worm.getMaxWorld() == 0) { SelectLevelScreen sls = (SelectLevelScreen) Resources.get("select.level.screen.0"); sls.open(); } else { SelectWorldScreen.show(); } break; case GAME_MODE_ENDLESS: SelectEndlessLevelScreen.show(); break; case GAME_MODE_SURVIVAL: SelectSurvivalLevelScreen.show(); break; case GAME_MODE_SANDBOX: SelectSandboxLevelScreen.show(); break; case GAME_MODE_XMAS: initXmas(true); break; default: assert false : "Unknown game mode "+mode; } } /** * Initialise a Christmas mode game (instead of calling {@link #doInit(int)}) */ private void initXmas(boolean reset) { System.out.println("Initialising Xmas Mode"); waitForMouse = true; xmasReset = reset; if (reset) { metaState = null; metaState = new MetaState(GAME_MODE_XMAS); } else { metaState.reset(); } GameScreen.beginGame(this); beginLevel(); initXmasResearch(); doInitMessages(Game.getMessage("ultraworm.wormgamestate.init_xmas1"), Game.getMessage("ultraworm.wormgamestate.init_xmas2")); } /** * Initialise a survival mode game (instead of calling {@link #doInit(int)}) */ private void initSurvival(boolean reset) { System.out.println("Initialising Survival Mode"); waitForMouse = true; if (reset) { metaState = null; metaState = new MetaState(GAME_MODE_SURVIVAL); } else { metaState.reset(); } GameScreen.beginGame(this); beginLevel(); initSurvivalResearch(); doInitMessages(Game.getMessage("ultraworm.wormgamestate.init_survival1"), Game.getMessage("ultraworm.wormgamestate.init_survival2")); } /** * Initialise a sandbox mode game (instead of calling {@link #doInit(int)}) */ private void initSandbox() { System.out.println("Initialising Sandbox Mode"); waitForMouse = true; metaState = null; metaState = new MetaState(GAME_MODE_SANDBOX); GameScreen.beginGame(this); beginLevel(); //initSurvivalResearch(); //doInitMessages(Game.getMessage("ultraworm.wormgamestate.init_sandbox1"), Game.getMessage("ultraworm.wormgamestate.init_sandbox2")); doInitMessages("SANDBOX TEST", "No really, just a test!"); } /** * Begin a new game at the specified level * @param level */ public void doInit(int level) { System.out.println("DoInit: "+level); waitForMouse = true; if (level == 0) { // Research the default researchy things metaState = new MetaState(metaState.gameMode); initDefaultResearch(); } else { // Load research and powerups etc we had last time try { metaState = MetaState.load(level, metaState.gameMode); } catch (Exception e) { e.printStackTrace(System.err); metaState = new MetaState(metaState.gameMode); initDefaultResearch(); } } GameScreen.beginGame(this); if (Game.isRegistered()) { awardMedal(Medals.REGISTERED); } beginLevel(); doInitMessages(Game.getMessage("ultraworm.wormgamestate.init1"), Game.getMessage("ultraworm.wormgamestate.init2")); } /** * Begin a Survival game with the specified characteristics */ public void doInit(SurvivalParams survivalParams) { this.survivalParams = survivalParams; System.out.println("DoInit: "+survivalParams); initSurvival(survivalParams.getGenerateNew()); if (Game.isRegistered()) { awardMedal(Medals.REGISTERED); } } /** * @return the survival parameters, if we're in survival mode */ public SurvivalParams getSurvivalParams() { return survivalParams; } private void initDefaultResearch() { for (ResearchFeature rf : ResearchFeature.getResearch().values()) { if (rf.isDefaultAvailable()) { setResearched(rf.getID()); } } } private void initSurvivalResearch() { MetaState tempMetaState; try { tempMetaState = MetaState.load(survivalParams.getWorld().getIndex() * LEVELS_IN_WORLD + LEVELS_IN_WORLD, GAME_MODE_CAMPAIGN); for (String s : tempMetaState.researched) { setResearched(s); } } catch (Exception e) { e.printStackTrace(System.err); initDefaultResearch(); return; } } private void initXmasResearch() { for (String s : Xmas.RESEARCH) { setResearched(s); } } /** * Begin a Sandbox game with the specified characteristics */ public void doInit(SandboxParams sandboxParams) { this.sandboxParams = sandboxParams; System.out.println("DoInit: "+sandboxParams); initSandbox(); } /** * We've completed the game! */ private void completeGame() { if (getGameMode() == GAME_MODE_XMAS) { CompleteXmasScreen.show(); } else { CompleteGameScreen.show(); } } /** * Go to the next level. If we run out of levels, we complete the game. */ public void nextLevel() { metaState.level ++; if (getGameMode() == GAME_MODE_XMAS) { completeGame(); return; } if (getGameMode() == GAME_MODE_ENDLESS || getLevel() < MAX_LEVELS) { beginLevel(); } else { completeGame(); } } /** * Initialise a level. We need to load the game map, remove any entities, spawn the buildings, make a note of the spawn points on * the map, etc. */ public void beginLevel() { System.out.println("Beginning level "+metaState.gameMode+"/"+metaState.level); WorldFeature newWorld; StoryFeature[] sf; reset(); switch (metaState.gameMode) { case GAME_MODE_CAMPAIGN: // Set the level metaState.levelFeature = LevelFeature.getLevel(metaState.level); // We might have changed worlds newWorld = getLevelFeature().getWorld(); if (newWorld != getWorld()) { setWorld(newWorld); return; } currentStoryIndex = 0; sf = getLevelFeature().getStories(); currentStories = new ArrayList<StoryFeature>(sf.length); for (StoryFeature element : sf) { if (element.qualifies()) { currentStories.add(element); } } suppressMedals = false; showStoryScreen(); break; case GAME_MODE_ENDLESS: // Generate a random level metaState.levelFeature = LevelFeature.generateEndless(metaState.level); // We might have changed worlds newWorld = getLevelFeature().getWorld(); if (newWorld != getWorld()) { setWorld(newWorld); } currentStoryIndex = 0; sf = getLevelFeature().getStories(); currentStories = new ArrayList<StoryFeature>(sf.length); for (StoryFeature element : sf) { if (element.qualifies()) { currentStories.add(element); } } suppressMedals = false; showStoryScreen(); break; case GAME_MODE_SURVIVAL: if (survivalParams.getGenerateNew()) { // Generate a random level metaState.levelFeature = LevelFeature.generateSurvival(survivalParams); } else { // Use last level } metaState.difficulty = survivalParams.getDifficulty(); metaState.survivalParams = survivalParams; metaState.reset(); // We might have changed worlds newWorld = getLevelFeature().getWorld(); if (newWorld != getWorld()) { setWorld(newWorld); } currentStoryIndex = 0; sf = getLevelFeature().getStories(); currentStories = new ArrayList<StoryFeature>(sf.length); for (StoryFeature element : sf) { if (element.qualifies()) { currentStories.add(element); } } suppressMedals = false; showStoryScreen(); break; case GAME_MODE_SANDBOX: // Generate a random level metaState.levelFeature = LevelFeature.generateEndless(metaState.level); // We might have changed worlds newWorld = getLevelFeature().getWorld(); if (newWorld != getWorld()) { setWorld(newWorld); } suppressMedals = true; beginLevel2(); break; case GAME_MODE_XMAS: // Generate a random level metaState.levelFeature = LevelFeature.generateXmas(); metaState.difficulty = 0.0f; metaState.reset(); // We might have changed worlds newWorld = getLevelFeature().getWorld(); if (newWorld != getWorld()) { setWorld(newWorld); } currentStoryIndex = 0; sf = getLevelFeature().getStories(); currentStories = new ArrayList<StoryFeature>(sf.length); for (StoryFeature element : sf) { if (element.qualifies()) { currentStories.add(element); } } suppressMedals = false; showStoryScreen(); break; default: assert false : "Unknown game mode "+metaState.gameMode; } } private void nextStoryScreen() { if (++currentStoryIndex >= currentStories.size()) { StoryScreen.tidyUp("story.screen."+getWorld().getUntranslated()); // Open research screen, unless this is level 0 or survival mode or sandbox mode, in which case jump straight into the action if (getLevel() == 0 || getGameMode() == GAME_MODE_SURVIVAL || getGameMode() == GAME_MODE_SANDBOX) { beginLevel2(); } else { showResearchScreen(); } } else { // Open next story screen showStoryScreen(); } } private void previousStoryScreen() { if (--currentStoryIndex == -1) { switch (metaState.gameMode) { case GAME_MODE_CAMPAIGN: // Go to level select screen of current world StoryScreen.tidyUp("story.screen."+getWorld().getUntranslated()); showLevelSelectScreen(); break; case GAME_MODE_ENDLESS: case GAME_MODE_SURVIVAL: showLevelSelectScreen(); break; case GAME_MODE_SANDBOX: showLevelSelectScreen(); break; case GAME_MODE_XMAS: net.puppygames.applet.screens.TitleScreen.show(); break; default: assert false : "Shouldn't be here: "+metaState.gameMode; } } else { // Open previous story showStoryScreen(); } } public void showLevelSelectScreen() { switch (metaState.gameMode) { case GAME_MODE_CAMPAIGN: SelectLevelScreen sls = (SelectLevelScreen) Resources.get("select.level.screen."+metaState.level / LEVELS_IN_WORLD); sls.open(); break; case GAME_MODE_ENDLESS: SelectEndlessLevelScreen.show(); break; case GAME_MODE_SURVIVAL: SelectSurvivalLevelScreen.show(); break; case GAME_MODE_SANDBOX: SelectSandboxLevelScreen.show(); break; case GAME_MODE_XMAS: net.puppygames.applet.screens.TitleScreen.show(); break; default: assert false : "Shouldn't be here: "+metaState.gameMode; } } public void showResearchScreen() { ResearchScreen.show ( new Runnable() { @Override public void run() { previousStoryScreen(); } }, new Runnable() { @Override public void run() { Worm.getGameState().beginLevel2(); } } ); } private void showStoryScreen() { StoryScreen.show ( "story.screen."+getWorld().getUntranslated(), currentStoryIndex == 0, currentStories.get(currentStoryIndex), new Runnable() { @Override public void run() { previousStoryScreen(); } }, new Runnable() { @Override public void run() { nextStoryScreen(); } } ); } public void setMap(GameMap newMap) { this.map = newMap; } /** * Reinforcements! */ private void reinforcements() { for (Iterator<BuildingFeature> i = BuildingFeature.getBuildings().iterator(); i.hasNext(); ) { BuildingFeature bf = i.next(); if (bf.isAvailable()) { int num = bf.getNumAvailable(); if (num > 0) { addAvailableStock(bf, num); } } } } /** * Begin the level with the specified map. */ public void beginLevel2() { // Increase available stock of everything (barricades and mines) reinforcements(); // Ensure all entities are removed first for (Iterator<Entity> i = entities.iterator(); i.hasNext(); ) { Entity entity = i.next(); entity.remove(); } entities.clear(); // Init gids stuff at start of level Gidrah.init(); phase = PHASE_NORMAL; rush = false; levelTick = 0; tick = 0; beginLevel = !isResearched(ResearchFeature.FACTORY) || getMoney() < 250 || metaState.gameMode == GAME_MODE_SURVIVAL || metaState.gameMode == GAME_MODE_XMAS; nextSaucer = config.getSaucerInterval(); System.out.println("MODE: "+metaState.gameMode+"\nPHASE: "+phase); // Reset buffs scannerBoost = 0; batteryBoost = 0; reactorBoost = 0; coolingBoost = 0; shieldBoost = isResearched(ResearchFeature.NANOHARDENING) ? 1 : 0; capacitorBoost = isResearched(ResearchFeature.IONISATION) ? 1 : 0; // Reset counters factories = 0; warehouses = 0; autoloaders = 0; shieldGenerators = 0; spawners = 0; crystals = 0; initialCrystals = 0; // Reset powerups bezerkTick = 0; shieldTick = 0; freezeTick = 0; // Reset buildings value anyDamaged = false; valueOfBuiltBuildings = 0; valueOfDestroyedBuildings = 0; // Reset aliens spawned value aliensVanquishedValue = 0; aliensSpawnedValue = 0; // Clear medals earned this level medalsThisLevel.clear(); awesome = false; // Remember starting money startingMoney = metaState.money; attempts = Worm.getExtraLevelData(Game.getPlayerSlot(), metaState.level, metaState.gameMode, "attempts", 0); System.out.println("Number of attempts at this level so far: "+attempts); // Adjust difficulty by number of attempts calcBasicDifficulty(); calcPowerupDifficulty(); System.out.println("Base difficulty "+metaState.difficulty); // How long's the level? calcLevelDuration(map.getWidth(), map.getHeight()); // Clear everything out unminedCrystalList.clear(); Entity.reset(); System.gc(); // Sort out the map initMap(); // Open the game screen GameScreen.onBeginLevel(); // Maybe spawn bosses ResourceArray bosses = metaState.levelFeature.getBosses(); if (bosses != null && bosses.getNumResources() > 0) { spawnBosses(); } // Reset event sequence eventHintSeq = 0; leftMouseWasDown = true; waitForMouse = true; // Research medals if (metaState.gameMode != GAME_MODE_SURVIVAL && metaState.gameMode != GAME_MODE_XMAS) { Map<String, List<String>> medalGroups = ResearchFeature.getMedalGroups(); for (Entry<String, List<String>> entry : medalGroups.entrySet()) { String medal = entry.getKey(); List<String> researchRequired = entry.getValue(); synchronized (metaState.researched) { if (metaState.researched.containsAll(researchRequired)) { awardMedal(medal); } } } } } /** * Calculates base difficulty and research difficulty */ public void calcBasicDifficulty() { if (metaState.gameMode == GAME_MODE_SURVIVAL) { metaState.difficulty = survivalParams.getDifficulty(); return; } else if (metaState.gameMode == GAME_MODE_XMAS) { metaState.difficulty = 0.0f; return; } int diff = getDifficultyAdjust(getLevel(), getGameMode()); metaState.difficulty = diff * config.getDifficultyAdjustmentFactor(); // Central base levels - offset difficulty float offset = BaseMapGenerator.isBaseCentralForLevel(metaState.level, metaState.gameMode) ? config.getCentralDifficultyAdjustPerLevel() * metaState.level : 0.0f; // Endless mode: gets a bit harder every level after level 50... if (metaState.gameMode == GAME_MODE_ENDLESS && metaState.level > MAX_LEVELS) { offset -= (metaState.level - MAX_LEVELS) * config.getEndlessDifficultyCreep(); } metaState.difficulty -= offset; System.out.println("BASIC DIFFICULTY "+getBasicDifficulty()+" (was offset by "+offset+")"); } /** * @return the basic level difficulty (≥ 0.0f) */ public float getBasicDifficulty() { return Math.max(0.0f, metaState.difficulty + (config.getBankFactor() * getMoney()) / (config.getDifficultyFactor() + config.getDifficultyFactorPerLevel() * getLevel())); } /** * Initialise the map generated in the story screen */ private void initMap() { // Clear away the old spawn points spawnPoints.clear(); // Have we been here before? MapProcessor processor = new MapProcessor() { @Override public void addSpawnPoint(int x, int y, int type, boolean edge) { SpawnPoint sp = new SpawnPoint(x, y, type, edge); assert !spawnPoints.contains(sp); spawnPoints.add(sp); } @Override public void createBase(int x, int y) { getWorld().getBase().build(x * MapRenderer.TILE_SIZE, y * MapRenderer.TILE_SIZE); } @Override public void spawnEntity(EntitySpawningFeature feature, int tileX, int tileY) { // Spawn the entity... Entity e = feature.spawn(tileX, tileY); e.update(); // Remove from the map? if (feature.removeAfterSpawn()) { getMap().clearItem(tileX, tileY); } } }; // Process the map one tile at a time. for (int y = 0; y < map.getHeight(); y ++) { for (int x = 0; x < map.getWidth(); x ++) { for (int z = 0; z < GameMap.LAYERS; z ++) { Tile t = map.getTile(x, y, z); t.process(processor, x, y); } } } // Survival mode: create spawn point. if (getGameMode() == GAME_MODE_SURVIVAL) { createSurvivalSpawnPoint(); } } private ReadablePoint getEdgePoint() { int edge = getGameMode() == GAME_MODE_XMAS ? 0 : Util.random(0, 9); // Bias away from south: only 1/10 edges are picked from south boolean blocked; int x, y, ox, oy, count = 0; do { count ++; blocked = false; switch (edge) { case 0: // North case 1: case 2: x = Util.random(1, getMap().getWidth() - 2); y = getMap().getHeight() - 1; ox = 0; oy = 1; break; case 3: // East case 4: case 5: y = Util.random(1, getMap().getHeight() - 2); x = getMap().getWidth() - 1; ox = 1; oy = 0; break; case 6: // West case 7: case 8: y = Util.random(1, getMap().getHeight() - 2); x = 0; ox = -1; oy = 0; break; case 9: // South x = Util.random(1, getMap().getWidth() - 2); y = 0; ox = 0; oy = -1; break; default: assert false; return new Point(); } for (int z = 0; z < GameMap.LAYERS; z ++) { Tile t = getMap().getTile(x, y, z); if (t.isImpassable() || t.isSolid()) { blocked = true; break; } } } while (blocked && count < 50); return new Point(x + ox, y + oy); } /** * Creates a brand new survival mode spawnpoint by finding an empty tile on the edge of the map. */ private void createSurvivalSpawnPoint() { ReadablePoint edgePoint = getEdgePoint(); if (edgePoint == null) { return; } spawnPoints.add(new SpawnPoint(edgePoint.getX(), edgePoint.getY(), 0, true)); } /** * Hack for Earth-only survival / xmas bosses * @param n * @return */ private static String survivalBoss(int n) { StringBuilder sb = new StringBuilder(n); for (int i = 0; i < n; i ++) { sb.append('0'); } return sb.toString(); } /** * Spawns survival mode bosses */ private void createSurvivalBosses() { // Get best world player has seen... int bestWorldIndex = Math.min(WorldFeature.getNumWorlds() - 1, Worm.getMaxLevel(GAME_MODE_CAMPAIGN) / LEVELS_IN_WORLD); int numBossTypes = Math.min(WorldFeature.getWorld(bestWorldIndex).getSurvivalMaxBoss() + 1, Res.getNumSurvivalBosses()); String s = numBossTypes == 1 ? survivalBoss(survivalBoss) : Integer.toString(survivalBoss, numBossTypes); for (int i = 0; i < s.length(); i ++) { ReadablePoint edgePoint = getEdgePoint(); GidrahFeature bossFeature = Res.getSurvivalBoss(s.charAt(i) - '0'); if (bossFeature != null) { bossFeature.spawn(GameScreen.getInstance(), edgePoint.getX(), edgePoint.getY(), 0); } } survivalBoss ++; doBossWarning(s.length()); reinforcements(); } /** * @return the current map */ public GameMap getMap() { return map; } private void setWorld(WorldFeature newWorld) { assert newWorld != null; if (metaState.world == newWorld) { return; } metaState.world = newWorld; // Put up a New World screen in campaign mode if (metaState.gameMode == GAME_MODE_CAMPAIGN) { NewWorldScreen nw = (NewWorldScreen) Resources.get("newworld.screen."+getWorld().getUntranslated()); nw.open(); } } private void doInitMessages(String big, String small) { LabelEffect newLevelEffect = new LabelEffect(net.puppygames.applet.Res.getBigFont(), big, ReadableColor.WHITE, ReadableColor.RED, 40, 160); newLevelEffect.setLocation(Game.getWidth() / 2, Game.getHeight() / 2 + 60); newLevelEffect.setVisible(true); newLevelEffect.spawn(GameScreen.getInstance()); newLevelEffect.setOffset(null); LabelEffect hintEffect = new LabelEffect(net.puppygames.applet.Res.getSmallFont(), small, ReadableColor.WHITE, ReadableColor.RED, 40, 160); hintEffect.setDelay(30); hintEffect.setLocation(Game.getWidth() / 2, Game.getHeight() / 2 + 20); hintEffect.setVisible(true); hintEffect.spawn(GameScreen.getInstance()); hintEffect.setOffset(null); } @Override public void reinit() { GameScreen.beginGame(this); doInitMessages(Game.getMessage("ultraworm.wormgamestate.reinit1"), Game.getMessage("ultraworm.wormgamestate.reinit2")); waitForMouse = true; if (mode == Mode.MODE_BUILD) { setBuilding(building); } // Process entities for (Iterator<Entity> i = entities.iterator(); i.hasNext(); ) { Entity entity = i.next(); entity.respawn(GameScreen.getInstance()); } // And spawnpoints for (Iterator<SpawnPoint> i = spawnPoints.iterator(); i.hasNext(); ) { SpawnPoint sp = i.next(); sp.reinit(); } GameScreen.onBeginLevel(); if (!isAlive()) { MiniGame.endGame(); } } /** * @return Returns the entities. */ public ArrayList<Entity> getEntities() { return entities; } /** * @return Returns the buildings */ public ArrayList<Building> getBuildings() { return buildings; } /** * @return Returns the gidrahs */ public ArrayList<Gidrah> getGidrahs() { return gidrahs; } /** * @return all the saucers */ public ArrayList<Saucer> getSaucers() { return saucers; } /** * Called when it's game over time */ public void onGameOver() { if (phase == PHASE_END_OF_GAME) { return; } phase = PHASE_END_OF_GAME; tick = 0; // Stop building setBuilding(null); } /** * Freeze the gidrahs! * @param duration */ public void freeze(int duration) { freezeTick += duration; GameScreen.getInstance().onFreezeTimerIncreased(freezeTick); } /** * Called whenever a gidrah is killed. This removes the gidrah from the gidrahs list, * updates stats, and awards medals appropriately * @param gidrah The gidrah that was killed * @param causeOfDeath For stats and medals purposes */ public void onGidrahKilled(Gidrah gidrah, int causeOfDeath) { gidrahs.remove(gidrah); switch (causeOfDeath) { case CauseOfDeath.ATTACK: return; case CauseOfDeath.DISRUPTOR: case CauseOfDeath.BULLET: case CauseOfDeath.LASER: metaState.addStat(Stats.ALIENS_SHOT, 1); break; case CauseOfDeath.CAPACITOR: int fried = metaState.addStat(Stats.ALIENS_FRIED, 1); if (fried == 100) { awardMedal(Medals.FRIED_100); } else if (fried == 250) { awardMedal(Medals.FRIED_250); } else if (fried == 500) { awardMedal(Medals.FRIED_500); } break; case CauseOfDeath.CRUSHED: int crushed = metaState.addStat(Stats.ALIENS_CRUSHED, 1); if (crushed == 25) { awardMedal(Medals.CRUSHED_25); } else if (crushed == 50) { awardMedal(Medals.CRUSHED_50); } else if (crushed == 100) { awardMedal(Medals.CRUSHED_100); } break; case CauseOfDeath.EXPLOSION: int exploded = metaState.addStat(Stats.ALIENS_BLOWN_UP, 1); if (exploded == 100) { awardMedal(Medals.EXPLODED_100); } else if (exploded == 250) { awardMedal(Medals.EXPLODED_250); } else if (exploded == 500) { awardMedal(Medals.EXPLODED_500); } break; case CauseOfDeath.SMARTBOMB: int nuked = metaState.addStat(Stats.ALIENS_SMARTBOMBED, 1); if (nuked == 50) { awardMedal(Medals.NUKED_50); } else if (nuked == 100) { awardMedal(Medals.NUKED_100); } else if (nuked == 250) { awardMedal(Medals.NUKED_250); } break; case CauseOfDeath.GRID_BUG: // Bah return; default: assert false : "Unknown cause of death "+causeOfDeath; break; } int vanquished = metaState.addStat(Stats.ALIENS_VANQUISHED, 1); if (vanquished == 100) { awardMedal(Medals.VANQUISHED_100); } else if (vanquished == 250) { awardMedal(Medals.VANQUISHED_250); } else if (vanquished == 500) { awardMedal(Medals.VANQUISHED_500); } else if (vanquished == 1000) { awardMedal(Medals.VANQUISHED_1000); } else if (vanquished == 2500) { awardMedal(Medals.VANQUISHED_2500); } else if (vanquished == 5000) { awardMedal(Medals.VANQUISHED_5000); } else if (vanquished == 10000) { awardMedal(Medals.VANQUISHED_10000); } // Survival mode: gidrah deaths unlocks more gidrahs if (getGameMode() == GAME_MODE_SURVIVAL) { if (gidrah.getFeature().isBoss() || gidrah.getFeature().isGidlet()) { // Not bosses or gidlets } else { int type = gidrah.getType(); survivalGidrahKills[type] += gidrah.getFeature().isAngry() ? 10 : 1; if (survivalGidrahKills[type] >= survivalGidrahUnlockNext[type]) { int bestWorldIndex = Math.min(WorldFeature.getNumWorlds() - 1, Worm.getMaxLevel(GAME_MODE_CAMPAIGN) / LEVELS_IN_WORLD); if (type < survivalGidrahUnlock.length - 2 && survivalGidrahUnlock[type + 1] == 0) { // Unlock next type first. If there is one. int max = WorldFeature.getWorld(bestWorldIndex).getSurvivalMaxType(type + 1); if (max != -1) { survivalGidrahUnlock[type + 1] = 1; } else { metaState.difficulty += config.getSurvivalDifficultyAdjuster(); } } else { int max = WorldFeature.getWorld(bestWorldIndex).getSurvivalMaxType(type); if (max > survivalGidrahUnlock[type]) { survivalGidrahUnlock[type] ++; } else { metaState.difficulty += config.getSurvivalDifficultyAdjuster();; } } survivalGidrahUnlockNext[type] += survivalGidrahUnlockNext[type] + config.getSurvivalGidrahUnlockIntervalAdjust()[type]; } } } } /** * Marks a building type as having been researched * @param type */ public void setResearched(String type) { synchronized (metaState.researched) { if (metaState.researched.add(type)) { (ResearchFeature.getResearch().get(type)).onResearched(); } } } /** * Marks a building type as having NOT been researched * @param type */ public void setUnresearched(String type) { synchronized (metaState.researched) { if (metaState.researched.remove(type)) { (ResearchFeature.getResearch().get(type)).onUnresearched(); } } } /** * Is the specified ResearchItem researched? * @param type * @return */ public boolean isResearched(String type) { return metaState.researched.contains(type); } /** * @return */ public boolean isBuilding() { return building != null; } /** * @return the building we're building, or null, if we're not building */ public BuildingFeature getBuilding() { return building; } /** * @return the cost of the current building */ public int getBuildingCost() { return building.getShopValue(); } /** * @return true if the building we're trying to build is still available to build */ public boolean isBuildingAvailable() { return building.isAvailable() && building.isEnabledInShop(); } /** * @return the name of the current building */ public String getBuildingName() { return building.getTitle(); } /** * @return current level index, 0 based */ public int getLevel() { return metaState.level; } /** * @return the level in the world (0..LEVELS_IN_WORLD or so) */ public int getLevelInWorld() { return metaState.level % LEVELS_IN_WORLD; } /** * @return current world feature */ public WorldFeature getWorld() { return metaState.world; } /** * @return current level feature */ public LevelFeature getLevelFeature() { return metaState.levelFeature; } /** * Calculates and returns a difficulty value based on level number in the world. * @return a float value between 0.0f (very easy) and 1.0f (very hard) */ public float getDifficulty() { return currentDifficulty; } private void calcCurrentDifficulty() { if (forceDifficulty) { System.out.println("Difficulty forced to "+currentDifficulty); return; } float ret = 0.0f; float attenuate = 1.0f; float irate = 0.0f; boolean survival = getGameMode() == GAME_MODE_SURVIVAL; boolean xmas = getGameMode() == GAME_MODE_XMAS; // And then attenuate this difficulty by how badly the player is being kicked in: if (!survival && !xmas && valueOfDestroyedBuildings > 0 && valueOfBuiltBuildings > config.getBuiltBuildingsValueThreshold()) { float slaughterAttenuation = 1.0f; // Attenuate the attenuation by the aliens losses when level is ended if (!isLevelActive() && aliensSpawnedAtLevelEnd > 0 && aliensVanquishedSinceEndOfLevel > 0) { slaughterAttenuation = 1.0f - Math.min(1.0f, Math.max(0.0f, (float) aliensVanquishedSinceEndOfLevel / (float ) aliensSpawnedAtLevelEnd)); } attenuate = 1.0f - Math.min(1.0f, Math.max(0.0f, (float) valueOfDestroyedBuildings / (float ) valueOfBuiltBuildings)) * slaughterAttenuation; } // Calculate total money in-play float total = getMoney() * config.getBankFactor(); for (int i = buildings.size(); -- i >= 0; ) { Building b = buildings.get(i); if (b.isAlive()) { total += b.getCost(); irate += b.getAgitation(); } } // For every $DIFFICULTY_FACTOR in play add 1.0 difficulty switch (getGameMode()) { case GAME_MODE_SURVIVAL: total += valueOfDestroyedBuildings; // It never gets easier :) ret += total / config.getSurvivalModeDifficultyFactors()[survivalParams.getWorld().getIndex()]; break; case GAME_MODE_XMAS: total += valueOfDestroyedBuildings; ret += total / config.getXmasDifficultyFactor(); break; default: ret += total / (config.getDifficultyFactor() + config.getDifficultyFactorPerLevel() * getLevel()); ret *= attenuate; break; } // Add difficulty for powerups... currentDifficulty += powerupDifficulty * attenuate; // Finally, adjust by base difficulty offset float attemptsDifficulty = Worm.getAutoDifficulty() ? config.getDifficultyAttempts()[Math.min(config.getDifficultyAttempts().length -1, attempts)] : 0.0f; currentDifficulty = Math.max(0.0f, irate + ret + metaState.difficulty + attemptsDifficulty); } public int getResearchHash() { return metaState.researched.hashCode(); } /** * Kill the player */ public void kill() { alive = false; } /** * Quit the game */ public void quit() { MiniGame.endGame(); } private void reset() { alive = true; rush = false; mode = Mode.MODE_NORMAL; beginLevel = false; startingMoney = 500; somethingInterestingHappenedTick = 0; crystals = 0; for (int i = entities.size(); --i >= 0; ) { (entities.get(i)).remove(); } entities.clear(); for (int i = spawnPoints.size(); --i >= 0; ) { (spawnPoints.get(i)).remove(); } spawnPoints.clear(); gidrahs.clear(); bosses.clear(); units.clear(); buildings.clear(); saucers.clear(); medalsThisLevel.clear(); armedCapacitors.clear(); unminedCrystalList.clear(); tick = 0; totalTicks = 0; saucerTick = 0; levelTick = 0; bezerkTick = 0; survivalSpawnPointTick = 0; crystalTick = 0; survivalCrystals.clear(); survivalNextBossTick = config.getSurvivalBossInterval(); survivalNextCrystalTick = 0; survivalNextSpawnPointTick = 0; survivalSpawnPointInterval = config.getSurvivalSpawnpointSpawnInterval(); numSurvivalSpawnPoints = 1; survivalGidrahUnlock[0] = 1; survivalGidrahUnlock[1] = 0; survivalGidrahUnlock[2] = 0; survivalGidrahUnlock[3] = 0; survivalGidrahUnlockNext[0] = config.getSurvivalGidrahUnlockInterval()[0]; survivalGidrahUnlockNext[1] = config.getSurvivalGidrahUnlockInterval()[1]; survivalGidrahUnlockNext[2] = config.getSurvivalGidrahUnlockInterval()[2]; survivalGidrahUnlockNext[3] = config.getSurvivalGidrahUnlockInterval()[3]; survivalGidrahKills[0] = 0; survivalGidrahKills[1] = 0; survivalGidrahKills[2] = 0; survivalGidrahKills[3] = 0; survivalBoss = 0; survivalBossTick = 0; GameScreen.getInstance().removeAllTickables(); } /** * Restart the level */ public void restart() { if (metaState.gameMode != GAME_MODE_SURVIVAL && metaState.gameMode != GAME_MODE_XMAS) { int attempts = Worm.getExtraLevelData(Game.getPlayerSlot(), metaState.level, metaState.gameMode, "attempts", 0); Worm.setExtraLevelData(metaState.level, metaState.gameMode, "attempts", attempts + 1); } reset(); doInit(metaState.level); } private void removeCrystals() { // Remove crystals properly so the exclude tiles are removed for (Building b : buildings) { if (b instanceof CrystalResource) { ((CrystalResource) b).clearMap(); } } } public void restartSurvival(boolean generateNew) { removeCrystals(); reset(); survivalParams.setGenerateNew(generateNew); doInit(survivalParams); } public void restartXmas(boolean generateNew) { removeCrystals(); reset(); initXmas(generateNew); } /** * Make the current level easier next time it's played */ public void easier() { int diff = getDifficultyAdjust(metaState.level, metaState.gameMode); setDifficultyAdjust(metaState.level, metaState.gameMode, diff + 1); if (metaState.gameMode == GAME_MODE_CAMPAIGN || metaState.gameMode == GAME_MODE_ENDLESS) { // Reset attempts count Worm.setExtraLevelData(metaState.level, metaState.gameMode, "attempts", 0); } } /** * Save and quit */ public void save() { saving = true; saveTick = 0; LabelEffect saveEffect = new LabelEffect(net.puppygames.applet.Res.getBigFont(), Game.getMessage("ultraworm.wormgamestate.saving_game"), new MappedColor("gui-bright"), new MappedColor("gui-dark"), SAVE_DURATION / 2, SAVE_DURATION / 2); saveEffect.setLocation(Game.getWidth() / 2, Game.getHeight() / 2); saveEffect.setVisible(true); saveEffect.spawn(GameScreen.getInstance()); saveEffect.setOffset(null); } public int getCapacitorBoost() { return capacitorBoost; } public int getBatteryBoost() { return batteryBoost; } public int getShieldBoost() { return shieldBoost; } public int getReactorBoost() { return reactorBoost; } public int getScannerBoost() { return scannerBoost; } public int getCoolingBoost() { return coolingBoost; } public void flagHint(String hintName) { if (getGameMode() == GAME_MODE_SURVIVAL) { // No hints in survival mode return; } HintFeature hint = HintFeature.getHint(hintName); if (hint == null) { return; } flagHint(hint); } private void flagHint(HintFeature hintFeature) { if (hintFeature.getMinLevel() > 0 && metaState.level <= hintFeature.getMinLevel()) { return; } if (hintFeature.getMaxLevel() > 0 && metaState.level > hintFeature.getMaxLevel()) { return; } GameScreen.getInstance().showHint(hintFeature); } public int getHintSequence(HintFeature hintFeature) { if (hintFeature.getText() != null) { // It's a medal hint return 0; } Integer seq = hintMap.get(hintFeature); if (seq == null) { seq = new Integer(0); } int seqValue = seq.intValue(); if (seqValue == -1) { // Hint suppressed return -1; } if (hintFeature.isRandom()) { return Util.random(0, hintFeature.getHints().getNumResources() - 1); } if (seqValue == hintFeature.getHints().getNumResources()) { // No hints left return -1; } hintMap.put(hintFeature, new Integer(seqValue + 1)); return seqValue; } public void suppressHint(String hint) { HintFeature hint2 = HintFeature.getHint(hint); if (hint2 == null) { System.out.println("Hint "+hint+" not defined"); return; } suppressHint(hint2); } public void suppressHint(HintFeature hintFeature) { hintMap.put(hintFeature, new Integer(-1)); GameScreen.getInstance().dequeueHint(hintFeature); } /** * @return the number of active Units */ public int getNumUnits() { return units.size(); } /** * Called when a building is destroyed by aliens * @param b */ public void onBuildingDestroyed(Building b, boolean deliberate) { if (b.isWorthAttacking()) { if (!deliberate) { valueOfDestroyedBuildings += b.getFeature().getInitialValue(); numberOfBuildingsDestroyed ++; if (b.isCity()) { GameScreen.instance.zoom(b.getMapX() + b.getCollisionX() - Game.getWidth() / 2, b.getMapY() + b.getCollisionY() - Game.getHeight() / 2); } int totalBuildingsDestroyed = addStat(Stats.BUILDINGS_DESTROYED, 1); if (totalBuildingsDestroyed == 10) { awardMedal(Medals.CARELESS); } else if (totalBuildingsDestroyed == 25) { awardMedal(Medals.RASH); } else if (totalBuildingsDestroyed == 50) { awardMedal(Medals.RECKLESS); } else if (totalBuildingsDestroyed == 100) { awardMedal(Medals.NEGLIGENT); } addStat(Stats.VALUE_OF_BUILDINGS_DESTROYED, b.getFeature().getInitialValue()); } else { valueOfBuiltBuildings -= b.getFeature().getInitialValue(); } } } /** * Called when any building worth attacking takes actual damage */ public void onBuildingDamaged() { anyDamaged = true; } /** * Is the level still active? This is the case when we're in {@link #PHASE_NORMAL} phase and either it's a survival mode game * (the level is always active), or the timer's still timing. * @return true if the level is still active */ public boolean isLevelActive() { return phase == PHASE_NORMAL && (metaState.gameMode == GAME_MODE_SURVIVAL || levelTick < getLevelDuration()); } public Building getBase() { return base; } public Building getNextUnminedCrystal() { if (unminedCrystalList.size() == 0) { return null; } Building ret = unminedCrystalList.remove(0); unminedCrystalList.add(ret); return ret; } public void addUnminedCrystal(Building crystal) { unminedCrystalList.add(crystal); } public void removeUnminedCrystal(Building crystal) { unminedCrystalList.remove(crystal); } public int getBezerkTick() { return bezerkTick; } public int getFreezeTick() { return freezeTick; } public int getShieldTick() { return shieldTick; } public int getAvailableStock(BuildingFeature bf) { return metaState.getAvailableStock(bf); } public void addAvailableStock(BuildingFeature bf, int n) { metaState.addAvailableStock(bf, n); GameScreen.getInstance().enableBuildings(); } public float getGidrahDeathRatio() { if (aliensSpawnedAtLevelEnd == 0) { return 0.0f; } return (float) aliensVanquishedSinceEndOfLevel / (float) aliensSpawnedAtLevelEnd; } private void onSomethingInterestingHappened() { somethingInterestingHappenedTick = 0; } /** * @return an unmodifiable map of the medals */ public Map<MedalFeature, Integer> getMedals() { return Collections.unmodifiableMap(metaState.medals); } /** * Award a medal * @param medal * @return a MedalFeature if the medal was awarded, null if not */ public MedalFeature awardMedal(String medal) { MedalFeature mf = MedalFeature.getMedals().get(medal); if (mf == null) { if (Game.DEBUG) { System.out.println("Warning: medal "+medal+" not found"); } return null; } Integer n = metaState.medals.get(mf); if (n == null) { n = Integer.valueOf(1); } else { if (mf.isRepeatable()) { n = Integer.valueOf(n.intValue() + 1); } else { return null; } } metaState.score += mf.getPoints(); addMoney(mf.getMoney()); RankFeature newRank = RankFeature.getRank(metaState.score); boolean storeSteamStats = false; if (newRank != metaState.rank) { System.out.println("New Rank: "+newRank.getTitle()); metaState.rank = newRank; addMoney(newRank.getPoints() / 10); if (!suppressMedals) { GameScreen.getInstance().showHint(newRank.getHint()); } SFX.newRank(); } metaState.medals.put(mf, n); System.out.println("Awarded "+medal+" ("+n+")"); // Update medals earned this level too if (!mf.getSuppressHint()) { medalsThisLevel.add(mf); } // Now, if we're currently playing a level, pop up a hint saying what they just got if (phase != PHASE_END_OF_GAME && !mf.getSuppressHint() && !suppressMedals) { if (mf.getHint() != null) { if (Game.DEBUG) { System.out.println("Queued hint "+mf.getHint()); } GameScreen.getInstance().showHint(mf.getHint()); if (mf.isRepeatable()) { SFX.medalAwarded(); } else { SFX.achievementUnlocked(); } } else { System.out.println("Medal "+mf+" doesn't have a hint!"); } } return mf; } /** * @return the total score for all awarded medals */ public int getScore() { return metaState.score; } /** * @return an unmodifiable map of all the medals earned in this level */ public Set<MedalFeature> getMedalsEarnedThisLevel() { return Collections.unmodifiableSet(medalsThisLevel); } public RankFeature getRank() { return metaState.rank; } /** * Gets the value of a stat * @param stat * @return */ public int getStat(String stat) { return addStat(stat, 0); } /** * Add a value to a stat * @param stat * @param n * @return the new stat value */ public int addStat(String stat, int n) { return metaState.addStat(stat, n); } /** * @return text for the story screen in Endless mode */ public String getStatsText() { boolean showAllStats = false; StringBuilder sb = new StringBuilder(256); if (getGameMode() == GAME_MODE_SURVIVAL) { sb.append(Game.getMessage("ultraworm.wormgamestate.stats_survival")); } else if (getGameMode() == GAME_MODE_XMAS) { if (getLevel() == 0) { // Goes in the ZX bot at the start... sb.append(Game.getMessage("ultraworm.wormgamestate.stats_xmas")); } else { // Goes on the Xmas victory screen at the end sb.append("{font:smallfont.glfont color:text-bold}"); sb.append(Game.getMessage("ultraworm.wormgamestate.xmas.battle_statistics")); sb.append("{font:tinyfont.glfont}\n\n"); int angry = getStat(Stats.ANGRY_SPAWNED); int bosses = getStat(Stats.BOSSES_SPAWNED); int gidlets = getStat(Stats.GIDLETS_SPAWNED); int spawned = getStat(Stats.ALIENS_SPAWNED); sb.append("{color:text-bold}"+spawned+" "+Game.getMessage("ultraworm.wormgamestate.xmas.aliens_spawned")); if (angry > 0 || bosses > 0 || gidlets > 0 || showAllStats) { sb.append("\n\t\t {color:text}"); int normal = spawned - (angry + gidlets + bosses); boolean addSpace = false; if (normal > 0 || showAllStats) { sb.append("{color:text}"+Game.getMessage("ultraworm.wormgamestate.xmas.normal_sized_aliens")+": "+normal); addSpace = true; } if (angry > 0 || showAllStats) { if (addSpace) { sb.append(' '); } sb.append("{color:text}"+Game.getMessage("ultraworm.wormgamestate.xmas.large_sized_aliens")+": "+angry); addSpace = true; } if (gidlets > 0 || showAllStats) { if (addSpace) { sb.append(' '); } sb.append("{color:text}"+Game.getMessage("ultraworm.wormgamestate.xmas.tiny_sized_aliens")+": "+gidlets); addSpace = true; } if (bosses > 0 || showAllStats) { if (addSpace) { sb.append(' '); } sb.append("{color:text}"+Game.getMessage("ultraworm.wormgamestate.xmas.bosses")+": "+bosses); } } int vanquished = getStat(Stats.ALIENS_VANQUISHED); sb.append("\n{color:text-bold}"+vanquished+" "+Game.getMessage("ultraworm.wormgamestate.xmas.aliens_vanquished")); int shot = getStat(Stats.ALIENS_SHOT); if (vanquished != shot || showAllStats) { int vanquishedCount = 1; sb.append("\n\t\t {color:text}"+Game.getMessage("ultraworm.wormgamestate.xmas.aliens_shot")+": "+shot); int crushed = getStat(Stats.ALIENS_CRUSHED); if (crushed > 0 || showAllStats) { sb.append(" {color:text}"+Game.getMessage("ultraworm.wormgamestate.xmas.aliens_crushed")+": "+crushed); vanquishedCount++; } int blownUp = getStat(Stats.ALIENS_BLOWN_UP); if (blownUp > 0 || showAllStats) { sb.append(" {color:text}"+Game.getMessage("ultraworm.wormgamestate.xmas.aliens_blown_up")+": "+blownUp); vanquishedCount++; } int fried = getStat(Stats.ALIENS_FRIED); if (fried > 0 || showAllStats) { vanquishedCount++; if (vanquishedCount>3) { vanquishedCount=0; sb.append("\n\t\t"); } sb.append(" {color:text}"+Game.getMessage("ultraworm.wormgamestate.xmas.aliens_fried")+": "+fried); } int nuked = getStat(Stats.ALIENS_SMARTBOMBED); if (nuked > 0 || showAllStats) { vanquishedCount++; if (vanquishedCount>3) { vanquishedCount=0; sb.append("\n\t\t"); } sb.append(" {color:text}"+Game.getMessage("ultraworm.wormgamestate.xmas.aliens_nuked")+": "+nuked); } } int alienAttacksOnBuildings = getStat(Stats.ALIEN_ATTACKS_ON_BUILDINGS); if (alienAttacksOnBuildings > 0 || showAllStats) { String msg = Game.getMessage("ultraworm.wormgamestate.xmas.alien_attacks"); msg = msg.replace("[num]", String.valueOf(alienAttacksOnBuildings)); msg = msg.replace("[value]", String.valueOf(getStat(Stats.VALUE_OF_BUILDINGS_DESTROYED))); sb.append(msg); } sb.append("\n{color:text-bold}"+getStat(Stats.BUILDINGS_BUILT)+" "+Game.getMessage("ultraworm.wormgamestate.xmas.buildings_built")); sb.append("\n\t\t {color:text}"+Game.getMessage("ultraworm.wormgamestate.xmas.buildings_destroyed")+": "+getStat(Stats.BUILDINGS_DESTROYED)); int recycled = getStat(Stats.RECYCLED); int sold = getStat(Stats.SOLD); sb.append(" {color:text}"+Game.getMessage("ultraworm.wormgamestate.xmas.buildings_sold")+": "+sold); sb.append(" {color:text}"+Game.getMessage("ultraworm.wormgamestate.xmas.buildings_recycled")+": "+recycled); int buildCosts = getStat(Stats.VALUE_OF_BUILDINGS_BUILT); sb.append("\n {color:text-bold}\\$"+buildCosts+" "+Game.getMessage("ultraworm.wormgamestate.xmas.money_spent")); } } else if (getLevel() == 0) { sb.append(Game.getMessage("ultraworm.wormgamestate.stats_level0")); } else { sb.append("{font:smallfont.glfont color:text-bold}"); sb.append(Game.getMessage("ultraworm.wormgamestate.battle_statistics")); sb.append("{font:tinyfont.glfont}\n\n"); int angry = getStat(Stats.ANGRY_SPAWNED); int bosses = getStat(Stats.BOSSES_SPAWNED); int gidlets = getStat(Stats.GIDLETS_SPAWNED); int spawned = getStat(Stats.ALIENS_SPAWNED); sb.append("{color:text-bold}"+spawned+" "+Game.getMessage("ultraworm.wormgamestate.aliens_spawned")); if (angry > 0 || bosses > 0 || gidlets > 0 || showAllStats) { sb.append("\n\t\t {color:text}"); int normal = spawned - (angry + gidlets + bosses); boolean addSpace = false; if (normal > 0 || showAllStats) { sb.append("{color:text}"+Game.getMessage("ultraworm.wormgamestate.normal_sized_aliens")+": "+normal); addSpace = true; } if (angry > 0 || showAllStats) { if (addSpace) { sb.append(' '); } sb.append("{color:text}"+Game.getMessage("ultraworm.wormgamestate.large_sized_aliens")+": "+angry); addSpace = true; } if (gidlets > 0 || showAllStats) { if (addSpace) { sb.append(' '); } sb.append("{color:text}"+Game.getMessage("ultraworm.wormgamestate.tiny_sized_aliens")+": "+gidlets); addSpace = true; } if (bosses > 0 || showAllStats) { if (addSpace) { sb.append(' '); } sb.append("{color:text}"+Game.getMessage("ultraworm.wormgamestate.bosses")+": "+bosses); } } int vanquished = getStat(Stats.ALIENS_VANQUISHED); sb.append("\n{color:text-bold}"+vanquished+" "+Game.getMessage("ultraworm.wormgamestate.aliens_vanquished")); int shot = getStat(Stats.ALIENS_SHOT); if (vanquished != shot || showAllStats) { int vanquishedCount = 1; sb.append("\n\t\t {color:text}"+Game.getMessage("ultraworm.wormgamestate.aliens_shot")+": "+shot); int crushed = getStat(Stats.ALIENS_CRUSHED); if (crushed > 0 || showAllStats) { sb.append(" {color:text}"+Game.getMessage("ultraworm.wormgamestate.aliens_crushed")+": "+crushed); vanquishedCount++; } int blownUp = getStat(Stats.ALIENS_BLOWN_UP); if (blownUp > 0 || showAllStats) { sb.append(" {color:text}"+Game.getMessage("ultraworm.wormgamestate.aliens_blown_up")+": "+blownUp); vanquishedCount++; } int fried = getStat(Stats.ALIENS_FRIED); if (fried > 0 || showAllStats) { vanquishedCount++; if (vanquishedCount>3) { vanquishedCount=0; sb.append("\n\t\t"); } sb.append(" {color:text}"+Game.getMessage("ultraworm.wormgamestate.aliens_fried")+": "+fried); } int nuked = getStat(Stats.ALIENS_SMARTBOMBED); if (nuked > 0 || showAllStats) { vanquishedCount++; if (vanquishedCount>3) { vanquishedCount=0; sb.append("\n\t\t"); } sb.append(" {color:text}"+Game.getMessage("ultraworm.wormgamestate.aliens_nuked")+": "+nuked); } } int alienAttacksOnBuildings = getStat(Stats.ALIEN_ATTACKS_ON_BUILDINGS); if (alienAttacksOnBuildings > 0 || showAllStats) { String msg = Game.getMessage("ultraworm.wormgamestate.alien_attacks"); msg = msg.replace("[num]", String.valueOf(alienAttacksOnBuildings)); msg = msg.replace("[value]", String.valueOf(getStat(Stats.VALUE_OF_BUILDINGS_DESTROYED))); sb.append(msg); } sb.append("\n{color:text-bold}"+getStat(Stats.BUILDINGS_BUILT)+" "+Game.getMessage("ultraworm.wormgamestate.buildings_built")); sb.append("\n\t\t {color:text}"+Game.getMessage("ultraworm.wormgamestate.buildings_destroyed")+": "+getStat(Stats.BUILDINGS_DESTROYED)); int recycled = getStat(Stats.RECYCLED); int sold = getStat(Stats.SOLD); sb.append(" {color:text}"+Game.getMessage("ultraworm.wormgamestate.buildings_sold")+": "+sold); sb.append(" {color:text}"+Game.getMessage("ultraworm.wormgamestate.buildings_recycled")+": "+recycled); int buildCosts = getStat(Stats.VALUE_OF_BUILDINGS_BUILT); sb.append("\n {color:text-bold}\\$"+buildCosts+" "+Game.getMessage("ultraworm.wormgamestate.money_spent")); } return sb.toString(); } public void setAwesome() { awesome = true; } public boolean isAwesome() { return awesome; } public int getCrystals() { return crystals; } public int getInitialCrystals() { return initialCrystals; } public PowerupFeature getCrappyPowerup() { return metaState.getCrappyPowerup(); } public PowerupFeature getRandomPowerup() { return metaState.getRandomPowerup(); } /** * @return an exotic powerup */ public PowerupFeature getExoticPowerup() { return metaState.getExoticPowerup(); } /** * @return a non-exotic powerup */ public PowerupFeature getPowerup() { return metaState.getPowerup(); } /** * @return an analysis of what went wrong */ public String getAnalysis() { boolean lotsOfMoneyLeft = getMoney() > 750 * (getLevel() / LEVELS_IN_WORLD); int numBuildings = 0; int numTurrets = 0; for (Building b : getBuildings()) { if (b.canSell()) { numBuildings ++; } if (b instanceof Turret) { numTurrets ++; } } boolean lotsOfBuildings = numBuildings > getLevel(); boolean lotsOfRefineries = totalFactories >= initialCrystals * 3; boolean lotsOfTurrets = numTurrets > spawnPoints.size() * 1.5f + getWorld().getIndex(); boolean hasUnrefinedCrystals = unminedCrystalList.size() > 0; boolean lotsOfAliensSlain = aliensVanquishedValue > aliensSpawnedValue * 0.75 && aliensSpawnedValue > 1000; boolean lotsDestroyed = (float) valueOfDestroyedBuildings / (float) valueOfBuiltBuildings > 0.5f; boolean hasPowerups = false; for (Integer i : metaState.powerups.values()) { if (i > 0) { hasPowerups = true; break; } } class Message implements Comparable<Message> { int priority; String msg; public Message(int priority, String msg) { this.priority = priority; this.msg = msg; } @Override public int compareTo(Message o) { if (o.priority > priority) { return 1; } else if (o.priority < priority) { return -1; } else { return 0; } } } List<Message> messages = new ArrayList<Message>(); if (!lotsOfTurrets) { messages.add(new Message(700, Game.getMessage("ultraworm.wormgamestate.hint1"))); } if (lotsDestroyed) { messages.add(new Message(800, Game.getMessage("ultraworm.wormgamestate.hint2"))); } else if (valueOfBuiltBuildings > 2500) { messages.add(new Message(600, Game.getMessage("ultraworm.wormgamestate.hint3"))); } if (!lotsOfAliensSlain && lotsOfRefineries) { messages.add(new Message(200, Game.getMessage("ultraworm.wormgamestate.hint4"))); } if (hasUnrefinedCrystals) { messages.add(new Message(100, Game.getMessage("ultraworm.wormgamestate.hint5"))); } if (!lotsOfRefineries && (getLevel() > 4 || getGameMode() == GAME_MODE_SURVIVAL)) { messages.add(new Message(900, Game.getMessage("ultraworm.wormgamestate.hint6"))); } if (hasPowerups) { messages.add(new Message(500, Game.getMessage("ultraworm.wormgamestate.hint7"))); } if (!lotsOfMoneyLeft) { if (lotsOfBuildings) { messages.add(new Message(100, Game.getMessage("ultraworm.wormgamestate.hint8"))); } else { if (isResearched(ResearchFeature.CONCRETE)) { messages.add(new Message(100, Game.getMessage("ultraworm.wormgamestate.hint9"))); } else if (isResearched(ResearchFeature.TANGLEWEB)) { messages.add(new Message(100, Game.getMessage("ultraworm.wormgamestate.hint10"))); } else if (isResearched(ResearchFeature.MINES)) { messages.add(new Message(100, Game.getMessage("ultraworm.wormgamestate.hint11"))); } else { messages.add(new Message(100, Game.getMessage("ultraworm.wormgamestate.hint12"))); } } } else { messages.add(new Message(1000, Game.getMessage("ultraworm.wormgamestate.hint13"))); } if (getLevelFeature().getBosses() != null) { if (getLevelFeature().getBosses().getNumResources() == 1) { messages.add(new Message(900, Game.getMessage("ultraworm.wormgamestate.hint14"))); } else { messages.add(new Message(900, Game.getMessage("ultraworm.wormgamestate.hint15"))); } } StringBuilder sb = new StringBuilder(256); sb.append("{color:text-bold}"); sb.append(Game.getMessage("ultraworm.wormgamestate.battle_analysis")); sb.append(":{color:text}"); Collections.sort(messages); int count = 0; for (Message m : messages) { sb.append("\n\t\t"); sb.append(m.msg); if (++ count == 3) { break; } } return sb.toString(); } public float getScavengeRate() { int rate = Worm.getGameState().isResearched(ResearchFeature.FINETUNING) ? 1 : 0; rate += Worm.getGameState().isResearched(ResearchFeature.EXTRACTION) ? 1 : 0; return config.getScavengeRate()[rate]; } // public MapGeneratorParams getMapGeneratorParams() { // return new MapGeneratorParams( this.getBasicDifficulty(), this.getGameMode(), this.getLevel(), this.getLevelFeature(), this.getLevelInWorld(), this.getMoney(), this.getResearchHash(), this.getWorld() ); // } /** * Hax! This returns true if we want the level generator thread to generate a new level, or false if not. * @return * @see #initXmas(boolean) */ public boolean isXmasReset() { return xmasReset; } }