/*
* 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.entities;
import java.util.ArrayList;
import net.puppygames.applet.Game;
import net.puppygames.applet.Screen;
import net.puppygames.applet.effects.BlastEffect;
import net.puppygames.applet.effects.Effect;
import net.puppygames.applet.effects.Emitter;
import net.puppygames.applet.effects.LabelEffect;
import org.lwjgl.util.Color;
import org.lwjgl.util.Point;
import org.lwjgl.util.ReadableColor;
import org.lwjgl.util.Rectangle;
import worm.AttenuatedColor;
import worm.CauseOfDeath;
import worm.ClickAction;
import worm.Entity;
import worm.GameMap;
import worm.GameStateInterface;
import worm.Hints;
import worm.Layers;
import worm.MapRenderer;
import worm.Medals;
import worm.Res;
import worm.SFX;
import worm.Stats;
import worm.Worm;
import worm.WormGameState;
import worm.brains.SmartBrainFeature;
import worm.effects.BuildingAttackedEffect;
import worm.features.GidrahFeature;
import worm.features.LayersFeature;
import worm.features.ResearchFeature;
import worm.screens.GameScreen;
import worm.weapons.WeaponFeature.WeaponInstance;
import com.shavenpuppy.jglib.Resources;
import com.shavenpuppy.jglib.interpolators.CosineInterpolator;
import com.shavenpuppy.jglib.interpolators.LinearInterpolator;
import com.shavenpuppy.jglib.interpolators.OpenLinearInterpolator;
import com.shavenpuppy.jglib.openal.ALBuffer;
import com.shavenpuppy.jglib.resources.Range;
import com.shavenpuppy.jglib.sound.SoundEffect;
import com.shavenpuppy.jglib.sound.SoundPlayer;
import com.shavenpuppy.jglib.sprites.Sprite;
import com.shavenpuppy.jglib.util.FPMath;
import com.shavenpuppy.jglib.util.Util;
/**
* $Id: Gidrah.java,v 1.177 2010/11/07 01:26:59 foo Exp $
* Gidrahs!
* @author $Author: foo $
* @version $Revision: 1.177 $
*/
public class Gidrah extends Entity {
private static final long serialVersionUID = 1L;
private static final ArrayList<Entity> COLLISIONS = new ArrayList<Entity>();
private static final double GIDRAH_INITIAL_DISTANCE = 32.0; // Calibrate gidrah hitpoints to 64 tiles distant from base
private static final float MAX_KNOCKBACK = 5.0f;
private static final int ATTACK_DURATION = 60;
private static final int MIN_ATTACK_DURATION = 10;
private static final int BOSS_ATTACK_DURATION = 10;
private static final int SPAWN_DURATION = 60;
private static final int DERES_DURATION = 10;
private static final int HITPOINTS_FADE = 16;
private static final int HITPOINTS_DURATION = 120;
private static final int MAX_SPAWNS = 16;
private static final float WRAITH_VISIBILITY_DISTANCE = 256.0f;
private static final int WRAITH_MIN_ALPHA = 10;
private static final int WRAITH_XRAY_MIN_ALPHA = 128;
private static final int WRAITHS_VISIBLE_TO_TURRETS_ALPHA = 224;
private static final int DANGER_RECALC_THRESHOLD = 20;
private static final int DANGER_DIVISOR = 5;
public static final int MAX_DANGER = 100;
private static final float MAX_SURVIVAL_DIFFICULTY_HITPOINTS_MULTIPLIER = 2.0f;
private static final float MAX_SURVIVAL_DIFFICULTY_BOSS_HITPOINTS_MULTIPLIER = 1.5f;
private static final float MAX_DIFFICULTY_HITPOINTS_MULTIPLIER = 3.0f;
private static final float HITPOINTS_PER_SURVIVAL_LEVEL_TICK = 1.0f / 3600.0f; // Every 60 seconds, all gidrahs get an extra hitpoint in survival mode at difficulty 0.0
private static final float MAX_HITPOINTS_PER_SURVIVAL_LEVEL_TICK = 1.0f / 1200.0f; // Every 20 seconds, all gidrahs get an extra hitpoint in survival mode at difficulty 1.0
private static final float MAX_DIFFICULTY_BOSS_HITPOINTS_MULTIPLIER = 2.0f;
// Sanity check timers.
private static final int GIDLET_SAFETY_TIME = 60;
private static final int CHECK_MAP_COLLISION_SAFETY = 60;
private static final int ATTACK_ABORT_TIME = -360;
private static final Rectangle TEMP_BOUNDS = new Rectangle();
private static final Color AWARD_COLOR = new Color(255,0,0,200);
/** The gidrah feature */
private final GidrahFeature feature;
/** Xrays researched? */
private boolean xrays;
/** Hitpoints */
private int hitPoints;
/** Gidrah wounds (0=healthy, increases as damage is taken) */
private int wounds;
/** Armoured damage tick */
private int armouredDamage;
/** Flash tick */
private int flashTick;
/** Hit by an exploding bullet? */
private ArrayList<Bullet> explodingBullets;
/** Smartbombs we've hit */
private ArrayList<Smartbomb> smartbombs;
/** Current appearance */
private LayersFeature appearance;
/** Emitters */
private transient Emitter[] emitter;
/** Stun */
private int stunTick;
/** Spawner tick */
private int spawnerTick;
/** Last attack tick (limits roaring) */
private int lastAttackTick;
/** Frozen */
private static final AttenuatedColor ICE_COLOR = new AttenuatedColor(new Color(0,240,255));
/** Exotic? */
private boolean exotic;
/** Weapon instance */
private WeaponInstance weaponInstance;
/** Movement handler */
private final Movement movement;
/** Building we're attacking */
private Building attacking;
/** Target building */
private Entity target;
/** Boss hitpoints */
private Sprite hitPointsSprite;
/** Hitpoints alpha */
private int hitPointsAlpha;
/** Target hitpoints alpha */
private int targetHitPointsAlpha;
/** Hitpoints alpha ticker */
private int hitPointsAlphaTick;
/** Parent gidrah */
private Gidrah parent;
/** Bomb we're carrying */
private Bomb bomb;
/** Gidlet safety tick */
private int gidletSafetyTick;
/** Map collision safety tick */
private int mapSafetyTick;
/** Appearance lock */
private boolean locked;
/** Phase */
private int phase;
private static final int PHASE_WAIT = 0;
private static final int PHASE_SPAWN = 1;
private static final int PHASE_ALIVE = 2;
private static final int PHASE_DERES = 3;
private static final int PHASE_DYING = 4;
/** Timer for attacks */
private int attackTick;
/** How many gidrahs have we spawed from ourselves? */
private int numSpawned;
/** Whether frozen */
private boolean frozen;
/** Knockback */
private float kx, ky;
/** Request to set layers next tick */
private LayersFeature layersRequest;
/** Current appearance */
private int currentAppearance = -1;
private static final int APPEARANCE_IDLE = 0;
private static final int APPEARANCE_MOVING = 1;
private static final int APPEARANCE_FROZEN = 2;
private static final int APPEARANCE_ATTACKING = 3;
private static final int APPEARANCE_DEATH = 4;
private static final int APPEARANCE_STUN = 5;
private transient Effect attackEffect;
private int type;
private int tangled;
/**
* C'tor
*/
public Gidrah(GidrahFeature feature, int tileX, int tileY, int type) {
this.feature = feature;
this.type = type;
setLocation(tileX * MapRenderer.TILE_SIZE, tileY * MapRenderer.TILE_SIZE);
if (feature.isFlying()) {
movement = new FlyingMovement(this);
} else {
movement = new GidrahMovement(this);
}
calcHitPoints();
}
/**
* Gets the gidrah type (0, 1, 2, or 3) - used in Survival mode
* @return int
*/
public int getType() {
return type;
}
/**
* Rethink routes
*/
public static void rethinkRoutes(Rectangle bounds) {
ArrayList<Gidrah> gidrahs = Worm.getGameState().getGidrahs();
int n = gidrahs.size();
for (int i = 0; i < n; i ++) {
Gidrah g = gidrahs.get(i);
g.movement.maybeRethink(bounds);
}
}
/**
* Rethink all targets
*/
public static void rethinkTargets() {
ArrayList<Gidrah> gidrahs = Worm.getGameState().getGidrahs();
int n = gidrahs.size();
for (int i = 0; i < n; i ++) {
Gidrah g = gidrahs.get(i);
g.findTarget();
}
}
@Override
public float getBeamXOffset() {
int bOff = feature.getBeamOffset().getX();
if (this.isMirrored()) {
bOff = -bOff;
}
return getFinalXOffset() + bOff;
}
@Override
public float getBeamYOffset() {
return getOffsetY() + getFinalYOffset() + feature.getBeamOffset().getY();
}
public int getWidth() {
return feature.getBounds().getWidth();
}
public int getHeight() {
return feature.getBounds().getHeight();
}
/**
* @param exotic the exotic to set
*/
public void setExotic(boolean exotic) {
this.exotic = exotic;
}
@Override
public boolean isShootable() {
return canCollide();
}
@Override
public boolean isLaserProof() {
return feature.getArmour() >= 8;
}
@Override
public boolean isSolid() {
return canCollide() && !feature.isWraith();
}
@Override
public boolean canCollide() {
return isActive() && phase == PHASE_SPAWN || phase == PHASE_ALIVE;
}
@Override
public final Rectangle getBounds(Rectangle bounds) {
if (bounds == null) {
bounds = new Rectangle();
}
Rectangle featureBounds = feature.getBounds();
bounds.setBounds((int) getMapX() + featureBounds.getX(), (int) getMapY() + featureBounds.getY(), featureBounds.getWidth(), featureBounds.getHeight());
return bounds;
}
@Override
public float getOffsetX() {
return feature.getBounds().getX() + feature.getBounds().getWidth() / 2;
}
@Override
public float getOffsetY() {
return feature.getBounds().getY() + feature.getBounds().getHeight() / 2;
}
@Override
public boolean isRound() {
return false;
}
@Override
public float getRadius() {
return 0.0f;
}
@Override
protected void createSprites(Screen screen) {
appearance = feature.getAppearance(); // A bit hacky, this - would be better to call normalAppearance()...
feature.getAppearance().createSprites(screen, this);
}
@Override
public void onCollision(Entity entity) {
entity.onCollisionWithGidrah(this);
}
@Override
public void onCollisionWithSmartbomb(Smartbomb smartbomb) {
if (smartbombs == null) {
smartbombs = new ArrayList<Smartbomb>(1);
} else if (smartbombs.contains(smartbomb)) {
return;
}
smartbombs.add(smartbomb);
damage(100, 100, CauseOfDeath.SMARTBOMB);
}
@Override
public void onCollisionWithBullet(Bullet bullet) {
if (bullet.getSource() == this) {
return;
}
if (!bullet.isDangerousToGidrahs()) {
return;
}
// Wraiths are unharmed by bullets most of the time
if (xrays || !feature.isWraith()) {
if (explodingBullets != null) {
for (int i = explodingBullets.size(); -- i >= 0; ) {
Bullet b = explodingBullets.get(i);
if (bullet == b) {
return;
}
if (!b.isActive()) {
explodingBullets.remove(i);
}
}
}
if (explodingBullets == null) {
explodingBullets = new ArrayList<Bullet>(8);
}
explodingBullets.add(bullet);
int damage = feature.isWraith() ? 1 + bullet.getExtraDamage() + bullet.getArmourPiercing() : bullet.getDamage();
boolean wasDamaged;
if (bullet.isExploding()) {
explosionDamage(damage, false);
wasDamaged = true;
} else if (feature.isGidlet() && !bullet.isDangerousToGidlets()) {
return;
} else {
wasDamaged = damage(damage, bullet.getArmourPiercing(), CauseOfDeath.BULLET);
}
if (bullet.isPassThrough() || feature.isWraith()) {
bullet.onPassThrough();
} else {
bullet.onHit(wasDamaged, this);
}
if (!wasDamaged) {
doDeflectAppearance(bullet);
}
if (isActive() && phase != PHASE_DYING) {
Emitter e = Res.getGidrahPainEmitter().spawn(GameScreen.getInstance());
e.setLocation(bullet.getMapX(), bullet.getMapY());
e.setOffset(GameScreen.getSpriteOffset());
if (!feature.isBoss()) {
if (!(feature.getNoStunOnAttack() && attacking != null)) {
stunTick = Math.min(120, stunTick + bullet.getStun());
if (stunTick > 0 && isShootable()) {
if (stunTick > 9) {
setAppearance(APPEARANCE_STUN, feature.getStunAppearance());
if (attacking != null || !isFrozen()) {
attacking = null; // Stop the attack
}
movement.dontAttack();
}
}
}
// Knockback - only do a little knockback if bullet deflected
float knockbackAmount = wasDamaged ? 1.0f : 0.5f;
double angle = Math.atan2(bullet.getDY(), bullet.getDX());
float factor = (bullet.getDamage() * 2.0f) / feature.getHitPoints() * knockbackAmount;
knockback((float) Math.cos(angle) * factor, (float) Math.sin(angle) * factor);
}
}
}
}
@Override
public void explosionDamage(int damageAmount, boolean friendly) {
if (!feature.isFlying()) {
if (feature.isExploding()) {
damage(100, 100, CauseOfDeath.EXPLOSION);
} else if (feature.isBoss()) {
// Bosses get randomized explosion damage to prevent minefields from killing them too easily
damage(Util.random(1, damageAmount), damageAmount, CauseOfDeath.EXPLOSION);
} else if (feature.isWraith()) {
// Wraiths barely damaged by explosives
damage(1, damageAmount, CauseOfDeath.EXPLOSION);
} else if (feature.isGidlet() && gidletSafetyTick != 0) {
// Immune!
return;
} else {
// Ordinary gidrahs armour has no protection against explosions
damage(damageAmount + feature.getArmour(), damageAmount, CauseOfDeath.EXPLOSION);
}
}
}
@Override
public boolean isClickable() {
return true;
}
@Override
public LayersFeature getMousePointer(boolean clicked) {
// Hackery! If we're calling getMousePointer it means the mouse is over us...
if (hitPointsSprite != null) {
targetHitPointsAlpha = 255;
hitPointsAlphaTick = HITPOINTS_DURATION;
}
WormGameState gameState = Worm.getGameState();
if (gameState.inRangeOfCapacitor()) {
if (gameState.isSmartbombMode()) {
return Res.getMousePointerSmartbomb();
} else if (gameState.isBezerk()) {
return Res.getMousePointerBezerkOnTarget();
} else {
return Res.getMousePointerOnTarget();
}
} else {
if (gameState.isSmartbombMode()) {
return Res.getMousePointerSmartbomb();
} else {
return Res.getMousePointerOutOfRange();
}
}
}
@Override
public int onClicked(int mode) {
return ClickAction.FIRE;
}
@Override
public int crush() {
if (feature.isFlying()) {
// Ignore
return 0;
}
if (feature.isBoss()) {
assert false;
return 0;
}
kill(CauseOfDeath.CRUSHED, false);
return feature.isAngry() ? 6 : feature.isGidlet() ? 1 : 2;
}
/**
* Damage and maybe kill the gidrah
* @param amount The amount of damage to inflict
* @param armourPiercing TODO
* @return true if we damaged the gidrah, false if not
*/
protected boolean damage(int amount, int armourPiercing, int source) {
int armour = Math.max(feature.getArmour() - armourPiercing, 0);
int newAmount = Math.max(0, amount - armour);
if (newAmount <= 0) {
armouredDamage ++;
if (armouredDamage > armour) {
newAmount = Math.max(1, amount / 4);
armouredDamage = 0;
} else {
newAmount = 0;
}
}
if (newAmount == 0) {
Worm.getGameState().flagHint(Hints.ARMOURED);
return false;
}
wounds += newAmount;
// If we've got hitpoints, make them visible
if (hitPointsSprite != null) {
updateHitPoints();
targetHitPointsAlpha = 255;
hitPointsAlphaTick = HITPOINTS_DURATION;
}
flashTick = 3;
setFlash(true);
if (wounds >= getHitPoints()) {
kill(source, true);
} else {
Emitter e = Res.getGidrahPainEmitter().spawn(GameScreen.getInstance());
e.setOffset(GameScreen.getSpriteOffset());
e.setLocation(getMapX(), getMapY());
}
return true;
}
private int getHitPoints() {
return hitPoints;
}
private void calcHitPoints() {
int gameMode = Worm.getGameState().getGameMode();
if (feature.isBoss()) {
float mult;
switch (gameMode) {
case WormGameState.GAME_MODE_SURVIVAL:
case WormGameState.GAME_MODE_XMAS:
mult = MAX_SURVIVAL_DIFFICULTY_BOSS_HITPOINTS_MULTIPLIER;
break;
default:
mult = MAX_DIFFICULTY_HITPOINTS_MULTIPLIER;
}
hitPoints = (int) OpenLinearInterpolator.instance.interpolate(feature.getHitPoints(), feature.getHitPoints() * mult, Worm.getGameState().getDifficulty() );
Building base = Worm.getGameState().getBase();
double dx = getTileX() - base.getTileX();
double dy = getTileY() - base.getTileY();
double actualDist = Math.sqrt(dx * dx + dy * dy);
double ratio = actualDist / GIDRAH_INITIAL_DISTANCE;
if (Game.DEBUG) {
System.out.println("Boss @ ("+getTileX()+","+getTileY()+") dist "+actualDist+" from base @ ("+base.getTileX()+","+base.getTileY()+") vs "+GIDRAH_INITIAL_DISTANCE+", ratio "+ratio);
System.out.println("Hitpoints were "+hitPoints);
}
hitPoints *= ratio;
if (Game.DEBUG) {
System.out.println("... and now "+hitPoints);
}
} else {
if (gameMode == WormGameState.GAME_MODE_SURVIVAL || gameMode == WormGameState.GAME_MODE_XMAS) {
hitPoints = (int) OpenLinearInterpolator.instance.interpolate
(
feature.getHitPoints() + Worm.getGameState().getLevelTick() * HITPOINTS_PER_SURVIVAL_LEVEL_TICK / (type + 1),
feature.getHitPoints() * MAX_SURVIVAL_DIFFICULTY_HITPOINTS_MULTIPLIER + Worm.getGameState().getLevelTick() * MAX_HITPOINTS_PER_SURVIVAL_LEVEL_TICK / (type + 1),
Worm.getGameState().getDifficulty()
);
} else {
hitPoints = (int) OpenLinearInterpolator.instance.interpolate(feature.getHitPoints(), feature.getHitPoints() * MAX_DIFFICULTY_HITPOINTS_MULTIPLIER, Worm.getGameState().getDifficulty());
}
}
}
@Override
public boolean isDisruptorProof() {
return feature.isDisruptorProof();
}
@Override
public void disruptorDamage(int amount, boolean friendly) {
if (!friendly || feature.isFlying() || isDisruptorProof()) {
return;
}
stunDamage(amount);
damage(amount, 100, CauseOfDeath.DISRUPTOR); // Penetrates all known armour
}
@Override
public boolean laserDamage(int amount) {
if (feature.isWraith()) {
return false;
}
damage(amount, amount, CauseOfDeath.LASER); // Varying armour penetration
return true;
}
@Override
public void capacitorDamage(int amount) {
damage(amount, 100, CauseOfDeath.CAPACITOR); // Penetrates all known armour
}
@Override
public void stunDamage(int amount) {
if (feature.isBoss()) {
return;
}
if (feature.isDropAttack() && attacking != null) {
return;
}
if (stunTick == 0) {
Emitter e = Res.getGidrahPainEmitter().spawn(GameScreen.getInstance());
e.setOffset(GameScreen.getSpriteOffset());
e.setLocation(getMapX(), getMapY());
}
stunTick += amount;
// stop any attack
if (attacking != null) {
attacking = null;
movement.dontAttack();
setAppearance(APPEARANCE_STUN, feature.getStunAppearance());
} else if (!isFrozen()) {
setAppearance(APPEARANCE_STUN, feature.getStunAppearance());
}
flashTick = 3;
setFlash(true);
}
/**
* Kill the gidrah
* @param award TODO
*/
private void kill(int causeOfDeath, boolean award) {
if (!isActive()) {
return;
}
movement.dontAttack();
attacking = null;
WormGameState gameState = Worm.getGameState();
if (feature.isBoss()) {
gameState.addMoney(feature.getPoints());
LabelEffect effect = new LabelEffect
(
net.puppygames.applet.Res.getTinyFont(),
"$"+String.valueOf(feature.getPoints()),
ReadableColor.WHITE,
AWARD_COLOR,
50,
20
);
effect.spawn(GameScreen.getInstance());
effect.setLayer(Layers.HUD);
effect.setLocation(getX(), getMapY() + getHeight() + 10);
effect.setVelocity(0.0f, 0.5f);
effect.setAcceleration(0.0f, -0.01f);
effect.setDelay(0);
}
// Increase danger
if (causeOfDeath != CauseOfDeath.ATTACK) {
int oldDanger = gameState.getMap().getDanger(getTileX(), getTileY());
// Tangled gids warn other gids if they're killed whilst tangled
int newDanger = isTangled() ? MAX_DANGER : Math.min(MAX_DANGER, oldDanger + feature.getPoints() / DANGER_DIVISOR);
gameState.getMap().setDanger(getTileX(), getTileY(), newDanger);
if (newDanger / DANGER_RECALC_THRESHOLD > oldDanger / DANGER_RECALC_THRESHOLD || (newDanger == MAX_DANGER && oldDanger < newDanger)) {
// All gidrahs rethink your routes!
Gidrah.rethinkRoutes(new Rectangle(getTileX() - 1, getTileY() - 1, 3, 3));
}
}
unfreeze();
gameState.onGidrahKilled(this, causeOfDeath);
// Maybe spawn some more gidrahs?
if (feature.getSpawnType() == GidrahFeature.SPAWN_TYPE_EXPLODE && causeOfDeath != CauseOfDeath.CRUSHED && causeOfDeath != CauseOfDeath.SMARTBOMB && award) {
int numToSpawn = (int) (feature.getSpawnRate().getValue() + feature.getSpawnRate().getMin() * gameState.getSpawners());
for (int i = 0; i < numToSpawn; i ++) {
Gidrah spawned = feature.getSpawn().spawn(GameScreen.getInstance(), getTileX(), getTileY(), 0);
spawned.phase = PHASE_ALIVE;
// Randomize gidlet location
if (feature.getSpawn().isGidlet()) {
spawned.setLocation(spawned.getMapX() + Util.random(0, MapRenderer.TILE_SIZE - 1), spawned.getMapY() + Util.random(0, MapRenderer.TILE_SIZE - 1));
}
if (!spawned.isValidMove()) {
spawned.remove();
}
}
}
// Maybe drop a powerup?
if (feature.isAngry() && award) {
Saucer saucer = new Saucer(getMapX() + getCollisionX(), getMapY() + getCollisionY());
saucer.spawn(GameScreen.getInstance());
}
// Maybe it was a boss, moments away from killing the player?
if (feature.isBoss() && target != null && getDistanceTo(target) < 64.0f) {
gameState.awardMedal(Medals.PHEW_THAT_WAS_CLOSE);
}
if (feature.getMedal() != null && !feature.isBoss()) {
gameState.awardMedal(feature.getMedal());
}
// chaz hack! remove hitpoints counter thing - maybe should fade out?
if (feature.isBoss()) {
if (hitPointsSprite != null) {
hitPointsSprite.deallocate();
hitPointsSprite = null;
}
Game.allocateSound(feature.getDeathBuffer(), feature.getDeathBuffer().getGain() * Worm.calcLoudGain(getX(), getY()), feature.getDeathBuffer().getPitch(), Game.class);
} else {
Game.allocateSound(feature.getDeathBuffer(), feature.getDeathBuffer().getGain() * Worm.calcGain(getX(), getY()), feature.getDeathBuffer().getPitch() * (feature.isAngry() ? 0.8f : 1.0f), Game.class);
}
setFlash(false);
if (feature.getDeathAppearance() != null) {
setAppearance(APPEARANCE_DEATH, feature.getDeathAppearance());
phase = PHASE_DYING;
} else if (feature.getDeathAppearanceLeft() != null && feature.getDeathAppearanceRight() != null){
// chaz hack! for boss gidrah death
if (isMirrored()) {
setAppearance(APPEARANCE_DEATH, feature.getDeathAppearanceLeft());
} else {
setAppearance(APPEARANCE_DEATH, feature.getDeathAppearanceRight());
}
phase = PHASE_DYING;
} else {
// Just remove us for now
remove();
}
}
public boolean isFrozen() {
return frozen;
}
/**
* Freeze the gidrah for a number of ticks
* @param duration
*/
public void freeze() {
if (frozen || attacking != null && feature.isDropAttack()) {
return;
}
frozen = true;
attacking = null;
movement.dontAttack();
if (bomb != null) {
dropBomb(0.0f, 0.0f);
}
setAppearance(APPEARANCE_FROZEN, feature.getFrozenAppearance());
createIceShards();
}
@Override
public final void addToGameState(GameStateInterface gsi) {
gsi.addToGidrahs(this);
}
@Override
public final void removeFromGameState(GameStateInterface gsi) {
gsi.removeFromGidrahs(this);
}
@Override
protected final void doSpawn() {
emitter = feature.getAppearance().createEmitters(GameScreen.getInstance(), getMapX(), getMapY());
if (feature.getWeapon() != null) {
weaponInstance = feature.getWeapon().spawn(this);
}
if (feature.isBoss()) {
hitPointsSprite = GameScreen.getInstance().allocateSprite(this);
hitPointsSprite.setAlpha(0);
hitPointsSprite.setLayer(Layers.HITPOINTS);
hitPointsSprite.setScale(FPMath.fpValue(0.5));
updateHitPoints();
}
if (feature.isGidlet()) {
gidletSafetyTick = GIDLET_SAFETY_TIME;
}
if (feature.getBomb() != null) {
spawnBomb();
}
xrays = Worm.getGameState().isResearched(ResearchFeature.XRAYS);
tick();
update();
}
private void updateHitPoints() {
hitPointsSprite.setAppearance(Res.getBossHitPoints((float) wounds / getHitPoints()));
}
@Override
protected final void doRespawn() {
emitter = feature.getAppearance().createEmitters(GameScreen.getInstance(), getMapX(), getMapY());
}
@Override
protected final void doRemove() {
setEmitters(null);
if (hitPointsSprite != null) {
hitPointsSprite.deallocate();
hitPointsSprite = null;
}
// Clean up brain and remove our occupation status
movement.remove();
if (parent != null) {
parent.numSpawned --;
}
}
private void setParent(Gidrah parent) {
this.parent = parent;
}
private void setEmitters(Emitter[] newEmitter) {
if (emitter != null) {
for (Emitter element : emitter) {
if (element != null) {
element.remove();
}
}
}
emitter = newEmitter;
}
private void setAppearance(int appearanceType, LayersFeature newAppearance) {
if (currentAppearance == appearanceType) {
return;
}
if (currentAppearance == APPEARANCE_DEATH) {
// Don't allow anything to stop or restart death appearance
return;
}
currentAppearance = appearanceType;
forceSetAppearance(newAppearance);
}
private void forceSetAppearance(LayersFeature newAppearance) {
if (newAppearance == null) {
// Only really put this check in here because gidrahs don't have idle anims specified yet
return;
}
appearance = newAppearance;
layersRequest = null;
boolean mirrored = isMirrored();
setEmitters(newAppearance.createEmitters(GameScreen.getInstance(), getMapX(), getMapY()));
newAppearance.createSprites(GameScreen.getInstance(), getMapX(), getMapY(), this);
setMirrored(mirrored);
}
@Override
public LayersFeature getAppearance() {
return appearance;
}
@Override
public void setMirrored(boolean mirrored) {
super.setMirrored(mirrored);
if (bomb != null) {
bomb.setMirrored(mirrored);
}
}
@Override
public void requestSetAppearance(LayersFeature newAppearance) {
layersRequest = newAppearance;
}
@Override
protected LayersFeature getCurrentAppearance() {
return appearance;
}
@Override
public float getZ() {
return feature.getHeight();
}
@Override
protected void calculateScreenPosition() {
super.calculateScreenPosition();
// Add feature offsets too (so angry gidrahs are better lookin')
Point offset = feature.getOffset();
if (offset != null) {
setScreenX(getScreenX() + offset.getX());
setScreenY(getScreenY() + offset.getY());
}
}
@Override
protected final void doUpdate() {
if (emitter != null && appearance != null) {
appearance.updateEmitters(emitter, getMapX(), getMapY());
}
if (hitPointsSprite != null) {
hitPointsSprite.setLocation(getScreenX()+feature.getHitPointsX(), getScreenY()+feature.getHitPointsY());
}
}
@Override
protected final void doTick() {
switch (phase) {
case PHASE_WAIT:
doWaitTick();
break;
case PHASE_SPAWN:
doSpawnTick();
break;
case PHASE_ALIVE:
doAliveTick();
break;
case PHASE_DERES:
doDeResTick();
break;
case PHASE_DYING:
doDyingTick();
break;
default:
assert false;
break;
}
}
private void doWaitTick() {
// Wait until the location we're spawning at is empty
maybeAddToCollisionManager();
checkCollisions(COLLISIONS);
for (int i = 0; i < COLLISIONS.size(); i ++) {
Entity e = COLLISIONS.get(i);
if (e instanceof Gidrah) {
Gidrah g = (Gidrah) e;
if (feature.isFlying() && g.isFlying()) {
// Only worried about other flying gids being in the way
return;
} else if (e.isSolid()) {
return;
}
}
}
// Ok, spawn now
phase = PHASE_SPAWN;
attackTick = 0;
}
private void doDyingTick() {
if (layersRequest != null) {
forceSetAppearance(layersRequest);
layersRequest = null;
}
// Wait for sprite 0 to flag an event 1
if (getEvent() == 1) {
if (feature.getMedal() != null && feature.isBoss()) {
Worm.getGameState().awardMedal(feature.getMedal());
}
remove();
}
}
private void doSpawnTick() {
attackTick ++;
if (attackTick > SPAWN_DURATION) {
phase = PHASE_ALIVE;
attackTick = 0;
setAlpha(calcAlpha());
} else {
setAlpha((int) CosineInterpolator.instance.interpolate(0.0f, calcAlpha(), (float) attackTick / SPAWN_DURATION));
}
}
public boolean isVisibleToTurrets() {
if (feature.isWraith()) {
return xrays || calcAlpha() > WRAITHS_VISIBLE_TO_TURRETS_ALPHA;
} else if (feature.isGidlet()) {
return false;
} else {
return true;
}
}
private int calcAlpha() {
if (feature.isWraith()) {
// Get distance from target and interpolate
int minAlpha = xrays ? WRAITH_XRAY_MIN_ALPHA : WRAITH_MIN_ALPHA;
if (target == null) {
return minAlpha;
}
return (int) LinearInterpolator.instance.interpolate(255.0f, minAlpha, getDistanceTo(target) / WRAITH_VISIBILITY_DISTANCE);
} else {
return 255;
}
}
private float getWeaponRange() {
return OpenLinearInterpolator.instance.interpolate(feature.getMinWeaponRange(), feature.getMaxWeaponRange(), Worm.getGameState().getDifficulty());
}
private void doDeResTick() {
attackTick ++;
if (attackTick > DERES_DURATION) {
phase = PHASE_SPAWN;
attackTick = 0;
setAlpha(calcAlpha());
} else {
setAlpha((int) LinearInterpolator.instance.interpolate(calcAlpha(), 0.0f, (float) attackTick / DERES_DURATION));
}
}
private void doAliveTick() {
if (attackEffect != null && !attackEffect.isActive()) {
attackEffect = null;
}
if (flashTick > 0) {
flashTick --;
if (flashTick == 0) {
setFlash(false);
}
}
if (lastAttackTick > 0) {
lastAttackTick --;
}
if (frozen && Worm.getGameState().getFreezeTick() == 0) {
unfreeze();
} else if (!frozen && Worm.getGameState().getFreezeTick() > 0) {
freeze();
}
if (hitPointsAlpha < targetHitPointsAlpha) {
hitPointsAlpha = Math.min(255, hitPointsAlpha + HITPOINTS_FADE);
if (hitPointsSprite != null) {
hitPointsSprite.setAlpha(hitPointsAlpha);
}
} else if (hitPointsAlphaTick > 0) {
hitPointsAlphaTick --;
if (hitPointsAlphaTick == 0) {
targetHitPointsAlpha = 0;
}
} else if (hitPointsAlpha > targetHitPointsAlpha) {
hitPointsAlpha = Math.max(0, hitPointsAlpha - HITPOINTS_FADE);
if (hitPointsSprite != null) {
hitPointsSprite.setAlpha(hitPointsAlpha);
}
}
if (gidletSafetyTick > 0) {
gidletSafetyTick --;
}
if (tangled > 0) {
tangled --;
}
// Are we frozen?
if (isFrozen()) {
doKnockback();
return;
}
if (stunTick > 0) {
stunTick --;
if (stunTick == 0) {
setAppearance(APPEARANCE_IDLE, feature.getAppearance());
}
doKnockback();
return;
}
if (isSolid() && !feature.isFlying()) {
mapSafetyTick ++;
if (mapSafetyTick > CHECK_MAP_COLLISION_SAFETY) {
mapSafetyTick = 0;
if (!Worm.getGameState().getMap().isClearPX(getBounds(TEMP_BOUNDS))) {
kill(CauseOfDeath.GRID_BUG, false);
System.out.println("Gidrah "+this+" was killed due to a grid bug");
return;
}
}
}
// If we're attacking something, continue attacking:
if (attacking != null) {
if (attacking.isAlive()) {
if (attackTick > 0) {
attackTick --;
if (attackTick <= 0) {
attack(attacking);
}
} else {
// If event=1, do the attack
if (currentAppearance != APPEARANCE_ATTACKING) {
// Er. Stop attacking!
attacking = null;
movement.dontAttack();
normalAppearance();
attackTick = 0;
} else if (getEvent() == 1) {
doAttack();
} else if (getEvent() == 2) {
finishAttack();
} else {
attackTick --;
if (attackTick < ATTACK_ABORT_TIME) {
// Hm, something is awry - stop attacking
attacking = null;
movement.dontAttack();
normalAppearance();
attackTick = 0;
}
}
}
if (layersRequest != null) {
forceSetAppearance(layersRequest);
layersRequest = null;
}
// Don't otherwise move or shoot
return;
} else {
attacking = null;
movement.dontAttack();
// Return appearance to normal
normalAppearance();
attackTick = 0;
}
}
// If we've got no target, find one:
if (target == null || !target.isAttackableByGidrahs()) {
findTarget();
}
if (target == null) {
// Don't move or shoot
setAppearance(APPEARANCE_IDLE, feature.getIdleAppearance());
doKnockback();
return;
}
// Shoot weapon if it's in range of the target AND we're allowed to
if (weaponInstance != null) {
weaponInstance.tick();
if (weaponInstance.isAutomatic()) {
// Aim at the nearest building and shoot it.
fire();
}
}
// Spawner?
GameMap map = Worm.getGameState().getMap();
if (numSpawned < MAX_SPAWNS && feature.getSpawnType() == GidrahFeature.SPAWN_TYPE_CONSTANT && Worm.getGameState().isLevelActive() &&
getTileX() > 2 && getTileY() > 2 && getTileX() <= map.getWidth() - 2 && getTileY() <= map.getHeight() - 2)
{
if (spawnerTick > 0) {
spawnerTick --;
}
if (spawnerTick == 0) {
numSpawned ++;
spawnerTick = (int) feature.getSpawnRate().getValue();
Gidrah spawned = feature.getSpawn().spawn(GameScreen.getInstance(), getTileX(), getTileY(), 0);
spawned.setParent(this);
// Randomize gidlet location
if (feature.getSpawn().isGidlet()) {
Range range = feature.getSpawnDistance();
if (range == null) {
spawned.setLocation(spawned.getMapX() + Util.random(0, MapRenderer.TILE_SIZE - 1), spawned.getMapY() + Util.random(0, MapRenderer.TILE_SIZE - 1));
} else {
float xx = spawned.getMapX() + MapRenderer.TILE_SIZE / 2;
float yy = spawned.getMapY() + MapRenderer.TILE_SIZE / 2;
double angle = Math.random() * Math.PI * 2.0;
float dist = range.getValue();
xx += Math.cos(angle) * dist;
yy += Math.sin(angle) * dist;
spawned.setLocation(xx, yy);
}
}
if (!spawned.isValidMove()) {
spawned.remove();
}
}
}
// Now move if we're not being knocked back
if (layersRequest != null) {
forceSetAppearance(layersRequest);
layersRequest = null;
}
if (kx == 0.0f && ky == 0.0f) {
movement.tick();
if (movement.isMoving()) {
if (feature.isFlying() && bomb == null) {
// Flying gid dropped bomb - wait for event=2 and then go back to normal appearance
if (getEvent() == 2) {
normalAppearance();
}
} else {
setAppearance(APPEARANCE_MOVING, feature.getAppearance());
}
} else {
// Only set idle appearance when not "locked"
if (!locked) {
setAppearance(APPEARANCE_IDLE, feature.getIdleAppearance());
}
}
} else {
setAppearance(APPEARANCE_IDLE, feature.getIdleAppearance());
doKnockback();
}
setAlpha(calcAlpha());
}
/**
* Fire the weapon at a nearby target. If there is one.
*/
public void fire() {
if (!weaponInstance.isReady()) {
return;
}
Building aimAt = (Building) SmartBrainFeature.getInstance().findTarget(this);
if (aimAt != null && getDistanceTo(aimAt) < getWeaponRange()) {
weaponInstance.fire((int) (aimAt.getMapX() + aimAt.getCollisionX()), (int) (aimAt.getMapY() + aimAt.getCollisionY()));
}
}
private void doKnockback() {
if (kx == 0.0f && ky == 0.0f) {
return;
}
float oldX = getMapX();
float oldY = getMapY();
mapSafetyTick = 0;
setLocation(oldX + kx, oldY + ky);
if (!isValidMove()) {
setLocation(oldX, oldY);
kx = 0.0f;
ky = 0.0f;
} else {
// Slow down
kx *= 0.4f;
ky *= 0.4f;
if (Math.abs(kx) < 0.1f) {
kx = 0.0f;
}
if (Math.abs(ky) < 0.1f) {
ky = 0.0f;
}
}
// When we've stopped, tell the movement about it so it can work out how to get back on track
if (kx == 0.0f && ky == 0.0f) {
movement.adjust(getMapX(), getMapY());
}
}
private void knockback(float kx, float ky) {
if (feature.isWraith() || feature.isBoss() || feature.isGidlet() || feature.isFlying()) {
return;
}
if (attacking != null) {
attacking = null;
movement.dontAttack();
}
this.kx += kx;
this.ky += ky;
float kb = (float) Math.sqrt(kx * kx + ky * ky);
if (kb > MAX_KNOCKBACK) {
kx = MAX_KNOCKBACK * kx / kb;
ky = MAX_KNOCKBACK * ky / kb;
}
}
/**
* Determines if our move takes us into another gidrah or solid building or map tile
*/
private boolean isValidMove() {
// Flying gidrahs / ghosts can move anywhere
if (!isSolid() || feature.isFlying()) {
return true;
}
if (!Worm.getGameState().getMap().isClearPX(getBounds(TEMP_BOUNDS))) {
return false;
}
Entity.getCollisions(TEMP_BOUNDS, COLLISIONS);
int n = COLLISIONS.size();
for (int i = 0; i < n; i ++) {
Entity entity = COLLISIONS.get(i);
if (entity == this) {
continue;
}
if (entity.isActive() && entity.canCollide() && entity.isTouching(this) && entity.isSolid()) {
return false;
}
}
return true;
}
/**
* @return the target
*/
public Entity getTarget() {
return target;
}
private void unfreeze() {
if (frozen) {
createIceShards();
for (int i = 0; i < getNumSprites(); i ++) {
getSprite(i).setPaused(false);
}
// Back to normal appearance... unless we're a flying gid
if (feature.isFlying() && feature.getAttackAppearance()!=null) {
attackAppearance();
} else {
normalAppearance();
}
attacking = null;
movement.dontAttack();
frozen = false;
}
}
private void createIceShards() {
Emitter iceShards;
if (feature.isBoss()) {
iceShards = Res.getIceShardsBossEmitter().spawn(GameScreen.getInstance());
} else if (feature.isAngry()) {
iceShards = Res.getIceShardsAngryEmitter().spawn(GameScreen.getInstance());
} else {
iceShards = Res.getIceShardsSmallEmitter().spawn(GameScreen.getInstance());
}
iceShards.setLocation(getX(), getY());
iceShards.setOffset(GameScreen.getSpriteOffset());
}
private void doDeflectAppearance(Bullet bullet) {
Emitter emitter = feature.getDeflectEmitter().spawn(GameScreen.getInstance());
emitter.setAngle(bullet.getHitAngle());
int defXOffset = feature.getDeflectXOffset();
if (defXOffset!=0 && isMirrored()) {
defXOffset*=-1;
}
emitter.setLocation(getX()+defXOffset, getY()+feature.getDeflectYOffset());
emitter.setOffset(GameScreen.getSpriteOffset());
}
@Override
public void onCollisionWithBuilding(Building building) {
if (attacking != null || isFrozen() || feature.isFlying()) {
// Already attacking, or frozen
return;
}
if (building.isMineField() || building.isCrystal()) {
// It's a mine or crystal - ignore it
return;
}
// Wraiths and gidlets ignore most barriers
if (!building.isGidletProof() && (feature.isWraith() || feature.isGidlet())) {
return;
}
if (building.isSlowDown()) {
if (!feature.isBoss() && !feature.isWraith() && !feature.isFlying() && !feature.isGidlet()) {
tangled = 2;
}
return;
}
initAttack(building);
}
/**
* @return true if the gidrah is tangled
*/
public boolean isTangled() {
return tangled > 0;
}
/**
* Package private so we can call it from {@link FlyingMovement}
*/
void initAttack(Building building) {
float xx = (getMapX() + building.getMapX()) / 2;
float yy = (getMapY() + building.getMapY()) / 2;
if (building.shouldShowAttackWarning() && attackEffect == null) {
attackEffect = new BuildingAttackedEffect(building, xx, yy);
attackEffect.setOffset(GameScreen.getSpriteOffset());
attackEffect.spawn(GameScreen.getInstance());
}
ALBuffer roarBuffer = feature.getRoar();
ALBuffer ambientRoarBuffer = feature.getAmbientRoar();
if (roarBuffer != null && ambientRoarBuffer != null) {
if (Game.isSFXEnabled() && lastAttackTick == 0) {
lastAttackTick = 60;
SoundPlayer player = Resources.get("gidrah.player");
SoundEffect roar = player.allocate(roarBuffer, this);
if (roar != null) {
roar.setGain(roarBuffer.getGain() * Game.getSFXVolume() * Worm.calcGain(xx, yy), this);
float twiddle = 1.0f - ((float)Math.random() / 20.0f);
if (feature.isAngry()) {
roar.setPitch(0.8f * roarBuffer.getPitch() * twiddle, this);
} else {
roar.setPitch(roarBuffer.getPitch() * twiddle, this);
}
SoundEffect ambient = player.allocate(ambientRoarBuffer, this);
if (ambient != null) {
ambient.setGain(ambientRoarBuffer.getGain() * Game.getSFXVolume() * Worm.calcGain(xx, yy), this);
if (feature.isAngry()) {
ambient.setPitch(0.8f * ambientRoarBuffer.getPitch() * twiddle, this);
} else {
ambient.setPitch(ambientRoarBuffer.getPitch() * twiddle, this);
}
}
}
}
} else {
if (Game.DEBUG) {
System.out.println("Gidrah "+feature+" has no roar or ambient roar!");
}
}
attack(building);
}
private void attack(Building building) {
if (building.isWorthAttacking()) {
Worm.getGameState().flagHint(Hints.TURRETS_IN_PATH);
}
// Start attack animation. On event id = 1 we perform the attack.
attackAppearance();
attacking = building;
attackTick = 0;
movement.attack();
}
private LayersFeature getAttackAppearance() {
if (feature.getAttackAppearance() != null) {
return feature.getAttackAppearance();
} else {
return isMirrored() ? feature.getAttackAppearanceLeft() : feature.getAttackAppearanceRight();
}
}
/**
* Called when event=1 and there's a building being attacked
*/
private void doAttack() {
Emitter emitter = Res.getBuildingDamageEmitter().spawn(GameScreen.getInstance());
float attackLocationX = (getMapX() + getCollisionX() + attacking.getMapX() + attacking.getCollisionX()) / 2;
float attackLocationY = (getMapY() + getCollisionY() + attacking.getMapY() + attacking.getCollisionY()) / 2;
float gain;
if (attacking.isBarricade()) {
gain = 0.25f;
} else {
gain = attacking.getMaxHitPoints() / 40.0f;
}
SFX.buildingDamaged(attackLocationX, attackLocationY, gain);
emitter.setLocation
(
attackLocationX,
attackLocationY
);
if (attacking.isWorthAttacking()) {
Worm.getGameState().addStat(Stats.ALIEN_ATTACKS_ON_BUILDINGS, 1);
}
// If gidrah is explody, blow it to bits
if (feature.isExploding()) {
BlastEffect effect = new BlastEffect(getMapX() + getCollisionX(), getMapY() + getCollisionY(), 16, 16, feature.getExplosionRadius() * 2.0f, feature.getExplosionRadius() * 2.0f, Res.getExplosionTexture());
effect.setFadeWhenExpanding(true);
effect.setOffset(GameScreen.getSpriteOffset());
effect.spawn(GameScreen.getInstance());
// Damage nearby buildings
ArrayList<Building> buildings = new ArrayList<Building>(Worm.getGameState().getBuildings());
int n = buildings.size();
for (int i = 0; i < n; i ++) {
Building b = buildings.get(i);
if (b.canCollide() && b.isActive()) {
if (b.isTouching(getMapX() + getCollisionX(), getMapY() + getCollisionY(), feature.getExplosionRadius())) {
b.explosionDamage(feature.getStrength(), false);
}
}
}
kill(CauseOfDeath.ATTACK, false);
setEvent(0);
return;
} else {
attacking.damage(feature.getStrength());
if (attacking.isBarricade() || feature.isBoss() || feature.isAngry()) {
// If it's a barricade, or we're a boss, or we're angry, we slowly knock it down. Otherwise we die.
// Slowly weaken angry gidrahs: they get 4 attacks, ish
if ((feature.isWraith() || feature.isAngry()) && !attacking.isBarricade()) {
wounds += Math.max(1, getHitPoints() / 4);
if (wounds > getHitPoints()) {
kill(CauseOfDeath.ATTACK, false);
return;
}
}
} else {
kill(CauseOfDeath.ATTACK, false);
}
}
setEvent(0);
}
/**
* Called when event=2 and there's a building being attacked... if the gidrah hasnt been killed attacking
*/
private void finishAttack() {
if (feature.isDropAttack()) {
return;
}
// Back to normal appearance...
normalAppearance();
// Then wait a bit before calling attack() again and starting the attack animation
attackTick = getAttackDuration();
}
private void normalAppearance() {
setAppearance(APPEARANCE_IDLE, feature.getAppearance());
}
private void attackAppearance() {
setAppearance(APPEARANCE_ATTACKING, getAttackAppearance());
}
private int getAttackDuration() {
if (feature.isBoss()) {
return BOSS_ATTACK_DURATION;
} else {
return (int) LinearInterpolator.instance.interpolate(ATTACK_DURATION, MIN_ATTACK_DURATION, Worm.getGameState().getDifficulty());
}
}
/**
* Find a new target.
*/
public void findTarget() {
Entity newTarget = feature.getBrain().findTarget(this);
if (newTarget != target) {
movement.reset();
target = newTarget;
}
}
/**
* @return the feature
*/
public GidrahFeature getFeature() {
return feature;
}
public void dropBomb(float vx, float vy) {
if (bomb != null) {
bomb.drop(vx, vy);
bomb = null;
if (phase == PHASE_ALIVE) {
attackAppearance();
}
}
}
public void spawnBomb() {
if (feature.getBomb() != null && bomb == null) {
bomb = new Bomb(feature.getBomb(), this);
bomb.spawn(GameScreen.getInstance());
normalAppearance();
}
}
@Override
public String toString() {
return "Gidrah["+System.identityHashCode(this)+","+feature+","+getTileX()+","+getTileY()+"]";
}
@Override
public boolean isAttackableByUnits() {
return !feature.isWraith() && canCollide();
}
public static void resetTotalThinkTime() {
GidrahMovement.resetTotalThinkTime();
}
public static void init() {
GidrahMovement.init();
}
/**
* Lock or unlock appearance
* @param locked
*/
public void setLocked(boolean locked) {
this.locked = locked;
}
public void setHitPoints(int hp) {
this.hitPoints = hp;
}
@Override
public boolean isFlying() {
return feature.isFlying();
}
/**
* Called when a gidrah fails to plot a path to its destination several times in succession. We figure it's never going to by
* this point, which is a bug, really. So we'll kill the gidrah.
*/
public void onMovementFail() {
System.out.println("Gidrah "+this+" failed to move!");
kill(CauseOfDeath.GRID_BUG, false);
}
}