/*
* Copyright (c) 2004 Covalent Software Ltd
* All rights reserved.
*/
package net.puppygames.applet.effects;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.WeakHashMap;
import net.puppygames.applet.Game;
import net.puppygames.applet.Screen;
import net.puppygames.applet.TickableObject;
import org.lwjgl.util.ReadablePoint;
import com.shavenpuppy.jglib.openal.ALBuffer;
import com.shavenpuppy.jglib.resources.Attenuator;
import com.shavenpuppy.jglib.sound.SoundEffect;
import com.shavenpuppy.jglib.sprites.SpriteAllocator;
/**
* Special effects. Although special effects are {@link TickableObject TickableObjects}, and therefore derived from
* {@link Serializable}, they're not meant to be serialized (legacy restriction).
*/
public abstract class Effect extends TickableObject {
/** Default offsets on various screens */
private static final WeakHashMap<SpriteAllocator, ReadablePoint> DEFAULT_OFFSETS = new WeakHashMap<SpriteAllocator, ReadablePoint>();
/** Default attenuators on various screens */
private static final WeakHashMap<SpriteAllocator, Attenuator> DEFAULT_ATTENUATORS = new WeakHashMap<SpriteAllocator, Attenuator>();
/** Default layer for effects */
private static final int DEFAULT_LAYER = 100;
/** Delay before effect is ticked plus 1 tick */
private int delay = 1;
/** Paused */
private boolean paused;
/** Sound effect to play when the effect starts */
private ALBuffer sound;
/** Offset */
private ReadablePoint offset;
/** Sound attenuator */
private Attenuator attenuator;
/** Sound effect */
private transient SoundEffect soundEffect;
/**
* Sets the default offset to be used for foreground effects on a particular screen. This is effectively like calling {@link #setOffset(ReadablePoint)}
* on all your effects spawned on that screen.
* @param screen The screen to define; may not be null
* @param defaultOffset The default offset; may be null
*/
public static void setDefaultOffset(SpriteAllocator screen, ReadablePoint defaultOffset) {
DEFAULT_OFFSETS.put(screen, defaultOffset);
}
/**
* Sets the default attenuator to be used for foreground effects on a particular screen. This is effectively like calling {@link #setAttenuator(Attenuator)}
* on all your effects spawned on that screen.
* @param screen The screen to define; may not be null
* @param defaultAttenuator the default attenuator to set; may be null
*/
public static void setDefaultAttenuator(SpriteAllocator screen, Attenuator defaultAttenuator) {
DEFAULT_ATTENUATORS.put(screen, defaultAttenuator);
}
/**
* C'tor
*/
public Effect() {
setLayer(getDefaultLayer());
}
/**
* Sets the offset for this effect. You don't need to do this if you've called {@link #setDefaultOffset(Screen, ReadablePoint)};
* or alternatively, you may want to override the default offset, and pass in some other offset (such as null)
* @param offset the offset to set, may be null
*/
public void setOffset(ReadablePoint offset) {
this.offset = offset;
}
/**
* @return the offset
*/
public ReadablePoint getOffset() {
return offset;
}
/**
* Sets the sound attenuator for this effect. You don't need to do this if you've called {@link #setDefaultAttenuator(Screen, Attenuator)};
* or alternatively, you may want to override the default attenuator, and pass in some other attenuator (such as null)
* @param attenuator the attenuator to set for sound volume; may be null
*/
public void setAttenuator(Attenuator attenuator) {
this.attenuator = attenuator;
}
/**
* @return the attenuator
*/
public Attenuator getAttenuator() {
return attenuator;
}
/**
* @return true if the effect has started
*/
public boolean isStarted() {
return delay == 0;
}
/**
* Pause or unpause ticking.
* @param paused
*/
public final void setPaused(boolean paused) {
if (this.paused != paused) {
this.paused = paused;
onPausedChanged();
}
}
protected void onPausedChanged() {}
/**
* @return true if we're paused
*/
public final boolean isPaused() {
return paused;
}
/**
* Start the effect
*/
public void start() {
delay = 0;
if (sound != null) {
playSound(sound);
}
init();
}
/**
* Play a sound for this effect when the effect starts. Override to provide attenuated sound or prevent sounds.
* @param sound The sound; may not be null
*/
protected void playSound(ALBuffer sound) {
soundEffect = Game.allocateSound(sound, 1.0f, 1.0f, this);
}
@Override
protected final void doSpawn() {
if (!isBackgroundEffect()) {
if (offset == null) {
setOffset(DEFAULT_OFFSETS.get(getScreen()));
}
if (attenuator == null) {
setAttenuator(DEFAULT_ATTENUATORS.get(getScreen()));
}
}
doSpawnEffect();
}
protected void doSpawnEffect() {
}
/**
* Set the delay
* @param delay
*/
public final void setDelay(int delay) {
this.delay = Math.max(1, delay + 1);
}
/**
* Tick.
*/
@Override
public final void tick() {
if (paused) {
return;
}
if (delay > 0) {
delay --;
if (delay == 0) {
start();
}
}
if (delay == 0) {
doTick();
}
if (!isEffectActive()) {
remove();
}
}
@Override
public void update() {
if (delay == 0) {
doUpdate();
if (soundEffect != null && attenuator != null) {
updateSound(sound.getGain(), soundEffect);
}
}
}
/**
* Update the sound effect. Called if there is a sound effect and an attenuator, from {@link #update()}.
* @param gain The master gain for the sound
* @param effect The sound effect to update
*/
protected void updateSound(float gain, SoundEffect effect) {
}
/**
* Updates effect graphics
*/
protected void doUpdate() {
}
/**
* @param sound The sound to set.
*/
public final void setSound(ALBuffer sound) {
this.sound = sound;
}
/**
* @return the sound
*/
public final ALBuffer getSound() {
return sound;
}
/**
* Init. Called for delayed effects the moment they are ready.
*/
protected void init() {
}
/**
* Tick the effect
*/
protected abstract void doTick();
/**
* "Finish" the effect. This lets the effect decide when to remove itself.
* The default is to remove the effect immediately.
*/
public void finish() {
remove();
}
/**
* @return true if the effect is finished
*/
public boolean isFinished() {
return !isActive();
}
/**
* @return true if the effect is active
*/
public boolean isEffectActive() {
return isActive();
}
/**
* @return true if this is a background effect
*/
public boolean isBackgroundEffect() {
return false;
}
/**
* Gets the default layer for effects of this type. Normally effects are drawn right on top of everything so we use
* {@value #DEFAULT_LAYER}. Override this for more reasonable layer values.
* <em>Note</em> this method is called from the constructor so it can't rely on any post-construction stuff
* @return integer
*/
public int getDefaultLayer() {
return DEFAULT_LAYER;
}
/**
* Prevent serialization
*/
private void writeObject(ObjectOutputStream out) throws IOException {
throw new NotSerializableException(this+" is not serializable");
}
/**
* Prevent deserialization
*/
private void readObject(ObjectInputStream in) throws IOException {
throw new NotSerializableException(this+" is not serializable");
}
}