View Javadoc

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