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