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   */
20  package net.sf.firemox.stack;
21  
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.util.ArrayList;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Set;
29  import java.util.Stack;
30  
31  import javax.swing.Icon;
32  import javax.swing.JOptionPane;
33  
34  import net.sf.firemox.action.MAction;
35  import net.sf.firemox.action.MoveCard;
36  import net.sf.firemox.action.PayMana;
37  import net.sf.firemox.action.context.ActionContextWrapper;
38  import net.sf.firemox.action.context.ManaCost;
39  import net.sf.firemox.action.listener.Waiting;
40  import net.sf.firemox.action.target.ChosenTarget;
41  import net.sf.firemox.clickable.ability.Ability;
42  import net.sf.firemox.clickable.ability.ReplacementAbility;
43  import net.sf.firemox.clickable.ability.TriggeredAbility;
44  import net.sf.firemox.clickable.target.Target;
45  import net.sf.firemox.clickable.target.card.AbstractCard;
46  import net.sf.firemox.clickable.target.card.MCard;
47  import net.sf.firemox.clickable.target.card.SystemCard;
48  import net.sf.firemox.clickable.target.card.TriggeredCard;
49  import net.sf.firemox.clickable.target.player.Player;
50  import net.sf.firemox.event.context.ContextEventListener;
51  import net.sf.firemox.event.context.MContextCardCardIntInt;
52  import net.sf.firemox.network.MMiniPipe;
53  import net.sf.firemox.test.TestOn;
54  import net.sf.firemox.token.IdCommonToken;
55  import net.sf.firemox.token.IdPositions;
56  import net.sf.firemox.token.IdTokens;
57  import net.sf.firemox.token.IdZones;
58  import net.sf.firemox.tools.IntegerList;
59  import net.sf.firemox.tools.Log;
60  import net.sf.firemox.tools.MToolKit;
61  import net.sf.firemox.tools.PairCardInt;
62  import net.sf.firemox.ui.MagicUIComponents;
63  import net.sf.firemox.ui.UIHelper;
64  import net.sf.firemox.ui.i18n.LanguageManager;
65  import net.sf.firemox.zone.ZoneManager;
66  
67  /***
68   * Represents all methods to cast a spell/ability. Manage the succession of
69   * actions of a ability.
70   * 
71   * @since 0.12 enable to abort mana pay of a spell/ability <br>
72   *        0.13 ability will be differenced from other spell, copy is highlighted
73   *        to green <br>
74   *        0.24 save and restore choice list of opponent too <br>
75   *        0.30 an option playing automatically single "YOU MUST" ability is
76   *        supported <br>
77   *        0.30 stack resolution activated when two players declines to response
78   *        to a spell<br>
79   *        0.31 additional "skipping" options added <br>
80   *        0.52 "hop" instruction supported, to enable 'if then else', 'for' like
81   *        instructions <br>
82   *        0.52 private values added, used as registers for each spell. <br>
83   *        0.72 requesting 'refresh' for cards have been implemented. <br>
84   *        0.80 looping actions supported <br>
85   *        0.85 Game restart after a game lost is supported <br>
86   *        0.85 Mana pay can be aborted. <br>
87   *        0.86 Cost are initialized and can be canceled/replayed. <br>
88   *        0.94 Additional costs are handled and filtered to ActionManager <br>
89   * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
90   */
91  public final class StackManager implements StackContext {
92  
93  	/***
94  	 * Create a new instance of this class.
95  	 */
96  	private StackManager() {
97  		super();
98  	}
99  
100 	/***
101 	 * Reset completely the stack
102 	 */
103 	public static void reset() {
104 		lostGameStatus = 0;
105 		gameLostProceed = false;
106 		CONTEXTES.clear();
107 		disableAbort();
108 		SAVED_INT_LISTS.clear();
109 		SAVED_TARGET_LISTS.clear();
110 		noReplayToken.release();
111 		REFRESH_PROPERTIES.clear();
112 		REFRESH_REGISTERS.clear();
113 		REFRESH_CONTROLLER.clear();
114 		REFRESH_TYPES.clear();
115 		REFRESH_ABILITIES.clear();
116 		REFRESH_COLORS.clear();
117 		idHandedPlayer = -1;
118 	}
119 
120 	/***
121 	 * Read from the specified stream the initial registers of players.
122 	 * <ul>
123 	 * Structure of InputStream : Data[size]
124 	 * <li>register of starting player [IdTokens.PLAYER_REGISTER_SIZE]</li>
125 	 * <li>register of non starting player [IdTokens.PLAYER_REGISTER_SIZE]</li>
126 	 * <li>abortion idZone [1]</li>
127 	 * <li>additional-costs [Test,Action[]]</li>
128 	 * </ul>
129 	 * 
130 	 * @param inputFile
131 	 *          is the file containing the stack definition.
132 	 * @param firstPlayer
133 	 *          the player's id starting the game.
134 	 * @throws IOException
135 	 *           if error occurred during the reading process from the specified
136 	 *           input stream
137 	 */
138 	public void init(InputStream inputFile, int firstPlayer) throws IOException {
139 
140 		// load the registers
141 		PLAYERS[0].registers = new int[IdTokens.PLAYER_REGISTER_SIZE];
142 		PLAYERS[1].registers = new int[IdTokens.PLAYER_REGISTER_SIZE];
143 		for (int i = 0; i < IdTokens.PLAYER_REGISTER_SIZE; i++) {
144 			PLAYERS[firstPlayer].registers[i] = inputFile.read();
145 		}
146 		for (int i = 0; i < IdTokens.PLAYER_REGISTER_SIZE; i++) {
147 			PLAYERS[1 - firstPlayer].registers[i] = inputFile.read();
148 		}
149 
150 		// place where aborted spell would be placed
151 		zoneAbortion = inputFile.read();
152 
153 		// Load additional costs
154 		final int count = inputFile.read();
155 		additionalCosts = new ArrayList<AdditionalCost>();
156 		for (int i = 0; i < count; i++) {
157 			additionalCosts.add(new AdditionalCost(inputFile));
158 		}
159 
160 		getInstance().abortingAbility = null;
161 		actionManager = new ActionManager(null, null);
162 	}
163 
164 	/***
165 	 * Add a spell/activated ability to the stack
166 	 * 
167 	 * @param ability
168 	 *          is the ability played
169 	 * @param advancedMode
170 	 *          if true, this ability would be added with advanced mode option.
171 	 * @since 0.13 ability will be differenced from other spell, copy is
172 	 *        highlighted to green
173 	 * @since 0.85 advanced mode supported
174 	 */
175 	public static void newSpell(Ability ability, boolean advancedMode) {
176 		// initialize parameter of this spell/ability
177 		Log.info("NEW SPELL " + MToolKit.getLogCardInfo(ability.getCard())
178 				+ (ability.isAutoResolve() ? ", autoResolve=true" : ""));
179 		instance.initNewSpell(ability, ability.getCard().controller, null);
180 		if (ability.isPlayAsSpell()) {
181 			tokenCard = ability.getCard();
182 			// it's a spell, we place the card of this spell into the stack
183 			previousPlace = ((MCard) tokenCard).getIdZone();
184 			// TODO previousPosition = ((MCard) tokenCard).getIdZone();
185 			tokenCard.moveCard(IdZones.STACK, tokenCard.controller, false,
186 					IdPositions.ON_THE_TOP);
187 			((MCard) tokenCard).unregisterAbilities();
188 		} else {
189 			tokenCard = ability.getCardCopy();
190 			// we place a copy of the card of the ability on the top of the stack
191 			ZoneManager.stack.addTop((MCard) tokenCard);
192 			previousPlace = -1;
193 		}
194 		MagicUIComponents.logListing.append(spellController.idPlayer, ability
195 				.getLog(null));
196 	}
197 
198 	/***
199 	 * Add a triggered ability to the stack. If this ability is an hidden one, the
200 	 * stack is resolved immediately and then another triggered card can be added.
201 	 * Otherwise, this triggered ability is added to the stack and the 'wait
202 	 * triggered buffer choice' process is reloaded until no more triggered
203 	 * ability can be added to the stack.
204 	 * 
205 	 * @param triggered
206 	 *          is the triggered ability to add to t he stack
207 	 * @return true if the stack can be resolved after this call.
208 	 * @since 0.60
209 	 */
210 	public static boolean newSpell(TriggeredCard triggered) {
211 		// initialize parameter of this spell/ability
212 		Log.info("NEW TRIGGERED ABILITY " + triggered);
213 		instance.initNewSpell(triggered.triggeredAbility, triggered.controller,
214 				triggered);
215 		StackManager.triggered = triggered;
216 		StackManager.tokenCard = triggered;
217 		previousPlace = -1;
218 		if (currentAbility.getCard() == SystemCard.instance) {
219 			MagicUIComponents.logListing.append(-1, currentAbility.getLog(instance
220 					.getAbilityContext()));
221 		} else {
222 			MagicUIComponents.logListing.append(spellController.idPlayer,
223 					currentAbility.getLog(instance.getAbilityContext()));
224 		}
225 		if (triggered.triggeredAbility.isHidden()) {
226 			return true;
227 		}
228 		triggered.moveCard(IdZones.STACK, triggered.triggeredAbility.getCard()
229 				.getController(), false, IdPositions.ON_THE_TOP);
230 		// check again waiting triggered abilities
231 		StackManager.activePlayer().waitTriggeredBufferChoice(true);
232 		return false;
233 	}
234 
235 	/***
236 	 * Save context, initialize variables for this new spell/ability
237 	 * 
238 	 * @param ability
239 	 *          the new spell/ability to add to the stack
240 	 * @param controller
241 	 *          the controller of this new spell.
242 	 */
243 	private void initNewSpell(Ability ability, Player controller,
244 			TriggeredCard triggered) {
245 		CONTEXTES.push(new StackElement());
246 		spellController = controller;
247 		registers = new int[IdTokens.STACK_REGISTER_SIZE];
248 		getInstance().targetedList = new TargetedList(ability);
249 		intList = new IntegerList();
250 		targetOptions = new TargetManager();
251 		StackManager.triggered = triggered;
252 		canBeAborted = true;
253 		abilityID++;
254 
255 		// add this ability in the abilities list
256 		currentAbility = ability;
257 
258 		// Built the new action manager
259 		actionManager = new ActionManager(currentAbility,
260 				getAdditionalCost(ability));
261 	}
262 
263 	/***
264 	 * Returns the current additional costs.
265 	 * 
266 	 * @return the current additional costs.
267 	 */
268 	public List<AdditionalCost> getAdditionalCost() {
269 		return additionalCosts;
270 	}
271 
272 	/***
273 	 * Returns the additional cost can be applied to the given ability.
274 	 * 
275 	 * @param ability
276 	 *          the ability to test.
277 	 * @return the additional cost can be applied to the given ability.
278 	 */
279 	public MAction[] getAdditionalCost(Ability ability) {
280 		MAction[] addiCosts = null;
281 		for (AdditionalCost addiCost : additionalCosts) {
282 			if (addiCost.constraint.test(ability, ability.getCard())) {
283 				if (addiCosts == null) {
284 					addiCosts = addiCost.cost;
285 				} else {
286 					final MAction[] tmp = addiCosts;
287 					addiCosts = new MAction[addiCosts.length + addiCost.cost.length];
288 					System.arraycopy(tmp, 0, addiCosts, 0, tmp.length);
289 					System.arraycopy(addiCost.cost, 0, addiCosts, tmp.length,
290 							addiCost.cost.length);
291 				}
292 			}
293 		}
294 		return addiCosts;
295 	}
296 
297 	/***
298 	 * Resolve the stack <br>
299 	 * <ul>
300 	 * An ability is split into 4 parts :
301 	 * <li>paying part (cost)
302 	 * <li>raise event "casting", stack waiting triggered abilities and active
303 	 * player gets priority
304 	 * <li>effect part (effects)
305 	 * <li>restore context, stack waiting triggered abilities and active player
306 	 * gets priority
307 	 * </ul>
308 	 */
309 	public static void resolveStack() {
310 		do {
311 			if (gameLostProceed) {
312 				return;
313 			}
314 			if (spellController != null) {
315 				idActivePlayer = spellController.idPlayer;
316 			}
317 
318 			if (isEmpty()) {
319 				// The active player gets priority (if no triggered have to be stacked)
320 				StackManager.activePlayer().waitTriggeredBufferChoice(true);
321 				return;
322 			}
323 			if (aborted) {
324 				// the current spell has been aborted
325 				getInstance().finishSpell();
326 				return;
327 			}
328 		} while (actionManager.playNextAction());
329 		// resolveStack();
330 
331 	}
332 
333 	/***
334 	 * Cancel the current spell or ability. A canceled ability is removed from the
335 	 * stack. A canceled spell is returned from the stack to it's previous zone. A
336 	 * canceled triggered ability, if hidden, is stopped immediately and the stack
337 	 * resolution continues. A canceled triggered ability that is not hidden is
338 	 * simply restarted : it is neither removed from the stack neither replaced in
339 	 * the TBZ.
340 	 * 
341 	 * @since 0.86
342 	 * @since 0.90 The spell is only canceled if the current ability is in "cost
343 	 *        part".
344 	 */
345 	public static void cancel() {
346 		if (actionManager.advancedEffectMode) {
347 			// we are in advanced mode in the "effect part", restart only this part
348 			Log.info("\t...ability " + triggered
349 					+ " canceled in effect part, restart it");
350 			actionManager.rollback();
351 			actionManager.restart(currentAbility);
352 			resolveStack();
353 		} else {
354 
355 			// Terminate the current 'Waiting' action
356 			((Waiting) actionManager.currentAction).finished();
357 
358 			// Is the current ability is a triggered one?
359 			if (actionManager.advancedMode) {
360 				actionManager.rollback();
361 			}
362 			if (triggered == null) {
363 				if (tokenCard.isACopy()) {
364 					/*
365 					 * it was an activated ability, so since card is a copy in stack panel
366 					 * we have to remove it from stack to put it into the abyss.
367 					 */
368 					Log.info("\t...restore context, " + tokenCard
369 							+ "'s ability is canceled");
370 					ZoneManager.stack.remove(tokenCard);
371 				} else {
372 					/*
373 					 * it was a spell. This card has to be placed into it's previous zone.
374 					 */
375 					tokenCard.moveCard(previousPlace, spellController, false, 0);
376 
377 					// restore available abilities
378 					((MCard) tokenCard).registerAbilities(MCard.getIdZone(previousPlace,
379 							null));
380 				}
381 				Log.info("\t...restore context, " + tokenCard + " spell is canceled");
382 
383 				// we restore the context of the stack and continue to resolve it
384 				CONTEXTES.pop().restore();
385 			} else {
386 				/*
387 				 * it was a triggered ability, this card has NOT to be placed into it's
388 				 * previous zone. This ability is not canceled, but only reinitialized.
389 				 */
390 				if (triggered.triggeredAbility.isHidden()) {
391 					// this ability has never been put in the stack, should not happens
392 					Log.warn("\t...restore context, hidden triggered canceled "
393 							+ triggered);
394 					CONTEXTES.pop().restore();
395 				} else {
396 					Log.info("\t...triggered canceled " + triggered + ", restarting it");
397 					StackManager.actionManager.restart(currentAbility);
398 					StackManager.resolveStack();
399 				}
400 			}
401 		}
402 	}
403 
404 	/***
405 	 * This method finish the current spell. Remove it from the stack and restore
406 	 * the last context of stack.
407 	 */
408 	public void finishSpell() {
409 		// Is the game is finished?
410 		if (processGameLost()) {
411 			// Ok, stop stack resolution now
412 			gameLostProceed = true;
413 			return;
414 		}
415 
416 		// Is the current ability is a triggered one?
417 		if (triggered == null) {
418 			if (tokenCard.isACopy()) {
419 				/***
420 				 * it was an activated ability, so since card is a copy in stack panel
421 				 * we have to remove it from stack to put it into the abyss.
422 				 */
423 				Log.info("\t...restore context, " + tokenCard + "'s ability is done");
424 				ZoneManager.stack.remove(tokenCard);
425 			} else {
426 				/***
427 				 * it was a spell. This card has at least on action as part of it's
428 				 * effects that moves itself into another place like graveyard or hand.
429 				 * So the card is now in graveyard or in play now or somewhere else but
430 				 * no more in the stack.
431 				 */
432 				if (aborted) {
433 					// the current spell has just been aborted, so we have to remove it
434 					targetedList.clear();
435 					// the spell really abort
436 					MoveCard.moveCard((MCard) tokenCard, TestOn.OWNER, zoneAbortion,
437 							null, IdPositions.ON_THE_TOP, currentAbility, false);
438 				}
439 				Log.info("\t...restore context, " + tokenCard + "'s spell is done");
440 			}
441 		} else {
442 			/***
443 			 * it was a triggered ability, we remove it from the stack to put it in
444 			 * the abyss.
445 			 */
446 			Log.info("\t...restore context, triggered done " + triggered);
447 			if (!triggered.triggeredAbility.isHidden()) {
448 				// this ability has been put in the stack
449 				ZoneManager.stack.remove(triggered);
450 			}
451 			if (triggered.getAbilityContext() != null) {
452 				triggered.getAbilityContext().removeTimestamp();
453 			}
454 		}
455 		CONTEXTES.pop().restore();
456 	}
457 
458 	/***
459 	 * enable abort action
460 	 * 
461 	 * @since 0.12 enable to abort mana pay of a spell/ability
462 	 */
463 	public static void enableAbort() {
464 		// enable cancel button
465 		MagicUIComponents.skipButton.setIcon(CANCEL_ICON);
466 		MagicUIComponents.skipButton.setToolTipText(TT_CANCEL);
467 		MagicUIComponents.skipMenu.setIcon(CANCEL_ICON);
468 		MagicUIComponents.skipMenu.setText(CANCEL_TXT);
469 		MagicUIComponents.skipMenu.setToolTipText(TT_CANCEL);
470 		if (actionManager.advancedMode) {
471 			MagicUIComponents.chosenCostPanel.cancelButton.setEnabled(true);
472 		}
473 	}
474 
475 	/***
476 	 * disable abort action
477 	 * 
478 	 * @since 0.12 enable to abort mana pay of a spell/ability
479 	 */
480 	public static void disableAbort() {
481 		MagicUIComponents.skipButton.setIcon(DECLINE_ICON);
482 		MagicUIComponents.skipButton.setToolTipText(TT_DECLINE);
483 		MagicUIComponents.skipMenu.setIcon(DECLINE_ICON);
484 		MagicUIComponents.skipMenu.setToolTipText(TT_DECLINE);
485 		MagicUIComponents.skipMenu.setText(DECLINE_TXT);
486 		MagicUIComponents.chosenCostPanel.cancelButton.setEnabled(false);
487 		canBeAborted = false;
488 	}
489 
490 	/***
491 	 * tell if it stills yet any abilities in the stack
492 	 * 
493 	 * @return true if it stills yet any abilities in the stack
494 	 */
495 	public static boolean isEmpty() {
496 		return CONTEXTES.isEmpty();
497 	}
498 
499 	public MCard getSourceCard() {
500 		if (triggered != null) {
501 			/*
502 			 * the real card source of current ability is the card having generated
503 			 * this triggered
504 			 */
505 			return triggered.triggeredAbility.getCard();
506 		}
507 		// else this an ability or a spell directly placed into the stack
508 		return (MCard) tokenCard;
509 	}
510 
511 	public void abortion(AbstractCard card, Ability source) {
512 		MagicUIComponents.logListing.append(0, "abortion of " + card + '\n');
513 		Log.info("abortion of " + card);
514 
515 		boolean wasCurrent = card == tokenCard;
516 		if (wasCurrent) {
517 			/*
518 			 * normal end of the current spell, as it would be done by the normal
519 			 * process in resolveStack() method
520 			 */
521 			aborted = true;
522 			getInstance().abortingAbility = source;
523 			getInstance().finishSpell();
524 		} else {
525 			// the spell to remove is somewhere in the stack,
526 			if (card instanceof TriggeredCard) {
527 				/*
528 				 * it was a triggered ability, we remove it from the stack to put it in
529 				 * the abyss.
530 				 */
531 				if (triggered.triggeredAbility.isHidden()) {
532 					// nothing to do since this ability has not been put in the stack
533 					Log.info("restore context, triggered "
534 							+ triggered.triggeredAbility.getName() + "  is done (hidden)"
535 							+ MToolKit.getLogCardInfo(triggered.triggeredAbility.getCard()));
536 				} else {
537 					Log.info("restore context, triggered "
538 							+ triggered.triggeredAbility.getName() + " is done"
539 							+ MToolKit.getLogCardInfo(triggered.triggeredAbility.getCard()));
540 					ZoneManager.stack.remove(card);
541 				}
542 			} else if (card.isACopy()) {
543 				/*
544 				 * it was an ability, so since card is a copy in stack panel we have to
545 				 * remove it from stack to put it into the abyss.
546 				 */
547 				Log.info("restore context, " + card + "'s ability is aborted");
548 				ZoneManager.stack.remove(card);
549 			} else {
550 
551 				/*
552 				 * It's a spell. This card has at least on action as part of it's
553 				 * effects that moves itself into another place like graveyard or hand.
554 				 * But in this case (abortion) the effects managing this end of spell
555 				 * have not been played, so we have to do it.
556 				 */
557 				Log.info("restore context, " + card
558 						+ "'s spell has been aborted, move it to abortion place");
559 
560 				// clean the context of this spell
561 				StackContext context = getContextOf(card);
562 				context.getActionManager().clean(context.getActionManager());
563 
564 				// restore available abilities
565 				((MCard) card).registerAbilities(zoneAbortion);
566 
567 				card.moveCard(zoneAbortion, ((MCard) card).getOwner(), false, 0);
568 			}
569 			/*
570 			 * now we have to indicate that the ability has been aborted without
571 			 * destroying the stack of context
572 			 */
573 			for (int i = CONTEXTES.size(); i-- > 0;) {
574 				if (CONTEXTES.get(i).ctxtokenCard == card) {
575 					// the removed card has been found in the stack of context
576 					CONTEXTES.get(i).ctxaborted = true;
577 					return;
578 				}
579 			}
580 			throw new InternalError("couldn't find the card to abort");
581 		}
582 	}
583 
584 	/***
585 	 * Return the target list.
586 	 * 
587 	 * @return the target list.
588 	 */
589 	public static List<Target> getTargetListAccess() {
590 		return getInstance().getTargetedList().list;
591 	}
592 
593 	/***
594 	 * return the current player
595 	 * 
596 	 * @return the current player
597 	 */
598 	public static Player currentPlayer() {
599 		return PLAYERS[idCurrentPlayer];
600 	}
601 
602 	/***
603 	 * return the active player
604 	 * 
605 	 * @return the active player
606 	 */
607 	public static Player activePlayer() {
608 		return PLAYERS[idActivePlayer];
609 	}
610 
611 	/***
612 	 * return the non-active player
613 	 * 
614 	 * @return the non-active player
615 	 */
616 	public static Player nonActivePlayer() {
617 		return PLAYERS[1 - idActivePlayer];
618 	}
619 
620 	/***
621 	 * Indicates if the current player is you. Current player is the player'turn.
622 	 * 
623 	 * @return true if the current player is you
624 	 */
625 	public static boolean currentIsYou() {
626 		return currentPlayer() == PLAYERS[0];
627 	}
628 
629 	/***
630 	 * Indicates if the specified player is you
631 	 * 
632 	 * @param player
633 	 *          the player to test
634 	 * @return true if the specified player is you
635 	 */
636 	public static boolean isYou(Player player) {
637 		return StackManager.PLAYERS[0] == player;
638 	}
639 
640 	/***
641 	 * Indicates if the specified replacement ability has already been used to
642 	 * replace the main action. This function verify this ability is not in the
643 	 * stack between the top and the last non-replacement ability.
644 	 * 
645 	 * @param ability
646 	 *          the replacement ability to search
647 	 * @return true if the specified replacement ability has already been used to
648 	 *         replace the main action.
649 	 */
650 	public static boolean isPlaying(ReplacementAbility ability) {
651 		if (currentAbility == ability) {
652 			return true;
653 		}
654 		if (currentAbility instanceof ReplacementAbility) {
655 			// we have to search in context the last non-replacement ability
656 			return isPlaying(ability, CONTEXTES.size() - 1);
657 		}
658 		// the current ability is not a replacement one
659 		return false;
660 
661 	}
662 
663 	/***
664 	 * Indicates if the specified replacement ability has already been used to
665 	 * replace the main action. This function verify this ability is not in the
666 	 * stack between the top and the last non-replacement ability.
667 	 * 
668 	 * @param index
669 	 *          the index of context to visit
670 	 * @return the real card of the current ability. If index is bellow 0 (the
671 	 *         bottom of the stack) an error is thrown.
672 	 */
673 	private static boolean isPlaying(ReplacementAbility ability, int index) {
674 		if (index < 0) {
675 			throw new InternalError("Cannot find a real card into the stack");
676 		}
677 		if (CONTEXTES.get(index).ctxcurrentAbility == ability) {
678 			return true;
679 		}
680 		if (CONTEXTES.get(index).ctxcurrentAbility instanceof ReplacementAbility) {
681 			return isPlaying(ability, index - 1);
682 		}
683 		return false;
684 	}
685 
686 	/***
687 	 * The ability corresponding to the specified card
688 	 * 
689 	 * @param card
690 	 *          the card linked to the searched ability
691 	 * @return the ability corresponding to the specified card
692 	 */
693 	public static Ability getAbilityOf(MCard card) {
694 		if (tokenCard == card) {
695 			return currentAbility;
696 		}
697 
698 		// this is not the current ability, search in the stack contexts
699 		return getActionManager(card).currentAbility;
700 	}
701 
702 	/***
703 	 * Return the action context corresponding to the specified card. If the given
704 	 * card is not found in the context, an exception is thrown.
705 	 * 
706 	 * @param card
707 	 *          the card linked to the searched ability
708 	 * @return the action context corresponding to the specified card
709 	 */
710 	public static ActionManager getActionManager(AbstractCard card) {
711 		return getContextOf(card).getActionManager();
712 	}
713 
714 	public ActionManager getActionManager() {
715 		return actionManager;
716 	}
717 
718 	/***
719 	 * Return the context associated to the given card. Return <code>null</code>
720 	 * if the given card the current spell instance. If the given card is not
721 	 * found in the context, an exception is thrown.
722 	 * 
723 	 * @param card
724 	 *          the card linked to the searched ability
725 	 * @return the context corresponding to the specified card.
726 	 */
727 	public static StackContext getContextOf(AbstractCard card) {
728 		if (tokenCard == card) {
729 			// this is the current spell. No real context assiated yet.
730 			return getInstance();
731 		}
732 
733 		// this is not the current ability, search in the stack contexts
734 		Iterator<StackElement> it = CONTEXTES.iterator();
735 		while (it.hasNext()) {
736 			StackElement context = it.next();
737 			if (context.ctxtokenCard == card) {
738 				return context;
739 			}
740 		}
741 		throw new RuntimeException("The searched card '" + card
742 				+ "' within the stack has not been found.");
743 	}
744 
745 	/***
746 	 * Return the real card of the current ability. In fact, this method return
747 	 * the card owning the last non-replacement ability.<br>
748 	 * TODO is the given card is the card owning the current ability, in this case
749 	 * the parameter is obsolete.
750 	 * 
751 	 * @param owner
752 	 *          the card owning the action requesting the real card.
753 	 * @return the real card of the current ability.
754 	 */
755 	public static MCard getRealSource(MCard owner) {
756 		if ((currentAbility instanceof ReplacementAbility || owner == SystemCard.instance
757 				&& triggered != null)
758 				&& triggered.getAbilityContext() != null
759 				&& triggered.getAbilityContext() instanceof MContextCardCardIntInt
760 				&& !((MContextCardCardIntInt) triggered.getAbilityContext()).isNull2()) {
761 			return ((MContextCardCardIntInt) triggered.getAbilityContext())
762 					.getCard2();
763 		}
764 		return owner;
765 	}
766 
767 	/***
768 	 * Return the player controlling the current spell
769 	 * 
770 	 * @return the player controlling the current spell
771 	 */
772 	public static Player getSpellController() {
773 		if (spellController == null) {
774 			return activePlayer();
775 		}
776 		return spellController;
777 	}
778 
779 	/***
780 	 * For each cards having posted a refresh request, process to the refreshing
781 	 * 
782 	 * @return true if there was one or several posted request.
783 	 */
784 	public static boolean processRefreshRequests() {
785 		boolean res = false;
786 		if (!REFRESH_TYPES.isEmpty()) {
787 			for (MCard card : REFRESH_TYPES) {
788 				card.refreshIdCard();
789 			}
790 			REFRESH_TYPES.clear();
791 			res = true;
792 		}
793 		if (!REFRESH_CONTROLLER.isEmpty()) {
794 			for (MCard card : REFRESH_CONTROLLER) {
795 				card.refreshController();
796 			}
797 			REFRESH_CONTROLLER.clear();
798 			res = true;
799 		}
800 		if (!REFRESH_COLORS.isEmpty()) {
801 			for (MCard card : REFRESH_COLORS) {
802 				card.refreshIdColor();
803 			}
804 			REFRESH_COLORS.clear();
805 			res = true;
806 		}
807 		if (!REFRESH_PROPERTIES.isEmpty()) {
808 			for (PairCardInt pair : REFRESH_PROPERTIES) {
809 				pair.card.refreshProperties(pair.value);
810 			}
811 			REFRESH_PROPERTIES.clear();
812 			res = true;
813 		}
814 		if (!REFRESH_REGISTERS.isEmpty()) {
815 			for (PairCardInt pair : REFRESH_REGISTERS) {
816 				pair.card.refreshRegisters(pair.value);
817 			}
818 			REFRESH_REGISTERS.clear();
819 			res = true;
820 		}
821 		if (!REFRESH_ABILITIES.isEmpty()) {
822 			for (MCard card : REFRESH_ABILITIES) {
823 				card.refreshAbilities();
824 			}
825 			REFRESH_ABILITIES.clear();
826 			res = true;
827 		}
828 		return res;
829 	}
830 
831 	/***
832 	 * Add the "lose status" to specified player
833 	 * 
834 	 * @param idPlayer
835 	 *          the loosing player.
836 	 * @since 0.85
837 	 */
838 	public static void postLoseGame(int idPlayer) {
839 		lostGameStatus |= idPlayer + 1;
840 	}
841 
842 	/***
843 	 * Post a request to refresh the card types of a card. A card can be refreshed
844 	 * only once for the card types.
845 	 * 
846 	 * @param card
847 	 *          the card to refresh.
848 	 */
849 	public static void postRefreshIdCard(MCard card) {
850 		REFRESH_TYPES.add(card);
851 	}
852 
853 	/***
854 	 * Post a request to refresh the abilities of a card. A card can be refreshed
855 	 * only once for the abilities.
856 	 * 
857 	 * @param card
858 	 *          the card to refresh.
859 	 */
860 	public static void postRefreshAbilities(MCard card) {
861 		REFRESH_ABILITIES.add(card);
862 	}
863 
864 	/***
865 	 * Post a request to refresh the colors of a card. A card can be refreshed
866 	 * only once for the colors.
867 	 * 
868 	 * @param card
869 	 *          the card to refresh.
870 	 */
871 	public static void postRefreshColor(MCard card) {
872 		REFRESH_COLORS.add(card);
873 	}
874 
875 	/***
876 	 * Post a request to refresh a property of a card. A card can be refreshed
877 	 * only once for a same register.
878 	 * 
879 	 * @param card
880 	 *          the card to refresh.
881 	 * @param property
882 	 *          the property to refresh.
883 	 */
884 	public static void postRefreshProperties(MCard card, int property) {
885 		REFRESH_PROPERTIES.add(new PairCardInt(card, property));
886 	}
887 
888 	/***
889 	 * Post a request to refresh a register of a card. A card can be refreshed
890 	 * only once for a same register.
891 	 * 
892 	 * @param card
893 	 *          the card to refresh.
894 	 * @param index
895 	 *          the register index to refresh.
896 	 */
897 	public static void postRefreshRegisters(MCard card, int index) {
898 		REFRESH_REGISTERS.add(new PairCardInt(card, index));
899 	}
900 
901 	/***
902 	 * Post a request to refresh the controller of a card. A card can be refreshed
903 	 * only once for the abilities.
904 	 * 
905 	 * @param card
906 	 *          the card to refresh.
907 	 */
908 	public static void postRefreshController(MCard card) {
909 		REFRESH_CONTROLLER.add(card);
910 	}
911 
912 	/***
913 	 * Return the target saved into the specified ability
914 	 * 
915 	 * @param ability
916 	 *          the ability containing the saved component
917 	 * @return the target saved into the specified ability
918 	 */
919 	public static Target getSaved(Ability ability) {
920 		return ((TriggeredAbility) ability).getDelayedCard().saved;
921 	}
922 
923 	/***
924 	 * Read game status to know if the continues or not.
925 	 * 
926 	 * @return true if the game is not ended.
927 	 * @since 0.85
928 	 */
929 	static boolean processGameLost() {
930 		switch (lostGameStatus) {
931 		case 0:
932 			return false;
933 		case 1:
934 			// You lose
935 			JOptionPane.showMessageDialog(MagicUIComponents.magicForm,
936 					LanguageManager.getString("youlose"), "End of Game",
937 					JOptionPane.INFORMATION_MESSAGE);
938 			return true;
939 		case 2:
940 			// You win
941 			JOptionPane.showMessageDialog(MagicUIComponents.magicForm,
942 					LanguageManager.getString("youwin"), "End of Game",
943 					JOptionPane.INFORMATION_MESSAGE);
944 			return true;
945 		case 3:
946 			// It's a draw
947 			JOptionPane.showMessageDialog(MagicUIComponents.magicForm,
948 					LanguageManager.getString("draw"), "End of Game",
949 					JOptionPane.INFORMATION_MESSAGE);
950 			return true;
951 		default:
952 			throw new InternalError("Unknown lost game status : " + lostGameStatus);
953 		}
954 	}
955 
956 	/***
957 	 * Indicates the current action is waiting for a target.
958 	 * 
959 	 * @return true if the current action is waiting for a target.
960 	 */
961 	public static boolean isTargetMode() {
962 		return StackManager.currentAbility != null
963 				&& StackManager.actionManager.currentAction != null
964 				&& StackManager.actionManager.currentAction instanceof ChosenTarget;
965 	}
966 
967 	/***
968 	 * Return the current context. Null if current ability is not a triggered one.
969 	 * 
970 	 * @return the current context. Null if current ability is not a triggered
971 	 *         one.
972 	 */
973 	public ContextEventListener getAbilityContext() {
974 		if (triggered == null) {
975 			return null;
976 		}
977 		return triggered.getAbilityContext();
978 	}
979 
980 	/***
981 	 * Correspond to the bits of player added to lose game
982 	 * 
983 	 * @since 0.85
984 	 */
985 	private static int lostGameStatus;
986 
987 	/***
988 	 * the player controlling the current spell
989 	 */
990 	public static Player spellController;
991 
992 	/***
993 	 * the target option associated to the current spell
994 	 */
995 	public static TargetManager targetOptions;
996 
997 	public TargetedList getTargetedList() {
998 		return targetedList;
999 	}
1000 
1001 	/***
1002 	 * Set a new targeted list.
1003 	 * 
1004 	 * @param savedTargeted
1005 	 *          the new targeted list.
1006 	 */
1007 	public void setTargetedList(TargetedList savedTargeted) {
1008 		this.targetedList = savedTargeted;
1009 	}
1010 
1011 	/***
1012 	 * The target option of the current spell. this target option is owned by the
1013 	 * current spell. May be reseted, changed by the spell itself.
1014 	 */
1015 	private TargetedList targetedList;
1016 
1017 	/***
1018 	 * The current integer list. This list is private to each ability.
1019 	 */
1020 	public static IntegerList intList;
1021 
1022 	/***
1023 	 * private registers for each spell/ability
1024 	 */
1025 	public static int[] registers = new int[2];
1026 
1027 	/***
1028 	 * card representing card in stack
1029 	 */
1030 	public static AbstractCard tokenCard;
1031 
1032 	/***
1033 	 * Current spell is a triggered ability if not null
1034 	 */
1035 	public static TriggeredCard triggered;
1036 
1037 	/***
1038 	 * the current ability in the stack
1039 	 */
1040 	public static Ability currentAbility;
1041 
1042 	/***
1043 	 * Is the previous place of current spell. So means something only when user
1044 	 * can abort, and only if the card representing this spell is not tokenized.
1045 	 * If is -1, abort spell cause the current spell in stack to be removed (not
1046 	 * moved to an existing zone)
1047 	 */
1048 	public static int previousPlace;
1049 
1050 	/***
1051 	 * Indicates if the current spell can be aborted or not
1052 	 */
1053 	public static boolean canBeAborted;
1054 
1055 	/***
1056 	 * Is the game finished?
1057 	 */
1058 	static boolean gameLostProceed;
1059 
1060 	/***
1061 	 * Ability ID
1062 	 */
1063 	public static long abilityID = 0;
1064 
1065 	/***
1066 	 * The current action manager.
1067 	 */
1068 	public static volatile ActionManager actionManager;
1069 
1070 	/***
1071 	 * Identifier of last player having hand (not always active player). This id
1072 	 * is set just before <code>idHandedPlayer</code> is set to <code>-1</code>
1073 	 */
1074 	public static volatile int oldIdHandedPlayer = -1;
1075 
1076 	/***
1077 	 * Identifier of active player
1078 	 */
1079 	public static volatile int idActivePlayer = 0;
1080 
1081 	/***
1082 	 * Current player
1083 	 */
1084 	public static volatile int idCurrentPlayer = 0; // Player's turn
1085 
1086 	/***
1087 	 * Identifier of player having hand (not always active player)
1088 	 */
1089 	public static volatile int idHandedPlayer = -1;
1090 
1091 	/***
1092 	 * players of the play
1093 	 */
1094 	public static final Player[] PLAYERS = new Player[2];
1095 
1096 	/***
1097 	 * Indicates the current ability has been previously aborted
1098 	 */
1099 	static boolean aborted;
1100 
1101 	/***
1102 	 * The current cards set waiting for a refresh of their id card
1103 	 * 
1104 	 * @since 0.72
1105 	 */
1106 	private static final Set<MCard> REFRESH_TYPES = new HashSet<MCard>();
1107 
1108 	/***
1109 	 * The current cards set waiting for a refresh of their abilities
1110 	 * 
1111 	 * @since 0.86
1112 	 */
1113 	private static final Set<MCard> REFRESH_ABILITIES = new HashSet<MCard>();
1114 
1115 	/***
1116 	 * The current cards set waiting for a refresh of their controller
1117 	 * 
1118 	 * @since 0.72
1119 	 */
1120 	private static final Set<MCard> REFRESH_CONTROLLER = new HashSet<MCard>();
1121 
1122 	/***
1123 	 * The current cards set waiting for a refresh of their id color
1124 	 * 
1125 	 * @since 0.72
1126 	 */
1127 	private static final Set<MCard> REFRESH_COLORS = new HashSet<MCard>();
1128 
1129 	/***
1130 	 * The current cards set waiting for a refresh of their registers
1131 	 * 
1132 	 * @since 0.72
1133 	 */
1134 	private static final Set<PairCardInt> REFRESH_REGISTERS = new HashSet<PairCardInt>();
1135 
1136 	/***
1137 	 * The current cards set waiting for a refresh of their properties
1138 	 * 
1139 	 * @since 0.72
1140 	 */
1141 	private static final Set<PairCardInt> REFRESH_PROPERTIES = new HashSet<PairCardInt>();
1142 
1143 	/***
1144 	 * The object used to exclusively manage player events.
1145 	 */
1146 	public static MMiniPipe noReplayToken = new MMiniPipe(false);
1147 
1148 	/***
1149 	 * represents the place where aborted spells would be placed
1150 	 */
1151 	public static int zoneAbortion;
1152 
1153 	/***
1154 	 * contains all contexts
1155 	 */
1156 	public static final Stack<StackElement> CONTEXTES = new Stack<StackElement>();
1157 
1158 	/***
1159 	 * Represents the target list saved during this turn. This list is
1160 	 * automatically reseted at the beginning of a turn, and before any triggered
1161 	 * or activated abilities can be played.
1162 	 */
1163 	public static final List<List<Target>> SAVED_TARGET_LISTS = new ArrayList<List<Target>>();
1164 
1165 	/***
1166 	 * All saved integer list.
1167 	 */
1168 	public static final List<IntegerList> SAVED_INT_LISTS = new ArrayList<IntegerList>();
1169 
1170 	/***
1171 	 * Private class mContext corresponding to the whole context of the actual
1172 	 * play
1173 	 * 
1174 	 * @author Fabrice Daugan
1175 	 * @since 0.11 registers, target options, target list, triggered card, spell,
1176 	 *        'can be aborted' token, 'already paid' token, action index within
1177 	 *        the current ability and the previous place of card
1178 	 * @since 0.24 save and restore choice list of opponent too
1179 	 * @since 0.6 choice list are no more saved
1180 	 */
1181 	static class StackElement implements StackContext {
1182 
1183 		/***
1184 		 * Save the whole context of the play
1185 		 */
1186 		protected StackElement() {
1187 			/* save variables */
1188 			ctxtargetOptions = StackManager.targetOptions;
1189 			ctxtargetedList = StackManager.getInstance().getTargetedList();
1190 			ctxintList = StackManager.intList;
1191 			ctxregisters = StackManager.registers;
1192 			ctxcanBeAborted = StackManager.canBeAborted;
1193 			ctxtriggered = StackManager.triggered;
1194 			ctxpreviousPlace = StackManager.previousPlace;
1195 			ctxtokenCard = StackManager.tokenCard;
1196 			ctxactionManager = StackManager.actionManager;
1197 			ctxcurrentAbility = StackManager.currentAbility;
1198 			ctxidActivePlayer = StackManager.idActivePlayer;
1199 			ctxspellController = spellController;
1200 			ctxabilityID = abilityID;
1201 		}
1202 
1203 		public TargetedList getTargetedList() {
1204 			return ctxtargetedList;
1205 		}
1206 
1207 		public ContextEventListener getAbilityContext() {
1208 			if (ctxtriggered == null) {
1209 				return null;
1210 			}
1211 			return ctxtriggered.getAbilityContext();
1212 		}
1213 
1214 		public ActionManager getActionManager() {
1215 			return ctxactionManager;
1216 		}
1217 
1218 		public MCard getSourceCard() {
1219 			if (ctxtriggered != null) {
1220 				/*
1221 				 * the real card source of current ability is the card having generated
1222 				 * this triggered
1223 				 */
1224 				return ctxtriggered.triggeredAbility.getCard();
1225 			}
1226 			// else this an ability or a spell directly placed into the stack
1227 			return (MCard) ctxtokenCard;
1228 		}
1229 
1230 		/***
1231 		 * Restore the whole context of the play
1232 		 */
1233 		protected void restore() {
1234 			final ResolveStackHandler handler = currentAbility;
1235 			MagicUIComponents.chosenCostPanel.clean();
1236 
1237 			// restore player history
1238 			StackManager.targetOptions = ctxtargetOptions;
1239 			StackManager.getInstance().getTargetedList().clear();
1240 			StackManager.getInstance().setTargetedList(ctxtargetedList);
1241 			StackManager.registers = ctxregisters;
1242 			StackManager.triggered = ctxtriggered;
1243 			StackManager.canBeAborted = ctxcanBeAborted;
1244 			StackManager.previousPlace = ctxpreviousPlace;
1245 			StackManager.tokenCard = ctxtokenCard;
1246 			StackManager.aborted = ctxaborted;
1247 			StackManager.actionManager.clean(ctxactionManager);
1248 			StackManager.actionManager = ctxactionManager;
1249 			StackManager.intList.clear();
1250 			StackManager.intList = ctxintList;
1251 			abilityID = ctxabilityID;
1252 
1253 			// restore old active player and also the current spell controller
1254 
1255 			StackManager.currentAbility = ctxcurrentAbility;
1256 			StackManager.idActivePlayer = ctxidActivePlayer;
1257 			StackManager.spellController = ctxspellController;
1258 
1259 			ctxspellController = null;
1260 			ctxcurrentAbility = null;
1261 			ctxtokenCard = null;
1262 			ctxtriggered = null;
1263 			ctxtargetedList = null;
1264 			ctxintList = null;
1265 			ctxregisters = null;
1266 			ctxtargetOptions = null;
1267 			ctxactionManager = null;
1268 
1269 			// continue the current ability where we where
1270 			handler.resolveStack();
1271 		}
1272 
1273 		public Ability getAbortingAbility() {
1274 			return abortingAbility;
1275 		}
1276 
1277 		public void abortion(AbstractCard card, Ability source) {
1278 			abortingAbility = source;
1279 		}
1280 
1281 		private Ability abortingAbility;
1282 
1283 		private IntegerList ctxintList;
1284 
1285 		private int ctxidActivePlayer;
1286 
1287 		private TargetManager ctxtargetOptions;
1288 
1289 		/***
1290 		 * List of targets added by the current ability.
1291 		 */
1292 		protected TargetedList ctxtargetedList;
1293 
1294 		private int[] ctxregisters;
1295 
1296 		private TriggeredCard ctxtriggered;
1297 
1298 		boolean ctxcanBeAborted;
1299 
1300 		private int ctxpreviousPlace;
1301 
1302 		/***
1303 		 * Previous ability's representation
1304 		 */
1305 		protected AbstractCard ctxtokenCard;
1306 
1307 		/***
1308 		 * Previous ability.
1309 		 */
1310 		protected Ability ctxcurrentAbility;
1311 
1312 		/***
1313 		 * Previous information about abortion state.
1314 		 */
1315 		protected boolean ctxaborted;
1316 
1317 		ActionManager ctxactionManager;
1318 
1319 		private Player ctxspellController;
1320 
1321 		long ctxabilityID;
1322 
1323 	}
1324 
1325 	/***
1326 	 * Decline tooltip text
1327 	 */
1328 	public static final String TT_DECLINE = LanguageManager
1329 			.getString("menu_game_skip.tooltip");
1330 
1331 	/***
1332 	 * The cancel tooltip text
1333 	 */
1334 	public static final String TT_CANCEL = LanguageManager
1335 			.getString("menu_game_cancel.tooltip");
1336 
1337 	/***
1338 	 * The cancel text
1339 	 */
1340 	public static final String CANCEL_TXT = LanguageManager
1341 			.getString("menu_game_cancel");
1342 
1343 	/***
1344 	 * The decline text
1345 	 */
1346 	public static final String DECLINE_TXT = LanguageManager
1347 			.getString("menu_game_skip");
1348 
1349 	/***
1350 	 * icon of skip button
1351 	 */
1352 	private static final Icon CANCEL_ICON = UIHelper
1353 			.getIcon("menu_game_cancel.gif");
1354 
1355 	/***
1356 	 * icon of next button
1357 	 */
1358 	private static final Icon DECLINE_ICON = UIHelper
1359 			.getIcon("menu_game_skip.gif");
1360 
1361 	/***
1362 	 * Unique instance of this class.
1363 	 */
1364 	private static StackManager instance = new StackManager();
1365 
1366 	/***
1367 	 * Return the unique instance of this class.
1368 	 * 
1369 	 * @return the unique instance of this class.
1370 	 */
1371 	public static StackManager getInstance() {
1372 		return instance;
1373 	}
1374 
1375 	/***
1376 	 * Return HTML representation of total mana paid for the specified card.
1377 	 * 
1378 	 * @param card
1379 	 *          the card associated to a spell in the stack
1380 	 * @return HTML representation of total mana paid for the specified card.
1381 	 */
1382 	public static String getHtmlManaPaid(MCard card) {
1383 		final ActionContextWrapper[] context = StackManager.getActionManager(card)
1384 				.getTotalActionContexts();
1385 		final int[] res = new int[6];
1386 		if (context != null) {
1387 			for (ActionContextWrapper contextI : context) {
1388 				if (contextI != null && contextI.actionContext != null
1389 						&& contextI.actionContext instanceof ManaCost) {
1390 					final int[] manaPaid = ((ManaCost) contextI.actionContext).manaPaid;
1391 					for (int j = IdCommonToken.COLOR_NAMES.length; j-- > 0;) {
1392 						res[j] += manaPaid[j];
1393 					}
1394 				}
1395 			}
1396 		}
1397 		return PayMana.toHtmlString(res);
1398 	}
1399 
1400 	/***
1401 	 * Return HTML representation of total mana cost of the specified card.
1402 	 * 
1403 	 * @param card
1404 	 *          the card associated to a spell in the stack
1405 	 * @return HTML representation of total mana cost of the specified card.
1406 	 */
1407 	public static String getHtmlManaCost(MCard card) {
1408 		final ActionContextWrapper[] context = StackManager.getActionManager(card)
1409 				.getTotalActionContexts();
1410 		final int[] res = new int[6];
1411 		if (context != null) {
1412 			for (ActionContextWrapper contextI : context) {
1413 				if (contextI != null && contextI.actionContext != null
1414 						&& contextI.actionContext instanceof ManaCost) {
1415 					final int[] manaCost = ((ManaCost) contextI.actionContext).manaCost;
1416 					for (int j = IdCommonToken.COLOR_NAMES.length; j-- > 0;) {
1417 						res[j] += manaCost[j];
1418 					}
1419 				}
1420 			}
1421 		}
1422 		return PayMana.toHtmlString(res);
1423 	}
1424 
1425 	/***
1426 	 * Return total mana cost of the specified card.
1427 	 * 
1428 	 * @param card
1429 	 *          the card associated to a spell in the stack
1430 	 * @return total mana cost of the specified card.
1431 	 */
1432 	public static int getTotalManaCost(AbstractCard card) {
1433 		final ActionContextWrapper[] context = StackManager.getActionManager(card)
1434 				.getTotalActionContexts();
1435 		if (context == null) {
1436 			return 0;
1437 		}
1438 		int res = 0;
1439 		for (ActionContextWrapper contextI : context) {
1440 			if (contextI != null && contextI.actionContext != null
1441 					&& contextI.actionContext instanceof ManaCost) {
1442 				int[] manaCost = ((ManaCost) contextI.actionContext).manaCost;
1443 				for (int j = IdCommonToken.COLOR_NAMES.length; j-- > 0;) {
1444 					res += manaCost[j];
1445 				}
1446 			}
1447 		}
1448 		return res;
1449 	}
1450 
1451 	/***
1452 	 * Return total mana paid for the specified card.
1453 	 * 
1454 	 * @param card
1455 	 *          the card associated to a spell in the stack
1456 	 * @return total mana paid for the specified card.
1457 	 */
1458 	public static int getTotalManaPaid(MCard card) {
1459 		final ActionContextWrapper[] context = StackManager.getActionManager(card)
1460 				.getTotalActionContexts();
1461 		if (context == null) {
1462 			return 0;
1463 		}
1464 		int res = 0;
1465 		for (ActionContextWrapper contextI : context) {
1466 			if (contextI != null && contextI.actionContext != null
1467 					&& contextI.actionContext instanceof ManaCost) {
1468 				int[] manaPaid = ((ManaCost) contextI.actionContext).manaPaid;
1469 				for (int j = IdCommonToken.COLOR_NAMES.length; j-- > 0;) {
1470 					res += manaPaid[j];
1471 				}
1472 			}
1473 		}
1474 		return res;
1475 	}
1476 
1477 	/***
1478 	 * Return mana paid for the specified card and specified color.
1479 	 * 
1480 	 * @param card
1481 	 *          the card associated to a spell in the stack
1482 	 * @param color
1483 	 *          the color of mana paid.
1484 	 * @return mana paid for the specified card and specified color.
1485 	 */
1486 	public static int getManaPaid(MCard card, int color) {
1487 		final ActionContextWrapper[] context = StackManager.getActionManager(card)
1488 				.getTotalActionContexts();
1489 		if (context == null) {
1490 			return 0;
1491 		}
1492 		int res = 0;
1493 		for (ActionContextWrapper contextI : context) {
1494 			if (contextI != null && contextI.actionContext != null
1495 					&& contextI.actionContext instanceof ManaCost) {
1496 				res += ((ManaCost) contextI.actionContext).manaPaid[color];
1497 			}
1498 		}
1499 		return res;
1500 	}
1501 
1502 	public Ability getAbortingAbility() {
1503 		return abortingAbility;
1504 	}
1505 
1506 	private Ability abortingAbility;
1507 
1508 	private List<AdditionalCost> additionalCosts;
1509 }