View Javadoc

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