1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package net.sf.magicproject.stack;
23
24 import java.awt.event.ActionEvent;
25 import java.awt.event.ActionListener;
26 import java.io.FileInputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.util.Arrays;
30
31 import javax.swing.JCheckBoxMenuItem;
32 import javax.swing.JLabel;
33 import javax.swing.JOptionPane;
34 import javax.swing.JPopupMenu;
35
36 import net.sf.magicproject.clickable.ability.AbilityFactory;
37 import net.sf.magicproject.clickable.ability.SystemAbility;
38 import net.sf.magicproject.clickable.targetable.card.SystemCard;
39 import net.sf.magicproject.event.MEventListener;
40 import net.sf.magicproject.event.phase.BeforePhase;
41 import net.sf.magicproject.event.phase.BeginningPhase;
42 import net.sf.magicproject.event.phase.EndOfPhase;
43 import net.sf.magicproject.modifier.ModifierFactory;
44 import net.sf.magicproject.modifier.StaticModifierModel;
45 import net.sf.magicproject.stack.phasetype.PhaseType;
46 import net.sf.magicproject.token.IdConst;
47 import net.sf.magicproject.token.IdTokens;
48 import net.sf.magicproject.token.MCommonVars;
49 import net.sf.magicproject.tools.Log;
50 import net.sf.magicproject.tools.MToolKit;
51 import net.sf.magicproject.ui.MagicUIComponents;
52 import net.sf.magicproject.ui.UIHelper;
53 import net.sf.magicproject.ui.i18n.LanguageManager;
54 import net.sf.magicproject.zone.ZoneManager;
55
56 import org.apache.commons.io.IOUtils;
57
58 /***
59 * This class manage the turn structure : phase order, loop and phase's UI
60 * manager(highlightment, breakpoints, pass)
61 *
62 * @since 0.21 a graphical representation of phase
63 * @since 0.30 an option "auto play single "YOU MUST" ability is suported
64 * @since 0.30 an option "skip all" is suported
65 * @since 0.31 an option "skip all even opponent's spell" is suported
66 * @since 0.31 graphical representation of phases for both players
67 * @since 0.31 attack phase is suported
68 * @since 0.52 support option PLAYED_ONCE_BY_PHASE and AUTOMATICALLY_PLAYED
69 * @since 0.53 turns are counted
70 * @since 0.80 BEFORE_PHASE and END_OF_PHASE_... event can be replaced.
71 * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
72 */
73 public final class EventManager {
74
75 private static final String TURN_STR = LanguageManager.getString("turnid")
76 + " : ";
77
78 /***
79 * Create a new instance of this class.
80 */
81 private EventManager() {
82 super();
83 }
84
85 /***
86 * Create an instance of MEventManager by reading a file
87 *
88 * @since 0.31 graphical representation of phases for both players
89 */
90 public static void init() {
91 MPhase.popupMenu = new JPopupMenu();
92 MPhase.optionsMenu = new JPopupMenu();
93 JCheckBoxMenuItem item = new JCheckBoxMenuItem(LanguageManager
94 .getString("breakpoint"), new javax.swing.ImageIcon(IdConst.IMAGES_DIR
95 + "breakpoint.gif"));
96 item.setFont(MToolKit.defaultFont);
97 item.setToolTipText("<html>"
98 + LanguageManager.getString("breakpoint.tooltip"));
99 item.setMnemonic('b');
100 item.addActionListener(new ActionListener() {
101 public void actionPerformed(ActionEvent evt) {
102 MPhase.triggerPhase.setBreakpoint(((JCheckBoxMenuItem) evt.getSource())
103 .isSelected());
104 MPhase.triggerPhase.repaint();
105 }
106 });
107 MPhase.optionsMenu.add(item);
108 item = new JCheckBoxMenuItem(LanguageManager.getString("skipPhase"),
109 UIHelper.getIcon("skipall1.gif"));
110 item.setFont(MToolKit.defaultFont);
111 item.setToolTipText("<html>"
112 + LanguageManager.getString("skipPhase.tooltip"));
113 item.setMnemonic('l');
114 item.addActionListener(new ActionListener() {
115 public void actionPerformed(ActionEvent evt) {
116 MPhase.triggerPhase.setSkipAll(((JCheckBoxMenuItem) evt.getSource())
117 .isSelected());
118 MPhase.triggerPhase.repaint();
119 }
120 });
121 MPhase.optionsMenu.add(item);
122 item = new JCheckBoxMenuItem(LanguageManager.getString("skipPhaseOnce"),
123 UIHelper.getIcon("skipall2.gif"));
124 item.setFont(MToolKit.defaultFont);
125 item.setToolTipText("<html>"
126 + LanguageManager.getString("skipPhaseOnce.tooltip") + "<br><br>"
127 + MagicUIComponents.HTML_ICON_TIP
128 + LanguageManager.getString("skipPhaseOnceTTtip"));
129 item.setMnemonic('o');
130 item.addActionListener(new ActionListener() {
131 public void actionPerformed(ActionEvent evt) {
132 MPhase.triggerPhase.setSkipAllTmp(((JCheckBoxMenuItem) evt.getSource())
133 .isSelected());
134 MPhase.triggerPhase.repaint();
135 }
136 });
137
138 MPhase.optionsMenu.add(item);
139 item = new JCheckBoxMenuItem(LanguageManager.getString("skipPhaseMedium"),
140 UIHelper.getIcon("skipallm2.gif"));
141 item.setFont(MToolKit.defaultFont);
142 item.setToolTipText("<html>"
143 + LanguageManager.getString("skipPhaseMedium.tooltip") + "<br>"
144 + MagicUIComponents.HTML_ICON_WARNING
145 + LanguageManager.getString("skipPhaseAllTTwarn"));
146 item.setMnemonic('m');
147 item.addActionListener(new ActionListener() {
148 public void actionPerformed(ActionEvent evt) {
149 MPhase.triggerPhase.setSkipMedium(((JCheckBoxMenuItem) evt.getSource())
150 .isSelected());
151 MPhase.triggerPhase.repaint();
152 }
153 });
154 MPhase.optionsMenu.add(item);
155 item = new JCheckBoxMenuItem(LanguageManager
156 .getString("skipPhaseMediumOnce"), UIHelper.getIcon("skipallm.gif"));
157 item.setFont(MToolKit.defaultFont);
158 item.setToolTipText("<html>"
159 + LanguageManager.getString("skipPhaseMediumOnce.tooltip") + "<br>"
160 + MagicUIComponents.HTML_ICON_WARNING
161 + LanguageManager.getString("skipPhaseAllTTwarn"));
162 item.setMnemonic('p');
163 item.addActionListener(new ActionListener() {
164 public void actionPerformed(ActionEvent evt) {
165 MPhase.triggerPhase.setSkipMediumTmp(((JCheckBoxMenuItem) evt
166 .getSource()).isSelected());
167 MPhase.triggerPhase.repaint();
168 }
169 });
170
171 MPhase.optionsMenu.add(item);
172 item = new JCheckBoxMenuItem(LanguageManager.getString("skipPhaseAll"),
173 UIHelper.getIcon("skipall3.gif"));
174 item.setFont(MToolKit.defaultFont);
175 item.setToolTipText("<html>"
176 + LanguageManager.getString("skipPhaseAll.tooltip") + "<br>"
177 + MagicUIComponents.HTML_ICON_WARNING
178 + LanguageManager.getString("skipPhaseAllTTwarn"));
179 item.setMnemonic('u');
180 item.addActionListener(new ActionListener() {
181 public void actionPerformed(ActionEvent evt) {
182 MPhase.triggerPhase
183 .setSkipAllVery(((JCheckBoxMenuItem) evt.getSource()).isSelected());
184 MPhase.triggerPhase.repaint();
185 }
186 });
187 MPhase.optionsMenu.add(item);
188 item = new JCheckBoxMenuItem(LanguageManager.getString("skipPhaseAllOnce"),
189 UIHelper.getIcon("skipall4.gif"));
190 item.setFont(MToolKit.defaultFont);
191 item.setToolTipText("<html>"
192 + LanguageManager.getString("skipPhaseAllOnce.tooltip") + "<br>"
193 + MagicUIComponents.HTML_ICON_WARNING
194 + LanguageManager.getString("skipPhaseAllTTwarn"));
195 item.setMnemonic('t');
196 item.addActionListener(new ActionListener() {
197 public void actionPerformed(ActionEvent evt) {
198 MPhase.triggerPhase.setSkipAllVeryTmp(((JCheckBoxMenuItem) evt
199 .getSource()).isSelected());
200 MPhase.triggerPhase.repaint();
201 }
202 });
203 MPhase.optionsMenu.add(item);
204 }
205
206 /***
207 * remove all events in the stack of this phase, read new system abilities,
208 * turn structure and set the current phase.
209 * <ul>
210 * Structure of InputStream : Data[size]
211 * <li>number of phases type [1]</li>
212 * <li>phases type i [...]</li>
213 * <li>number of phases in one turn [1]</li>
214 * <li>phase identifiant i [1]</li>
215 * <li>phase index (not identifiant) for first turn [1]</li>
216 * <li>number of statebased ability of play [1]</li>
217 * <li>statebased ability i [...]</li>
218 * <li>number of static modifier of game [1]</li>
219 * <li>static modifier of game i [...]</li>
220 * </ul>
221 *
222 * @param dbStream
223 * the mdb file containing rules
224 * @param settingFile
225 * setting file attached to this mdb
226 * @throws IOException
227 */
228 public static void init(FileInputStream dbStream, String settingFile)
229 throws IOException {
230 nextCurrentPlayer = -1;
231 nextPhaseIndex = -1;
232 turnsLbl.setText(TURN_STR + MCommonVars.registers[IdTokens.TURN_ID]);
233 MagicUIComponents.logListing.setText("");
234
235
236 MEventListener.reset();
237
238
239 int nbPhases = dbStream.read();
240 PhaseType[] phaseTypes = new PhaseType[nbPhases];
241 while (nbPhases-- > 0) {
242 PhaseType phaseType = new PhaseType(dbStream);
243 phaseTypes[phaseType.id] = phaseType;
244 }
245
246
247 int nbPhasesPerTurn = dbStream.read();
248 turnStructure = new PhaseType[nbPhasesPerTurn];
249 Log.debug("Turn Structure :");
250 for (int i = 0; i < nbPhasesPerTurn; i++) {
251 turnStructure[i] = phaseTypes[dbStream.read()];
252 Log.debug("\t" + i + ":" + turnStructure[i].phaseName);
253 }
254
255
256 int startIdPhase = dbStream.read();
257 Log.debug("First phase of first turn is "
258 + turnStructure[startIdPhase].phaseName + "(" + startIdPhase + ")");
259
260
261 try {
262 final InputStream in = MToolKit.getResourceAsStream(settingFile.replace(
263 ".mdb", ".pref"));
264 MPhase.phases = new MPhase[2][turnStructure.length];
265 for (int i = 0; i < turnStructure.length; i++) {
266 MPhase.phases[0][i] = new MPhase(turnStructure[i], 0, in);
267 }
268 for (int i = 0; i < EventManager.turnStructure.length; i++) {
269 MPhase.phases[1][i] = new MPhase(turnStructure[i], 1, in);
270 }
271 IOUtils.closeQuietly(in);
272 } catch (IOException e) {
273 JOptionPane.showMessageDialog(MagicUIComponents.magicForm,
274 LanguageManager.getString("loadtbssettingspb") + " : "
275 + e.getMessage() + e.getStackTrace()[0], LanguageManager
276 .getString("error"), JOptionPane.WARNING_MESSAGE);
277 }
278
279
280 StackManager.PLAYERS[StackManager.idCurrentPlayer].reset(MPhase.phases[StackManager.idCurrentPlayer]);
281 StackManager.PLAYERS[1-StackManager.idCurrentPlayer].reset(MPhase.phases[1-StackManager.idCurrentPlayer]);
282
283
284 int nbTriggered = dbStream.read();
285 Log.debug("System abilities (" + nbTriggered + "):");
286 while (nbTriggered-- > 0) {
287
288 AbilityFactory.readAbility(dbStream, SystemCard.instance)
289 .registerToManager();
290 }
291
292
293 MPhase.popupMenu.removeAll();
294 for (int i = MPhase.phases[0].length; i-- > 1;) {
295 MPhase.phases[0][i].reset();
296 MPhase.phases[1][i].reset();
297 }
298
299
300 phaseIndex = startIdPhase - 1;
301 currentIdPhase = turnStructure[phaseIndex].id;
302 parsingBeforePhaseEvent = false;
303 parsingEndPhaseEvent = false;
304 replacingBefore = false;
305
306
307 int nbStaticModifiers = dbStream.read();
308 Log.debug("Static-modifiers (" + nbStaticModifiers + "):");
309 while (nbStaticModifiers-- > 0) {
310
311 ((StaticModifierModel) ModifierFactory.readModifier(dbStream))
312 .addModifierFromModel(SystemAbility.instance, SystemCard.instance);
313 }
314
315 }
316
317 /***
318 * Go to the first phase of first turn
319 */
320 public static void start() {
321 MagicUIComponents.timer.start();
322 gotoNextPhase();
323 }
324
325 /***
326 * Goto the next phase.For each player (current first), if mana pool isn't
327 * empty -> mana burn. phase will be ID__BEFORE_PHASE_UNTAP
328 */
329 public static void gotoNextPhase() {
330 StackManager.idActivePlayer = StackManager.idCurrentPlayer;
331 Log.debug("Ending phase " + turnStructure[phaseIndex].phaseName + "("
332 + phaseIndex + ")");
333
334 /***
335 * Since we are currently parsing the BEFORE_PHASE_... event, we do not go
336 * to the next pahse, but only return to the last instruction where the
337 * markup <code>parsingBeforePhaseEvent</code> has been previously set.
338 */
339 if (parsingBeforePhaseEvent) {
340 Log
341 .debug("parsingBeforePhaseEvent is true, gotoNextPhase has been cancelled");
342 } else {
343
344 if (parsingEndPhaseEvent) {
345 Log.debug("\tEnding AGAIN " + turnStructure[phaseIndex].phaseName + "("
346 + phaseIndex + ")");
347 } else {
348 if (!EndOfPhase.tryAction(currentIdPhase)) {
349
350 Log.debug("\tEnd of Phase " + turnStructure[phaseIndex].phaseName
351 + "(" + phaseIndex + ") has been replaced");
352 return;
353 }
354 parsingEndPhaseEvent = true;
355 EndOfPhase.dispatchEvent();
356 if (!StackManager.activePlayer().waitTriggeredBufferChoice(false)) {
357
358
359 return;
360 }
361 if (!StackManager.isEmpty()) {
362 throw new IllegalStateException(
363 "The stack must be empty before going to the next phase, stack="
364 + Arrays.toString(ZoneManager.stack.getComponents()));
365 }
366 }
367 }
368 parsingEndPhaseEvent = false;
369 replacingBefore = false;
370
371 while (true) {
372
373 if (StackManager.gameLostProceed) {
374 return;
375 }
376
377
378 if (nextCurrentPlayer != -1) {
379 StackManager.idCurrentPlayer = nextCurrentPlayer;
380 nextCurrentPlayer = -1;
381 /***
382 * changing current player make restart the turn even if
383 * <code>nextPhaseIndex</code> has been specified
384 */
385 phaseIndex = -1;
386
387
388
389
390
391
392 StackManager.SAVED_TARGET_LISTS.clear();
393 StackManager.PLAYERS[StackManager.idCurrentPlayer].clearDamages();
394 StackManager.PLAYERS[1-StackManager.idCurrentPlayer].clearDamages();
395 }
396
397
398 if (!parsingBeforePhaseEvent) {
399 updatePhase();
400 }
401
402 Log.debug("Current phase is : " + turnStructure[phaseIndex].phaseName
403 + "(index=" + phaseIndex + ", id=" + currentIdPhase + ")");
404
405 StackManager.idActivePlayer = StackManager.idCurrentPlayer;
406 turnsLbl.setText(TURN_STR + MCommonVars.registers[IdTokens.TURN_ID]);
407
408 /***
409 * Even if the current phase does not really come (due to a 'skip token'),
410 * we raise the 'MEventBeforePhase' event. All abilities triggering this
411 * way should have the 'isHidden' tag since no player action is allowed
412 * since this is a very special event. After this call, the stack should
413 * be empty since abilities are immediatly resolved (triggered -> stacked ->
414 * resolved).
415 */
416 if (replacingBefore) {
417 Log.debug("\t... BeforePhase can no more be replaced");
418 } else {
419 replacingBefore = true;
420 if (!BeforePhase.tryAction(currentIdPhase)) {
421 return;
422
423 }
424 }
425
426 if (parsingBeforePhaseEvent) {
427 Log.debug("\t... BeforePhase event is not raised again");
428 } else {
429 parsingBeforePhaseEvent = true;
430 BeforePhase.dispatchEvent();
431 if (!StackManager.activePlayer().waitTriggeredBufferChoice(false)) {
432 return;
433
434 }
435 }
436 replacingBefore = false;
437 parsingBeforePhaseEvent = false;
438
439 if (nextPhaseIndex != -1 || nextCurrentPlayer != -1) {
440 if (nextPhaseIndex != -1) {
441 phaseIndex = nextPhaseIndex - 1;
442 currentIdPhase = turnStructure[phaseIndex].id;
443 nextPhaseIndex = -1;
444 }
445 if (nextCurrentPlayer != -1) {
446 StackManager.idCurrentPlayer = nextCurrentPlayer;
447 StackManager.idActivePlayer = StackManager.idCurrentPlayer;
448 nextCurrentPlayer = -1;
449 }
450 } else {
451
452 break;
453 }
454
455 updatePhasesGUI();
456 }
457
458
459 if (currentPhase().skipThisPhase) {
460
461
462
463
464 Log.debug("\t-> this phase is skipped");
465 currentPhase().skipThisPhase = false;
466 gotoNextPhase();
467 } else {
468 /***
469 * This phase can really start, and 'Beginning event' is raised. The
470 * associated triggerred abilities can be added to the TBZ (triggered
471 * buffer zone). So after this call the TBZ may contains many abilities
472 * and would be managed later.
473 */
474 BeginningPhase.dispatchEvent();
475 StackManager.activePlayer().waitTriggeredBufferChoice(true);
476 }
477 }
478
479 /***
480 *
481 */
482 private static void updatePhase() {
483 if (nextPhaseIndex != -1) {
484 phaseIndex = nextPhaseIndex;
485 currentIdPhase = turnStructure[phaseIndex].id;
486 nextPhaseIndex = -1;
487 } else {
488 phaseIndex = ++phaseIndex % turnStructure.length;
489 currentIdPhase = turnStructure[phaseIndex].id;
490 }
491 updatePhasesGUI();
492 }
493
494 /***
495 * Update the phases GUI : colors indicating the current phases, and the
496 * handed player
497 */
498 public static void updatePhasesGUI() {
499 for (int i = MPhase.phases[StackManager.idActivePlayer].length; i-- > 0;) {
500 MPhase.phases[StackManager.idCurrentPlayer][i].setActive(i == phaseIndex,
501 StackManager.idCurrentPlayer == StackManager.idCurrentPlayer);
502 MPhase.phases[1 - StackManager.idCurrentPlayer][i].setActive(false,
503 StackManager.idCurrentPlayer != StackManager.idCurrentPlayer);
504 }
505 }
506
507 /***
508 * return the phase type associate to the current phase
509 *
510 * @return the phase type associate to the current phase
511 */
512 public static PhaseType currentPhaseType() {
513 return turnStructure[phaseIndex];
514 }
515
516 /***
517 * return the phase type associate to the current phase
518 *
519 * @return the phase type associate to the current phase
520 */
521 public static MPhase currentPhase() {
522 return MPhase.phases[StackManager.idCurrentPlayer][phaseIndex];
523 }
524
525 /***
526 * Return true if we are currently parsing the "before phase" event.
527 *
528 * @return true if we are currently parsing the "before phase" event.
529 */
530 public static boolean parsinfBeforeEnd() {
531 return parsingBeforePhaseEvent;
532 }
533
534 /***
535 * This markup indicates we are currently parsing the BEFORE_PHASE_... event.
536 * This token is used by the stack manager to know if the next time the
537 * gotoNextPhase() method is called would effectively make going to the next
538 * phase or simply skip the already parsing step of the BEFORE_PHASE_...
539 * event. Also, when this token is set to true just before the parse begin,
540 * and set to false the next time the gotoNextPhase() method would be called
541 * by the stack manager. When this markup is set to true, instead of giving
542 * priority to player, we resolve the stack as if that player has choosen to
543 * decline to response.
544 */
545 private static boolean parsingBeforePhaseEvent;
546
547 /***
548 * This markup indicates we are currently parsing the END_PHASE_... event.
549 * This token is used by the stack manager to release a maximum of stack
550 * frames. During the stack resolution, instead of calling the "gotoNextPhase"
551 * method, if this markup is true, the process simply return.
552 */
553 public static boolean parsingEndPhaseEvent;
554
555 /***
556 * is the current idPhase (not the index of phase)
557 */
558 public static int currentIdPhase = 0;
559
560 /***
561 * The turn label
562 */
563 public static JLabel turnsLbl;
564
565 /***
566 * The moreInfo label
567 */
568 public static JLabel moreInfoLbl;
569
570 /***
571 * List of successive phase of any turn
572 */
573 public static PhaseType[] turnStructure;
574
575 /***
576 * The next 'currentplayer' for the next phase. If -1, the next
577 * 'currentplayer' would not change.
578 */
579 public static int nextCurrentPlayer;
580
581 /***
582 * The next 'currentIdPhase'. If -1, the next phase will follow the turn
583 * structure.
584 */
585 public static int nextPhaseIndex;
586
587 /***
588 * represents the current index of phase
589 */
590 public static int phaseIndex;
591
592 /***
593 * This markup is used to prevent multiple replacement of "BEFORE_PHASE_..."
594 * event since this event can be replaced only once per phase.
595 */
596 private static boolean replacingBefore;
597
598 }