/*
* 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 java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import net.puppygames.applet.Screen;
import net.puppygames.applet.effects.Emitter;
import net.puppygames.applet.effects.EmitterFeature;
import org.lwjgl.util.Point;
import org.lwjgl.util.ReadableColor;
import org.lwjgl.util.Rectangle;
import worm.ClickAction;
import worm.Entity;
import worm.GameStateInterface;
import worm.Hints;
import worm.Mode;
import worm.Res;
import worm.SFX;
import worm.Worm;
import worm.animation.SimpleThingWithLayers;
import worm.buildings.BuildingFeature;
import worm.effects.HitPointsEffect;
import worm.effects.ProximityEffect;
import worm.features.LayersFeature;
import worm.features.ResearchFeature;
import worm.screens.GameScreen;
import com.shavenpuppy.jglib.interpolators.LinearInterpolator;
import com.shavenpuppy.jglib.util.Util;
/**
* $Id: Building.java,v 1.118 2010/11/02 17:21:33 foo Exp $
* A building
* @author $Author: foo $
* @version $Revision: 1.118 $
*/
public abstract class Building extends Entity {
private static final long serialVersionUID = 1L;
private static final int MAX_SHIELDS = 4;
public static final float PROXIMITY_DISTANCE = 64.0f;
private static final int BOSS_ATTACK_DAMAGE = 100;
private static final int SELL_TICK_TIME = 600; // you get 10 seconds to sell a building after it's built and you'll get 100% of your money back
private static final int CLOAK_MIN_ALPHA = 32;
private static final int CLOAK_MAX_ALPHA = 128;
private static final int CLOAK_MIN_CYCLE = 120;
private static final int CLOAK_MAX_CYCLE = 240;
/*
* Phases
*/
private static final int PHASE_NORMAL = 0;
private static final int PHASE_DYING = 1;
private static final int PHASE_DEAD = 2;
private static final int PHASE_GHOST = 3;
/*
* Shield types
*/
private static final int SHIELD_NONE = 0;
private static final int SHIELD_FORCEFIELD = 1;
private static final int SHIELD_INVULN = 2;
/** Feature */
private final BuildingFeature feature;
/** Phase */
private int phase = PHASE_NORMAL;
/** Hitpoints */
private int hitPoints;
/** Shielded */
private boolean shielded;
/** Flash ticker */
private int flashTick;
/** Hovered */
private boolean hovered;
/** Proximity */
private int reactors, scanners, batteries, coolingTowers, shields, factories, bases, capacitors, spawners, autoLoaders, warehouses, collectors, crystals, turrets, cloaks;
/** More sprites */
private SimpleThingWithLayers shieldedLayers, hoveredLayers;
/** Hitpoints effect */
private transient HitPointsEffect hitPointsEffect;
/** Emitters */
private transient Emitter[] emitter, shieldedEmitter, hoveredEmitter;
/** Ghost effects */
private transient ArrayList<ProximityEffect> proximityEffects;
/** Burning & flames */
private transient Emitter damagedEmitter;
/** Shield type */
private int shieldType = SHIELD_NONE;
/** What buildings we've added */
private final Set<Building> proximal = new HashSet<Building>();
/** What this building cost to build */
private int cost;
/** Sell tick */
private int sellTick;
/** Cloaking tick */
private int cloakTick, cloakCycle;
/** Exploding bullets that have hit us */
private ArrayList<Bullet> explodingBullets;
/**
* C'tor
*/
protected Building(BuildingFeature feature, boolean ghost) {
this.feature = feature;
if (ghost) {
phase = PHASE_GHOST;
}
hitPoints = getMaxHitPoints();
}
/**
* @param cost the cost to set
*/
public void setCost(int cost) {
this.cost = cost;
}
/**
* @return the cost
*/
public int getCost() {
return cost;
}
/**
* Creates the proximity effects for this building
* @param color The color to use
*/
protected void createProximityEffects(ReadableColor color) {
finishProximityEffects();
ArrayList<Building> buildings = getAllAffectedBuildings();
int n = buildings.size();
proximityEffects = null;
proximityEffects = new ArrayList<ProximityEffect>(n);
for (int i = 0; i < n; i ++) {
Building target = buildings.get(i);
ProximityEffect fx = new ProximityEffect(color, PROXIMITY_DISTANCE, target.getMapX() + target.getCollisionX(), target.getMapY() + target.getCollisionY());
fx.setOffset(GameScreen.getSpriteOffset());
fx.spawn(GameScreen.getInstance());
fx.setTarget(getMapX() + getCollisionX(), getMapY() + getCollisionY());
fx.tick();
proximityEffects.add(fx);
}
}
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if (proximityEffects != null) {
for (ProximityEffect pe : proximityEffects) {
pe.setVisible(visible);
}
}
}
/**
* Apply proximity adjustment
* @param delta
*/
protected final void doProximityAdjustment(int delta) {
if (phase == PHASE_GHOST) {
return;
}
ArrayList<Building> buildings = getNearbyAffectedBuildings();
int n = buildings.size();
for (int i = 0; i < n; i ++) {
Building target = buildings.get(i);
boolean tweak = false;
if (delta == 1) {
if (!proximal.contains(target)) {
this.proximal.add(target);
target.proximal.add(this);
tweak = true;
}
} else {
if (proximal.contains(target)) {
this.proximal.remove(target);
target.proximal.remove(this);
tweak = true;
}
}
if (tweak) {
this.adjustProximity(target, delta);
target.adjustProximity(this, delta);
}
}
}
protected final ArrayList<Building> getNearbyAffectedBuildings() {
ArrayList<Building> buildings = getNearbyBuildings();
return getAffectedBuildings(buildings);
}
protected final ArrayList<Building> getAllAffectedBuildings() {
ArrayList<Building> buildings = Worm.getGameState().getBuildings();
return getAffectedBuildings(buildings);
}
private ArrayList<Building> getAffectedBuildings(ArrayList<Building> buildings) {
int n = buildings.size();
ArrayList<Building> ret = new ArrayList<Building>();
for (int i = 0; i < n; i ++) {
Building target = buildings.get(i);
if (target != this && isAffectedBy(target) && target.isAffectedBy(this)) {
ret.add(target);
}
}
return ret;
}
protected final ArrayList<Building> getNearbyBuildings() {
ArrayList<Building> buildings = Worm.getGameState().getBuildings();
int n = buildings.size();
ArrayList<Building> ret = new ArrayList<Building>();
for (int i = 0; i < n; i ++) {
Building target = buildings.get(i);
if (target != this && (target.isAlive() || target.isGhost()) && this.getDistanceTo(target.getMapX() + target.getCollisionX(), target.getMapY() + target.getCollisionY()) <= PROXIMITY_DISTANCE) {
ret.add(target);
}
}
return ret;
}
/**
* Apply ghost proximity adjustment - this is called every tick
* @param delta
*/
protected final void calcGhostProximity() {
assert phase == PHASE_GHOST;
// Reset
reactors = scanners = batteries = coolingTowers = shields = factories = bases = capacitors = spawners = autoLoaders = warehouses = collectors = crystals = turrets = 0;
ArrayList<Building> buildings = Worm.getGameState().getBuildings();
int n = buildings.size();
for (int i = 0; i < n; i ++) {
Building target = buildings.get(i);
if (target == this) {
continue;
}
if (target.isAlive() && isAffectedBy(target) && this.getDistanceTo(target.getX(), target.getY()) <= PROXIMITY_DISTANCE) {
assert target.isAffectedBy(this);
target.adjustProximity(this, 1);
}
}
}
protected boolean isAffectedBy(Building building) {
return feature.isAffectedBy(building.feature);
}
/**
* Adjust the appropriate proximity value of the target building by delta
* @param target
* @param delta
*/
protected void adjustProximity(Building target, int delta) {
}
/**
* Finish proximity effects
*/
private void finishProximityEffects() {
if (proximityEffects != null) {
for (Iterator<ProximityEffect> i = proximityEffects.iterator(); i.hasNext(); ) {
ProximityEffect e = i.next();
e.finish();
}
proximityEffects = null;
}
}
/**
* @return true if this building is a ghost
*/
public final boolean isGhost() {
return phase == PHASE_GHOST;
}
/**
* @return true if this building is a minefield
*/
public boolean isMineField() {
return false;
}
public boolean isCrystal() {
return false;
}
/**
* @return true if this is a reactor
*/
public boolean isReactor() {
return false;
}
/**
* @return true if this building is a barricade
*/
public boolean isBarricade() {
return false;
}
/**
* @return true if this is the "slowdown" substance ("tangleweb")
*/
public boolean isSlowDown() {
return false;
}
/**
* @return true if this building blocks gidlets
*/
public boolean isGidletProof() {
return true;
}
@Override
public boolean isShootable() {
// Player can't shoot buildings
return false;
}
@Override
public boolean isSolid() {
return !isMineField();
}
@Override
public final boolean isClickable() {
return phase == PHASE_NORMAL;
}
@Override
public final boolean isHoverable() {
return isAlive();
}
/**
* @return true if the building is active, and solid
*/
public boolean isAlive() {
return isActive() && phase == PHASE_NORMAL;
}
@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 boolean canCollide() {
return isActive() && phase == PHASE_NORMAL;
}
/**
* Can we build this building on top of the target?
* @param target A solid, collidable, active entity
* @return true if we can
*/
public boolean canBuildOnTopOf(Entity target) {
if (target instanceof Building) {
return feature.canBuildOnTopOf(((Building) target).feature);
} else {
return
!(target instanceof Gidrah)
|| target instanceof Gidrah
&& !((Gidrah) target).getFeature().isBoss();
}
}
public boolean canBuild() {
return true;
}
/* (non-Javadoc)
* @see worm.Entity#isRound()
*/
@Override
public boolean isRound() {
return false;
}
@Override
public float getRadius() {
return 0.0f;
}
@Override
public void onCollision(Entity entity) {
entity.onCollisionWithBuilding(this);
}
/**
* @return the feature this building is based on
*/
public final BuildingFeature getFeature() {
return feature;
}
/**
* Damage the building
*/
public final void damage(int amount) {
if (!isAlive() || phase != PHASE_NORMAL || shielded && amount < BOSS_ATTACK_DAMAGE) {
return;
}
cancelUndo();
createHitPointsEffect();
int armour = Math.min(MAX_SHIELDS, shields + (Worm.getGameState().isResearched(ResearchFeature.NANOHARDENING) ? 1 : 0));
int damage = Math.max(0, amount - armour);
if (damage == 0) {
return;
}
hitPoints = Math.max(0, hitPoints - damage);
if (isWorthAttacking()) {
Worm.getGameState().onBuildingDamaged();
}
updateDamageIndicators();
setFlash(true);
flashTick += 4;
if (hitPoints == 0) {
destroy(false);
} else {
onDamaged();
}
}
protected final void updateDamageIndicators() {
// No emitters for barricades
if (isBarricade() || isMineField()) {
return;
}
if (hitPointsEffect != null) {
hitPointsEffect.setHitPoints(hitPoints);
}
if (damagedEmitter != null) {
damagedEmitter.finish();
damagedEmitter = null;
}
if (hitPoints <= getMaxHitPoints() * 0.33f) {
// Flames!
damagedEmitter = feature.getFlamesEmitter().spawn(GameScreen.getInstance());
Point offset = feature.getFlamesOffset();
if (offset == null) {
offset = new Point((int) getCollisionX(), (int) (getCollisionY() * 0.33f));
}
damagedEmitter.setLocation(getMapX() + offset.getX(), getMapY() + offset.getY());
damagedEmitter.setFloor(feature.getFloor() + getMapY());
if (hitPoints > 0) {
onDestructionImminent();
}
} else if (hitPoints <= getMaxHitPoints() * 0.66f) {
// Smoke!
damagedEmitter = Res.getBuildingSmokeEmitter().spawn(GameScreen.getInstance());
damagedEmitter.setLocation(getMapX() + getCollisionX(), getMapY() + getCollisionY() * 0.5f);
damagedEmitter.setFloor(feature.getFloor() + getMapY());
}
}
/**
* Called when the building is down to 1/3rd its hitpoints
*/
protected void onDestructionImminent() {}
/**
* Called if a building takes some damage but isn't destroyed
*/
protected void onDamaged() {}
/**
* Called if a building gains hitpoints
*/
protected void onRepaired() {}
@Override
public void disruptorDamage(int amount, boolean friendly) {
if (isBarricade() || isMineField() || Worm.getGameState().isResearched(ResearchFeature.SHIELDING)) {
return;
}
if (friendly) {
amount = 1;
Worm.getGameState().flagHint(Hints.DISRUPTOR);
}
damage(amount);
}
@Override
public void explosionDamage(int damageAmount, boolean friendly) {
if (isMineField()) {
return;
}
// Always just 1 point
damage(1);
if (friendly) {
Worm.getGameState().flagHint(Hints.EXPLOSIVES);
}
}
/**
* Destroy the building
*/
public void destroy(boolean deliberate) {
if (!isActive() || phase != PHASE_NORMAL) {
return;
}
setFlash(false);
doBuildingDestroy();
LayersFeature appearance = feature.getDeathAppearance();
if (appearance != null) {
removeSpecialEffects();
phase = PHASE_DYING;
appearance.createSprites(GameScreen.getInstance(), getX(), getY(), this);
setEmitters(appearance.createEmitters(GameScreen.getInstance(), getMapX(), getMapY()));
if (damagedEmitter != null) {
damagedEmitter.remove();
damagedEmitter = null;
}
} else {
// No death appearance - just destroy and remove
remove();
}
if (!deliberate) {
float gain;
if (isBarricade()) {
gain = 0.25f;
} else {
gain = getMaxHitPoints() / 40.0f;
}
SFX.buildingDestroyed(getX(), getY(), gain);
}
Worm.getGameState().onBuildingDestroyed(this, deliberate);
}
private void maybeRemoveEmitters() {
if (emitter != null) {
for (Emitter element : emitter) {
if (element != null) {
element.remove();
}
}
emitter = null;
}
}
/**
* Removes any existing sprites and emitters if our appearance has changed
*/
protected void updateAppearance() {
if (!isAlive()) {
return;
}
LayersFeature appearance = feature.getAppearance(this);
appearance.createSprites(GameScreen.getInstance(), getX(), getY(), this);
setEmitters(appearance.createEmitters(GameScreen.getInstance(), getMapX(), getMapY()));
updateDamageIndicators();
}
public void setAppearance(LayersFeature newAppearance) {
}
protected final void setEmitters(Emitter[] newEmitters) {
maybeRemoveEmitters();
this.emitter = newEmitters;
float ey = getMapY() + feature.getFloor();
if (emitter != null) {
for (Emitter element : emitter) {
if (element != null) {
element.setFloor(ey);
}
}
}
}
private void removeSpecialEffects() {
maybeRemoveEmitters();
finishProximityEffects();
removeShieldEffects();
removeHoveredEffects();
doRemoveSpecialEffects();
if (damagedEmitter != null) {
damagedEmitter.remove();
damagedEmitter = null;
}
if (hitPointsEffect != null) {
hitPointsEffect.finish();
hitPointsEffect = null;
}
}
protected void doRemoveSpecialEffects() {}
@Override
protected final void doRemove() {
doProximityAdjustment(-1);
removeSpecialEffects();
doBuildingRemove();
}
protected void doBuildingRemove() {
}
protected void doBuildingDestroy() {
}
private void removeShieldEffects() {
if (shieldedLayers != null) {
shieldedLayers.remove();
shieldedLayers = null;
}
if (shieldedEmitter != null) {
for (Emitter element : shieldedEmitter) {
if (element != null) {
element.remove();
}
}
shieldedEmitter = null;
}
shieldType = SHIELD_NONE;
}
private void removeHoveredEffects() {
if (hoveredLayers != null) {
hoveredLayers.remove();
hoveredLayers = null;
}
if (hoveredEmitter != null) {
for (Emitter element : hoveredEmitter) {
if (element != null) {
element.remove();
}
}
hoveredEmitter = null;
}
}
public void createGhostProximityEffects() {
createProximityEffects(ReadableColor.RED);
}
@Override
protected final void doSpawn() {
if (phase == PHASE_GHOST) {
createGhostProximityEffects();
doGhostSpawn();
return;
}
// Create emitters
setEmitters(feature.getAppearance(this).createEmitters(GameScreen.getInstance(), getMapX(), getMapY()));
sellTick = SELL_TICK_TIME;
addProximities();
doBuildingSpawn();
}
protected void doBuildingSpawn() {}
protected void doGhostSpawn() {}
public final void onBuild() {
finishProximityEffects();
doProximityAdjustment(1);
Worm.getGameState().addAvailableStock(getFeature(), -1);
doOnBuild();
}
protected void doOnBuild() {}
private void addProximities() {
if (isMineField() || isBarricade()) {
return;
}
createProximityEffects(ReadableColor.CYAN);
}
@Override
protected void createSprites(Screen screen) {
feature.getAppearance(this).createSprites(screen, getX(), getY(), this);
}
private void createHitPointsEffect() {
if (!shouldShowAttackWarning()) {
return;
}
if (hitPointsEffect == null) {
hitPointsEffect = new HitPointsEffect(this);
hitPointsEffect.spawn(GameScreen.getInstance());
hitPointsEffect.setHitPoints(getHitPoints());
}
hitPointsEffect.reset();
}
@Override
protected final void doTick() {
switch (phase) {
case PHASE_GHOST:
calcGhostProximity();
doGhostTick();
return;
case PHASE_DEAD:
// Wait for death to finish. This is an event 2 from sprite 0.
if (getEvent() == 2) {
remove();
}
return;
case PHASE_DYING:
// Wait to explode. This is an event 1 from sprite 0.
if (getEvent() == 1) {
phase=PHASE_DEAD;
}
return;
case PHASE_NORMAL:
if (GameScreen.getInstance().isBlocked()) {
finishProximityEffects();
} else if (shouldShowAttackWarning() && !isGhost()) {
if (hovered) {
createHitPointsEffect();
} else {
if (hitPointsEffect != null) {
hitPointsEffect.setShow(false);
}
finishProximityEffects();
}
}
if (sellTick > 0 && Worm.getGameState().isLevelStarted()) {
sellTick --;
}
if (hitPointsEffect != null && !hitPointsEffect.isActive()) {
hitPointsEffect = null;
}
if (shielded && Worm.getGameState().getShieldTick() == 0) {
shielded = false;
updateShieldAppearance();
} else if (!shielded && Worm.getGameState().getShieldTick() > 0) {
shielded = true;
updateShieldAppearance();
}
if (flashTick > 0) {
flashTick --;
if (flashTick == 0) {
setFlash(false);
}
}
if (isCloaked()) {
cloakTick ++;
if (cloakCycle == 0) {
cloakCycle = Util.random(CLOAK_MIN_CYCLE, CLOAK_MAX_CYCLE);
}
if (cloakTick > cloakCycle) {
cloakTick = 0;
}
double angle = (Math.PI * 2.0 * cloakTick) / cloakCycle;
setAlpha((int) ((Math.cos(angle) + 1.0) * 0.5 * (CLOAK_MAX_ALPHA - CLOAK_MIN_ALPHA) + CLOAK_MIN_ALPHA));
}
doBuildingTick();
break;
default:
assert false;
break;
}
}
protected void doBuildingTick() {}
protected void doGhostTick() {}
/**
* Repair this building with a fraction of a shield
*/
public void repair() {
if (phase == PHASE_NORMAL && hitPoints < feature.getHitPoints()) {
hitPoints = Math.min(getMaxHitPoints(), hitPoints + BuildingFeature.HITPOINTS_DIVISOR);
updateAppearance();
spawnRepairEmitter();
onRepaired();
}
}
/**
* @return true if this building has been damaged
*/
public final boolean isDamaged() {
return hitPoints < getMaxHitPoints();
}
private void updateShieldAppearance() {
if (phase != PHASE_NORMAL) {
return;
}
// Invulnerability takes precedence
if (shielded) {
if (shieldedLayers == null && feature.getShieldedAppearance() != null && shieldType != SHIELD_INVULN) {
shieldedLayers = new SimpleThingWithLayers(GameScreen.getInstance());
feature.getShieldedAppearance().createSprites(GameScreen.getInstance(), shieldedLayers);
shieldedEmitter = feature.getShieldedAppearance().createEmitters(GameScreen.getInstance(), getMapX(), getMapY());
shieldType = SHIELD_INVULN;
}
} else if (shields > 0) {
if (shieldedLayers == null && feature.getForcefieldAppearance() != null && shieldType != SHIELD_FORCEFIELD) {
shieldedLayers = new SimpleThingWithLayers(GameScreen.getInstance());
feature.getForcefieldAppearance().createSprites(GameScreen.getInstance(), shieldedLayers);
shieldedEmitter = feature.getForcefieldAppearance().createEmitters(GameScreen.getInstance(), getMapX(), getMapY());
shieldType = SHIELD_FORCEFIELD;
}
} else {
removeShieldEffects();
}
}
@Override
public final void addToGameState(GameStateInterface gsi) {
if (phase == PHASE_GHOST) {
return;
}
gsi.addToBuildings(this);
}
@Override
public final void removeFromGameState(GameStateInterface gsi) {
if (phase == PHASE_GHOST) {
return;
}
gsi.removeFromBuildings(this);
}
@Override
public void onCollisionWithBullet(Bullet bullet) {
if (bullet.getSource() == this) {
return;
}
if (!bullet.isDangerousToBuildings()) {
return;
}
if (isBarricade() || isMineField()) {
return;
}
if (explodingBullets != null && explodingBullets.contains(bullet)) {
return;
}
if (explodingBullets == null) {
explodingBullets = new ArrayList<Bullet>(8);
}
explodingBullets.add(bullet);
damage(bullet.getDamage());
bullet.onHit(true, this);
}
/**
* @return Returns the hitPoints remaining
*/
public int getHitPoints() {
return hitPoints;
}
/**
* Repairs the building to full health
*/
public void repairFully() {
if (phase != PHASE_NORMAL || isMineField() || isBarricade() || isGhost()) {
return;
}
spawnRepairEmitter();
if (hitPoints < getMaxHitPoints()) {
hitPoints = getMaxHitPoints();
updateAppearance();
onRepaired();
}
}
@Override
public LayersFeature getMousePointer(boolean clicked) {
if (canSell() && Worm.getGameState().isSelling()) {
return Res.getMousePointerSellOn();
} else {
return super.getMousePointer(clicked);
}
}
public int getMaxHitPoints() {
return feature.getHitPoints();
}
@Override
protected final void onSetLocation() {
if (emitter != null) {
feature.getAppearance(this).updateEmitters(emitter, getMapX(), getMapY());
}
if (proximityEffects != null) {
int n = proximityEffects.size();
for (int i = 0; i < n; i ++) {
ProximityEffect fx = proximityEffects.get(i);
fx.setTarget(getMapX() + getCollisionX(), getMapY() + getCollisionY());
}
}
doBuildingSetLocation();
}
protected void doBuildingSetLocation() {}
@Override
protected final void doUpdate() {
if (shieldedLayers != null) {
for (int i = 0; i < shieldedLayers.getSprites().length; i ++) {
shieldedLayers.getSprite(i).setLocation(getScreenX(), getScreenY());
}
}
if (hoveredLayers != null) {
for (int i = 0; i < hoveredLayers.getSprites().length; i ++) {
hoveredLayers.getSprite(i).setLocation(getScreenX(), getScreenY());
}
}
doBuildingUpdate();
}
protected void doBuildingUpdate() {}
@Override
protected LayersFeature getCurrentAppearance() {
switch (phase) {
case PHASE_NORMAL:
return feature.getAppearance(this);
case PHASE_DYING:
return feature.getDeathAppearance();
default:
return null;
}
}
/**
* @return true if this is a city
*/
public boolean isCity() {
return false;
}
/**
* @return true if this is apparently a high-value building (a target for the rank and file)
*/
public boolean isApparentlyValuable() {
return isCity();
}
protected boolean dontShowLabel() {
return false;
}
@Override
public void onHovered(int mode) {
if (phase != PHASE_NORMAL) {
return;
}
if (proximityEffects == null) {
createProximityEffects(Res.GREEN);
}
net.puppygames.applet.effects.SFX.buttonHover();
hovered = true;
if (hoveredLayers == null && feature.getHoveredAppearance() != null) {
hoveredLayers = new SimpleThingWithLayers(GameScreen.getInstance());
feature.getHoveredAppearance().createSprites(GameScreen.getInstance(), hoveredLayers);
hoveredEmitter = feature.getHoveredAppearance().createEmitters(GameScreen.getInstance(), getMapX(), getMapY());
}
}
@Override
public int onClicked(int mode) {
if (mode == Mode.MODE_SELL) {
if (canSell()) {
Worm.getGameState().sell(this);
return ClickAction.CONSUME;
} else {
return ClickAction.IGNORE;
}
} else {
return super.onClicked(mode);
}
}
@Override
public void onLeave(int mode) {
if (phase != PHASE_NORMAL) {
return;
}
hovered = false;
removeHoveredEffects();
}
@Override
public boolean isAttackableByGidrahs() {
return isActive() && phase == PHASE_NORMAL;
}
/**
* Used by gidrahs to determine if it's worth targeting this building for attack
* @return
*/
public boolean isWorthAttacking() {
return true;
}
/**
* @return true if this building should have the "help I'm being attacked" effect
*/
public boolean shouldShowAttackWarning() {
return isAttackableByGidrahs();
}
public int getReactors() {
return reactors + Worm.getGameState().getReactorBoost();
}
public int getCoolingTowers() {
return coolingTowers + Worm.getGameState().getCoolingBoost();
}
public int getBatteries() {
return batteries + Worm.getGameState().getBatteryBoost();
}
public int getShields() {
return shields + Worm.getGameState().getShieldBoost();
}
public int getScanners() {
return scanners + Worm.getGameState().getScannerBoost();
}
public int getCrystals() {
return crystals;
}
public int getCapacitors() {
return capacitors;
}
public int getBases() {
return bases;
}
public int getFactories() {
return factories;
}
public int getSpawners() {
return spawners;
}
public int getAutoLoaders() {
return autoLoaders;
}
public int getCollectors() {
return collectors;
}
public int getWarehouses() {
return warehouses;
}
public int getTurrets() {
return turrets;
}
public int getCloaks() {
return cloaks;
}
private boolean canAdjustProximity() {
return phase == PHASE_NORMAL || phase == PHASE_GHOST;
}
public void addReactors(int n) {
if (!canAdjustProximity()) {
return;
}
reactors += n;
assert reactors >= 0 : this;
}
public void addCoolingTowers(int n) {
if (!canAdjustProximity()) {
return;
}
coolingTowers += n;
if (n > 0) {
Worm.getGameState().flagHint(Hints.STACK);
}
assert coolingTowers >= 0 : this;
}
public void addBatteries(int n) {
if (!canAdjustProximity()) {
return;
}
batteries += n;
if (n > 0) {
Worm.getGameState().flagHint(Hints.STACK);
}
assert batteries >= 0 : this;
}
public void addFactories(int n) {
if (!canAdjustProximity()) {
return;
}
factories += n;
assert factories >= 0 : this;
}
public void addBases(int n) {
if (!canAdjustProximity()) {
return;
}
bases += n;
assert bases >= 0 : this;
}
public void addCrystals(int n) {
if (!canAdjustProximity()) {
return;
}
crystals += n;
assert crystals >= 0 : this;
}
public void addShields(int n) {
if (!canAdjustProximity()) {
return;
}
shields += n;
assert shields >= 0 : this;
updateShieldAppearance();
}
public void addShieldBoost() {
if (!canAdjustProximity()) {
return;
}
updateShieldAppearance();
}
public void addScanners(int n) {
if (!canAdjustProximity()) {
return;
}
scanners += n;
if (n > 0) {
Worm.getGameState().flagHint(Hints.STACK);
}
assert scanners >= 0 : this;
}
public void addAutoLoaders(int n) {
if (!canAdjustProximity()) {
return;
}
autoLoaders += n;
assert autoLoaders >= 0 : this;
}
public void addCollectors(int n) {
if (!canAdjustProximity()) {
return;
}
collectors += n;
assert collectors >= 0 : this;
}
public void addWarehouses(int n) {
if (!canAdjustProximity()) {
return;
}
warehouses += n;
assert warehouses >= 0 : this;
}
public void addCapacitors(int n) {
if (!canAdjustProximity()) {
return;
}
capacitors += n;
assert capacitors >= 0 : this;
}
public void addSpawners(int n) {
if (!canAdjustProximity()) {
return;
}
spawners += n;
assert spawners >= 0 : this;
}
public void addTurrets(int n) {
if (!canAdjustProximity()) {
return;
}
turrets += n;
assert turrets >= 0 : this;
}
public void addCloaks(int n) {
if (!canAdjustProximity()) {
return;
}
cloaks += n;
assert cloaks >= 0 : this;
if (cloaks == 0) {
setAlpha(255);
}
}
/**
* @return true if the building is cloaked
*/
public boolean isCloaked() {
return cloaks > 0;
}
/**
* Called at the end of the level on active buildings.
*/
public void onEndLevel() {
}
@Override
public int crush() {
remove();
return 0;
}
/**
* @return true if we're being hovered by the mouse
*/
public boolean isHovered() {
return hovered;
}
private void spawnRepairEmitter() {
EmitterFeature ef = feature.getRepairEmitter();
if (ef != null) {
Emitter e = ef.spawn(GameScreen.getInstance());
e.setLocation(getX(), getY()+(float)(getBounds(null).getHeight()*0.25));
e.setOffset(GameScreen.getSpriteOffset());
}
}
// chaz hack! test if offset will fix yoffset prob
@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());
}
}
/**
* Cancel the grace period after building to get 100% of your money back
*/
public final void cancelUndo() {
sellTick = 0;
}
/**
* @return the value of this building if it's sold.
*/
public int getSalePrice() {
int value = (int) LinearInterpolator.instance.interpolate(0.0f, cost * (sellTick > 0 ? 1.0f : 0.4f), (float) getHitPoints() / (float) getMaxHitPoints());
value /= 50;
value *= 50;
return value;
}
/**
* Called when a Bezerk powerup is activated
*/
public void onBezerk() {
}
@Override
public String toString() {
return getClass().getName()+"[phase="+phase+", "+getTileX()+", "+getTileY()+"]";
}
@Override
public boolean laserDamage(int amount) {
damage(amount);
return true;
}
public boolean canSell() {
return isActive() && phase == PHASE_NORMAL;
}
/**
* @return the building's agitation factor
*/
public float getAgitation() {
return feature.getAgitation();
}
}