1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package net.sf.firemox.action.target;
21
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.util.ArrayList;
25 import java.util.List;
26
27 import net.sf.firemox.action.context.ActionContextWrapper;
28 import net.sf.firemox.action.context.TargetList;
29 import net.sf.firemox.action.handler.ChosenAction;
30 import net.sf.firemox.action.listener.WaitingCard;
31 import net.sf.firemox.action.listener.WaitingPlayer;
32 import net.sf.firemox.clickable.ability.Ability;
33 import net.sf.firemox.clickable.target.Target;
34 import net.sf.firemox.clickable.target.card.MCard;
35 import net.sf.firemox.clickable.target.player.Player;
36 import net.sf.firemox.event.context.ContextEventListener;
37 import net.sf.firemox.event.context.MContextTarget;
38 import net.sf.firemox.expression.ExpressionFactory;
39 import net.sf.firemox.stack.ActionManager;
40 import net.sf.firemox.stack.StackManager;
41 import net.sf.firemox.stack.TargetHelper;
42 import net.sf.firemox.test.Test;
43 import net.sf.firemox.test.TestFactory;
44 import net.sf.firemox.token.IdConst;
45 import net.sf.firemox.token.IdTargets;
46 import net.sf.firemox.token.IdTokens;
47 import net.sf.firemox.tools.Log;
48 import net.sf.firemox.tools.MToolKit;
49 import net.sf.firemox.ui.MagicUIComponents;
50
51 /***
52 * Add to the target list card(s) or player(s) following the specified mode and
53 * the specified type. If mode is 'choose' or 'opponent-choose', then a
54 * 'targeted' event is raised when the choice is made. In case of 'all',
55 * 'random', 'myself' or any target known with access register, no event is
56 * generated. The friendly test, and the type are necessary only for the modes
57 * 'random', 'all', 'choose' and 'opponent-choose' to list the valid targets.
58 * The target list is used by the most actions. <br>
59 * For example, if the next action 'damage', all cards/player from the target
60 * list would be damaged. When a new ability is added to the stack, a new list
61 * is created and attached to it. Actions may modify, access it until the
62 * ability is completely resolved.
63 *
64 * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan</a>
65 * @since 0.54
66 * @since 0.70 added modes : 'current-player', 'non-current-player',
67 * 'context.card', 'context.player'
68 * @since 0.71 abortion (when no valid target are found) is supported. To
69 * support abortion, hop attribute must be set to 'all' or 'abort-me'
70 * @since 0.82 manage "targeted" event generation for target action
71 * @since 0.82 "share" options with 'same-name' option is supported. This option
72 * would add the target into the target list of the first ability of
73 * stack having the save name.
74 * @since 0.82 restriction zone supported to optimize the target processing.
75 * @since 0.86 new algorithm to determine valid targets
76 * @since 0.86 a target cannot be added twice in the target-list
77 */
78 public abstract class ChosenTarget extends net.sf.firemox.action.Target
79 implements WaitingPlayer, WaitingCard, ChosenAction {
80
81 /***
82 * Create an instance by reading a file Offset's file must pointing on the
83 * first byte of this action <br>
84 * <ul>
85 * Structure of InputStream : Data[size]
86 * <li>[super]</li>
87 * <li>hop for abort [Expression]</li>
88 * <li>test [Test]</li>
89 * <li>restriction Zone id [int]</li>
90 * <li>is preempted [boolean]</li>
91 * </ul>
92 * For the available options, see interface IdTargets
93 *
94 * @param type
95 * the concerned target's type previously read by the factory
96 * @param options
97 * the options previously read by the factory
98 * @param inputFile
99 * file containing this action
100 * @throws IOException
101 * If some other I/O error occurs
102 * @see IdTargets
103 * @since 0.82 options contains "share" option too
104 */
105 ChosenTarget(int type, int options, InputStream inputFile) throws IOException {
106 super(inputFile);
107 this.type = type;
108 this.options = options;
109 hop = ExpressionFactory.readNextExpression(inputFile).getValue(null, null,
110 null);
111 test = TestFactory.readNextTest(inputFile);
112 restrictionZone = inputFile.read() - 1;
113 canBePreempted = inputFile.read() == 1;
114 }
115
116 @Override
117 public final void rollback(ActionContextWrapper actionContext,
118 ContextEventListener context, Ability ability) {
119 if (actionContext.actionContext != null) {
120 ((TargetList) actionContext.actionContext).rollback();
121 }
122 }
123
124 /***
125 * No generated event. Let the active player playing this action.
126 *
127 * @param actionContext
128 * the context containing data saved by this action during the
129 * 'choose" process.
130 * @param ability
131 * is the ability owning this test. The card component of this
132 * ability should correspond to the card owning this test too.
133 * @param context
134 * is the context attached to this action.
135 * @return true if the stack can be resolved just after this call.
136 */
137 public boolean init(ActionContextWrapper actionContext,
138 ContextEventListener context, Ability ability) {
139 final int choiceMode = options & 0x0F;
140 final MCard owner = ability.getCard();
141 final List<Target> validTargets = new ArrayList<Target>();
142 String targetTxt = null;
143 switch (type) {
144 case IdTokens.DEALTABLE:
145
146 targetTxt = "Target is creature or player";
147 if (test
148 .test(ability, StackManager.PLAYERS[StackManager.idCurrentPlayer])) {
149 validTargets.add(StackManager.PLAYERS[StackManager.idCurrentPlayer]);
150 }
151 if (test.test(ability,
152 StackManager.PLAYERS[1 - StackManager.idCurrentPlayer])) {
153 validTargets
154 .add(StackManager.PLAYERS[1 - StackManager.idCurrentPlayer]);
155 }
156
157 processCardSearch(validTargets, ability, context);
158 break;
159 case IdTokens.PLAYER:
160
161 targetTxt = "Target is player";
162 if (test
163 .test(ability, StackManager.PLAYERS[StackManager.idCurrentPlayer])) {
164 validTargets.add(StackManager.PLAYERS[StackManager.idCurrentPlayer]);
165 }
166 if (test.test(ability,
167 StackManager.PLAYERS[1 - StackManager.idCurrentPlayer])) {
168 validTargets
169 .add(StackManager.PLAYERS[1 - StackManager.idCurrentPlayer]);
170 }
171 break;
172 case IdTokens.CARD:
173
174 targetTxt = "Target is card";
175 processCardSearch(validTargets, ability, context);
176 break;
177 default:
178 throw new InternalError("Unknown type :" + type);
179 }
180 if (StackManager.getInstance().getTargetedList().list != null) {
181 validTargets.removeAll(StackManager.getInstance().getTargetedList().list);
182 }
183 Log.debug("## size=" + validTargets.size() + ", ignored="
184 + StackManager.getInstance().getTargetedList().list);
185 switch (choiceMode) {
186 case IdTargets.ALL:
187 Log.debug("all mode, size=" + validTargets.size());
188 MagicUIComponents.magicForm.moreInfoLbl.setText(targetTxt
189 + " - all mode)");
190 if (!validTargets.isEmpty()) {
191 if (actionContext == null) {
192 StackManager.getInstance().getTargetedList().list
193 .addAll(validTargets);
194 } else if (actionContext.actionContext == null) {
195 actionContext.actionContext = new TargetList();
196 ((TargetList) actionContext.actionContext).targetList = validTargets;
197 } else {
198 ((TargetList) actionContext.actionContext).targetList
199 .addAll(validTargets);
200 }
201
202 return true;
203 }
204 break;
205 case IdTargets.RANDOM:
206 if (!validTargets.isEmpty()) {
207
208 MagicUIComponents.magicForm.moreInfoLbl.setText(targetTxt
209 + " - is random)");
210 final Target target = validTargets.get(MToolKit.getRandom(validTargets
211 .size()));
212 StackManager.getInstance().getTargetedList().list.add(target);
213 if (StackManager.actionManager.advancedMode) {
214 TargetList targetList = (TargetList) StackManager.actionManager
215 .getActionContext().actionContext;
216 if (targetList == null) {
217 targetList = new TargetList();
218 }
219 targetList.add(target, test);
220 StackManager.actionManager.getActionContext().actionContext = targetList;
221 }
222 return true;
223 }
224 break;
225 default:
226
227 }
228
229 if (validTargets.isEmpty()) {
230
231
232
233 if (hop == IdConst.ALL) {
234
235 MagicUIComponents.magicForm.moreInfoLbl.setText(targetTxt
236 + " -> no valid target, abortion");
237 Log.debug(MagicUIComponents.magicForm.moreInfoLbl.getText());
238 StackManager.getInstance().abortion(StackManager.tokenCard,
239 StackManager.currentAbility);
240 return false;
241 }
242 MagicUIComponents.magicForm.moreInfoLbl.setText(targetTxt
243 + " -> no valid target, hop is " + hop);
244 Log.debug(MagicUIComponents.magicForm.moreInfoLbl.getText());
245 StackManager.actionManager.setHop(hop);
246 return true;
247 }
248
249
250 String message = null;
251 Player nextHandedPlayer = null;
252 switch (choiceMode & 0x07) {
253 case IdTargets.CHOOSE:
254
255 message = "youchoose";
256 nextHandedPlayer = owner.getController();
257 break;
258 case IdTargets.OPPONENT_CHOOSE:
259
260 message = "opponentchoose";
261 nextHandedPlayer = owner.getController().getOpponent();
262 break;
263 case IdTargets.CONTEXT_CHOOSE:
264
265 message = "contextchoose";
266 nextHandedPlayer = ((MContextTarget) context).getTargetable().isPlayer() ? ((MContextTarget) context)
267 .getPlayer()
268 : ((MContextTarget) context).getCard().getController();
269 break;
270 case IdTargets.TARGET_CHOOSE:
271
272 message = "target-choose";
273 nextHandedPlayer = (Player) StackManager.getInstance().getTargetedList()
274 .get(0);
275 break;
276 case IdTargets.ATTACHED_TO_CONTROLLER_CHOOSE:
277
278 message = "attachedto.controller-choose";
279 nextHandedPlayer = ((MCard) owner.getParent()).getController();
280 break;
281 case IdTargets.STACK0_CHOOSE:
282
283
284
285
286 message = "-stack0-choose";
287 nextHandedPlayer = StackManager.PLAYERS[StackManager.registers[0]];
288 break;
289 default:
290 throw new InternalError("Unknown choice type :" + choiceMode);
291 }
292 MagicUIComponents.magicForm.moreInfoLbl.setText(targetTxt + " - mode='"
293 + message + "'");
294
295 StackManager.targetOptions.manageTargets(validTargets);
296
297
298 nextHandedPlayer.setHandedPlayer();
299 return false;
300 }
301
302 private void processCardSearch(List<Target> validTargets, Ability ability,
303 ContextEventListener context) {
304 Player controllerOptimize = test.getOptimizedController(ability, context);
305 if (restrictionZone != -1) {
306 if (controllerOptimize != null) {
307 controllerOptimize.zoneManager.getContainer(restrictionZone)
308 .checkAllCardsOf(test, validTargets, ability);
309 } else {
310 StackManager.PLAYERS[StackManager.idCurrentPlayer].zoneManager
311 .getContainer(restrictionZone).checkAllCardsOf(test, validTargets,
312 ability);
313 StackManager.PLAYERS[1 - StackManager.idCurrentPlayer].zoneManager
314 .getContainer(restrictionZone).checkAllCardsOf(test, validTargets,
315 ability);
316 }
317 } else {
318 if (controllerOptimize != null) {
319 controllerOptimize.zoneManager.checkAllCardsOf(test, validTargets,
320 ability);
321 } else {
322 StackManager.PLAYERS[StackManager.idCurrentPlayer].zoneManager
323 .checkAllCardsOf(test, validTargets, ability);
324 StackManager.PLAYERS[1 - StackManager.idCurrentPlayer].zoneManager
325 .checkAllCardsOf(test, validTargets, ability);
326 }
327 }
328 }
329
330 public void disactivate(ActionContextWrapper actionContext,
331 ContextEventListener context, Ability ability) {
332 StackManager.targetOptions.clearTarget();
333 }
334
335 @Override
336 public final boolean replay(ActionContextWrapper actionContext,
337 ContextEventListener context, Ability ability) {
338 if (actionContext != null && actionContext.actionContext != null) {
339 final TargetList targetList = (TargetList) actionContext.actionContext;
340 targetList
341 .replay(
342 options & 0x30,
343 StackManager.actionManager.idHandler == ActionManager.HANDLER_AD_REPLAY);
344 }
345 return true;
346 }
347
348 public boolean choose(ActionContextWrapper actionContext,
349 ContextEventListener context, Ability ability) {
350 return init(actionContext, context, ability);
351 }
352
353 @Override
354 public final boolean play(ContextEventListener context, Ability ability) {
355 return init(null, context, ability);
356 }
357
358 public final boolean clickOn(MCard target) {
359 if (isValidTarget(target)) {
360 return true;
361 }
362 MagicUIComponents.magicForm.moreInfoLbl.setText("Card:" + target
363 + " is not a valid target");
364 return false;
365 }
366
367 public final boolean clickOn(Player target) {
368 if (isValidTarget(target)) {
369 return true;
370 }
371 MagicUIComponents.magicForm.moreInfoLbl.setText("Player:" + target
372 + " is not a valid target");
373 return false;
374 }
375
376 public final boolean succeedClickOn(Player player) {
377 MagicUIComponents.magicForm.moreInfoLbl.setText("Player:" + player
378 + " added as target");
379 MagicUIComponents.logListing.append(0, "Player:" + player
380 + " added as target");
381 Log.debug("Player:" + player + " added as target");
382 return succeedClickOn((Target) player);
383 }
384
385 public final boolean succeedClickOn(MCard card) {
386 MagicUIComponents.magicForm.moreInfoLbl.setText("Card:" + card
387 + " added as target");
388 MagicUIComponents.logListing.append(0, "Card:" + card + " added as target");
389 Log.debug("Card:" + card + " added as target");
390 return succeedClickOn((Target) card);
391 }
392
393 /***
394 * @param target
395 * @return true if this action is completed
396 */
397 protected boolean succeedClickOn(Target target) {
398 finished();
399 if (StackManager.actionManager.advancedMode) {
400 TargetList targetList = (TargetList) StackManager.actionManager
401 .getActionContext().actionContext;
402 if (targetList == null) {
403 targetList = new TargetList();
404 StackManager.actionManager.getActionContext().actionContext = targetList;
405 }
406 targetList.add(target, test);
407 } else {
408 StackManager.getInstance().getTargetedList().addTarget(options & 0x30,
409 target, test, false);
410 }
411 return true;
412 }
413
414 public final void finished() {
415
416 StackManager.targetOptions.clearTarget();
417 }
418
419 /***
420 * Is the specified card is a valid target?
421 *
422 * @param target
423 * the tested card.
424 * @return true if the given card is a valid target
425 * @since 0.86 a target cannot be added twice in the target-list
426 */
427 public final boolean isValidTarget(MCard target) {
428 return (type == IdTokens.CARD
429 && (restrictionZone == -1 || target.getIdZone() == restrictionZone) || type == IdTokens.DEALTABLE)
430 && !StackManager.getTargetListAccess().contains(target)
431 && test.test(StackManager.currentAbility, target);
432 }
433
434 /***
435 * Is the specified player is a valid target?
436 *
437 * @param target
438 * the tested player.
439 * @return true if the given card is a valid target
440 */
441 public final boolean isValidTarget(Player target) {
442 return (type == IdTokens.PLAYER || type == IdTokens.DEALTABLE)
443 && test.test(StackManager.currentAbility, target);
444 }
445
446 private boolean canCancel() {
447 if ((options & IdTargets.ALLOW_CANCEL) != 0) {
448 if (StackManager.actionManager.advancedMode) {
449 return StackManager.actionManager.getActionContext().repeat != IdConst.ALL
450 || StackManager.actionManager.getActionContext().done > 0;
451 }
452 return true;
453 }
454 return false;
455 }
456
457 public final boolean manualSkip() {
458
459 if (canCancel()) {
460 StackManager.targetOptions.clearTarget();
461 StackManager.actionManager.setHop(hop);
462 if (StackManager.actionManager.advancedMode) {
463
464 StackManager.actionManager.getActionContext().recordIndex = 1;
465 }
466 return true;
467 }
468
469
470 if (StackManager.actionManager.advancedMode) {
471 StackManager.cancel();
472 return false;
473 }
474
475
476 return init(
477 StackManager.actionManager.advancedMode ? StackManager.actionManager
478 .getActionContext() : null, StackManager.getInstance()
479 .getAbilityContext(), StackManager.currentAbility);
480 }
481
482 @Override
483 public String toString(Ability ability) {
484 String res = null;
485 switch (type) {
486 case IdTokens.CARD:
487 if (options == IdTargets.ALL) {
488 return "[all cards]";
489 }
490 if (options == IdTargets.RANDOM) {
491 return "[a random card]";
492 }
493 res = "[a card ";
494 break;
495 case IdTokens.PLAYER:
496 if (options == IdTargets.ALL) {
497 return "[all players]";
498 }
499 if (options == IdTargets.RANDOM) {
500 return "[a random player]";
501 }
502 res = "[a player ";
503 break;
504 case IdTokens.DEALTABLE:
505 if (options == IdTargets.ALL) {
506 return "[all cards and players]";
507 }
508 res = "[a dealtable ";
509 break;
510 default:
511 res = "[? ";
512 }
513 switch (options & 0x07) {
514 case IdTargets.CHOOSE:
515 return res + "you choose]";
516 case IdTargets.OPPONENT_CHOOSE:
517 return res + "opponent chooses]";
518 case IdTargets.CONTEXT_CHOOSE:
519 return res + "context player chooses]";
520 case IdTargets.TARGET_CHOOSE:
521 return res + "player chooses]";
522 case IdTargets.ATTACHED_TO_CONTROLLER_CHOOSE:
523 return res + "controller of attached-to chooses]";
524 case IdTargets.STACK0_CHOOSE:
525 return res + "-stack0- chooses]";
526 default:
527 return res + "]";
528 }
529 }
530
531 public String toHtmlString(Ability ability, ContextEventListener context,
532 ActionContextWrapper actionContext) {
533 return super.toHtmlString(ability, context);
534 }
535
536 /***
537 * Is this action is really targeting.
538 *
539 * @return true if this action is really targeting.
540 */
541 public abstract boolean isTargeting();
542
543 /***
544 * Verify if this target action may cause abortion of the current ability.
545 *
546 * @param ability
547 * is the ability owning this test. The card component of this
548 * ability should correspond to the card owning this test too.
549 * @param hopCounter
550 * the minimum valid target to have. If is negative or zero return
551 * true.
552 * @return true if this target action may cause abortion of the current
553 * ability.
554 * @since 0.86 new algorythme to determine valid targets
555 */
556 public final boolean checkTarget(Ability ability, int hopCounter) {
557 if (hopCounter <= 0) {
558 return true;
559 }
560 try {
561 return TargetHelper.getInstance().checkTarget(ability, ability.getCard(),
562 test, type, options & 0x0F, null, restrictionZone, hopCounter,
563 canBePreempted);
564 } catch (Throwable t) {
565
566 return true;
567 }
568 }
569
570 /***
571 * Represents the options of this target action
572 */
573 protected int options;
574
575 /***
576 * represents the test applied to target before to be added to the list
577 */
578 protected Test test;
579
580 /***
581 * represents the type of target (player, card, dealtable)
582 *
583 * @see IdTargets
584 */
585 protected int type;
586
587 /***
588 * Jump to do in case of abortion. Note if this value is equal to 0, this
589 * target process cannot been aborted since the next action would the current
590 * one.
591 */
592 protected int hop;
593
594 /***
595 * The zone identifant where the scan is restricted. If is equal to -1, there
596 * would be no restriction zone.
597 *
598 * @see net.sf.firemox.token.IdZones
599 */
600 protected int restrictionZone;
601
602 /***
603 * <code>true</code> if the valid targets can be derterminated before
604 * runtime.
605 */
606 private boolean canBePreempted;
607 }