View Javadoc

1   /*
2    *   Firemox is a turn based strategy simulator
3    *   Copyright (C) 2003-2007 Fabrice Daugan
4    *
5    *   This program is free software; you can redistribute it and/or modify it 
6    * under the terms of the GNU General Public License as published by the Free 
7    * Software Foundation; either version 2 of the License, or (at your option) any
8    * later version.
9    *
10   *   This program is distributed in the hope that it will be useful, but WITHOUT 
11   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12   * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
13   * details.
14   *
15   *   You should have received a copy of the GNU General Public License along  
16   * with this program; if not, write to the Free Software Foundation, Inc., 
17   * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  package net.sf.firemox.stack;
20  
21  import net.sf.firemox.action.BackgroundMessaging;
22  import net.sf.firemox.action.Hop;
23  import net.sf.firemox.action.LoopAction;
24  import net.sf.firemox.action.MAction;
25  import net.sf.firemox.action.PayMana;
26  import net.sf.firemox.action.Repeat;
27  import net.sf.firemox.action.WaitTriggeredBufferChoice;
28  import net.sf.firemox.action.context.ActionContextWrapper;
29  import net.sf.firemox.action.context.Int;
30  import net.sf.firemox.action.context.ManaCost;
31  import net.sf.firemox.action.handler.ChosenAction;
32  import net.sf.firemox.action.handler.FollowAction;
33  import net.sf.firemox.action.handler.InitAction;
34  import net.sf.firemox.action.handler.Replayable;
35  import net.sf.firemox.action.handler.RollBackAction;
36  import net.sf.firemox.action.handler.StandardAction;
37  import net.sf.firemox.action.listener.Waiting;
38  import net.sf.firemox.action.listener.WaitingAbility;
39  import net.sf.firemox.action.listener.WaitingAction;
40  import net.sf.firemox.action.listener.WaitingCard;
41  import net.sf.firemox.action.listener.WaitingMana;
42  import net.sf.firemox.action.listener.WaitingPlayer;
43  import net.sf.firemox.action.listener.WaitingTriggeredCard;
44  import net.sf.firemox.action.target.RealTarget;
45  import net.sf.firemox.clickable.ability.Ability;
46  import net.sf.firemox.clickable.action.JChosenAction;
47  import net.sf.firemox.clickable.mana.Mana;
48  import net.sf.firemox.clickable.target.card.MCard;
49  import net.sf.firemox.clickable.target.card.SystemCard;
50  import net.sf.firemox.clickable.target.card.TriggeredCard;
51  import net.sf.firemox.clickable.target.player.Player;
52  import net.sf.firemox.event.Casting;
53  import net.sf.firemox.event.MovedCard;
54  import net.sf.firemox.event.context.ContextEventListener;
55  import net.sf.firemox.operation.Operation;
56  import net.sf.firemox.token.IdCardColors;
57  import net.sf.firemox.token.IdConst;
58  import net.sf.firemox.token.IdZones;
59  import net.sf.firemox.tools.Log;
60  import net.sf.firemox.ui.MagicUIComponents;
61  
62  /***
63   * The most important class of this application, and also the hardest to
64   * understand. This manager schedules the actions handlers and the ability stack
65   * process.
66   * 
67   * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
68   * @since 0.86
69   */
70  public class ActionManager {
71  
72  	/***
73  	 * Create a new instance from the StackManager. Additional cost are inserted
74  	 * at the beginning of cost part.
75  	 */
76  	ActionManager(Ability currentAbility, MAction[] additionalCost) {
77  		this.currentAbility = currentAbility;
78  		this.advancedEffectMode = false;
79  		this.requiredMana = new int[IdCardColors.CARD_COLOR_NAMES.length];
80  		this.overMana = new int[IdCardColors.CARD_COLOR_NAMES.length];
81  		if (currentAbility == null) {
82  			this.nbCosts = 0;
83  			this.effectList = null;
84  			this.actionList = null;
85  		} else {
86  			if (additionalCost != null) {
87  				this.actionList = new MAction[currentAbility.actionList().length
88  						+ additionalCost.length];
89  				System.arraycopy(additionalCost, 0, this.actionList, 0,
90  						additionalCost.length);
91  				System.arraycopy(currentAbility.actionList(), 0, this.actionList,
92  						additionalCost.length, currentAbility.actionList().length);
93  			} else {
94  				this.actionList = currentAbility.actionList();
95  			}
96  			this.idHandler = HANDLER_INITIALIZATION;
97  			this.nbCosts = this.actionList.length;
98  			this.effectList = currentAbility.effectList();
99  		}
100 		restart(currentAbility);
101 	}
102 
103 	void restart(Ability currentAbility) {
104 		hop = 1;
105 		loopingIndex = 0;
106 		currentIdAction = -1;
107 		internalCounter = 0;
108 		actionsContextsWrapper = null;
109 		advancedMode = nbCosts > 0 && !currentAbility.isHidden();
110 		restoreTargetList();
111 		if (nbCosts > 0) {
112 			this.idHandler = HANDLER_INITIALIZATION;
113 		} else {
114 			this.idHandler = HANDLER_MIDDLE;
115 		}
116 	}
117 
118 	/***
119 	 * Clean up this object, and initialize the previous interrupted ActionManager
120 	 * instance. The hop value of this one is reseted to <code>1</code>
121 	 * 
122 	 * @param previousInterrupted
123 	 *          the previous interrupted action manager.
124 	 */
125 	void clean(ActionManager previousInterrupted) {
126 		actionsContextsWrapper = null;
127 		validatedActionsContextsWrapper = null;
128 		currentAbility = null;
129 		currentAction = null;
130 		// previousInterrupted.internalCounter = 0;
131 		previousInterrupted.hop = 1;
132 	}
133 
134 	/***
135 	 * Rollback a specified action index.
136 	 * 
137 	 * @param actionIndex
138 	 *          the action index to rollback.
139 	 */
140 	private void rollbackAction(int actionIndex) {
141 		final MAction action = actionList()[actionIndex];
142 		final ContextEventListener context = StackManager.getInstance()
143 				.getAbilityContext();
144 		if (action instanceof RollBackAction) {
145 			((RollBackAction) action).rollback(actionsContextsWrapper[actionIndex],
146 					context, currentAbility);
147 		} else if (action instanceof Replayable) {
148 			((Replayable) action).replay(actionsContextsWrapper[actionIndex],
149 					context, currentAbility);
150 			// else
151 			// TODO Is this check is necessary?
152 			// throw new InternalError("Unsupported action (id=" + actionIndex
153 			// + ") for rollback purpose : " + action.getClass());
154 		}
155 	}
156 
157 	/***
158 	 * Rollback all actions starting from the last executed action.
159 	 */
160 	void rollback() {
161 		if (currentIdAction >= nbCosts) {
162 			currentIdAction = nbCosts - 1;
163 		}
164 		for (int actionIndex = currentIdAction + 1; actionIndex-- > 0;) {
165 			if (rollbackPath[actionIndex]) {
166 				rollbackAction(actionIndex);
167 			}
168 		}
169 	}
170 
171 	/***
172 	 * Pause stack resolution : wait a mana source ability or a ChosenAction
173 	 * activation
174 	 */
175 	void waitActionChoice() {
176 		completeChosenAction(MagicUIComponents.chosenCostPanel
177 				.playFirstUncompleted());
178 	}
179 
180 	/***
181 	 * Action/Effect list.
182 	 * 
183 	 * @return Action/Effect list.
184 	 */
185 	public MAction[] actionList() {
186 		if (advancedEffectMode) {
187 			return effectList;
188 		}
189 		return actionList;
190 	}
191 
192 	/***
193 	 * Restore the target list as it was at the beginning of the current bloc
194 	 * (cost/effects)
195 	 */
196 	private void restoreTargetList() {
197 		if (!advancedEffectMode
198 				&& StackManager.getInstance().getTargetedList() != null) {
199 			StackManager.getInstance().getTargetedList().clear();
200 		} else {
201 			StackManager.getInstance().setTargetedList(savedTargeted);
202 		}
203 	}
204 
205 	/***
206 	 * Return the current ability's context.
207 	 * 
208 	 * @return the current ability's context.
209 	 */
210 	public ContextEventListener getAbilityContext() {
211 		return StackManager.getInstance().getAbilityContext();
212 	}
213 
214 	boolean playNextAction() {
215 		// The middle effect is not yet finished, return to the previous entrance...
216 		if (waitingOnMiddle) {
217 			StackManager.activePlayer().waitTriggeredBufferChoice(true);
218 			return false;
219 		}
220 
221 		// Looping action first
222 		if (loopingIndex > 0) {
223 			loopingIndex--;
224 			return playLoopingAction();
225 		}
226 
227 		// Set up the next action
228 		goNextAction();
229 
230 		// manage handler
231 		if (!advancedMode && idHandler == HANDLER_INITIALIZATION) {
232 			if (currentIdAction >= nbCosts) {
233 				idHandler = HANDLER_MIDDLE;
234 				currentIdAction = 0;
235 			} else {
236 				currentAction = actionList[currentIdAction];
237 				return playCurrentAction();
238 			}
239 		} else if (advancedMode && idHandler == HANDLER_INITIALIZATION) {
240 
241 			// play remaining InitAction
242 			while (currentIdAction < nbCosts) {
243 				currentAction = actionList()[currentIdAction];
244 				if (currentAction instanceof InitAction) {
245 					if (!(((InitAction) currentAction).init(getActionContext(),
246 							getAbilityContext(), currentAbility))) {
247 						return false;
248 					}
249 				} else if (currentAction instanceof ChosenAction) {
250 					// we initialize the context of this ChosenAction
251 					getActionContext();
252 				}
253 				goNextAction();
254 			}
255 
256 			// All InitAction have been initialized, [re]play all actions
257 			currentIdAction = 0;
258 			idHandler = HANDLER_AD_SERIALIZATION;
259 
260 			// restore target list as it was at the beginning of cost/effects part
261 			restoreTargetList();
262 
263 			// execute all InitAction until the first ChosenAction
264 			while (currentIdAction < nbCosts) {
265 				currentAction = actionList()[currentIdAction];
266 				rollbackPath[currentIdAction] = true;
267 				// do not replay "repeat" action in the serialization handler
268 				if (!(currentAction instanceof Repeat)) {
269 					if (currentAction instanceof Hop) {
270 						((Hop) currentAction).replay(getActionContext(), StackManager
271 								.getInstance().getAbilityContext(), currentAbility);
272 						goNextAction();
273 					} else if (currentAction instanceof ChosenAction) {
274 						// activate the ChosenAction panel
275 						return MagicUIComponents.chosenCostPanel.reset(StackManager
276 								.getInstance().getSourceCard(), actionsContextsWrapper);
277 					} else if (currentAction instanceof InitAction) {
278 						((InitAction) currentAction).replay(getActionContext(),
279 								getAbilityContext(), currentAbility);
280 					} else if (currentAction instanceof FollowAction) {
281 						((FollowAction) currentAction).simulate(getActionContext(),
282 								getAbilityContext(), currentAbility);
283 					} else {
284 						throw new InternalError("Unsupported action (id=" + currentIdAction
285 								+ ") for serialization purpose : " + currentAction.getClass());
286 					}
287 				}
288 				goNextAction();
289 			}
290 			// here and no ChosenAction have been found
291 			idHandler = HANDLER_AD_PREPARE_REPLAY;
292 		} else if (advancedMode && idHandler == HANDLER_AD_SERIALIZATION) {
293 			// first, play all actions until the next ChosenAction
294 			if (!(currentAction instanceof ChosenAction)) {
295 				throw new InternalError(
296 						"The serialization handler must start to process a ChosenAction,class="
297 								+ currentAction.getClass().getName() + " : " + currentAction);
298 			}
299 
300 			if (currentIdAction < nbCosts) {
301 				// First pass : play all actions until the next ChosenAction)
302 				while (currentIdAction < nbCosts) {
303 					currentAction = actionList()[currentIdAction];
304 					rollbackPath[currentIdAction] = true;
305 					// do not replay initialize action in serialization handler
306 					if (!(currentAction instanceof Repeat)) {
307 						if (currentAction instanceof Hop) {
308 							((Hop) currentAction).replay(getActionContext(), StackManager
309 									.getInstance().getAbilityContext(), currentAbility);
310 						} else if (currentAction instanceof FollowAction) {
311 							((FollowAction) currentAction).simulate(getActionContext(),
312 									getAbilityContext(), currentAbility);
313 						} else if (currentAction instanceof RealTarget) {
314 							// is always considered as completed
315 							if (!getActionContext().isCompleted()) {
316 								throw new InternalError(
317 										"RealTarget should be completed in Serialization");
318 							}
319 							getActionContext().done++;
320 							((InitAction) currentAction).replay(getActionContext(),
321 									getAbilityContext(), currentAbility);
322 						} else if (currentAction instanceof ChosenAction) {
323 							if (!getActionContext().isCompleted()) {
324 								// all follow actions have been played, wait player's choice
325 								waitActionChoice();
326 								return false;
327 							}
328 						} else if (currentAction instanceof InitAction) {
329 							((InitAction) currentAction).replay(getActionContext(),
330 									getAbilityContext(), currentAbility);
331 						}
332 					}
333 					goNextAction();
334 				}
335 			}
336 			// here and no ChosenAction have been found --> next handler
337 			idHandler = HANDLER_AD_PREPARE_REPLAY;
338 		}
339 
340 		if (advancedMode && idHandler == HANDLER_AD_PREPARE_REPLAY) {
341 			// rollback all actions
342 			currentIdAction = nbCosts;
343 			rollback();
344 			idHandler = HANDLER_AD_REPLAY;
345 			currentIdAction = 0;
346 		}
347 
348 		if (advancedMode && idHandler == HANDLER_PLAY_INIT) {
349 			// replay all action
350 			while (currentIdAction < nbCosts) {
351 				currentAction = actionList()[currentIdAction];
352 				if (currentAction instanceof ChosenAction) {
353 					// replay initAction
354 					if (!((ChosenAction) currentAction).choose(getActionContext(),
355 							getAbilityContext(), currentAbility)) {
356 						// wait player answer
357 						return false;
358 					}
359 				} else if (currentAction instanceof InitAction) {
360 					// replay initAction
361 					((InitAction) currentAction).replay(getActionContext(), StackManager
362 							.getInstance().getAbilityContext(), currentAbility);
363 				} else {
364 					// normal action
365 					if (!playCurrentAction()) {
366 						// stack resolution is broken
367 						return false;
368 					}
369 				}
370 				goNextAction();
371 			}
372 			// here and no ChosenAction have been found
373 			idHandler = HANDLER_MIDDLE;
374 		}
375 
376 		if (advancedMode && idHandler == HANDLER_AD_REPLAY) {
377 			while (currentIdAction < nbCosts) {
378 				currentAction = actionList()[currentIdAction];
379 				if (currentAction instanceof Repeat) {
380 					if (currentIdAction + 1 < actionList().length
381 							&& !(actionList()[currentIdAction + 1] instanceof InitAction)
382 							&& !(actionList()[currentIdAction + 1] instanceof ChosenAction)) {
383 						((Repeat) currentAction).replayOnDemand(getActionContext(),
384 								getAbilityContext(), currentAbility);
385 					}
386 				} else {
387 					if (currentAction instanceof InitAction) {
388 						// replay the initAction
389 						if (!((InitAction) currentAction).replay(getActionContext(),
390 								getAbilityContext(), currentAbility)) {
391 							return false;
392 						}
393 					} else if (currentAction instanceof ChosenAction) {
394 						// replay the ChosenAction
395 						((ChosenAction) currentAction).replay(getActionContext(),
396 								getAbilityContext(), currentAbility);
397 					} else {
398 						// Play the normal action
399 						if (!playCurrentAction()) {
400 							return false;
401 						}
402 						// Looping action first
403 						while (loopingIndex-- > 0) {
404 							if (!playLoopingAction()) {
405 								// TODO Looping action broken in the HANDLER_AD_REPLAY step,
406 								// unwanted behavior may happen (not yet implemented
407 								Log
408 										.warn("Looping action broken in the HANDLER_AD_REPLAY step, unwanted behavior may happen (not yet implemented");
409 								return false;
410 							}
411 						}
412 					}
413 				}
414 				goNextAction();
415 			}
416 			idHandler = HANDLER_MIDDLE;
417 		}
418 
419 		if (idHandler == HANDLER_MIDDLE) {
420 			if (advancedEffectMode) {
421 				idHandler = HANDLER_EFFECTS;
422 			} else {
423 				if (currentIdAction > nbCosts) {
424 					/*
425 					 * TODO Abort ability is supported, but we should not be here
426 					 */
427 					Log
428 							.debug("WARNING : action id is greater than the amount of actions in cost part of "
429 									+ currentAbility);
430 				}
431 				// Is the game is finished?
432 				if (StackManager.processGameLost()) {
433 					// OK, stop stack resolution now
434 					StackManager.gameLostProceed = true;
435 					return false;
436 				}
437 				rollbackPath = null;
438 				// save the contexts of cost part.
439 				validatedActionsContextsWrapper = actionsContextsWrapper;
440 				actionsContextsWrapper = null;
441 				advancedMode = false;
442 
443 				// Is the resolution is automatically performed with this ability?
444 				if (StackManager.triggered != null || currentAbility.isAutoResolve()) {
445 					/*
446 					 * OK, so no player gets priority, nevertheless all hidden triggered
447 					 * abilities are stacked and played.
448 					 */
449 					if (!currentAbility.isHidden()) {
450 						StackManager.actionManager.currentAction = WaitTriggeredBufferChoice
451 								.getInstance();
452 						if (!StackManager.activePlayer().processHiddenTriggered()
453 								&& !StackManager.nonActivePlayer().processHiddenTriggered()) {
454 							if (StackManager.tokenCard instanceof MCard) {
455 								final MCard currentCard = (MCard) StackManager.tokenCard;
456 								if (StackManager.triggered == null
457 										&& currentCard.getIdZone() == IdZones.STACK) {
458 									/*
459 									 * Simulate the move PreviousZone --> Stack since now the cost
460 									 * has completely been paid.
461 									 */
462 									if (!currentAbility.isAutoResolve()) {
463 										currentCard.setIdZone(StackManager.previousPlace);
464 										MovedCard.dispatchEvent(currentCard, IdZones.STACK,
465 												currentCard.getController(), false);
466 										currentCard.setIdZone(IdZones.STACK);
467 									}
468 								}
469 								Casting.dispatchEvent(currentCard);
470 							}
471 						} else {
472 							return false;
473 						}
474 					}
475 					prepareEffects();
476 					currentIdAction = 0;
477 				} else {
478 					/*
479 					 * No, so we generate the casting event and let the player getting
480 					 * priority. Since cast part is finished and since active player would
481 					 * get priority, we stack the triggered abilities we set the active
482 					 * action as the WaitTriggeredBufferChoice one, and the active player
483 					 * gets priority (if no triggered have to be stacked)
484 					 */
485 					prepareEffects();
486 					currentIdAction = -1;
487 					final MCard currentCard = (MCard) StackManager.tokenCard;
488 					if (StackManager.triggered == null
489 							&& currentCard.getIdZone() == IdZones.STACK) {
490 						/*
491 						 * Simulate the move PreviousZone --> Stack since now the cost has
492 						 * completely been paid.
493 						 */
494 						currentCard.setIdZone(StackManager.previousPlace);
495 						MovedCard.dispatchEvent(currentCard, IdZones.STACK, currentCard
496 								.getController(), false);
497 						currentCard.setIdZone(IdZones.STACK);
498 					}
499 					Casting.dispatchEvent(currentCard);
500 					waitingOnMiddle = true;
501 					/* Some hidden abilities have be added to the stack and played */
502 					StackManager.activePlayer().waitTriggeredBufferChoice(true);
503 					waitingOnMiddle = false;
504 					return false;
505 				}
506 			}
507 		}
508 
509 		if (idHandler == HANDLER_EFFECTS) {
510 
511 			// this part corresponds to effect of this ability
512 			if (currentIdAction == effectList.length) {
513 				// ability is finished
514 				StackManager.getInstance().finishSpell();
515 			} else {
516 				// ability is not finished
517 				if (advancedEffectMode) {
518 					throw new IllegalStateException(
519 							"In HANDLER_EFFECTS & advancedEffectMode with uncompleted ability");
520 				}
521 				if (currentIdAction == 0) {
522 					// re-check targets before proceeding to the effects
523 					if (!currentAbility.recheckTargets()) {
524 						// since all targets are invalid, this ability will be aborted
525 						StackManager.getInstance().abortion(StackManager.tokenCard,
526 								currentAbility);
527 						return false;
528 					}
529 
530 					// play remaining InitAction
531 					if (currentAbility.getCard() != SystemCard.instance) {
532 						while (currentIdAction < effectList.length) {
533 							if (effectList[currentIdAction] instanceof PayMana
534 									|| effectList[currentIdAction] instanceof RealTarget) {
535 								// at least one ChosenAction in effect part
536 								this.advancedEffectMode = true;
537 								this.advancedMode = true;
538 								this.savedTargeted = StackManager.getInstance()
539 										.getTargetedList().cloneList();
540 								this.nbCosts = effectList.length;
541 								restart(currentAbility);
542 								playNextAction();
543 								return false;
544 							}
545 							currentIdAction++;
546 						}
547 						currentIdAction = 0;
548 					}
549 				}
550 				currentAction = effectList[currentIdAction];
551 				return playCurrentAction();
552 			}
553 		}
554 		return false;
555 	}
556 
557 	private void prepareEffects() {
558 		idHandler = HANDLER_EFFECTS;
559 	}
560 
561 	private boolean playLoopingAction() {
562 		if (StackManager.triggered != null) {
563 			return ((LoopAction) currentAction).continueLoop(StackManager
564 					.getInstance().getAbilityContext(), loopingIndex, currentAbility);
565 		}
566 		return ((LoopAction) currentAction).continueLoop(null, loopingIndex,
567 				currentAbility);
568 	}
569 
570 	/***
571 	 * Play the current action
572 	 * 
573 	 * @return true if the stack resolution can continue. False if the stack
574 	 *         resolution is broken.
575 	 */
576 	private boolean playCurrentAction() {
577 		if (currentAction instanceof LoopAction) {
578 			if (currentAction instanceof BackgroundMessaging) {
579 				// This special return value indicates a break in stack resolution
580 				final int loopingIndexTMP = ((LoopAction) currentAction)
581 						.getStartIndex();
582 				if (loopingIndexTMP == Integer.MAX_VALUE) {
583 					return false;
584 				}
585 				loopingIndex = loopingIndexTMP;
586 			} else {
587 				loopingIndex = ((LoopAction) currentAction).getStartIndex();
588 			}
589 			if (loopingIndex > -1) {
590 				return playLoopingAction();
591 			}
592 			return true;
593 		}
594 		// Proceed to the handler management
595 		return ((StandardAction) currentAction).play(StackManager.getInstance()
596 				.getAbilityContext(), currentAbility);
597 	}
598 
599 	/***
600 	 * Send the message "manualSkip" to the active action of play. If the
601 	 * "skip/cancel" event is accepted by this action, we resolve the stack.
602 	 */
603 	public void manualSkip() {
604 		if (currentAction instanceof Waiting
605 				&& ((Waiting) currentAction).manualSkip()) {
606 			StackManager.resolveStack();
607 		}
608 	}
609 
610 	/***
611 	 * Will repeat the next action <code>nbNextAction</code> times
612 	 * 
613 	 * @param nbNextAction
614 	 *          is the times that the next action will be repeated
615 	 */
616 	public void setRepeat(int nbNextAction) {
617 		internalCounter = nbNextAction;
618 		currentIdAction += 1;
619 		hop = 1;
620 	}
621 
622 	/***
623 	 * The current action will become the current action index + "hop". The "hop"
624 	 * value is reseted to 1
625 	 */
626 	private void goNextAction() {
627 		if (internalCounter > 0) {
628 			// repeat this action -> do not go to the next action
629 			internalCounter--;
630 		} else {
631 			currentIdAction += hop;
632 		}
633 		hop = 1;
634 	}
635 
636 	/***
637 	 * Returns the context associated to the specified action id.
638 	 * 
639 	 * @return the context associated to the specified action id.
640 	 */
641 	public ActionContextWrapper getActionContextNull() {
642 		if (actionsContextsWrapper == null) {
643 			return null;
644 		}
645 		return actionsContextsWrapper[currentIdAction];
646 	}
647 
648 	/***
649 	 * Returns the context associated to the specified action id.
650 	 * 
651 	 * @param contextId
652 	 *          the requestion acion id.
653 	 * @return the context associated to the specified action id.
654 	 */
655 	public ActionContextWrapper getActionContext(int contextId) {
656 		if (actionsContextsWrapper == null) {
657 			// first access to action contexts
658 			actionsContextsWrapper = new ActionContextWrapper[actionList().length];
659 
660 			// create action path for rolback purpose
661 			rollbackPath = new boolean[nbCosts];
662 		}
663 		if (actionsContextsWrapper[contextId] == null) {
664 			// first access to this context
665 			if (contextId > 0 && actionsContextsWrapper[contextId - 1] != null
666 					&& actionsContextsWrapper[contextId - 1].action instanceof Repeat) {
667 				actionsContextsWrapper[contextId] = new ActionContextWrapper(contextId,
668 						actionList()[contextId], null,
669 						((Int) actionsContextsWrapper[contextId - 1].actionContext)
670 								.getInt());
671 			} else {
672 				actionsContextsWrapper[contextId] = new ActionContextWrapper(contextId,
673 						actionList()[contextId], null, 1);
674 			}
675 		}
676 		return actionsContextsWrapper[contextId];
677 	}
678 
679 	/***
680 	 * Returns the context associated to the current action id.
681 	 * 
682 	 * @return the context associated to the current action id.
683 	 */
684 	public ActionContextWrapper getActionContext() {
685 		return getActionContext(currentIdAction);
686 	}
687 
688 	/***
689 	 * Returns the whole action contexts of current step : cost OR effect.
690 	 * 
691 	 * @return the whole action contexts of current step : cost OR effect.
692 	 * @see #getAllActionContexts()
693 	 */
694 	public ActionContextWrapper[] getAllActionContexts() {
695 		return actionsContextsWrapper;
696 	}
697 
698 	/***
699 	 * Returns the whole action contexts : cost AND effect.
700 	 * 
701 	 * @return the whole action contexts : cost AND effect.
702 	 * @see #getAllActionContexts()
703 	 */
704 	public ActionContextWrapper[] getTotalActionContexts() {
705 		if (validatedActionsContextsWrapper != null) {
706 			if (actionsContextsWrapper != null) {
707 				final ActionContextWrapper[] total = new ActionContextWrapper[validatedActionsContextsWrapper.length
708 						+ actionsContextsWrapper.length];
709 				System.arraycopy(validatedActionsContextsWrapper, 0, total, 0,
710 						validatedActionsContextsWrapper.length);
711 				System.arraycopy(actionsContextsWrapper, 0, total,
712 						validatedActionsContextsWrapper.length,
713 						actionsContextsWrapper.length);
714 			} else {
715 				return validatedActionsContextsWrapper;
716 			}
717 		} else if (actionsContextsWrapper != null) {
718 			return actionsContextsWrapper;
719 		}
720 		return new ActionContextWrapper[0];
721 	}
722 
723 	/***
724 	 * Set the jump to do for the next action. If the specified "hop" is 0, the
725 	 * next action would be the current one.
726 	 * 
727 	 * @param hop
728 	 *          the jump to do for the next action.
729 	 */
730 	public void setHop(int hop) {
731 		if (hop == IdConst.ALL) {
732 			currentIdAction = effectList.length - 1;
733 			this.hop = 1;
734 			prepareEffects();
735 		} else if (idHandler == HANDLER_EFFECTS || advancedEffectMode) {
736 			this.hop = hop;
737 		} else if (hop + currentIdAction >= actionList().length) {
738 			this.hop = hop + 1;
739 			prepareEffects();
740 		} else {
741 			this.hop = hop;
742 		}
743 		internalCounter = 0;
744 		loopingIndex = -1;
745 	}
746 
747 	/***
748 	 * Called to specify the player choice for the current action
749 	 * 
750 	 * @param card
751 	 *          the clicked card by the active player for the current action
752 	 * @return true if this click has been managed. Return false if this click has
753 	 *         been ignored
754 	 */
755 	public boolean clickOn(MCard card) {
756 		if (currentAction instanceof WaitingCard) {
757 			return ((WaitingCard) currentAction).clickOn(card);
758 		}
759 		return false;
760 	}
761 
762 	/***
763 	 * Called to specify the player choice for the current action
764 	 * 
765 	 * @param player
766 	 *          the clicked player by the active player for the current action
767 	 * @return true if this click has been managed. Return false if this click has
768 	 *         been ignored
769 	 */
770 	public boolean clickOn(Player player) {
771 		if (currentAction instanceof WaitingPlayer) {
772 			return ((WaitingPlayer) currentAction).clickOn(player);
773 		}
774 		return false;
775 	}
776 
777 	/***
778 	 * Called to specify the player choice for the current action
779 	 * 
780 	 * @param triggeredCard
781 	 *          the clicked triggered card by the active player for the current
782 	 *          action
783 	 * @return true if this click has been managed. Return false if this click has
784 	 *         been ignored
785 	 */
786 	public boolean clickOn(TriggeredCard triggeredCard) {
787 		if (currentAction instanceof WaitingTriggeredCard) {
788 			return ((WaitingTriggeredCard) currentAction).clickOn(triggeredCard);
789 		}
790 		return false;
791 	}
792 
793 	/***
794 	 * Called to specify the player choice for the current action
795 	 * 
796 	 * @param ability
797 	 *          the clicked ability by the active player for the current action
798 	 * @return true if this click has been managed. Return false if this click has
799 	 *         been ignored
800 	 */
801 	public boolean clickOn(Ability ability) {
802 		if (currentAction instanceof WaitingAbility) {
803 			return ((WaitingAbility) currentAction).clickOn(ability);
804 		}
805 		return false;
806 	}
807 
808 	/***
809 	 * Called to specify the player choice for the current action
810 	 * 
811 	 * @param mana
812 	 *          the clicked mana by the active player for the current action
813 	 * @return true if this click has been managed. Return false if this click has
814 	 *         been ignored
815 	 */
816 	public boolean clickOn(Mana mana) {
817 		if (currentAction instanceof WaitingMana) {
818 			return ((WaitingMana) currentAction).clickOn(mana);
819 		}
820 		return false;
821 	}
822 
823 	/***
824 	 * Called to specify the player choice for the current action.
825 	 * 
826 	 * @param action
827 	 *          the clicked action by the active player for the current action
828 	 * @return true if this click has been managed. Return false if this click has
829 	 *         been ignored
830 	 */
831 	public boolean clickOn(JChosenAction action) {
832 		if (currentAction instanceof WaitingAction) {
833 			return ((WaitingAction) currentAction).clickOn(action);
834 		}
835 		return false;
836 	}
837 
838 	/***
839 	 * This function should be called by the 'clickOn' caller in case of the
840 	 * specified card has been handled during the checking validity of this click
841 	 * in the <code>clickOn(Card)</code> function. <br>
842 	 * <ul>
843 	 * The calls chain is :
844 	 * <li>actionListener call clickOn(Card)
845 	 * <li>if returned value is false we give hand to the player and exit, else
846 	 * we continue
847 	 * <li>actionListener call succeedClickOn(Card)
848 	 * </ul>
849 	 * 
850 	 * @param card
851 	 *          the card that was clicked and successfully handled by the
852 	 *          <code>clickOn(Card)</code> function.
853 	 * @see #clickOn(MCard)
854 	 */
855 	public void succeedClickOn(MCard card) {
856 		Player.unsetHandedPlayer();
857 		final boolean res = ((WaitingCard) currentAction).succeedClickOn(card);
858 		StackManager.actionManager.completeChosenAction(res);
859 	}
860 
861 	/***
862 	 * This function should be called by the 'clickOn' caller in case of the
863 	 * specified ability has been handled during the checking validity of this
864 	 * click in the <code>clickOn(Ability)</code> function. <br>
865 	 * <ul>
866 	 * The calls chain is :
867 	 * <li>actionListener call clickOn(Ability)
868 	 * <li>if returned value is false we give hand to the player and exit, else
869 	 * we continue
870 	 * <li>actionListener call succeedClickOn(Ability)
871 	 * </ul>
872 	 * 
873 	 * @param ability
874 	 *          the ability that was clicked and successfully handled by the
875 	 *          <code>clickOn(Ability)</code> function.
876 	 * @see #clickOn(Ability)
877 	 */
878 	public void succeedClickOn(Ability ability) {
879 		Player.unsetHandedPlayer();
880 		if (((WaitingAbility) currentAction).succeedClickOn(ability)) {
881 			StackManager.resolveStack();
882 		}
883 	}
884 
885 	/***
886 	 * This function should be called by the 'clickOn' caller in case of the
887 	 * specified triggered card has been handled during the checking validity of
888 	 * this click in the <code>clickOn(MTriggeredCard)</code> function. <br>
889 	 * <ul>
890 	 * The calls chain is :
891 	 * <li>actionListener call clickOn(MTriggeredCard)
892 	 * <li>if returned value is false we give hand to the player and exit, else
893 	 * we continue
894 	 * <li>actionListener call succeedClickOn(MTriggeredCard)
895 	 * </ul>
896 	 * 
897 	 * @param card
898 	 *          the triggered card that was clicked and successfully handled by
899 	 *          the <code>clickOn(MTriggeredCard)</code> function.
900 	 * @see #clickOn(TriggeredCard)
901 	 */
902 	public void succeedClickOn(TriggeredCard card) {
903 		Player.unsetHandedPlayer();
904 		final boolean res = ((WaitingTriggeredCard) currentAction)
905 				.succeedClickOn(card);
906 		StackManager.actionManager.completeChosenAction(res);
907 	}
908 
909 	/***
910 	 * This function should be called by the 'clickOn' caller in case of the
911 	 * specified mana has been handled during the checking validity of this click
912 	 * in the <code>clickOn(MMana)</code> function. <br>
913 	 * <ul>
914 	 * The calls chain is :
915 	 * <li>actionListener call clickOn(MMana)
916 	 * <li>if returned value is false we give hand to the player and exit, else
917 	 * we continue
918 	 * <li>actionListener call succeedClickOn(MMana)
919 	 * </ul>
920 	 * 
921 	 * @param mana
922 	 *          the mana that was clicked and successfully handled by the
923 	 *          <code>clickOn(MMana)</code> function.
924 	 * @see #clickOn(Mana)
925 	 */
926 	public void succeedClickOn(Mana mana) {
927 		Player.unsetHandedPlayer();
928 		final boolean res = ((WaitingMana) currentAction).succeedClickOn(mana);
929 		StackManager.actionManager.completeChosenAction(res);
930 	}
931 
932 	/***
933 	 * This function should be called by the 'clickOn' caller in case of the
934 	 * specified player has been handled during the checking validity of this
935 	 * click in the <code>clickOn(Player)</code> function. <br>
936 	 * <ul>
937 	 * The calls chain is :
938 	 * <li>actionListener call clickOn(Player)
939 	 * <li>if returned value is false we give hand to the player and exit, else
940 	 * we continue
941 	 * <li>actionListener call succeedClickOn(Player)
942 	 * </ul>
943 	 * 
944 	 * @param player
945 	 *          the player that was clicked and successfully handled by the
946 	 *          <code>clickOn(Player)</code> function.
947 	 * @see #clickOn(Player)
948 	 */
949 	public void succeedClickOn(Player player) {
950 		Player.unsetHandedPlayer();
951 		final boolean res = ((WaitingPlayer) currentAction).succeedClickOn(player);
952 		StackManager.actionManager.completeChosenAction(res);
953 	}
954 
955 	/***
956 	 * This function should be called by the 'clickOn' caller in case of the
957 	 * specified player has been handled during the checking validity of this
958 	 * click in the <code>clickOn(JChosenAction)</code> function. <br>
959 	 * <ul>
960 	 * The calls chain is :
961 	 * <li>actionListener call clickOn(JChosenAction)
962 	 * <li>if returned value is false we give hand to the player and exit, else
963 	 * we continue
964 	 * <li>actionListener call succeedClickOn(JChosenAction)
965 	 * </ul>
966 	 * 
967 	 * @param action
968 	 *          the action that was clicked and successfully handled by the
969 	 *          <code>clickOn(JChosenAction)</code> function.
970 	 * @see #clickOn(JChosenAction)
971 	 */
972 	public void succeedClickOn(JChosenAction action) {
973 		Player.unsetHandedPlayer();
974 		final boolean res = ((WaitingAction) currentAction).succeedClickOn(action);
975 		StackManager.actionManager.completeChosenAction(res);
976 	}
977 
978 	/***
979 	 * Complete the current action.
980 	 * 
981 	 * @param unitaryCompleted
982 	 *          is this action is completed as many times as required?
983 	 */
984 	public void completeChosenAction(boolean unitaryCompleted) {
985 		if (advancedMode) {
986 			if (unitaryCompleted) {
987 				// this action is unitary completed
988 				if (MagicUIComponents.chosenCostPanel.completeAction(
989 						StackManager.currentAbility, StackManager.getInstance()
990 								.getAbilityContext(), getActionContext())) {
991 					internalCounter = 0;
992 					// ok, process now all FollowAction associated to this action
993 					StackManager.resolveStack();
994 				} else {
995 					// this action need to be repeated
996 					if (currentAction instanceof InitAction) {
997 						if (((InitAction) currentAction).init(getActionContext(),
998 								getAbilityContext(), StackManager.currentAbility)) {
999 							StackManager.resolveStack();
1000 						}
1001 					} else if (((ChosenAction) currentAction).choose(getActionContext(),
1002 							getAbilityContext(), StackManager.currentAbility)) {
1003 						StackManager.resolveStack();
1004 					}
1005 				}
1006 			} else {
1007 				// this action is not unitary completed
1008 				// StackManager.getSpellController().setHandedPlayer();
1009 				StackManager.enableAbort();
1010 			}
1011 		} else if (unitaryCompleted) {
1012 			StackManager.resolveStack();
1013 		}
1014 	}
1015 
1016 	/***
1017 	 * Reactivate the current action
1018 	 */
1019 	public void reactivate() {
1020 		if (!(currentAction instanceof Waiting)
1021 				|| !(currentAction instanceof ChosenAction)) {
1022 			Log.error(
1023 					"The serialization handler must start to process a Waiting/ChosenAction,class="
1024 							+ currentAction.getClass().getName() + " : " + currentAction,
1025 					new InternalError());
1026 			// So, we should not be there, resolve the stack
1027 			playNextAction();
1028 		} else {
1029 			((ChosenAction) currentAction).choose(getActionContext(), StackManager
1030 					.getInstance().getAbilityContext(), currentAbility);
1031 			MagicUIComponents.chosenCostPanel.initUI(StackManager.getInstance()
1032 					.getSourceCard(), actionsContextsWrapper);
1033 		}
1034 	}
1035 
1036 	/***
1037 	 * Update the required mana of current ability.
1038 	 * 
1039 	 * @param op
1040 	 *          the operation to apply to the required mana.
1041 	 * @param reg
1042 	 *          the register index to update
1043 	 * @param value
1044 	 *          the value to update.
1045 	 */
1046 	public void updateRequiredMana(Operation op, int reg, int value) {
1047 		requiredMana[reg] = op.process(requiredMana[reg], value);
1048 	}
1049 
1050 	/***
1051 	 * Update the given required mana with the global required mana of attached
1052 	 * ability. This a complex process since in case of negative amounts, the
1053 	 * previous mana contexts are updated to honor as much as possible an
1054 	 * equilibrum.
1055 	 * 
1056 	 * @param requiredMana
1057 	 *          the required mana to update.
1058 	 */
1059 	public void updateRequiredMana(int[] requiredMana) {
1060 		for (int i = this.requiredMana.length; i-- > 0;) {
1061 			this.requiredMana[i] += requiredMana[i];
1062 			if (requiredMana[i] < 0) {
1063 				if (this.overMana[i] > 0) {
1064 					/*
1065 					 * This color is already reduced beyond 0, so we have not found any
1066 					 * way to fix the previous contexts.
1067 					 */
1068 					this.overMana[i] -= requiredMana[i];
1069 				} else {
1070 					/*
1071 					 * This color has not been reduced yet, this is the interesting part
1072 					 * of the process of this operation.
1073 					 */
1074 					for (ActionContextWrapper context : getTotalActionContexts()) {
1075 						if (context != null && context.actionContext != null
1076 								&& context.actionContext instanceof ManaCost) {
1077 							final ManaCost manaContext = (ManaCost) context.actionContext;
1078 							this.overMana[i] = manaContext
1079 									.reduceManaCost(i, this.overMana[i]);
1080 							if (this.overMana[i] == 0) {
1081 								break;
1082 							}
1083 							// Else, try the next context
1084 						}
1085 					}
1086 				}
1087 				requiredMana[i] = 0;
1088 			} else if (this.overMana[i] > 0) {
1089 				if (requiredMana[i] >= this.overMana[i]) {
1090 					requiredMana[i] -= this.overMana[i];
1091 					this.overMana[i] = 0;
1092 				} else {
1093 					this.overMana[i] -= requiredMana[i];
1094 					requiredMana[i] = 0;
1095 				}
1096 			}
1097 		}
1098 	}
1099 
1100 	/***
1101 	 * Return required mana. May contain some negative amount.
1102 	 * 
1103 	 * @return required mana. May contain some negative amount.
1104 	 */
1105 	public int[] getRequiredMana() {
1106 		return requiredMana;
1107 	}
1108 
1109 	/***
1110 	 * like M68k processor bra instruction, indicates the jump to do to go to the
1111 	 * next action. This jump is equal to 1 for normal ability, but for hop=3, the
1112 	 * 2 actions following the current one will be skipped. Hop=0 means infinite
1113 	 * loop.
1114 	 * 
1115 	 * @since 0.52
1116 	 */
1117 	public int hop;
1118 
1119 	/***
1120 	 * the current ability in the stack
1121 	 */
1122 	public Ability currentAbility;
1123 
1124 	/***
1125 	 * the active action managing the next player action
1126 	 */
1127 	public MAction currentAction;
1128 
1129 	/***
1130 	 * This tag indicate the index of current action using a 'for' or 'while'
1131 	 * instruction, generating several events. While this tag is greater than 0,
1132 	 * instead of resolving stacks, the current action still called as if the
1133 	 * setRepeat() method was called.
1134 	 */
1135 	public int loopingIndex;
1136 
1137 	/***
1138 	 * The index of current action
1139 	 */
1140 	public int currentIdAction;
1141 
1142 	/***
1143 	 * The current context of actions.
1144 	 */
1145 	private ActionContextWrapper[] actionsContextsWrapper = null;
1146 
1147 	/***
1148 	 * The validated context of actions. Is <code>null</code> while no contexts
1149 	 * have been totally validated for a cost part, so arriving to the
1150 	 * HANDLER_MIDDLE handler.
1151 	 */
1152 	private ActionContextWrapper[] validatedActionsContextsWrapper = null;
1153 
1154 	/***
1155 	 * Are we waiting for triggered/activated choice
1156 	 */
1157 	public boolean waitingOnMiddle;
1158 
1159 	/***
1160 	 * counter used for draw/targets count spells like a loop "for(i = 0 ... )"
1161 	 */
1162 	private int internalCounter = 0;
1163 
1164 	private TargetedList savedTargeted;
1165 
1166 	/***
1167 	 * Values are :<br>
1168 	 * HANDLER_INITIALIZATION<br>
1169 	 * HANDLER_AD_SERIALIZATION<br>
1170 	 * HANDLER_AD_PREPARE_REPLAY<br>
1171 	 * HANDLER_AD_REPLAY<br>
1172 	 * HANDLER_PLAY_INIT<br>
1173 	 * HANDLER_MIDDLE<br>
1174 	 * HANDLER_EFFECTS<br>
1175 	 */
1176 	public int idHandler = 0;
1177 
1178 	/***
1179 	 * The first handler : initialization.
1180 	 */
1181 	public static final int HANDLER_INITIALIZATION = 0;
1182 
1183 	/***
1184 	 * The second handler : serialization.
1185 	 */
1186 	private static final int HANDLER_AD_SERIALIZATION = 1;
1187 
1188 	/***
1189 	 * The third handler : prepare replay.
1190 	 */
1191 	private static final int HANDLER_AD_PREPARE_REPLAY = 2;
1192 
1193 	/***
1194 	 * The final handler : replay.
1195 	 */
1196 	public static final int HANDLER_AD_REPLAY = 3;
1197 
1198 	/***
1199 	 * The old init handler without rollback use.
1200 	 */
1201 	private static final int HANDLER_PLAY_INIT = 4;
1202 
1203 	/***
1204 	 * The old middle handler without rollback use.
1205 	 */
1206 	private static final int HANDLER_MIDDLE = 5;
1207 
1208 	/***
1209 	 * The old effects handler without rollback use.
1210 	 */
1211 	private static final int HANDLER_EFFECTS = 6;
1212 
1213 	/***
1214 	 * Is the advanced mode is used there for cost part.
1215 	 */
1216 	public boolean advancedMode;
1217 
1218 	private int nbCosts;
1219 
1220 	private boolean[] rollbackPath;
1221 
1222 	/***
1223 	 * The cost part of current ability.
1224 	 */
1225 	private final MAction[] actionList;
1226 
1227 	/***
1228 	 * The effect part of current ability.
1229 	 */
1230 	private final MAction[] effectList;
1231 
1232 	/***
1233 	 * Is the advanced mode is used there for effects part.
1234 	 */
1235 	public boolean advancedEffectMode;
1236 
1237 	/***
1238 	 * Required mana of attached ability. May contain some negative amount.
1239 	 */
1240 	private final int[] requiredMana;
1241 
1242 	/***
1243 	 * Negative amount of mana that have not been removed from other 'pay-mana'
1244 	 * actions.
1245 	 */
1246 	private final int[] overMana;
1247 }