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