View Javadoc

1   /*
2    *   Firemox is a turn based strategy simulator
3    *   Copyright (C) 2003-2007 Fabrice Daugan
4    *
5    *   This program is free software; you can redistribute it and/or modify it 
6    * under the terms of the GNU General Public License as published by the Free 
7    * Software Foundation; either version 2 of the License, or (at your option) any
8    * later version.
9    *
10   *   This program is distributed in the hope that it will be useful, but WITHOUT 
11   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12   * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
13   * details.
14   *
15   *   You should have received a copy of the GNU General Public License along  
16   * with this program; if not, write to the Free Software Foundation, Inc., 
17   * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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 			// Target is a player or card
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 			// Target is a player
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 			// Target is a card
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 				// continue the stack process
202 				return true;
203 			}
204 			break;
205 		case IdTargets.RANDOM:
206 			if (!validTargets.isEmpty()) {
207 				// random target, at least on target
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 			// unique target to choose
227 		}
228 
229 		if (validTargets.isEmpty()) {
230 			// no valid target --> this spell abort
231 			// if (restrictionZone!=-1)
232 
233 			if (hop == IdConst.ALL) {
234 				// abortion is not managed by this spell
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 		// At least one valid target --> determines the controller
250 		String message = null;
251 		Player nextHandedPlayer = null;
252 		switch (choiceMode & 0x07) {
253 		case IdTargets.CHOOSE:
254 			// target will be chosen by the active player
255 			message = "youchoose";
256 			nextHandedPlayer = owner.getController();
257 			break;
258 		case IdTargets.OPPONENT_CHOOSE:
259 			// target will be chosen by the non-active player
260 			message = "opponentchoose";
261 			nextHandedPlayer = owner.getController().getOpponent();
262 			break;
263 		case IdTargets.CONTEXT_CHOOSE:
264 			// target will be chosen by the player of current context's event
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 			// target will be chosen by the player of current context's event
272 			message = "target-choose";
273 			nextHandedPlayer = (Player) StackManager.getInstance().getTargetedList()
274 					.get(0);
275 			break;
276 		case IdTargets.ATTACHED_TO_CONTROLLER_CHOOSE:
277 			// target will be chosen by the player of current context's event
278 			message = "attachedto.controller-choose";
279 			nextHandedPlayer = ((MCard) owner.getParent()).getController();
280 			break;
281 		case IdTargets.STACK0_CHOOSE:
282 			/*
283 			 * target will be chosen by the player identified by the id stored in
284 			 * index 0 of register of current ability.
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 		// This player has to choose a target
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 		// we exit from the target mode
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 		// Is this target is required?
459 		if (canCancel()) {
460 			StackManager.targetOptions.clearTarget();
461 			StackManager.actionManager.setHop(hop);
462 			if (StackManager.actionManager.advancedMode) {
463 				// complete this action even if we'd do more
464 				StackManager.actionManager.getActionContext().recordIndex = 1;
465 			}
466 			return true;
467 		}
468 
469 		// No, the target is requested, cancel ability if we're in advanced mode
470 		if (StackManager.actionManager.advancedMode) {
471 			StackManager.cancel();
472 			return false;
473 		}
474 
475 		// Re-launch "wait again player's choice for target"
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 			// Ignore this error since target can depend on some contexts
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 }