View Javadoc

1   /*
2   @See License.txt@
3    */
4   package spellcast.beings;
5   
6   import org.apache.log4j.Logger;
7   
8   import spellcast.damage.Damage;
9   
10  import spellcast.enchantment.Enchantment;
11  
12  import spellcast.model.Id;
13  import spellcast.model.PropertyUpdater;
14  
15  import java.beans.PropertyChangeListener;
16  import java.beans.PropertyChangeSupport;
17  
18  import java.io.IOException;
19  import java.io.ObjectInputStream;
20  import java.io.Serializable;
21  
22  import java.util.ArrayList;
23  import java.util.Iterator;
24  import java.util.List;
25  
26  
27  /***
28   * The base class for all <code>Being</code>s in Spellcast.
29   * TODO: add equals() and hashCode()
30   *
31   * @author Barrie Treloar
32   * @version $Revision: 1.1 $
33   */
34  public abstract class Being implements Serializable, PropertyUpdater, IBeing {
35      /*** The property for the being's active value. */
36      public static final String ACTIVE_PROP = "active";
37  
38      /*** Use serialVersionUID for interoperability. */
39      private static final long serialVersionUID = 1L;
40  
41      /*** Log4J logger. */
42      private Logger logger = Logger.getLogger(Being.class);
43  
44      /*** The enchantments that are in effect on this <code>Being</code>. */
45      private List enchantments = new ArrayList();
46  
47      /***
48       * Used when a property changes to notify
49       * <code>PropertyChangeListener</code>s of the change. Not all properties
50       * are notified when a change occurs. Each class should document what
51       * properties get updated.
52       */
53      private transient PropertyChangeSupport propertySupport;
54  
55      /*** Determines if this <code>Being</code> is active this turn. */
56      private boolean active;
57  
58      /*** Determines if this <code>Being</code> is alive. */
59      private boolean alive;
60  
61      /*** The <code>Id</code> of this <code>Being</code>. */
62      private Id id;
63  
64      /*** The hit points of this <code>Being</code>. */
65      private int hitPoints;
66  
67      /***
68       * The Maximum Hit Points for this Being. A creature can never be healed
69       * beyond this value.
70       */
71      private int maxHitPoints;
72  
73      /*** The name of this <code>Being</code>. */
74      private String name;
75  
76      /*** The <code>Gender</code> of this <code>Being</code>. */
77      private Gender gender;
78  
79      /***
80       * Determines if this <code>Being</code> should leave a corpse behind upon
81       * death.
82       */
83      private boolean leavesCorpse;
84  
85      private boolean nullable;
86  
87      /***
88       * Provided for serialization, do not use as a constructor.
89       */
90       protected Being() {
91       }
92  
93      /***
94       * Creates a new BeingImpl object.
95       *
96       * @param anId the<code>Id</code> to use for this <code>Being</code>.
97       * @param aName the name of this<code>Being</code>.
98       * @param aGender the<code>Gender</code> of this <code>Being</code>.
99       * @param theMaxHitPoints the maximum hit points of this<code>Being</code>.
100      */
101     public Being(final Id anId, final String aName, final Gender aGender,
102         final int theMaxHitPoints) {
103         setId(anId);
104         setName(aName);
105         setGender(aGender);
106         setMaxHitPoints(theMaxHitPoints);
107         propertySupport = new PropertyChangeSupport(getId());
108         setAlive(true);
109         setHitPoints(getMaxHitPoints());
110     }
111 
112     /***
113      * Necessary in order to recreate the propertySupport object but without
114      * the listeners as they are client or server dependent.
115      *
116      * @param in stream that contains the serialized object
117      *
118      * @throws IOException {@link java.io.ObjectInputStream#readObject see
119      *         <code>ObjectInputStream.readObject()</code>}
120      * @throws ClassNotFoundException {@link
121      *         java.io.ObjectInputStream#readObject see
122      *         <code>ObjectInputStream.readObject()</code>}
123      *
124      * @see Serializable
125      * @see java.io.ObjectInputStream#readObject
126      */
127     private void readObject(final ObjectInputStream in)
128         throws IOException, ClassNotFoundException {
129         in.defaultReadObject();
130         propertySupport = new PropertyChangeSupport(getId());
131     }
132 
133     /***
134      * The <code>PropertyChangeSupport</code> object used to notify listeners
135      * of changes to properties.
136      *
137      * @return the <code>PropertyChangeSupport</code>.
138      */
139     protected final PropertyChangeSupport getPropertySupport() {
140         return propertySupport;
141     }
142 
143     /***
144      * Adds a <code>PropertyChangeListener</code> to this <code>Being</code>.
145      *
146      * @param listener the <code>PropertyChangeListener</code> to add.
147      */
148     public final void addPropertyChangeListener(
149         final PropertyChangeListener listener) {
150         propertySupport.addPropertyChangeListener(listener);
151     }
152 
153     /***
154      * Remove a <code>PropertyChangeListener</code> from this
155      * <code>Being</code>.
156      *
157      * @param listener the <code>PropertyChangeListener</code> to remove.
158      */
159     public final void removePropertyChangeListener(
160         final PropertyChangeListener listener) {
161         propertySupport.removePropertyChangeListener(listener);
162     }
163 
164     /***
165      * Add an enchantment to this Being.
166      *
167      * @param anEnchantment the enchantment to add.
168      */
169     public final void addEnchantment(final Enchantment anEnchantment) {
170         enchantments.add(anEnchantment);
171     }
172 
173     /***
174      * Remove the specified <code>Enchantment</code> from this
175      * <code>Being</code>. If the enchantment does not exist then nothing is
176      * removed.
177      *
178      * @param enchantment the enchantment to remove. Ignored if the enchantment
179      *        is not in effect on this <code>Being</code>.
180      */
181     public final void removeEnchantment(final Enchantment enchantment) {
182         enchantments.remove(enchantment);
183     }
184 
185     /***
186      * Return an array of all <code>Enchantment</code>s that are in effect upon
187      * this <code>Being</code>.
188      *
189      * @return an array of all <code>Enchantment</code>s that are in effect
190      *         upon this <code>Being</code>.
191      */
192     public final Enchantment[] getEnchantments() {
193         return (Enchantment[]) enchantments.toArray(new Enchantment[0]);
194     }
195 
196     /***
197      * Each <code>Enchantment</code> on this <code>Being</code> is notified
198      * that the turn has been completed via <code>turnComplete</code>. If the
199      * <code>Enchantment</code> indicates that it should no longer be in
200      * effect then it is removed.
201      *
202      * @see Enchantment#turnComplete
203      */
204     public final void updateEnchantments() {
205         Iterator iter = enchantments.iterator();
206 
207         while (iter.hasNext()) {
208             Enchantment enchantment = (Enchantment) iter.next();
209 
210             if (enchantment.turnComplete()) {
211                 iter.remove();
212             }
213         }
214     }
215 
216     /***
217      * Indicates whether this <code>Being</code> is active this turn.
218      *
219      * @return whether this <code>Being</code> is active this turn.
220      */
221     public final boolean isActive() {
222         return active;
223     }
224 
225     /***
226      * Sets this <code>Being</code> as active for this turn.
227      *
228      * @param isActive if this<code>Being</code> is active for this turn, false
229      *        otherwise.
230      */
231     public final void setActive(final boolean isActive) {
232         active = isActive;
233         propertySupport.firePropertyChange(ACTIVE_PROP, null,
234             new Boolean(active));
235     }
236 
237     /***
238      * Determine if this <code>Being</code> is currently alive. True if alive,
239      * false otherwise.
240      *
241      * @return true if the <code>Being</code> is alive, false otherwise.
242      */
243     public final boolean isAlive() {
244         return alive;
245     }
246 
247     /***
248      * Sets whether this <code>Being</code> is currently alive. True if alive,
249      * false otherwise.
250      *
251      * @param isAlive true if alive, false otherwise.
252      */
253     public final void setAlive(final boolean isAlive) {
254         alive = isAlive;
255     }
256 
257     /***
258      * Determine if this being should die. TODO this should probably return an
259      * object which indicates: who killed the Being and what killed them.
260      */
261     public final void shouldDie() {
262         if (getHitPoints() == 0) {
263             setAlive(false);
264         }
265     }
266 
267     /***
268      * The <code>Id</code> of this <code>Being</code>.
269      *
270      * @return the <code>Id</code> of this <code>Being</code>.
271      */
272     public final Id getId() {
273         return id;
274     }
275 
276     /***
277      * Set the <code>Id</code> of this <code>Being</code>.
278      *
279      * @param theId the <code>Id</code> of this <code>Being</code>.
280      */
281     public final void setId(final Id theId) {
282         id = theId;
283     }
284 
285     /***
286      * Returns the value of the current Hit Points for this <code>Being</code>.
287      * The current hit points will never exceed max hit points.
288      *
289      * @return the value of the current Hit Points for this <code>Being</code>.
290      */
291     public final int getHitPoints() {
292         return hitPoints;
293     }
294 
295     /***
296      * Set the value for the current <code>HitPoints</code> for this Being. Any
297      * values that are negative are converted into 0. Any values that are
298      * greater than <code>getMaxHitPoints</code> are converted into
299      * <code>getMaxHitPoints</code>.
300      *
301      * @param theHitPoints the value to set the Hit Points to. Any negative
302      *        values are converted to 0. Any values that are greater
303      *        than<code>getMaxHitPoints</code> are converted into
304      *        <code>getMaxHitPoints</code>.
305      */
306     public final void setHitPoints(final int theHitPoints) {
307         if (theHitPoints < 0) {
308             hitPoints = 0;
309         } else if (theHitPoints > getMaxHitPoints()) {
310             hitPoints = getMaxHitPoints();
311         } else {
312             hitPoints = theHitPoints;
313         }
314     }
315 
316     /***
317      * Returns the value for the Maximum Hit Points for this
318      * <code>Being</code>.
319      *
320      * @return the maximum hit points for this <code>Being</code>.
321      */
322     public final int getMaxHitPoints() {
323         return maxHitPoints;
324     }
325 
326     /***
327      * Set the value for the Maximum Hit Points for this <code>Being</code>.
328      * Only positive values are allowed for maxHitPoints.  Any invalid values
329      * are silently ignored and the value is set to 0.
330      *
331      * @param theMaxHitPoints the value to set the Maximum Hit Points to. Valid
332      *        values are > 0. Invalid values are silently ignored and the
333      *        value is set to 0.
334      */
335     public final void setMaxHitPoints(final int theMaxHitPoints) {
336         if (theMaxHitPoints > 0) {
337             maxHitPoints = theMaxHitPoints;
338         } else {
339             maxHitPoints = 0;
340         }
341     }
342 
343     /***
344      * The specified damage is dealt to this Being. If there are any
345      * <code>Enchantment</code>s that provide resistance to the
346      * <code>DamageType</code> being dealt then the damage is reduced to zero.
347      *
348      * @param damageDealt the damage to deal to this <code>Being</code>.
349      */
350     public final void takeDamage(final Damage damageDealt) {
351         boolean isResistant = false;
352         Iterator iter = enchantments.iterator();
353         while (iter.hasNext()) {
354             Enchantment theEnchantment = (Enchantment) iter.next();
355             if (theEnchantment.isResistant(damageDealt.getType())) {
356                 isResistant = true;
357                 break;
358             }
359         }
360         if (!isResistant) {
361             setHitPoints(getHitPoints() - damageDealt.getValue());
362         }
363     }
364 
365     /***
366      * The name of the <code>Being</code>.
367      *
368      * @return the name of the <code>Being</code>.
369      */
370     public final String getName() {
371         return name;
372     }
373 
374     /***
375      * Sets the name of this <code>Being</code>.
376      *
377      * @param theName the name for this <code>Being</code>.
378      */
379     public final void setName(final String theName) {
380         name = theName;
381     }
382 
383     /***
384      * Return the <code>Gender</code> of this <code>Being</code>.
385      *
386      * @return the <code>Gender</code> of this <code>Being</code>.
387      */
388     public final Gender getGender() {
389         return gender;
390     }
391 
392     /***
393      * Sets the <code>Gender</code> of this <code>Being</code>.
394      *
395      * @param theGender the <code>Gender</code> of this <code>Being</code>.
396      */
397     public final void setGender(final Gender theGender) {
398         gender = theGender;
399     }
400 
401     /***
402      * Indicates whether this <code>Being</code> should leave a corpse when it
403      * dies.
404      *
405      * @return true if a corpse should be created on death, false otherwise.
406      */
407     public final boolean getLeavesCorpse() {
408         return leavesCorpse;
409     }
410 
411     /***
412      * This <code>Being</code> will leave a corpse on death if this value is
413      * true, otherwise no corpse will be created.
414      *
415      * @param shouldLeaveCorpse true if a corpse should be created on death,
416      *        false otherwise.
417      */
418     public final void setLeavesCorpse(final boolean shouldLeaveCorpse) {
419         leavesCorpse = shouldLeaveCorpse;
420     }
421 
422     /***
423      * {@inheritDoc}
424      */
425     public final boolean isNull() {
426         return nullable;
427     }
428 
429     /***
430      * Set whether this object is a <code>Nullable</code>.
431      *
432      * @param isNullable true if this object is a Null instance, false
433      *        otherwise.
434      */
435     public final void setNull(boolean isNullable) {
436         nullable = isNullable;
437     }
438 
439     /***
440      * Return a Null <code>IBeing</code>.
441      *
442      * @return an instance of the Null <code>IBeing</code>.
443      */
444     public static IBeing newNull() {
445         return new NullBeing();
446     }
447 
448 }