1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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
150 zoneAbortion = inputFile.read();
151
152
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
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
182 previousPlace = ((MCard) tokenCard).getIdZone();
183
184 tokenCard.moveCard(IdZones.STACK, tokenCard.controller, false,
185 IdPositions.ON_THE_TOP);
186 ((MCard) tokenCard).unregisterAbilities();
187 } else {
188 tokenCard = ability.getCardCopy();
189
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
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
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
255 currentAbility = ability;
256
257
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
319 StackManager.activePlayer().waitTriggeredBufferChoice(true);
320 return;
321 }
322 if (aborted) {
323
324 getInstance().finishSpell();
325 return;
326 }
327 } while (actionManager.playNextAction());
328
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
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
355 ((Waiting) actionManager.currentAction).finished();
356
357
358 if (actionManager.advancedMode) {
359 actionManager.rollback();
360 }
361 if (triggered == null) {
362 if (tokenCard.isACopy()) {
363
364
365
366
367 Log.info("\t...restore context, " + tokenCard
368 + "'s ability is canceled");
369 ZoneManager.stack.remove(tokenCard);
370 } else {
371
372
373
374 tokenCard.moveCard(previousPlace, spellController, false, 0);
375
376
377 ((MCard) tokenCard).registerAbilities(MCard.getIdZone(previousPlace,
378 null));
379 }
380 Log.info("\t...restore context, " + tokenCard + " spell is canceled");
381
382
383 CONTEXTES.pop().restore();
384 } else {
385
386
387
388
389 if (triggered.triggeredAbility.isHidden()) {
390
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
409 if (processGameLost()) {
410
411 gameLostProceed = true;
412 return;
413 }
414
415
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
433 targetedList.clear();
434
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
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
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
502
503
504 return triggered.triggeredAbility.getCard();
505 }
506
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
518
519
520 aborted = true;
521 getInstance().abortingAbility = source;
522 getInstance().finishSpell();
523 } else {
524
525 if (card instanceof TriggeredCard) {
526
527
528
529
530 if (triggered.triggeredAbility.isHidden()) {
531
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
544
545
546 Log.info("restore context, " + card + "'s ability is aborted");
547 ZoneManager.stack.remove(card);
548 } else {
549
550
551
552
553
554
555
556 Log.info("restore context, " + card
557 + "'s spell has been aborted, move it to abortion place");
558
559
560 StackContext context = getContextOf(card);
561 context.getActionManager().restore(context.getActionManager());
562
563
564 ((MCard) card).registerAbilities(zoneAbortion);
565
566 card.moveCard(zoneAbortion, ((MCard) card).getOwner(), false, 0);
567 }
568
569
570
571
572 for (int i = CONTEXTES.size(); i-- > 0;) {
573 if (CONTEXTES.get(i).ctxtokenCard == card) {
574
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
655 return isPlaying(ability, CONTEXTES.size() - 1);
656 }
657
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
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
729 return getInstance();
730 }
731
732
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
773
774
775
776
777
778
779
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
942 JOptionPane.showMessageDialog(MagicUIComponents.magicForm,
943 LanguageManager.getString("youlose"), "End of Game",
944 JOptionPane.INFORMATION_MESSAGE);
945 return true;
946 case 2:
947
948 JOptionPane.showMessageDialog(MagicUIComponents.magicForm,
949 LanguageManager.getString("youwin"), "End of Game",
950 JOptionPane.INFORMATION_MESSAGE);
951 return true;
952 case 3:
953
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;
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
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
1228
1229
1230 return ctxtriggered.triggeredAbility.getCard();
1231 }
1232
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
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
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
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 }