/* * Copyright (c) 2004 Covalent Software Ltd * All rights reserved. */ package net.puppygames.applet.effects; import java.util.WeakHashMap; import net.puppygames.applet.*; 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; /** * $Id: Effect.java,v 1.9 2010/02/23 22:53:18 foo Exp $ * @version $Revision: 1.9 $ * @author $Author: foo $ * <p> */ public abstract class Effect implements Tickable { /** 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>(); /** Delay before effect is ticked plus 1 tick */ private int delay = 1; /** Paused */ private boolean paused; /** Screen */ private Screen screen; /** Sound effect to play when the effect starts */ private ALBuffer sound; /** Visible flag: invisible effects are not rendered */ private boolean visible = true; /** Offset */ private ReadablePoint offset; /** Spawned status */ private boolean spawned; /** Sound attenuator */ private Attenuator attenuator; /** Sound effect */ private transient SoundEffect soundEffect; /** * C'tor */ public Effect() { } /** * 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 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 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); } /** * 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 the visible */ public final boolean isVisible() { return visible; } /** * @param visible the visible to set */ public final void setVisible(boolean visible) { this.visible = visible; onSetVisible(); } /** * Called when setVisible is called */ protected void onSetVisible() { } /** * @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); } /* (non-Javadoc) * @see net.puppygames.applet.Thing#spawn(net.puppygames.applet.Screen) */ @Override public final void spawn(Screen screen) { if (spawned) { return; } this.screen = screen; if (isBackgroundEffect()) { screen.addBackgroundEffect(this); } else { screen.addForegroundEffect(this); if (offset == null) { setOffset(DEFAULT_OFFSETS.get(screen)); } if (attenuator == null) { setAttenuator(DEFAULT_ATTENUATORS.get(screen)); } } doSpawn(); spawned = true; } protected Screen getScreen() { return screen; } protected void doSpawn() { } /** * 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(); } } @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(); public final void render() { if (delay == 0 && visible) { doRender(); } } /** * Render the effect */ protected abstract void doRender(); /** * "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(); } /* (non-Javadoc) * @see net.puppygames.applet.Tickable#remove() */ @Override public final void remove() { if (!spawned) { return; } if (isBackgroundEffect()) { screen.removeBackgroundEffect(this); } else { screen.removeForegroundEffect(this); if (soundEffect != null) { soundEffect.stop(this); soundEffect = null; } } doRemove(); spawned = false; } protected void doRemove() { } /** * @return true if this is a background effect */ public boolean isBackgroundEffect() { return false; } }