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  
21  package net.sf.firemox.clickable.target.card;
22  
23  import static net.sf.firemox.clickable.target.card.CardFactory.ACTIVATED_COLOR;
24  import static net.sf.firemox.clickable.target.card.CardFactory.WARNING_PICTURE;
25  
26  import java.awt.Component;
27  import java.awt.LayoutManager;
28  import java.awt.event.MouseEvent;
29  import java.awt.event.MouseWheelEvent;
30  import java.awt.event.MouseWheelListener;
31  import java.io.InputStream;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collection;
35  import java.util.HashMap;
36  import java.util.HashSet;
37  import java.util.List;
38  import java.util.Map;
39  
40  import javax.swing.JMenuItem;
41  import javax.swing.JSeparator;
42  
43  import net.sf.firemox.action.MoveCard;
44  import net.sf.firemox.action.listener.WaitingAbility;
45  import net.sf.firemox.clickable.ability.Ability;
46  import net.sf.firemox.clickable.ability.ActivatedAbility;
47  import net.sf.firemox.clickable.ability.ReplacementAbility;
48  import net.sf.firemox.clickable.ability.UserAbility;
49  import net.sf.firemox.clickable.target.Target;
50  import net.sf.firemox.clickable.target.TargetFactory;
51  import net.sf.firemox.clickable.target.player.Player;
52  import net.sf.firemox.database.DatabaseCard;
53  import net.sf.firemox.database.DatabaseFactory;
54  import net.sf.firemox.deckbuilder.DeckConstraints;
55  import net.sf.firemox.event.ModifiedIdCard;
56  import net.sf.firemox.event.ModifiedIdColor;
57  import net.sf.firemox.event.ModifiedProperty;
58  import net.sf.firemox.event.ModifiedRegister;
59  import net.sf.firemox.event.context.ContextEventListener;
60  import net.sf.firemox.event.context.MContextCardCardIntInt;
61  import net.sf.firemox.modifier.AbilityModifier;
62  import net.sf.firemox.modifier.ColorModifier;
63  import net.sf.firemox.modifier.ControllerModifier;
64  import net.sf.firemox.modifier.IdCardModifier;
65  import net.sf.firemox.modifier.PlayableZoneModifier;
66  import net.sf.firemox.modifier.PropertyModifier;
67  import net.sf.firemox.modifier.RegisterIndirection;
68  import net.sf.firemox.modifier.RegisterModifier;
69  import net.sf.firemox.modifier.model.ModifierModel;
70  import net.sf.firemox.modifier.model.ObjectFactory;
71  import net.sf.firemox.network.ConnectionManager;
72  import net.sf.firemox.network.message.CoreMessageType;
73  import net.sf.firemox.operation.Add;
74  import net.sf.firemox.operation.Operation;
75  import net.sf.firemox.operation.Set;
76  import net.sf.firemox.stack.StackManager;
77  import net.sf.firemox.test.Test;
78  import net.sf.firemox.token.IdCommonToken;
79  import net.sf.firemox.token.IdConst;
80  import net.sf.firemox.token.IdPositions;
81  import net.sf.firemox.token.IdTokens;
82  import net.sf.firemox.token.IdZones;
83  import net.sf.firemox.tools.Log;
84  import net.sf.firemox.tools.MToolKit;
85  import net.sf.firemox.tools.Pair;
86  import net.sf.firemox.ui.Tappable;
87  import net.sf.firemox.ui.i18n.LanguageManager;
88  import net.sf.firemox.ui.layout.AttachmentLayout;
89  import net.sf.firemox.zone.ExpandableZone;
90  import net.sf.firemox.zone.MZone;
91  import net.sf.firemox.zone.ZoneManager;
92  
93  import org.apache.commons.lang.ArrayUtils;
94  
95  /***
96   * This class corresponds to the graphical element of a specified card, and
97   * corresponds to an actor. When loading a new card, first we take care that a
98   * card with same name has not been already loaded in order to save memory, so
99   * all cards having the same name share the same Image object. <br>
100  * 
101  * @since 0.52 "enableReverse" option
102  * @since 0.70 support modifiers
103  * @since 0.71 art and rule author
104  * @since 0.72 support static effects, removed art-author
105  * @since 0.85 property pictures are displayed
106  * @since 0.90 database + card model
107  * @since 0.91 keywords added, externalized to CardModel, explicit attachment
108  *        object, and is movable.
109  * @see net.sf.firemox.token.IdCardColors
110  * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
111  * @author <a href="mailto:kismet-sl@users.sourceforge.net">Stefano "Kismet"
112  *         Lenzi</a>
113  */
114 public class MCard extends AbstractCard implements Tappable, MouseWheelListener {
115 
116 	/***
117 	 * Create a new instance of Card reading from a file.
118 	 * 
119 	 * @param cardName
120 	 *          the card name
121 	 * @param inputFile
122 	 *          is the file read, containing information
123 	 * @param controller
124 	 *          is the controller of this card
125 	 * @param owner
126 	 *          is the owner of this card
127 	 * @param constraints
128 	 *          the constraints of the card for database management
129 	 */
130 	public MCard(String cardName, InputStream inputFile, Player controller,
131 			Player owner, Map<String, String> constraints) {
132 		super();
133 		// Read card name
134 		initUI(null, CardFactory.getCardModel(cardName, inputFile), constraints);
135 		initModel();
136 		this.copiedCard = null;
137 		this.owner = owner;
138 		this.controller = controller;
139 		this.originalController = controller;
140 		lastKnownInstances = new HashMap<Integer, LastKnownCardInfo>();
141 	}
142 
143 	/***
144 	 * 
145 	 */
146 	private void initModel() {
147 		final CardModel cardModel = getCardModel();
148 		// Cache registers
149 		registers = cardModel.getStaticRegisters().clone();
150 		cachedRegisters = registers.clone();
151 
152 		// Cache card type, coded with 2 byte
153 		cachedIdCard = cardModel.getIdCard();
154 
155 		// Cache card color, coded with one byte
156 		cachedIdColor = cardModel.getIdColor();
157 
158 		// Cache list of abilities
159 		cachedAbilities = new ArrayList<Ability>(cardModel.getAbilities().length);
160 		for (Ability ability : cardModel.getAbilities()) {
161 			cachedAbilities.add(ability.clone(this));
162 		}
163 
164 		// Cache list of properties, i.e. first strike,trample,Legend,Knight,...
165 		cachedProperties = new HashSet<Integer>();
166 		for (int property : cardModel.getProperties()) {
167 			cachedProperties.add(property);
168 		}
169 
170 		// build modifier from models
171 		indirections = new RegisterIndirection[IdTokens.CARD_REGISTER_SIZE];
172 		registerModifiers = new RegisterModifier[IdTokens.CARD_REGISTER_SIZE];
173 	}
174 
175 	/***
176 	 * Create a new instance of Card exactly like cardRef instance. Is called only
177 	 * from clone method.
178 	 * 
179 	 * @param cardRef
180 	 *          is the model for this new instance
181 	 * @param database
182 	 *          the database of this new card.
183 	 */
184 	public MCard(MCard cardRef, DatabaseCard database) {
185 		// Database is shared.
186 		this.database = database;
187 		this.copiedCard = null;
188 
189 		// Initialize UI of this new Card
190 		initUI(null, database.getCardModel(), null);
191 		initModel();
192 		lastKnownInstances = new HashMap<Integer, LastKnownCardInfo>();
193 
194 		if (cardRef != null) {
195 			controller = cardRef.controller;
196 			owner = cardRef.owner;
197 		}
198 		originalController = controller;
199 	}
200 
201 	/***
202 	 * Create a new instance of Card tokenized. No field is initialized.
203 	 */
204 
205 	protected MCard() {
206 		super();
207 		cachedAbilities = null;
208 		database = null;
209 		copiedCard = null;
210 		originalController = null;
211 	}
212 
213 	/***
214 	 * Create a new instance of tokenized Card. This constructor should be called
215 	 * only to obtain the a copy of the specified card sharing registers and
216 	 * abilities. Also, any modification done on registers or ability of one of
217 	 * these cards will be visible on the other. The zone information of cards is
218 	 * not shared, and the new card will have it's zone initialized with stack
219 	 * identifier instead of side. The picture used for this card will be the
220 	 * specified one if it is not null. Otherwise the cardRef picture will be
221 	 * used.
222 	 * 
223 	 * @param pictureName
224 	 *          is the picture name used to represent the copy. May be null.
225 	 * @param cardRef
226 	 *          is the model for this instance
227 	 * @see IdZones#STACK
228 	 */
229 	public MCard(String pictureName, MCard cardRef) {
230 		// Database is shared.
231 		database = cardRef.database;
232 
233 		// Initialize UI of this new Card
234 		initUI(pictureName, cardRef.database.getCardModel(), null);
235 
236 		this.idZone = IdZones.STACK;
237 		this.registers = cardRef.registers;
238 		this.cachedRegisters = cardRef.cachedRegisters;
239 		this.cachedProperties = cardRef.cachedProperties;
240 		this.cachedAbilities = null;
241 		this.copiedCard = cardRef;
242 		this.cachedIdCard = cardRef.cachedIdCard;
243 		this.cachedIdColor = cardRef.cachedIdColor;
244 		this.indirections = new RegisterIndirection[IdTokens.CARD_REGISTER_SIZE];
245 		this.registerModifiers = new RegisterModifier[IdTokens.CARD_REGISTER_SIZE];
246 		this.controller = cardRef.controller;
247 		this.originalController = controller;
248 		this.owner = cardRef.owner;
249 		tokenize();
250 	}
251 
252 	@Override
253 	public boolean isAbility(int abilityType) {
254 		return false;
255 	}
256 
257 	@Override
258 	public boolean isSpell() {
259 		return true;
260 	}
261 
262 	/***
263 	 * Indicates whether this card suits to the specified position code.
264 	 * 
265 	 * @param position
266 	 *          the matching position code
267 	 * @return true if this card suits to the specified position code.
268 	 * @see net.sf.firemox.token.IdPositions#ON_THE_BOTTOM
269 	 * @see net.sf.firemox.token.IdPositions#ON_THE_TOP
270 	 */
271 	public boolean isSamePosition(int position) {
272 		return getContainer().isSamePosition(this, position);
273 	}
274 
275 	/***
276 	 * Return the container of this card.
277 	 * 
278 	 * @return the container of this card.
279 	 */
280 	public MZone getContainer() {
281 		return controller.zoneManager.getContainer(getIdZone());
282 	}
283 
284 	@Override
285 	public boolean isACopy() {
286 		return copiedCard != null;
287 	}
288 
289 	/***
290 	 * Indicates if the specified sort of card idCard contains the card's sort.
291 	 * 
292 	 * @param idCard
293 	 *          are the sorts we match.
294 	 * @return true if the specified sort idCard contains the card's sort.
295 	 * @see #hasIdCard(int,int)
296 	 */
297 	public boolean hasIdCard(int idCard) {
298 		return hasIdCard(getIdCard(), idCard);
299 	}
300 
301 	/***
302 	 * Indicates if idCardBIG contains completely idCardMatched. <br>
303 	 * 
304 	 * @param idCardBIG
305 	 *          are the set of types.
306 	 * @param idCardMatched
307 	 *          are the type we match.
308 	 * @return true if idCardBIG contains completely idCardMatched. <br>
309 	 */
310 	public static boolean hasIdCard(int idCardBIG, int idCardMatched) {
311 		return (idCardBIG & idCardMatched) == idCardMatched;
312 	}
313 
314 	/***
315 	 * Indicates if idCardBIG contains partially idCardMatched. <br>
316 	 * 
317 	 * @param idCardBIG
318 	 *          are the set of types.
319 	 * @param idCardMatched
320 	 *          are the type we match.
321 	 * @return true if idCardBIG contains partially idCardMatched. <br>
322 	 */
323 	public static boolean intersectionIdCard(int idCardBIG, int idCardMatched) {
324 		return (idCardBIG & idCardMatched) != 0;
325 	}
326 
327 	/***
328 	 * Indicates if propertyBIG contains completely propertyMatched. <br>
329 	 * 
330 	 * @param propertyBIG
331 	 *          are the set of properties.
332 	 * @param propertyMatched
333 	 *          is the property we match.
334 	 * @return true if propertyBIG contains completely propertyMatched. <br>
335 	 */
336 	public static boolean hasProperty(int[] propertyBIG, int propertyMatched) {
337 		return ArrayUtils.contains(propertyBIG, propertyMatched);
338 	}
339 
340 	/***
341 	 * Indicates if the specified color idColor contains the card's colors. <br>
342 	 * 
343 	 * @param idColor
344 	 *          are the colors we match.
345 	 * @return true if the specified color idColor contains the card's colors.
346 	 * @see #hasIdColor(int,int)
347 	 */
348 	public boolean hasIdColor(int idColor) {
349 		return hasIdColor(getIdColor(), idColor);
350 	}
351 
352 	/***
353 	 * Indicates if the specified color idColorBIG contains the other specified
354 	 * color idColorMatched. <br>
355 	 * 
356 	 * @param idColorBIG
357 	 *          are the colors that idColorMatched must have
358 	 * @param idColorMatched
359 	 *          are the colors we match.
360 	 * @return true if the specified color idColorBIG contains the other specified
361 	 *         color idColorMatched.
362 	 */
363 	public static boolean hasIdColor(int idColorBIG, int idColorMatched) {
364 		return (idColorBIG & idColorMatched) == idColorMatched;
365 	}
366 
367 	/***
368 	 * <ul>
369 	 * Indicates if this card match with the specified place and constraint. As
370 	 * the specified place is an integer, it may contain another information about
371 	 * constraint in the high bits : xxyy
372 	 * <li>xx represents the constraint, may be : "must be untapped" or "must be
373 	 * tapped".
374 	 * <li>yy represents the zone identifier.
375 	 * </ul>
376 	 * The zones are compared, then the constraint tapped/untapped is checked.
377 	 * <br>
378 	 * If <code>zoneConstaint</code> is ID__ANYWHERE, return true. <br>
379 	 * If <code>zoneConstaint</code> is same as current zone of this card and
380 	 * the constraint is validated, return true <br>
381 	 * 
382 	 * @param zoneConstaint
383 	 *          the zone id and optional tapped information.
384 	 * @return true if <code>zoneConstaint</code> is ID__ANYWHERE or
385 	 *         <code>zoneConstaint</code> is same as current place of this card
386 	 *         and the constraint is verified.
387 	 * @see IdZones#PLAY_TAPPED
388 	 * @see IdZones#PLAY_UNTAPPED
389 	 * @see IdZones
390 	 */
391 	public boolean isSameState(int zoneConstaint) {
392 		switch (zoneConstaint) {
393 		case IdZones.PLAY_TAPPED:
394 			return idZone == IdZones.PLAY && tapped;
395 		case IdZones.PLAY_UNTAPPED:
396 			return idZone == IdZones.PLAY && !tapped;
397 		default:
398 			return idZone == zoneConstaint;
399 		}
400 	}
401 
402 	/***
403 	 * Compare a zone with the current card'szone
404 	 * 
405 	 * @param idZone
406 	 *          the other zone
407 	 * @return true the specified zone, and the current card's zone are the same.
408 	 */
409 	public boolean isSameIdZone(int idZone) {
410 		return isSameIdZone(this.idZone, idZone);
411 	}
412 
413 	/***
414 	 * @param idZone
415 	 *          the other zone
416 	 * @param other
417 	 *          another zone
418 	 * @return true the specified zones are the same.
419 	 */
420 	public static boolean isSameIdZone(int idZone, int other) {
421 		return getIdZone(idZone, null) == getIdZone(other, null);
422 	}
423 
424 	/***
425 	 * Return the zone identifier of this card.
426 	 * 
427 	 * @see IdZones
428 	 * @return the zone identifier of this card. IdZones#PLAY, IdZones#HAND,...
429 	 * @see IdZones
430 	 */
431 	public int getIdZone() {
432 		return idZone;
433 	}
434 
435 	/***
436 	 * Return the place where is this card.
437 	 * 
438 	 * @param idZone
439 	 *          the zone identifier This identifier may contain staus information
440 	 *          like "tapped", "untapped"
441 	 * @param context
442 	 *          The optional context attached to the request.
443 	 * @return the place identifier corresponding to the specified zone, and with
444 	 *         any status information
445 	 * @see IdZones
446 	 */
447 	public static int getIdZone(int idZone, ContextEventListener context) {
448 		if (idZone == IdZones.CONTEXT && context != null) {
449 			// TODO complete to support the AccessibleContext pattern
450 			return context.getZoneContext();
451 		}
452 		return idZone & 0x0F;
453 	}
454 
455 	/***
456 	 * Return a zone code representing the zone and the state of this card.
457 	 * 
458 	 * @param idZone
459 	 *          the zone identifier without status information like "tapped",
460 	 *          "untapped"
461 	 * @param tapped
462 	 *          indicate the state of this car.
463 	 * @return the place identifier corresponding to the specified zone, and with
464 	 *         any status information
465 	 * @see IdZones
466 	 */
467 	public static int getIdZone(int idZone, boolean tapped) {
468 		return idZone == IdZones.PLAY ? tapped ? IdZones.PLAY_TAPPED
469 				: IdZones.PLAY_UNTAPPED : idZone;
470 	}
471 
472 	/***
473 	 * returns all IdColors of this card
474 	 * 
475 	 * @return all IdColors of this card
476 	 * @see net.sf.firemox.token.IdCardColors
477 	 */
478 	public int getIdColor() {
479 		return cachedIdColor;
480 	}
481 
482 	/***
483 	 * returns all IdCards of this card
484 	 * 
485 	 * @return all IdCards of this card
486 	 */
487 	public int getIdCard() {
488 		return cachedIdCard;
489 	}
490 
491 	/***
492 	 * Indicates if this card has this property.
493 	 * 
494 	 * @param property
495 	 *          is the type required
496 	 * @return true if this card has the required property.
497 	 */
498 	public boolean hasProperty(int property) {
499 		if (cachedProperties == null) {
500 			return false;
501 		}
502 		if (property < DeckConstraints.getMinProperty()
503 				|| property > DeckConstraints.getMaxProperty()) {
504 			return cachedProperties.contains(property);
505 		}
506 		return cachedProperties.contains(property)
507 				|| cachedProperties.contains(DeckConstraints.MASTER);
508 	}
509 
510 	/***
511 	 * indicates if this card has this idType.
512 	 * 
513 	 * @param idType
514 	 *          is the type required
515 	 * @param creator
516 	 *          the card that has created the modifier to ignore.
517 	 * @return true if this card has the required type
518 	 */
519 	public boolean hasPropertyNotFromCreator(int idType, MCard creator) {
520 		boolean found;
521 		int[] properties = getCardModel().getProperties();
522 		found = properties != null && Arrays.binarySearch(properties, idType) >= 0;
523 		if (creator == this) {
524 			found = false;
525 		}
526 		if (propertyModifier != null) {
527 			return propertyModifier.hasPropertyNotFromCreator(idType, found, creator);
528 		}
529 		return found;
530 	}
531 
532 	@Override
533 	public boolean needReverse() {
534 		if (getParent() != null) {
535 			if (isAttached()) {
536 				return !StackManager.isYou(((MCard) getParent()).controller);
537 			}
538 			switch (getIdZone()) {
539 			case IdZones.STACK:
540 			case IdZones.DELAYED:
541 			case IdZones.TRIGGERED:
542 			case IdZones.SIDE:
543 				return false;
544 			default:
545 				return !StackManager.isYou(controller);
546 			}
547 		}
548 		return !StackManager.isYou(controller);
549 	}
550 
551 	@Override
552 	public void moveCard(int destinationZone, Player newController,
553 			boolean newIsTapped, int idPosition) {
554 
555 		// Timestamp process
556 		if (timestampReferences > 0) {
557 			// one or several triggered abilities make reference to this card
558 			lastKnownInstances.put(timeStamp, new LastKnownCardInfoImpl(this,
559 					timestampReferences, destinationZone));
560 			timestampReferences = 0;
561 		}
562 		boolean fromPlayToPlay = idZone == IdZones.PLAY
563 				&& destinationZone == IdZones.PLAY;
564 
565 		// remove card from it's previous zone
566 		if (idZone == IdZones.PLAY) {
567 			if (!fromPlayToPlay) {
568 				// clear damages
569 				clearDamages();
570 			}
571 
572 			final MCard attachedTo = isAttached() ? (MCard) getParent() : null;
573 			controller.zoneManager.play.remove(this);
574 
575 			if (!fromPlayToPlay) {
576 				tap(false);
577 			}
578 
579 			if (attachedTo != null) {
580 				// this card was attached to another one in play
581 				attachedTo.ui.updateLayout();
582 			}
583 			controller.zoneManager.play.repaint();
584 		} else if (getParent() != null) {
585 			getContainer().remove(this);
586 		}
587 
588 		// update positions and controller
589 		if (controller == null) {
590 			owner = newController;
591 		}
592 		controller = newController;
593 		idZone = destinationZone;
594 		timeStamp++;
595 		isHighLighted = false;
596 		reversed = needReverse();
597 		clearPrivateNamedObject();
598 		ui.updateLayout();
599 
600 		// move to the destination this card
601 		switch (destinationZone) {
602 		case IdZones.PLAY:
603 			// Initialize the registers
604 			if (!fromPlayToPlay) {
605 				int[] staticregisters = getCardModel().getStaticRegisters();
606 				System.arraycopy(staticregisters, 0, registers, 0,
607 						staticregisters.length);
608 			}
609 			// update card UI
610 			tap(newIsTapped);
611 			newController.zoneManager.play.addBottom(this);
612 			break;
613 		case IdZones.NOWHERE:
614 			// the specified card will never be seen again
615 			break;
616 		default:
617 			switch (idPosition) {
618 			case IdPositions.ON_THE_TOP:
619 				newController.zoneManager.getContainer(destinationZone).addTop(this);
620 				break;
621 			case IdPositions.ON_THE_BOTTOM:
622 			default:
623 				newController.zoneManager.getContainer(destinationZone).addBottom(this);
624 			}
625 		}
626 	}
627 
628 	/***
629 	 * Indicates whether the card with the specified zone identifier is tapped or
630 	 * not.
631 	 * 
632 	 * @param idZone
633 	 *          the zone id + tapped position information.
634 	 * @return true if the card with the specified zone identifier is tapped or
635 	 *         not.
636 	 */
637 	public static boolean isTapped(int idZone) {
638 		return (idZone & IdZones.PLAY_TAPPED) == IdZones.PLAY_TAPPED;
639 	}
640 
641 	/***
642 	 * Register abilities of this card, supposing card is in the specified zone.
643 	 * 
644 	 * @param zone
645 	 *          The supposed zone this card will go to
646 	 */
647 	public void registerAbilities(int zone) {
648 		for (Ability ability : cachedAbilities) {
649 			if (ability.eventComing().isWellPlaced(zone)) {
650 				ability.registerToManager();
651 			}
652 		}
653 	}
654 
655 	/***
656 	 * Return a default ability associated to this card. There is no guarantee
657 	 * this method returns always the same ability.
658 	 * 
659 	 * @return a default ability associated to this card.
660 	 */
661 	public Ability getDummyAbility() {
662 		if (cachedAbilities == null || cachedAbilities.isEmpty())
663 			// TODO Manage the no-ability case for dummyAbility
664 			return null;
665 		return cachedAbilities.get(0);
666 	}
667 
668 	/***
669 	 * Register abilities of this card, supposing card is in the specified zone.
670 	 * 
671 	 * @param zone
672 	 *          The supposed zone this card will go to
673 	 */
674 	public void registerReplacementAbilities(int zone) {
675 		for (Ability ability : cachedAbilities) {
676 			if (ability instanceof ReplacementAbility
677 					&& ability.eventComing().isWellPlaced(zone)) {
678 				ability.registerToManager();
679 			}
680 		}
681 	}
682 
683 	/***
684 	 * Unregister useless abilities from the eventManager.
685 	 */
686 	public void unregisterAbilities() {
687 		for (Ability ability : cachedAbilities) {
688 			if (!ability.eventComing().isWellPlaced()) {
689 				ability.removeFromManager();
690 			}
691 		}
692 	}
693 
694 	/***
695 	 * Initialize card picture, database, layout and zone location (SIDE). If the
696 	 * specified card picture is null, the picture associated to the model will be
697 	 * used.
698 	 * 
699 	 * @param pictureName
700 	 *          the picture name used to represent the copy. May be null.
701 	 * @param cardModel
702 	 *          rules author of this card
703 	 * @param constraints
704 	 *          the constraints of the card
705 	 */
706 	private void initUI(String pictureName, CardModel cardModel,
707 			Map<String, String> constraints) {
708 		isHighLighted = false;
709 		idZone = IdZones.SIDE;
710 
711 		// Get the proxy/database information if it is not already set
712 		if (database == null) {
713 			database = DatabaseFactory.getDatabase(pictureName, cardModel,
714 					constraints);
715 		}
716 		this.originalDatabase = database;
717 		setName(cardModel.getCardName());
718 
719 		ui = new VirtualCard(this);
720 		add(ui);
721 		setLayout(new AttachmentLayout());
722 		ui.updateSizes();
723 		addMouseWheelListener(this);
724 	}
725 
726 	public void tap(boolean tapped) {
727 		if (this.tapped != tapped) {
728 			this.tapped = tapped;
729 			// [un]tap the attached elements too
730 			for (int i = getComponentCount(); i-- > 0;) {
731 				((Tappable) getComponent(i)).tap(tapped);
732 			}
733 			ui.updateLayout();
734 		}
735 	}
736 
737 	@Override
738 	public void reverse(boolean reversed) {
739 		super.reverse(reversed);
740 		ui.updateLayout();
741 	}
742 
743 	@Override
744 	public void highLight(boolean... highlightedZones) {
745 		super.highLight(ACTIVATED_COLOR);
746 		switch (getIdZone()) {
747 		case IdZones.SIDE:
748 		case IdZones.STACK:
749 			highlightedZones[idZone] = true;
750 			break;
751 		default:
752 			highlightedZones[controller.idPlayer * IdZones.NB_ZONE + idZone] = true;
753 		}
754 	}
755 
756 	@Override
757 	public void targetize(boolean... highlightedZones) {
758 		super.highLight(TargetFactory.TARGET_COLOR);
759 		switch (getIdZone()) {
760 		case IdZones.SIDE:
761 		case IdZones.STACK:
762 			highlightedZones[idZone] = true;
763 			break;
764 		default:
765 			highlightedZones[controller.idPlayer * IdZones.NB_ZONE + idZone] = true;
766 		}
767 	}
768 
769 	public void mouseWheelMoved(MouseWheelEvent e) {
770 		// Update mouse wheel layout only if this card is in play with nested cards
771 		if (getIdZone() == IdZones.PLAY && getComponentCount() > 1) {
772 			final LayoutManager layoutManager = getLayout();
773 			if (layoutManager != null && layoutManager instanceof AttachmentLayout
774 					&& e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
775 				if (e.getWheelRotation() < 0) {
776 					((AttachmentLayout) layoutManager).decreaseCardLayout(-e
777 							.getWheelRotation());
778 				} else {
779 					((AttachmentLayout) layoutManager).increaseCardLayout(e
780 							.getWheelRotation());
781 				}
782 			}
783 			doLayout();
784 			getParent().doLayout();
785 			getContainer().doLayout();
786 		}
787 	}
788 
789 	@Override
790 	public void mouseClicked(MouseEvent e) {
791 		if (!(getParent() instanceof MZone) && !(getParent() instanceof MCard)) {
792 			return;
793 		}
794 		StackManager.noReplayToken.take();
795 		MZone container = getContainer();
796 		try {
797 			TargetFactory.triggerTargetable = this;
798 			if (ConnectionManager.isConnected()
799 					&& e.getButton() == MouseEvent.BUTTON1) {
800 				// only if left button is pressed
801 				if (!StackManager.actionManager.clickOn(this)) {
802 					// this card is activated and you control it
803 					if (!(StackManager.actionManager.currentAction instanceof WaitingAbility)) {
804 						// we are not waiting ability
805 						if (container instanceof ExpandableZone) {
806 							((ExpandableZone) container).toggle();
807 						}
808 						return;
809 					}
810 					final List<Ability> abilities = ((WaitingAbility) StackManager.actionManager.currentAction)
811 							.abilitiesOf(this);
812 					final List<Ability> advAbilities = ((WaitingAbility) StackManager.actionManager.currentAction)
813 							.advancedAbilitiesOf(this);
814 
815 					// is there any playable ability with this card ?
816 					if ((abilities == null || abilities.isEmpty())
817 							&& (advAbilities == null || advAbilities.isEmpty())) {
818 						// no playable ability, and this card is not wait for -> toggle
819 						if (container instanceof ExpandableZone) {
820 							((ExpandableZone) container).toggle();
821 						}
822 						return;
823 					}
824 
825 					// a choice has to be made
826 					if (advAbilities != null && !advAbilities.isEmpty()
827 							|| abilities != null && abilities.size() > 1) {
828 						/*
829 						 * several abilities for this card, we fill the popup ability menu
830 						 */
831 						TargetFactory.abilitiesMenu.removeAll();
832 						if (abilities != null) {
833 							for (Ability ability : abilities) {
834 								final JMenuItem item = new JMenuItem("<html>"
835 										+ ability.toHtmlString(null) + "</html>");
836 								item.addActionListener(this);
837 								TargetFactory.abilitiesMenu.add(item);
838 							}
839 						}
840 						if (advAbilities != null && !advAbilities.isEmpty()) {
841 							TargetFactory.abilitiesMenu.add(new JSeparator());
842 							for (Ability ability : advAbilities) {
843 								final JMenuItem item = new JMenuItem("<html>"
844 										+ ability.toHtmlString(null) + "</html>", WARNING_PICTURE);
845 								item.addActionListener(this);
846 								TargetFactory.abilitiesMenu.add(item);
847 							}
848 						}
849 						// show the option popup menu
850 						TargetFactory.abilitiesMenu.show(e.getComponent(), e.getX(), e
851 								.getY());
852 						return;
853 					} else if (abilities != null && abilities.size() == 1
854 							&& (advAbilities == null || advAbilities.isEmpty())) {
855 						// only one playable ability, we select it automatically
856 						((UserAbility) abilities.get(0)).mouseClicked(0);
857 					}
858 				} else if (StackManager.idHandedPlayer != 0) {
859 					if (container instanceof ExpandableZone) {
860 						((ExpandableZone) container).toggle();
861 					}
862 				} else {
863 					// Since this click has been handled, the opponent will be informed on
864 					sendClickToOpponent();
865 					StackManager.actionManager.succeedClickOn(this);
866 				}
867 			} else if (ConnectionManager.isConnected()) {
868 				// only if not left button is pressed
869 				CardFactory.contextMenu.removeAll();
870 				TargetFactory.triggerTargetable = this;
871 
872 				// add counter item
873 				CardFactory.countItem.setText(LanguageManager.getString("countcard",
874 						container.toString(), String.valueOf(container.getCardCount())));
875 				CardFactory.contextMenu.add(CardFactory.countItem);
876 
877 				// add refresh picture item
878 				CardFactory.contextMenu.add(CardFactory.reloadPictureItem);
879 
880 				// add expand/gather item
881 				if (container instanceof ExpandableZone) {
882 					if (container == ZoneManager.expandedZone) {
883 						CardFactory.contextMenu.add(CardFactory.gatherItem);
884 					} else {
885 						CardFactory.contextMenu.add(CardFactory.expandItem);
886 					}
887 				}
888 				CardFactory.contextMenu.add(new JSeparator());
889 
890 				// add the "database" item --> activate the tabbed panel
891 				CardFactory.contextMenu.add(CardFactory.databaseCardInfoItem);
892 
893 				// TODO add "java properties" item --> show an inspect popup.
894 				// CardFactory.contextMenu.add(CardFactory.javaDebugItem);
895 
896 				// TODO add "show id" option
897 
898 				// show the option popup menu
899 				CardFactory.contextMenu.show(e.getComponent(), e.getX(), e.getY());
900 			}
901 		} catch (Throwable t) {
902 			t.printStackTrace();
903 		} finally {
904 			StackManager.noReplayToken.release();
905 		}
906 	}
907 
908 	@Override
909 	public void sendClickToOpponent() {
910 		ConnectionManager.send(CoreMessageType.CLICK_CARD, getBytes());
911 	}
912 
913 	/***
914 	 * Return the data array representing this card. This array can be send throw
915 	 * network.
916 	 * 
917 	 * @return bytes representing this card.
918 	 */
919 	public final byte[] getBytes() {
920 		Pair<Integer, Integer> index = controller.zoneManager.getContainer(idZone)
921 				.getRealIndexOf(this);
922 		// get index of this card within it's container
923 		return new byte[] { (byte) (1 - controller.idPlayer), (byte) getIdZone(),
924 				(byte) index.key.intValue(), (byte) index.value.intValue() };
925 	}
926 
927 	/***
928 	 * This method is invoked when opponent has clicked on this object.
929 	 * 
930 	 * @param data
931 	 *          data sent by opponent.
932 	 */
933 	public static void clickOn(byte[] data) {
934 		// reading card information
935 		MCard card = getCard(data);
936 		StackManager.actionManager.clickOn(card);
937 		StackManager.actionManager.succeedClickOn(card);
938 	}
939 
940 	/***
941 	 * Return the component from information read from opponent.
942 	 * 
943 	 * @param data
944 	 *          data sent by opponent.
945 	 * @return the card read from the specified input stream
946 	 */
947 	public static MCard getCard(byte[] data) {
948 		// waiting for card information
949 		int idPlayer = data[0];
950 		int idZone = data[1];
951 		int index = data[2];
952 		int childIndex = data[3];
953 		if (childIndex == 0) {
954 			// has no parent
955 			return StackManager.PLAYERS[idPlayer].zoneManager.getContainer(idZone)
956 					.getCard(index);
957 		}
958 		// is within another card (has parent)
959 		return (MCard) StackManager.PLAYERS[idPlayer].zoneManager.getContainer(
960 				idZone).getCard(index).getComponent(childIndex);
961 	}
962 
963 	@Override
964 	public int getValue(int index) {
965 		if (index == IdTokens.MANA_POOL) {
966 			if (idZone == IdZones.STACK) {
967 				// return the total cost of associated ability
968 				return StackManager.getTotalManaPaid(this);
969 			}
970 			return MToolKit.manaPool(cachedRegisters);
971 		}
972 		if (index == IdTokens.ID) {
973 			return getId();
974 		}
975 
976 		if (index >= IdTokens.FIRST_FREE_CARD_INDEX) {
977 			return registers[index];
978 		}
979 		if (cachedRegisters == null) {
980 			throw new InternalError("modifiedRegisters is null in getValue in card");
981 		}
982 		return cachedRegisters[index];
983 	}
984 
985 	@Override
986 	public int getValueIndirection(int index) {
987 		// first, read registers indirections
988 		if (indirections[index] != null) {
989 			return indirections[index].getValue(registers[index]);
990 		}
991 		return registers[index];
992 	}
993 
994 	/***
995 	 * Add a modifier to this object
996 	 * 
997 	 * @param modifier
998 	 *          the color-modifier to add to this object
999 	 */
1000 	public void addModifier(ColorModifier modifier) {
1001 		if (colorModifier != null) {
1002 			colorModifier = (ColorModifier) colorModifier.addModifier(modifier);
1003 		} else {
1004 			colorModifier = modifier;
1005 		}
1006 	}
1007 
1008 	/***
1009 	 * Add a modifier to this object
1010 	 * 
1011 	 * @param modifier
1012 	 *          the id card modifier to add to this object
1013 	 */
1014 	public void addModifier(IdCardModifier modifier) {
1015 		if (idCardModifier != null) {
1016 			idCardModifier = (IdCardModifier) idCardModifier.addModifier(modifier);
1017 		} else {
1018 			idCardModifier = modifier;
1019 		}
1020 	}
1021 
1022 	/***
1023 	 * Add a modifier to this object
1024 	 * 
1025 	 * @param modifier
1026 	 *          the ability-modifier to add to this object
1027 	 */
1028 	public void addModifier(AbilityModifier modifier) {
1029 		if (abilityModifier != null) {
1030 			abilityModifier = (AbilityModifier) abilityModifier.addModifier(modifier);
1031 		} else {
1032 			abilityModifier = modifier;
1033 		}
1034 	}
1035 
1036 	/***
1037 	 * Add a modifier to this object
1038 	 * 
1039 	 * @param modifier
1040 	 *          the property-modifier to add to this object
1041 	 */
1042 	public void addModifier(PropertyModifier modifier) {
1043 		if (propertyModifier != null) {
1044 			propertyModifier = (PropertyModifier) propertyModifier
1045 					.addModifier(modifier);
1046 		} else {
1047 			propertyModifier = modifier;
1048 		}
1049 	}
1050 
1051 	/***
1052 	 * Add a modifier to this object
1053 	 * 
1054 	 * @param modifier
1055 	 *          the controller-modifier to add to this object
1056 	 */
1057 	public void addModifier(ControllerModifier modifier) {
1058 		if (controllerModifier != null) {
1059 			controllerModifier = (ControllerModifier) controllerModifier
1060 					.addModifier(modifier);
1061 		} else {
1062 			controllerModifier = modifier;
1063 		}
1064 	}
1065 
1066 	/***
1067 	 * Add a modifier to this object
1068 	 * 
1069 	 * @param modifier
1070 	 *          the playable zone-modifier to add to this object
1071 	 */
1072 	public void addModifier(PlayableZoneModifier modifier) {
1073 		if (playableZoneModifier != null) {
1074 			playableZoneModifier = (PlayableZoneModifier) playableZoneModifier
1075 					.addModifier(modifier);
1076 		} else {
1077 			playableZoneModifier = modifier;
1078 		}
1079 	}
1080 
1081 	@Override
1082 	public void removeModifier(RegisterModifier modifier, int index) {
1083 		if (registerModifiers[index] != null) {
1084 			registerModifiers[index] = (RegisterModifier) registerModifiers[index]
1085 					.removeModifier(modifier);
1086 		} else {
1087 			Log.warn("Card moved, unable to remove a null modifier");
1088 		}
1089 
1090 	}
1091 
1092 	@Override
1093 	public void removeModifier(RegisterIndirection indirection, int index) {
1094 		if (indirections[index] != null) {
1095 			indirections[index] = (RegisterIndirection) indirections[index]
1096 					.removeModifier(indirection);
1097 		} else {
1098 			Log.warn("Card moved, unable to remove a null modifier");
1099 		}
1100 	}
1101 
1102 	/***
1103 	 * Remove the specified id card modifier
1104 	 * 
1105 	 * @param modifier
1106 	 *          the id card modifier to be removed from this object
1107 	 */
1108 	public void removeModifier(IdCardModifier modifier) {
1109 		if (modifier != null) {
1110 			idCardModifier = (IdCardModifier) idCardModifier.removeModifier(modifier);
1111 		} else {
1112 			Log.warn("Card moved, unable to remove a null modifier");
1113 		}
1114 	}
1115 
1116 	/***
1117 	 * Remove the specified ability-modifier
1118 	 * 
1119 	 * @param modifier
1120 	 *          the ability-modifier to be removed from this object
1121 	 */
1122 	public void removeModifier(AbilityModifier modifier) {
1123 		if (modifier != null) {
1124 			abilityModifier = (AbilityModifier) abilityModifier
1125 					.removeModifier(modifier);
1126 		} else {
1127 			Log.warn("Card moved, unable to remove a null modifier");
1128 		}
1129 	}
1130 
1131 	/***
1132 	 * Remove the specified controller-modifier
1133 	 * 
1134 	 * @param modifier
1135 	 *          the controller-modifier to be removed from this object
1136 	 */
1137 	public void removeModifier(ControllerModifier modifier) {
1138 		if (modifier != null) {
1139 			controllerModifier = (ControllerModifier) controllerModifier
1140 					.removeModifier(modifier);
1141 		} else {
1142 			Log.warn("Card moved, unable to remove a null modifier");
1143 		}
1144 	}
1145 
1146 	/***
1147 	 * Remove the specified property-modifier
1148 	 * 
1149 	 * @param modifier
1150 	 *          the property-modifier to be removed from this object
1151 	 */
1152 	public void removeModifier(PropertyModifier modifier) {
1153 		if (modifier != null) {
1154 			propertyModifier = (PropertyModifier) propertyModifier
1155 					.removeModifier(modifier);
1156 		} else {
1157 			Log.warn("Card moved, unable to remove a null modifier");
1158 		}
1159 	}
1160 
1161 	/***
1162 	 * Remove the specified playable zone-modifier
1163 	 * 
1164 	 * @param modifier
1165 	 *          the playable zone-modifier to be removed from this object
1166 	 */
1167 	public void removeModifier(PlayableZoneModifier modifier) {
1168 		if (modifier != null) {
1169 			playableZoneModifier = (PlayableZoneModifier) playableZoneModifier
1170 					.removeModifier(modifier);
1171 		} else {
1172 			Log.warn("Card moved, unable to remove a null modifier");
1173 		}
1174 	}
1175 
1176 	/***
1177 	 * Remove the specified color-modifier.
1178 	 * 
1179 	 * @param modifier
1180 	 *          the color-modifier to be removed from this object
1181 	 */
1182 	public void removeModifier(ColorModifier modifier) {
1183 		if (modifier != null) {
1184 			colorModifier = (ColorModifier) colorModifier.removeModifier(modifier);
1185 		} else {
1186 			Log.warn("Card moved, unable to remove a null modifier");
1187 		}
1188 	}
1189 
1190 	/***
1191 	 * Indicated if this card is attached or not.
1192 	 * 
1193 	 * @return true value if this card is attached.
1194 	 */
1195 	public boolean isAttached() {
1196 		return getParent() != null && getParent() instanceof MCard;
1197 	}
1198 
1199 	@Override
1200 	public String getTooltipString() {
1201 		return ui.getTooltipString();
1202 	}
1203 
1204 	/***
1205 	 * Refresh the id card of this card, and raise an event if the value has been
1206 	 * changed.
1207 	 */
1208 	public void refreshIdCard() {
1209 		int modifiedIdCard = idCardModifier != null ? idCardModifier
1210 				.getIdCard(getCardModel().getIdCard()) : getCardModel().getIdCard();
1211 		if (this.cachedIdCard != modifiedIdCard) {
1212 			// the id card of this card has changed
1213 			final int modifiedIdCardTmp = this.cachedIdCard;
1214 			this.cachedIdCard = modifiedIdCard;
1215 			ModifiedIdCard.dispatchEvent(this, modifiedIdCard | modifiedIdCardTmp);
1216 			ui.resetCachedData();
1217 		}
1218 	}
1219 
1220 	/***
1221 	 * Refresh the granted abilities of this card. No event raised.
1222 	 */
1223 	public void refreshAbilities() {
1224 		if (abilityModifier == null) {
1225 			registerAbilities(idZone);
1226 		} else {
1227 			final List<Ability> toRegister = new ArrayList<Ability>(cachedAbilities
1228 					.size() + 2);
1229 
1230 			for (Ability ability : cachedAbilities) {
1231 				if (ability.eventComing().isWellPlaced(idZone)) {
1232 					toRegister.add(ability);
1233 				}
1234 			}
1235 
1236 			abilityModifier.calculateDeltaAbilities(toRegister);
1237 
1238 			// First, unregister the disabled abilities
1239 			for (Ability ability : cachedAbilities) {
1240 				if (!toRegister.contains(ability)) {
1241 					// this ability has to been unregistered
1242 					ability.removeFromManager();
1243 				}
1244 			}
1245 
1246 			// Then, register the granted abilities
1247 			for (Ability ability : toRegister) {
1248 				ability.registerToManager();
1249 			}
1250 		}
1251 	}
1252 
1253 	/***
1254 	 * Refresh the colors of this card, and raise an event if the value has been
1255 	 * changed.
1256 	 */
1257 	public void refreshIdColor() {
1258 		int modifiedIdColor = colorModifier != null ? colorModifier
1259 				.getIdColor(getCardModel().getIdColor()) : getCardModel().getIdColor();
1260 		if (this.cachedIdColor != modifiedIdColor) {
1261 			// the color of this card has changed
1262 			int modifiedIdColorTmp = this.cachedIdColor;
1263 			this.cachedIdColor = modifiedIdColor;
1264 			ModifiedIdColor.dispatchEvent(this, modifiedIdColor | modifiedIdColorTmp);
1265 			this.cachedIdColor = modifiedIdColor;
1266 			ui.resetCachedData();
1267 		}
1268 	}
1269 
1270 	/***
1271 	 * Refresh the properties of this card, and raise an event if the value has
1272 	 * been changed. NOTE : there is no cache for properties.
1273 	 * 
1274 	 * @param property
1275 	 *          the property to refresh.
1276 	 */
1277 	public void refreshProperties(int property) {
1278 		final CardModel cardModel = getCardModel();
1279 		boolean raised = false;
1280 		if (property == IdConst.ALL) {
1281 			// All properties have to be refreshed
1282 			java.util.Set<Integer> oldProperties = new HashSet<Integer>(
1283 					cachedProperties);
1284 			cachedProperties.clear();
1285 			for (int initProperty : cardModel.getProperties()) {
1286 				cachedProperties.add(initProperty);
1287 			}
1288 			if (propertyModifier != null) {
1289 				propertyModifier.fillProperties(cachedProperties);
1290 			}
1291 			for (Integer cacheProperty : cachedProperties) {
1292 				if (!oldProperties.contains(cacheProperty)) {
1293 					ModifiedProperty.dispatchEvent(this, cacheProperty);
1294 					raised = true;
1295 				}
1296 			}
1297 			for (Integer oldProperty : oldProperties) {
1298 				if (!cachedProperties.contains(oldProperty)) {
1299 					ModifiedProperty.dispatchEvent(this, oldProperty);
1300 					raised = true;
1301 				}
1302 			}
1303 		} else {
1304 			final boolean oldFound = cachedProperties.contains(property);
1305 			int[] properties = cardModel.getProperties();
1306 			boolean found = properties != null
1307 					&& Arrays.binarySearch(properties, property) >= 0;
1308 			if (propertyModifier != null) {
1309 				found = propertyModifier.hasProperty(property, found);
1310 			}
1311 			if (oldFound != found) {
1312 				if (found) {
1313 					cachedProperties.add(property);
1314 				} else {
1315 					cachedProperties.remove(property);
1316 				}
1317 				ModifiedProperty.dispatchEvent(this, property);
1318 				raised = true;
1319 			}
1320 		}
1321 		if (raised) {
1322 			ui.resetCachedData();
1323 			repaint();
1324 		}
1325 	}
1326 
1327 	/***
1328 	 * Refresh the registers of this card, and raise an event if the value has
1329 	 * been changed.
1330 	 * 
1331 	 * @param index
1332 	 *          is the register index to refresh
1333 	 */
1334 	public void refreshRegisters(int index) {
1335 		if (index == IdConst.ALL) {
1336 			for (int i = 0; i < registerModifiers.length; i++) {
1337 				refreshRegisters(i);
1338 			}
1339 		} else {
1340 			final int modifiedRegister;
1341 			if (registerModifiers[index] != null)
1342 				modifiedRegister = registerModifiers[index]
1343 						.getValue(getValueIndirection(index));
1344 			else {
1345 				modifiedRegister = getValueIndirection(index);
1346 			}
1347 			if (this.cachedRegisters[index] != modifiedRegister) {
1348 				// the register of this card has changed. Negative values are not
1349 				// permitted
1350 				cachedRegisters[index] = modifiedRegister < 0 ? 0 : modifiedRegister;
1351 				ModifiedRegister.dispatchEvent(this, this, IdTokens.CARD, index, Set
1352 						.getInstance(), modifiedRegister);
1353 				ui.resetCachedData();
1354 				repaint();
1355 			}
1356 		}
1357 	}
1358 
1359 	/***
1360 	 * Refresh the controller of this card, and raise an event if the value has
1361 	 * been changed.
1362 	 */
1363 	public void refreshController() {
1364 		final Player controller = controllerModifier != null ? controllerModifier
1365 				.getPlayer(originalController) : originalController;
1366 		if (this.controller != controller) {
1367 			// the controller of this card has changed
1368 			MoveCard.moveCard(this, controller, getIdZone(idZone, tapped), null, 0,
1369 					null, false);
1370 		}
1371 		ui.resetCachedData();
1372 	}
1373 
1374 	@Override
1375 	public int countAllCardsOf(Test test, Ability ability, boolean canBePreempted) {
1376 		int result = 0;
1377 		if (canBePreempted) {
1378 			result = test.test(ability, this) ? 1 : 0;
1379 		} else {
1380 			result = test.testPreemption(ability, this) ? 1 : 0;
1381 		}
1382 		for (int j = getComponentCount(); j-- > 1;) {
1383 			if (getComponent(j) instanceof MCard) {
1384 				if (canBePreempted) {
1385 					if (test.test(ability, (MCard) getComponent(j))) {
1386 						result++;
1387 					}
1388 				} else if (!canBePreempted
1389 						&& test.testPreemption(ability, (MCard) getComponent(j))) {
1390 					result++;
1391 				}
1392 			}
1393 		}
1394 		return result;
1395 	}
1396 
1397 	@Override
1398 	public void checkAllCardsOf(Test test, List<Target> list, Ability ability) {
1399 		if (test.test(ability, this)) {
1400 			list.add(this);
1401 		}
1402 		for (int j = getComponentCount(); j-- > 1;) {
1403 			if (getComponent(j) instanceof MCard
1404 					&& test.test(ability, (MCard) getComponent(j))) {
1405 				list.add((MCard) getComponent(j));
1406 			}
1407 		}
1408 	}
1409 
1410 	/***
1411 	 * Indicates this card can be played from a specified zone.
1412 	 * 
1413 	 * @param supposedZone
1414 	 *          the zone where the this card would be played from.
1415 	 * @param idZone
1416 	 *          the zone where this card can be played from.
1417 	 * @return true if this card is playable from the supposed zone
1418 	 */
1419 	public boolean playableZone(int supposedZone, int idZone) {
1420 		if (playableZoneModifier != null) {
1421 			return playableZoneModifier.playableIn(idZone, idZone == supposedZone);
1422 		}
1423 		return supposedZone == idZone;
1424 	}
1425 
1426 	/***
1427 	 * [un]Register ActivatedAbilities depending on the current zone of this card
1428 	 */
1429 	public void updateAbilities() {
1430 		for (Ability ability : cachedAbilities) {
1431 			if (ability instanceof ActivatedAbility) {
1432 				ability.removeFromManager();
1433 				for (int j = IdZones.STACK; j-- > 0;) {
1434 					if (ability.eventComing().isWellPlaced(j)) {
1435 						ability.registerToManager();
1436 						break;
1437 					}
1438 				}
1439 			}
1440 		}
1441 	}
1442 
1443 	/***
1444 	 * Set to the register of this card a value to a specified index. The given
1445 	 * operation is uesed to apply operation on old and the given value. To set
1446 	 * the given value as the new one, use the "set" operation.
1447 	 * 
1448 	 * @param index
1449 	 *          is the index of register to modify
1450 	 * @param operation
1451 	 *          the operation to use
1452 	 * @param rightValue
1453 	 *          is the value to use as right operand for the operation
1454 	 */
1455 	public void setValue(int index, Operation operation, int rightValue) {
1456 		registers[index] = operation.process(registers[index], rightValue);
1457 		if (index == IdCommonToken.DAMAGE) {
1458 			MContextCardCardIntInt context = null;
1459 			if (StackManager.getInstance().getAbilityContext() != null
1460 					&& StackManager.triggered.getAbilityContext() instanceof MContextCardCardIntInt
1461 					&& operation instanceof Add) {
1462 				context = (MContextCardCardIntInt) StackManager.triggered
1463 						.getAbilityContext();
1464 				if (context.getValue() != rightValue) {
1465 					throw new InternalError("wrong damage value regValue=" + rightValue
1466 							+ ", context.int=" + context.getValue());
1467 				}
1468 			}
1469 			if (operation instanceof Add) {
1470 				Log.debug(new StringBuilder("Add ").append(rightValue).append(
1471 						" damage to card ").append(getCardName()).append("@").append(
1472 						hashCode()).append(":").append(rightValue));
1473 				Damage damage = new Damage(context == null ? SystemCard.instance
1474 						: context.getCard2(), rightValue, context == null ? 0 : context
1475 						.getValue2());
1476 				damage.tap(tapped);
1477 				add(damage);
1478 			} else {
1479 				// in any other cases, we remove all damages
1480 				clearDamages();
1481 			}
1482 			ui.updateLayout();
1483 		}
1484 		if (index < IdTokens.FIRST_FREE_CARD_INDEX) {
1485 			// we need to refresh the cache of this index
1486 			int modifiedRegister = registerModifiers[index] != null ? registerModifiers[index]
1487 					.getValue(getValueIndirection(index))
1488 					: getValueIndirection(index);
1489 			if (this.cachedRegisters[index] != modifiedRegister) {
1490 				// the register of this card has changed.
1491 				// Negative values are not
1492 				cachedRegisters[index] = modifiedRegister < 0 ? 0 : modifiedRegister;
1493 				ui.resetCachedData();
1494 				// no generated event, this managed in the ModifyRegister action
1495 			}
1496 		} else {
1497 			repaint();
1498 		}
1499 	}
1500 
1501 	/***
1502 	 * Set a new zone for this card.
1503 	 * 
1504 	 * @param idZone
1505 	 *          the new zone.
1506 	 */
1507 	public void setIdZone(int idZone) {
1508 		this.idZone = idZone;
1509 	}
1510 
1511 	@Override
1512 	public void addTimestampReference() {
1513 		timestampReferences++;
1514 	}
1515 
1516 	@Override
1517 	public void decrementTimestampReference(int timestamp) {
1518 		if (this.timeStamp == timestamp) {
1519 			timestampReferences--;
1520 		} else if (lastKnownInstances.get(timestamp) != null
1521 				&& lastKnownInstances.get(timestamp).removeTimestamp(timestamp)) {
1522 			/*
1523 			 * There is no more reference to this timestamp, so we need no longer the
1524 			 * saved information about this card.
1525 			 */
1526 			lastKnownInstances.remove(timestamp);
1527 		}
1528 	}
1529 
1530 	/***
1531 	 * Return all properties of this card
1532 	 * 
1533 	 * @return all properties of this card
1534 	 */
1535 	public java.util.Set<Integer> getProperties() {
1536 		return cachedProperties;
1537 	}
1538 
1539 	@Override
1540 	public Target getLastKnownTargetable(int timeStamp) {
1541 		if (timeStamp == this.timeStamp) {
1542 			assert timestampReferences > 0;
1543 			return this;
1544 		}
1545 		if (lastKnownInstances.get(timeStamp) == null) {
1546 			return this;
1547 		}
1548 		return lastKnownInstances.get(timeStamp).createLastKnownCard();
1549 	}
1550 
1551 	@Override
1552 	public int getTimestamp() {
1553 		return timeStamp;
1554 	}
1555 
1556 	/***
1557 	 * Return occurrences number of the given object with the given name attached
1558 	 * to this card.
1559 	 * 
1560 	 * @param objectName
1561 	 *          the object's name to find within the register modifiers chain.
1562 	 * @param objectTest
1563 	 *          The test applied on specific modifier to be removed.
1564 	 * @return occurrences number of the given object with the given name attached
1565 	 *         to this card.
1566 	 */
1567 	public int getNbObjects(String objectName, Test objectTest) {
1568 		return ObjectFactory.getObjectModifierModel(objectName).getNbObject(this,
1569 				objectTest);
1570 	}
1571 
1572 	@Override
1573 	public void clearDamages() {
1574 		for (int i = getComponentCount(); i-- > 0;) {
1575 			if (getComponent(i) instanceof Damage) {
1576 				remove(getComponent(i));
1577 			}
1578 			registers[IdCommonToken.DAMAGE] = 0;
1579 			cachedRegisters[IdCommonToken.DAMAGE] = 0;
1580 		}
1581 	}
1582 
1583 	/***
1584 	 * The database configuration of this card
1585 	 */
1586 	private DatabaseCard originalDatabase;
1587 
1588 	/***
1589 	 * The modified id card.
1590 	 */
1591 	public int cachedIdCard;
1592 
1593 	/***