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