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