/*
* 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.Emitter;
import net.puppygames.applet.effects.LabelEffect;
import org.lwjgl.util.Color;
import org.lwjgl.util.Rectangle;
import worm.ClickAction;
import worm.Entity;
import worm.GameMap;
import worm.GameStateInterface;
import worm.Layers;
import worm.MapRenderer;
import worm.Res;
import worm.Tile;
import worm.Worm;
import worm.WormGameState;
import worm.effects.SaucerEffect;
import worm.features.LayersFeature;
import worm.powerups.PowerupFeature;
import worm.screens.GameScreen;
import com.shavenpuppy.jglib.sound.SoundEffect;
import com.shavenpuppy.jglib.util.Util;
/**
* $Id: Saucer.java,v 1.53 2010/10/13 23:04:54 foo Exp $
* The bonus saucer!
*
* @author $Author: foo $
* @version $Revision: 1.53 $
*/
public class Saucer extends Entity {
private static final long serialVersionUID = 1L;
private static final int MIN_SAUCER_DURATION = 200;
private static final int SAUCER_DURATION = 900;
private static final int SAUCER_DURATION_PER_LEVEL = 10;
private static final int MIN_DROPPED_SAUCER_DURATION = 100;
private static final int DROPPED_SAUCER_DURATION = 600;
private static final int DROPPED_SAUCER_DURATION_PER_LEVEL = 6;
private static final int POWERUP_LABEL_OFFSET = 6;
private transient SoundEffect soundEffect;
/** Phase */
private int phase;
private static final int PHASE_APPEAR = 0;
private static final int PHASE_NORMAL = 1;
private static final int PHASE_DEAD = 2;
/** Tick */
private int tick;
/** Emitters */
private transient Emitter[] emitter;
/** Powerup */
private PowerupFeature powerup;
/** Dropped by a gidrah */
private boolean dropped;
/**
* C'tor
*/
public Saucer() {
powerup = Worm.getGameState().getRandomPowerup();
}
public Saucer(float x, float y) {
dropped = true;
setLocation(x, y);
powerup = Worm.getGameState().getCrappyPowerup();
}
@Override
public boolean isShootable() {
return false;
}
@Override
public boolean isClickable() {
return phase == PHASE_NORMAL;
}
@Override
protected void doSpawn() {
// Hint about usage
Worm.getGameState().flagHint(powerup.getHint());
// Choose the powerup to use
if (!dropped) {
// Find an empty spot on the map;
int count = 0;
boolean ok = false;
ArrayList<Entity> entities = Worm.getGameState().getEntities();
int n = entities.size();
int x, y;
GameMap map = Worm.getGameState().getMap();
outer: while (++count < 1000 && !ok) {
x = Util.random(MapRenderer.FADE_SIZE, map.getWidth() - MapRenderer.FADE_SIZE);
y = Util.random(MapRenderer.FADE_SIZE, map.getHeight() - MapRenderer.FADE_SIZE);
for (int z = 0; z < GameMap.LAYERS; z ++) {
Tile t = map.getTile(x, y, z);
if (t != null && (t.isSolid() || t.isImpassable())) {
continue outer;
}
}
setLocation(x * MapRenderer.TILE_SIZE + MapRenderer.TILE_SIZE / 2, y * MapRenderer.TILE_SIZE + MapRenderer.TILE_SIZE / 2);
// Check no solid entity in the way
// TODO: use quadtree
for (int i = 0; i < n; i ++) {
Entity entity = entities.get(i);
if (entity != this && entity.isActive() && entity.isSolid() && entity.canCollide() && entity.isTouching(this)) {
continue outer;
}
}
ok = true;
}
if (!ok) {
// Didn't find one
remove();
return;
}
}
init();
}
@Override
protected void createSprites(Screen screen) {
if (powerup.getAppearance() == null) {
System.out.println(powerup+" has no appearance");
} else {
powerup.getAppearance().createSprites(screen, this);
}
}
@Override
protected LayersFeature getCurrentAppearance() {
return powerup.getAppearance();
}
/**
* Initialise sound effects and emitters
*/
private void init() {
if (!dropped) {
// Start the sound effect
soundEffect = Game.allocateSound(Res.getSaucerSound(), Worm.calcLoudGain(getMapX() + getCollisionX(), getMapY() + getCollisionY()), 1.0f, this);
// And special effect
SaucerEffect fx = new SaucerEffect(this);
fx.spawn(GameScreen.getInstance());
}
// Create emitters
emitter = powerup.getAppearance().createEmitters(GameScreen.getInstance(), getMapX(), getMapY());
}
/* (non-Javadoc)
* @see invaders.Entity#doRespawn()
*/
@Override
protected void doRespawn() {
init();
}
/* (non-Javadoc)
* @see invaders.Entity#canCollide()
*/
@Override
public boolean canCollide() {
return phase == PHASE_NORMAL;
}
/* (non-Javadoc)
* @see invaders.Entity#onCollision(invaders.Entity)
*/
@Override
public void onCollision(Entity entity) {
entity.onCollisionWithSaucer(this);
}
private void kill() {
WormGameState gameState = Worm.getGameState();
String msg = powerup.getTitle();
Color labelColorStart = powerup.getLabelColorStart();
Color labelColorEnd = powerup.getLabelColorEnd();
LabelEffect le = new LabelEffect(net.puppygames.applet.Res.getSmallFont(), msg, labelColorStart, labelColorEnd, 60, 55);
le.setLocation(getMapX(), getMapY() + POWERUP_LABEL_OFFSET );
le.setVelocity(0.0f, 0.8f);
le.setAcceleration(0.0f, -0.0075f);
le.setLayer(Layers.HUD);
le.spawn(GameScreen.getInstance());
if (powerup.isCollectable()) {
// Save it for later
gameState.addPowerup(powerup, true);
} else {
// Immediate activation (money)
gameState.activatePowerup(powerup);
Game.allocateSound(powerup.getCollectSound());
}
stopSound();
if (powerup.getCollectAppearance() == null) {
// Just remove - no death appearance yet
remove();
} else {
powerup.getCollectAppearance().createSprites(GameScreen.getInstance(), this);
if (emitter != null) {
for (Emitter element : emitter) {
if (element != null) {
element.remove();
}
}
emitter = null;
}
emitter = powerup.getCollectAppearance().createEmitters(GameScreen.getInstance(), getMapX(), getMapY());
phase = PHASE_DEAD;
}
}
public boolean isDead() {
return phase == PHASE_DEAD;
}
private boolean isAlive() {
return phase == PHASE_NORMAL && isActive();
}
@Override
public void onCollisionWithSmartbomb(Smartbomb smartBomb) {
if (!isAlive()) {
return;
}
kill();
}
@Override
public LayersFeature getMousePointer(boolean clicked) {
return Worm.getGameState().isBezerk() ? Res.getMousePointerBezerkOnTarget() :
Worm.getGameState().isSmartbombMode() ? Res.getMousePointerSmartbomb() : Res.getMousePointerOnTarget();
}
@Override
public int onClicked(int mode) {
kill();
return ClickAction.CONSUME;
}
@Override
protected float getRoundCollisionY() {
return 10.0f;
}
@Override
public float getRadius() {
return 12.0f;
}
@Override
public boolean isRound() {
return true;
}
@Override
protected void doTick() {
switch (phase) {
case PHASE_APPEAR:
if (getEvent() == 1) {
phase = PHASE_NORMAL;
// Intentional fallthrough
} else {
break;
}
case PHASE_NORMAL:
if (GameScreen.getInstance().isFastForward()) {
return;
}
tick ++;
if (tick > getDuration()) {
phase = PHASE_DEAD;
powerup.getVanishAppearance().createSprites(GameScreen.getInstance(), this);
}
break;
case PHASE_DEAD:
// Wait for event 2
if (getEvent() == 2) {
remove();
}
break;
default:
assert false;
}
}
/* (non-Javadoc)
* @see worm.Entity#doUpdate()
*/
@Override
protected void doUpdate() {
if (soundEffect != null) {
soundEffect.setGain(Res.getSaucerSound().getGain() * Game.getSFXVolume() * Worm.calcGain(getMapX() + getOffsetX(), getMapY() + getOffsetY()), this);
}
}
/**
* @return the duration, in ticks, that the saucer should remain in normal state
*/
private int getDuration() {
if (Worm.getGameState().getGameMode() == WormGameState.GAME_MODE_SURVIVAL) {
return dropped ? DROPPED_SAUCER_DURATION : SAUCER_DURATION;
} else {
return dropped ?
Math.max(MIN_DROPPED_SAUCER_DURATION, DROPPED_SAUCER_DURATION - DROPPED_SAUCER_DURATION_PER_LEVEL * Worm.getGameState().getLevel())
:
Math.max(MIN_SAUCER_DURATION, SAUCER_DURATION - SAUCER_DURATION_PER_LEVEL * Worm.getGameState().getLevel());
}
}
/* (non-Javadoc)
* @see invaders.Entity#doRemove()
*/
@Override
protected void doRemove() {
stopSound();
if (emitter != null) {
for (Emitter element : emitter) {
element.remove();
}
emitter = null;
}
}
private void stopSound() {
if (soundEffect != null) {
soundEffect.stop(this);
soundEffect = null;
}
}
@Override
public final void addToGameState(GameStateInterface gsi) {
gsi.addToSaucers(this);
}
@Override
public final void removeFromGameState(GameStateInterface gsi) {
gsi.removeFromSaucers(this);
}
@Override
public Rectangle getBounds(Rectangle bounds) {
return null;
}
}