package ring.effects;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
/**
* This class represents an "effect." The effect could be part of a spell,
* a skill, magic items, or various other things. An Effect encapsulates
* one or more EffectCreators (which contain the actual modifications to the
* Affectable target) and the rules on how the effect is applied and removed.
* @author projectmoon
*
*/
public class Effect implements Serializable {
public static final long serialVersionUID = 1;
// Target of this effect.
private Affectable target;
//The effect creators and their parameters.
private HashMap<String, EffectCreator> effectCreators;
private HashMap<String, EffectCreatorParameters> parameterList;
//Has the effect begun?
private boolean begun;
// Duration variable.
private Duration duration;
// Timer variable... Measured in ticks.
private int timer;
// Is this effect supposed to be removed?
private boolean dead;
// Duration enum: Helps measure ... duration.
public enum Duration {
INSTANT, // this effect is instant
TIMED, // this effect has a timed duration: see private int timer
PERIODIC_TIMED, // this effect has a timed duration, but it calls
// effectLife() every time the effect is processed.
PERMANENT; // this effect is permanent until removed through external
// means. usually used by items.
}
private int numStarts; // keeps track of # times startEffect was called.
// used to properly remove periodic effects.
// this boolean allows setting and checking of some attributes to further
// help describe this effect
// certain attributes are set automatically depending on the constructor:
// * if target is not specified, INIT_TARGET_LATER is set.
// * if timer is set to zero on a TIMED effect, INIT_TIMER_LATER is set.
private boolean INIT_TARGET_LATER = false;
private boolean INIT_TIMER_LATER = false;
private boolean INIT_EFFECT_LATER = false;
//Constant to allow the timer to be set later. Used as a parameter to the constructor.
public static final int SET_TIMER_LATER = 0;
//Internal timer task to handle the management of this Effect.
private TimerTask effectTask = new TimerTask() {
@Override
public void run() {
decrementTimer();
}
};
private Timer effectTimer = new Timer();
/**
* Creates a new instant Effect with no EffectCreators.
*/
public Effect() {
initDefaults();
}
/**
* Initializes default effect information.
*/
private void initDefaults() {
effectCreators = new HashMap<String, EffectCreator>();
parameterList = new HashMap<String, EffectCreatorParameters>();
dead = false;
begun = false;
duration = Duration.INSTANT;
timer = 0;
numStarts = 0;
}
public Effect(Duration dur, int timer, Affectable target, EffectCreator... efcs) {
initDefaults();
if (efcs != null) {
for (EffectCreator ef : efcs)
effectCreators.put(ef.toString(), ef);
}
this.timer = timer;
this.target = target;
duration = dur;
dead = false;
INIT_TARGET_LATER = false;
numStarts = 0;
if ((dur == Duration.TIMED) && (timer == 0))
INIT_TIMER_LATER = true;
}
public Effect(Duration dur, int timer, EffectCreator... efcs) {
initDefaults();
if (efcs != null) {
for (EffectCreator ef : efcs)
effectCreators.put(ef.toString(), ef);
}
this.timer = timer;
target = null;
duration = dur;
dead = false;
INIT_TARGET_LATER = true;
numStarts = 0;
if ((dur == Duration.TIMED) && (timer == 0))
INIT_TIMER_LATER = true;
}
// This copy constructor is used when starting up effects in order to create
// a new, unique
// instance. This prevents strange timing issues when applying the same
// effect multiple times
// at once. It is up to the Effect-encapsulating classes (Spell,
// ClassFeature, Item, etc) to deal
// with this issue. They call uniqueInstance() to do this.
private Effect(Effect other) {
effectCreators = other.effectCreators;
parameterList = other.parameterList;
timer = other.timer;
target = other.target;
duration = other.duration;
dead = other.dead;
INIT_TARGET_LATER = other.INIT_TARGET_LATER;
INIT_EFFECT_LATER = other.INIT_EFFECT_LATER;
INIT_TIMER_LATER = other.INIT_TIMER_LATER;
numStarts = 0; // this is a new effect, so numStarts is still zero.
}
public void addEffectCreator(String efcKey, EffectCreator efc) {
effectCreators.put(efcKey, efc);
}
public void decrementTimer() {
// ignore permanent duration effects. they are removed other ways.
if (duration.equals(Duration.PERMANENT))
return;
if (timer <= 0) {
endEffect();
effectTimer.cancel();
dead = true;
System.out.println("effect ended...");
}
else {
// if this is a periodic effect, make it happen again!
if (duration == Duration.PERIODIC_TIMED) {
startEffect();
}
timer--;
}
}
// setTimer method.
// This allows the timer to be set to some postive number. It can only be
// set if INIT_TIME_LATER is set to true.
// After it is set, INIT_TIME_LATER goes false and can no longer be set.
// Returns true if successful,
// false otherwise.
public boolean setTimer(int n) {
if (INIT_TIMER_LATER == true && n >= 0) {
timer = n;
INIT_TIMER_LATER = false;
return true;
}
else {
return false;
}
}
public boolean isDead() {
return dead;
}
/**
* Begins the execution of this effect. Calls to
* begin() only work once. Subsequent calls are silently ignored.
*/
public void begin() {
if (!begun) {
passParameters();
//Periodic timed effects will be applied when timer decrements.
if (duration != Duration.PERIODIC_TIMED) {
startEffect();
}
begun = true;
effectTimer.scheduleAtFixedRate(effectTask, 0, 2000);
}
}
private void startEffect() {
numStarts++;
if (duration == Duration.INSTANT)
dead = true; // dead on arrival
for (EffectCreator ef : effectCreators.values())
ef.effectLife(target);
}
public void endEffect() {
dead = true;
for (int c = 0; c < numStarts; c++)
// call effectDeath a number of times equal to the number of
// effectLife calls.
for (EffectCreator ef : effectCreators.values())
ef.effectDeath(target);
numStarts = 0; // reset the numStarts counter for the next time this
// Effect is enacted.
}
// uniqueInstance method.
// This returns a unique instance of this Effect. Gets rid of timing issues
// and many other things.
public Effect uniqueInstance() {
return new Effect(this);
}
/**
* Adds a parameter to the specified EffectCreator (by key).
* @param efcKey
* @param paramName
* @param value
* @return
*/
public boolean addParameter(String efcKey, String paramName, Object value) {
EffectCreatorParameters params = parameterList.get(efcKey);
if (params == null) {
params = new EffectCreatorParameters();
}
params.add(paramName, value);
parameterList.put(efcKey, params);
return true;
}
// passParameters method.
// This method passes parameters to all EffectCreators in this Effect.
// Returns true
// if succesful in adding parameters to all EffectCreators, but false if one
// fails.
private void passParameters() {
for (String efcKey : effectCreators.keySet()) {
EffectCreatorParameters params = parameterList.get(efcKey);
EffectCreator efc = effectCreators.get(efcKey);
efc.setParameters(params);
}
}
public Affectable getTarget() {
return target;
}
public void setTarget(Affectable target) {
this.target = target;
}
public Duration getDuration() {
return duration;
}
public String toString() {
String text = "[Effect]: ";
for (String ef : effectCreators.keySet()) {
text += ef.toString() + ";";
}
return text;
}
}