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