View Javadoc

1   /*
2    *   Firemox is a turn based strategy simulator
3    *   Copyright (C) 2003-2007 Fabrice Daugan
4    *
5    *   This program is free software; you can redistribute it and/or modify it 
6    * under the terms of the GNU General Public License as published by the Free 
7    * Software Foundation; either version 2 of the License, or (at your option) any
8    * later version.
9    *
10   *   This program is distributed in the hope that it will be useful, but WITHOUT 
11   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12   * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
13   * details.
14   *
15   *   You should have received a copy of the GNU General Public License along  
16   * with this program; if not, write to the Free Software Foundation, Inc., 
17   * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  package net.sf.firemox.clickable.ability;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  
26  import net.sf.firemox.action.Actiontype;
27  import net.sf.firemox.action.InputChoice;
28  import net.sf.firemox.action.MAction;
29  import net.sf.firemox.action.RemoveObject;
30  import net.sf.firemox.action.Repeat;
31  import net.sf.firemox.action.target.AbstractTarget;
32  import net.sf.firemox.action.target.ChosenTarget;
33  import net.sf.firemox.clickable.target.Target;
34  import net.sf.firemox.clickable.target.card.CardCopy;
35  import net.sf.firemox.clickable.target.card.MCard;
36  import net.sf.firemox.clickable.target.card.TriggeredCard;
37  import net.sf.firemox.clickable.target.card.TriggeredCardChoice;
38  import net.sf.firemox.clickable.target.player.Player;
39  import net.sf.firemox.event.MEventListener;
40  import net.sf.firemox.event.context.ContextEventListener;
41  import net.sf.firemox.modifier.Unregisterable;
42  import net.sf.firemox.stack.ResolveStackHandler;
43  import net.sf.firemox.stack.StackManager;
44  import net.sf.firemox.token.IdZones;
45  import net.sf.firemox.token.TrueFalseAuto;
46  import net.sf.firemox.tools.MToolKit;
47  import net.sf.firemox.ui.i18n.LanguageManager;
48  
49  import org.apache.commons.lang.StringUtils;
50  
51  /***
52   * An ability contains a cost part and an effect part. Each ability is
53   * associated to an event conditioning it's activation.
54   * 
55   * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
56   * @since 0.1
57   * @since 0.60 name attribute added
58   * @since 0.86 Object to be removed from a component are checked be enabling an
59   *        ability.
60   * @since 0.86 The controller of this ability may be any player.
61   * @since 0.90 linked abilities added.
62   */
63  public abstract class Ability implements ResolveStackHandler, Unregisterable {
64  
65  	/***
66  	 * Create an instance of Ability
67  	 * <ul>
68  	 * Structure of InputStream : Data[size]
69  	 * <li>name name [String]</li>
70  	 * <li>priority [Priority]</li>
71  	 * <li>optimization [Optimization]</li>
72  	 * <li>play-as-spell [TrueFalseAuto]</li>
73  	 * </ul>
74  	 * 
75  	 * @param inputFile
76  	 *          file containing this ability
77  	 * @throws IOException
78  	 *           if error occurred during the reading process from the specified
79  	 *           input stream
80  	 */
81  	protected Ability(InputStream inputFile) throws IOException {
82  		// name of this ability
83  		this.name = StringUtils.trimToNull(MToolKit.readString(inputFile).intern());
84  
85  		// To enable recursive ability dependencies
86  		AbilityFactory.lastInstance = this;
87  
88  		/***
89  		 * We read the ability tag. If this ability has 'isHidden' tag, it would be
90  		 * considered as abstract and no picture would be used to represent it, so
91  		 * it would be played immediately without player intervention. If this
92  		 * ability has this tag and requires player intervention the play would
93  		 * crash.
94  		 */
95  		priority = Priority.valueOf(inputFile);
96  		optimizer = Optimization.valueOf(inputFile);
97  		if (isHidden()) {
98  			pictureName = null;
99  		} else {
100 			pictureName = StringUtils.trimToNull(MToolKit.readString(inputFile));
101 		}
102 		playAsSpell = TrueFalseAuto.deserialize(inputFile);
103 	}
104 
105 	/***
106 	 * Create an instance of Ability
107 	 * 
108 	 * @param name
109 	 *          Name of card used to display this ability in a stack
110 	 * @param optimizer
111 	 *          the optimizer to use.
112 	 * @param priority
113 	 *          the resolution type.
114 	 * @param pictureName
115 	 *          the picture name of this ability. If <code>null</code> the card
116 	 *          picture will be used instead.
117 	 */
118 	protected Ability(String name, Optimization optimizer, Priority priority,
119 			String pictureName) {
120 		// name of this ability
121 		this.name = StringUtils.trimToNull(name);
122 		this.optimizer = optimizer;
123 		this.priority = priority;
124 		this.pictureName = pictureName;
125 	}
126 
127 	/***
128 	 * Register this ability to manager trying to append test on existing ability
129 	 * with same effects.
130 	 * 
131 	 * @since 0.82
132 	 */
133 	public void optimizeRegisterToManager() {
134 		if (!MEventListener.TRIGGRED_ABILITIES.get(eventComing.getIdEvent())
135 				.contains(this)) {
136 			MEventListener.TRIGGRED_ABILITIES.get(eventComing.getIdEvent()).add(this);
137 		}
138 	}
139 
140 	/***
141 	 * Return the name of this ability
142 	 * 
143 	 * @return the new name
144 	 */
145 	public String getName() {
146 		return name;
147 	}
148 
149 	/***
150 	 * Verify in the 'cost' part there is no target action may cause abortion of
151 	 * this ability.
152 	 * 
153 	 * @return true if all actions in the 'cost' part can be played.
154 	 */
155 	public boolean checkTargetActions() {
156 		for (int i = 0; i < actionList().length; i++) {
157 			if (actionList()[i] instanceof ChosenTarget) {
158 				if (i != 0
159 						&& actionList()[i - 1].getIdAction() == Actiontype.REPEAT_ACTION
160 						&& !((ChosenTarget) actionList()[i]).checkTarget(this,
161 								((Repeat) actionList()[i - 1]).getPreemptionTimes(this, this
162 										.getCard()))) {
163 					return false;
164 				}
165 				if (!((ChosenTarget) actionList()[i]).checkTarget(this, 1)) {
166 					return false;
167 				}
168 			} else if (actionList()[i] instanceof InputChoice) {
169 				if (!((InputChoice) actionList()[i]).checkTarget(this, i)) {
170 					return false;
171 				}
172 				i += ((InputChoice) actionList()[i]).getSkipHop();
173 			}
174 		}
175 		return true;
176 	}
177 
178 	/***
179 	 * Checks too the other actions requiring a particular state, such as the
180 	 * presence of an object.
181 	 * 
182 	 * @return true if the other actions requiring a particular state, such as the
183 	 *         presence of an object are OK.
184 	 */
185 	public boolean checkObjectActions() {
186 		for (int i = 0; i < actionList().length; i++) {
187 			if (actionList()[i] instanceof RemoveObject && i != 0) {
188 				if (actionList()[i - 1] instanceof Repeat) {
189 					if (i > 1
190 							&& actionList()[i - 2] instanceof AbstractTarget
191 							&& !((RemoveObject) actionList()[i])
192 									.checkObject(((AbstractTarget) actionList()[i - 2])
193 											.getAbstractTarget(StackManager.getInstance()
194 													.getAbilityContext(), this),
195 											((Repeat) actionList()[i - 1]).getPreemptionTimes(this,
196 													null))) {
197 						return false;
198 					}
199 				} else if (actionList()[i - 1] instanceof AbstractTarget
200 						&& !((RemoveObject) actionList()[i]).checkObject(
201 								((AbstractTarget) actionList()[i - 1]).getAbstractTarget(
202 										StackManager.getInstance().getAbilityContext(), this), 1)) {
203 					return false;
204 				}
205 			}
206 		}
207 		return true;
208 	}
209 
210 	/***
211 	 * Is this ability contains targeting action.
212 	 * 
213 	 * @return true if this ability contains targeting action.
214 	 */
215 	public boolean recheckTargets() {
216 		return StackManager.getInstance().getTargetedList().recheckList(this) > 0;
217 	}
218 
219 	/***
220 	 * Return card where is this ability. As default, it return null.
221 	 * 
222 	 * @return true card where is this ability
223 	 */
224 	public abstract MCard getCard();
225 
226 	/***
227 	 * Return card where is this ability
228 	 * 
229 	 * @return true card where is this ability
230 	 */
231 	public Target getTargetable() {
232 		return getCard();
233 	}
234 
235 	public boolean isAutoResolve() {
236 		return priority.isAutoResolve();
237 	}
238 
239 	public boolean isHidden() {
240 		return priority.isHidden();
241 	}
242 
243 	/***
244 	 * Indicates whether this ability is chosen in priority to the others without
245 	 * this tag.
246 	 * 
247 	 * @return true if this ability is chosen in priority to the others without
248 	 *         this tag.
249 	 */
250 	public boolean hasHighPriority() {
251 		return priority.hasHighPriority();
252 	}
253 
254 	/***
255 	 * compare the current event to the event activating this ability. If
256 	 * matching, verify that there enough mana in the player's mana pool
257 	 * 
258 	 * @return true if this ability can be played responding the current event
259 	 */
260 	public abstract boolean isMatching();
261 
262 	/***
263 	 * Return a card representing this ability.
264 	 * 
265 	 * @return a card representing this ability
266 	 */
267 	public CardCopy getCardCopy() {
268 		return new CardCopy(pictureName, getCard());
269 	}
270 
271 	/***
272 	 * Return the picture name associated to this ability. Is <code>null</code>
273 	 * if no picture is used with this ability.
274 	 * 
275 	 * @return the picture name associated to this ability.
276 	 */
277 	public String getPictureName() {
278 		return pictureName;
279 	}
280 
281 	/***
282 	 * Return list of actions to play to cast this ability
283 	 * 
284 	 * @return list of actions to play to cast this ability
285 	 */
286 	public abstract MAction[] actionList();
287 
288 	/***
289 	 * Return list of actions effects of this ability
290 	 * 
291 	 * @return list of actions effects of this ability
292 	 */
293 	public abstract MAction[] effectList();
294 
295 	/***
296 	 * Return matched to activate this ability matched to activate this ability.
297 	 * As default, return null.
298 	 * 
299 	 * @return event matched to activate this ability
300 	 */
301 	public MEventListener eventComing() {
302 		return eventComing;
303 	}
304 
305 	/***
306 	 * Set the new event for this ability.
307 	 * 
308 	 * @param event
309 	 *          the new event for this ability.
310 	 */
311 	public void setEvent(MEventListener event) {
312 		this.eventComing = event;
313 	}
314 
315 	/***
316 	 * return a copy of this ability <br>
317 	 * TODO remove parameter container since it is not used in this constructor As
318 	 * default, return null
319 	 * 
320 	 * @param container
321 	 *          is not used here
322 	 * @return copy of this ability
323 	 */
324 	public Ability clone(MCard container) {
325 		return null;
326 	}
327 
328 	/***
329 	 * Return a MTriggeredCard representing this ability. This clone should used
330 	 * to be added into the triggered buffer zone of player controlling this
331 	 * ability.
332 	 * 
333 	 * @param context
334 	 *          the attached context of this ability
335 	 * @return a TriggeredCard object built from this and the specified context
336 	 */
337 	public TriggeredCard getTriggeredClone(ContextEventListener context) {
338 		return new TriggeredCard(this, context, StackManager.abilityID);
339 	}
340 
341 	/***
342 	 * Return a MTriggeredCard representing this ability. This clone should used
343 	 * to be added into the triggered buffer zone of player controlling this
344 	 * ability.
345 	 * 
346 	 * @param context
347 	 *          the attached context of this ability
348 	 * @return a TriggeredCardChoice object built from this and the specified
349 	 *         context
350 	 */
351 	public TriggeredCardChoice getTriggeredCloneChoice(
352 			ContextEventListener context) {
353 		return new TriggeredCardChoice(this, context, StackManager.abilityID);
354 	}
355 
356 	public void resolveStack() {
357 		if (StackManager.isEmpty()) {
358 			// the stack is empty, we resolve the stack as normal
359 			StackManager.idActivePlayer = StackManager.idCurrentPlayer;
360 			StackManager.resolveStack();
361 		} else {
362 			// re check waiting triggered abilities
363 			StackManager.activePlayer().waitTriggeredBufferChoice(true);
364 		}
365 	}
366 
367 	/***
368 	 * called when this ability is going to be triggered This method would add
369 	 * this ability to the triggered zone, or perform another play action
370 	 * 
371 	 * @param context
372 	 *          the context needed by event activated
373 	 * @return true if this ability has been added to the triggered buffer zone,
374 	 *         return false otherwise
375 	 */
376 	public boolean triggerIt(ContextEventListener context) {
377 		return true;
378 	}
379 
380 	@Override
381 	public String toString() {
382 		return name == null ? this.getClass().getName() + "-- name = ??"
383 				: getName();
384 	}
385 
386 	/***
387 	 * Return the HTML code representing this ability.
388 	 * 
389 	 * @param context
390 	 *          the context needed by event activated
391 	 * @return the HTML code representing this ability.
392 	 * @since 0.85 Event is displayed
393 	 */
394 	public String toHtmlString(ContextEventListener context) {
395 		return toString();
396 	}
397 
398 	/***
399 	 * Return ability html title. Type of ability and a few other information
400 	 * 
401 	 * @return ability html title. Type of ability and a few other information
402 	 */
403 	public String getAbilityTitle() {
404 		return "<br>" + LanguageManager.getString("card.name") + " : " + getCard();
405 	}
406 
407 	public void removeFromManager() {
408 		eventComing().removeFromManager(this);
409 		if (linkedAbilities != null) {
410 			for (Ability ability : linkedAbilities) {
411 				ability.removeFromManager();
412 			}
413 		}
414 	}
415 
416 	/***
417 	 * Add this ability to the looked for events. Linked abilities are also
418 	 * registered.
419 	 */
420 	public void registerToManager() {
421 		eventComing().registerToManager(this);
422 		if (linkedAbilities != null) {
423 			for (Ability ability : linkedAbilities) {
424 				ability.registerToManager();
425 			}
426 		}
427 	}
428 
429 	/***
430 	 * Return the controller of this ability
431 	 * 
432 	 * @return the controller of this ability
433 	 */
434 	public Player getController() {
435 		return getCard().getController();
436 	}
437 
438 	/***
439 	 * Add a linked ability.
440 	 * 
441 	 * @param ability
442 	 *          a linked ability to add.
443 	 */
444 	public final void addLinkedAbility(Ability ability) {
445 		if (linkedAbilities == null) {
446 			linkedAbilities = new ArrayList<Ability>();
447 		}
448 		linkedAbilities.add(ability);
449 	}
450 
451 	/***
452 	 * Compare two abilities the specified ability to the TBZ.
453 	 * 
454 	 * @param thisContext
455 	 *          the attached context of this ability
456 	 * @param ability
457 	 *          the ability to add
458 	 * @param context
459 	 *          the attached context of given ability
460 	 * @return true if the abilities are functionally equal in this context.
461 	 */
462 	public boolean equals(ContextEventListener thisContext, Ability ability,
463 			ContextEventListener context) {
464 		return context == ability;
465 	}
466 
467 	@Override
468 	public final boolean equals(Object object) {
469 		return object == this;
470 	}
471 
472 	@Override
473 	public int hashCode() {
474 		if (name == null)
475 			return super.hashCode();
476 		return name.hashCode();
477 	}
478 
479 	/***
480 	 * Is this ability is played as a spell.
481 	 * 
482 	 * @return <code>true</code> if this ability is played as a spell.
483 	 */
484 	public boolean isPlayAsSpell() {
485 		if (playAsSpell == TrueFalseAuto.AUTO)
486 			return !getCard().isSameIdZone(IdZones.PLAY);
487 
488 		return playAsSpell.getValue();
489 	}
490 
491 	/***
492 	 * Return a String identifying this ability with the name and/or card name.
493 	 * 
494 	 * @param context
495 	 *          the current context of this ability.
496 	 * @return a String identifying this ability with the name and/or card name.
497 	 */
498 	public abstract String getLog(ContextEventListener context);
499 
500 	/***
501 	 * The attached activation event.
502 	 */
503 	protected MEventListener eventComing;
504 
505 	/***
506 	 * The optimizer to use to manage the 'add' method to the TBZ
507 	 */
508 	public Optimization optimizer;
509 
510 	/***
511 	 * The resolution selector choose the right abstract zone where an hidden
512 	 * ability would be added.
513 	 */
514 	public Priority priority;
515 
516 	/***
517 	 * Ability name
518 	 */
519 	protected final String name;
520 
521 	/***
522 	 * The ability picture to use. Only if the ability is not hidden.
523 	 */
524 	protected final String pictureName;
525 
526 	/***
527 	 * The linked abilities to this ability. Registering/Unregistering this
528 	 * ability causes the same to these linked abilities.
529 	 */
530 	protected Collection<Ability> linkedAbilities;
531 
532 	/***
533 	 * If this ability is played as a copy of card or added to stack
534 	 */
535 	protected TrueFalseAuto playAsSpell;
536 }