View Javadoc

1   /*
2    *   Magic-Project 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.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 			// Target is a player or card
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 			// Target is a player
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 			// Target is a card
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 				// continue the stack process
204 				return true;
205 			}
206 			break;
207 		case IdTargets.RANDOM:
208 			if (!validTargets.isEmpty()) {
209 				// random target, at least on target
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 			// unique target to choose
228 		}
229 
230 		if (validTargets.isEmpty()) {
231 			// no valid target --> this spell abort
232 			// if (restrictionZone!=-1)
233 
234 			if (hop == IdConst.ALL) {
235 				// abortion is not managed by this spell
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 		// At least one valid target --> determines the controller
251 		String message = null;
252 		Player nextHandedPlayer = null;
253 		switch (choiceMode & 0x07) {
254 		case IdTargets.CHOOSE:
255 			// target will be choosen by the active player
256 			message = "youchoose";
257 			nextHandedPlayer = owner.getController();
258 			break;
259 		case IdTargets.OPPONENT_CHOOSE:
260 			// target will be choosen by the non-active player
261 			message = "opponentchoose";
262 			nextHandedPlayer = owner.getController().getOpponent();
263 			break;
264 		case IdTargets.CONTEXT_CHOOSE:
265 			// target will be choosen by the player of current context's event
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 			// target will be choosen by the player of current context's event
273 			message = "target-choose";
274 			nextHandedPlayer = (Player) StackManager.getInstance().getTargetedList()
275 					.get(0);
276 			break;
277 		case IdTargets.ATTACHED_TO_CONTROLLER_CHOOSE:
278 			// target will be choosen by the player of current context's event
279 			message = "attachedto.controller-choose";
280 			nextHandedPlayer = ((MCard) owner.getParent()).getController();
281 			break;
282 		case IdTargets.STACK0_CHOOSE:
283 			/*
284 			 * target will be choosen by the player identified by the id stored in
285 			 * index 0 of register of current ability.
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 		// This player has to choose a target
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 		// 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 				TargetList targetList = (TargetList) StackManager.actionManager
466 						.getActionContext().actionContext;
467 				if (targetList == null) {
468 					targetList = new TargetList();
469 				}
470 			}
471 			return true;
472 		}
473 
474 		// No, the target is requested, cancel ability if we're in advanced mode
475 		if (StackManager.actionManager.advancedMode) {
476 			StackManager.cancel();
477 			return false;
478 		}
479 
480 		// Re-launch "wait again player's choice for target"
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 			// Ignore this error since target can depend on some contextes
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 }