View Javadoc

1   /*
2    * MTriggeredCard.java
3    * Created on 14 f�vr. 2004
4    * 
5    *   Firemox is a turn based strategy simulator
6    *   Copyright (C) 2003-2007 Fabrice Daugan
7    *
8    *   This program is free software; you can redistribute it and/or modify it 
9    * under the terms of the GNU General Public License as published by the Free 
10   * Software Foundation; either version 2 of the License, or (at your option) any
11   * later version.
12   *
13   *   This program is distributed in the hope that it will be useful, but WITHOUT 
14   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15   * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
16   * details.
17   *
18   *   You should have received a copy of the GNU General Public License along  
19   * with this program; if not, write to the Free Software Foundation, Inc., 
20   * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21   * 
22   */
23  package net.sf.firemox.clickable.target.card;
24  
25  import java.awt.Color;
26  import java.awt.Dimension;
27  import java.awt.Graphics;
28  import java.awt.Graphics2D;
29  import java.awt.Image;
30  import java.awt.RenderingHints;
31  import java.awt.event.MouseEvent;
32  import java.awt.event.MouseListener;
33  import java.net.MalformedURLException;
34  import java.util.List;
35  
36  import net.sf.firemox.clickable.ability.Ability;
37  import net.sf.firemox.clickable.ability.TriggeredAbility;
38  import net.sf.firemox.clickable.target.Target;
39  import net.sf.firemox.clickable.target.player.Player;
40  import net.sf.firemox.event.context.ContextEventListener;
41  import net.sf.firemox.modifier.RegisterIndirection;
42  import net.sf.firemox.modifier.RegisterModifier;
43  import net.sf.firemox.network.ConnectionManager;
44  import net.sf.firemox.network.message.CoreMessageType;
45  import net.sf.firemox.stack.ActionManager;
46  import net.sf.firemox.stack.StackContext;
47  import net.sf.firemox.stack.StackManager;
48  import net.sf.firemox.stack.TargetHelper;
49  import net.sf.firemox.stack.TargetedList;
50  import net.sf.firemox.test.Test;
51  import net.sf.firemox.token.IdAbilities;
52  import net.sf.firemox.token.IdZones;
53  import net.sf.firemox.token.Visibility;
54  import net.sf.firemox.tools.Log;
55  import net.sf.firemox.tools.MToolKit;
56  import net.sf.firemox.tools.Picture;
57  import net.sf.firemox.ui.i18n.LanguageManager;
58  import net.sf.firemox.ui.wizard.Replacement;
59  import net.sf.firemox.zone.TriggeredBuffer;
60  import net.sf.firemox.zone.ZoneManager;
61  
62  /***
63   * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
64   * @since 0.54
65   * @since 0.86 Ability source is saved.
66   */
67  public class TriggeredCard extends AbstractCard implements MouseListener,
68  		StackContext {
69  
70  	/***
71  	 * @param triggeredAbility
72  	 *          the triggered ability associated to this card
73  	 * @param context
74  	 *          the context of the associated triggered ability
75  	 * @param abilityID
76  	 *          is the ability's Id making this triggered ability to be created.
77  	 */
78  	public TriggeredCard(Ability triggeredAbility, ContextEventListener context,
79  			long abilityID) {
80  		super(triggeredAbility.getCard().database);
81  		this.triggeredAbility = triggeredAbility;
82  		this.context = context;
83  		this.abilityID = abilityID;
84  		setSize(new Dimension(CardFactory.cardWidth, CardFactory.cardHeight));
85  		setPreferredSize(getSize());
86  		addMouseListener(this);
87  		controller = triggeredAbility.getCard().getController();
88  		this.setVisibility(Visibility.PUBLIC);
89  	}
90  
91  	/***
92  	 * Return the target option of the current spell. this target option is owned
93  	 * by the current spell. May be reseted, changed by the spell itself.
94  	 * 
95  	 * @return the targeted list of this context.
96  	 */
97  	public TargetedList getTargetedList() {
98  		return null;
99  	}
100 
101 	/***
102 	 * Return the current context. Null if current ability is not a triggered one.
103 	 * 
104 	 * @return the current context. Null if current ability is not a triggered
105 	 *         one.
106 	 */
107 	public ContextEventListener getAbilityContext() {
108 		return context;
109 	}
110 
111 	/***
112 	 * Return the action manager of this context.
113 	 * 
114 	 * @return the action manager of this context.
115 	 */
116 	public ActionManager getActionManager() {
117 		return null;
118 	}
119 
120 	/***
121 	 * Return the card source of the current capcity/spell in the stack
122 	 * 
123 	 * @return the card source of the current capcity/spell in the stack
124 	 */
125 	public MCard getSourceCard() {
126 		return triggeredAbility.getCard();
127 	}
128 
129 	/***
130 	 * Play this card as a spell.
131 	 * 
132 	 * @return true if this card has been completky played and if the stack can be
133 	 *         resolved after this call.
134 	 */
135 	public boolean newSpell() {
136 		return StackManager.newSpell(this);
137 	}
138 
139 	@Override
140 	public boolean isACopy() {
141 		return false;
142 	}
143 
144 	@Override
145 	public int countAllCardsOf(Test test, Ability ability, boolean canBePreempted) {
146 		if (triggeredAbility.getCard() == SystemCard.instance)
147 			return 0;
148 		if (canBePreempted)
149 			return test.test(ability, this) ? 1 : 0;
150 		return test.testPreemption(ability, this) ? 1 : 0;
151 	}
152 
153 	@Override
154 	public void checkAllCardsOf(Test test, List<Target> list, Ability ability) {
155 		if (triggeredAbility.getCard() != SystemCard.instance
156 				&& test.test(ability, this)) {
157 			list.add(this);
158 		}
159 	}
160 
161 	@Override
162 	public final boolean isAbility(int abilityType) {
163 		if (abilityType == IdAbilities.TRIGGERED_ABILITY
164 				|| abilityType == IdAbilities.ANY)
165 			return true;
166 		return false;
167 	}
168 
169 	@Override
170 	public final boolean isSpell() {
171 		return false;
172 	}
173 
174 	@Override
175 	public void paint(Graphics g) {
176 		Graphics2D g2D = (Graphics2D) g;
177 		g2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
178 				RenderingHints.VALUE_INTERPOLATION_BICUBIC);
179 
180 		// draw the card picture
181 		g2D.drawImage(scaledImage(), null, null);
182 
183 		if (getParent() == ZoneManager.stack) {
184 			// is in the stack
185 			g2D.setColor(Color.magenta);
186 		} else {
187 			/*
188 			 * draw the highlighted rectangle arround the card if not returned, or if
189 			 * we are in target mode.
190 			 */
191 			// draw the rounded black rectangle
192 			if (isHighLighted) {
193 				g2D.setColor(highLightColor);
194 			} else {
195 				g2D.setColor(Color.BLACK);
196 			}
197 		}
198 		// draw the rounded black rectangle
199 		g2D.draw3DRect(0, 0, CardFactory.cardWidth - 2, CardFactory.cardHeight - 2,
200 				false);
201 
202 		g2D.dispose();
203 	}
204 
205 	@Override
206 	public Image image() {
207 		if (cachedImage == null) {
208 			if (triggeredAbility.getPictureName() == null) {
209 				cachedImage = super.image();
210 			} else {
211 				try {
212 					cachedImage = Picture.loadImage(MToolKit
213 							.getTbsPicture(triggeredAbility.getPictureName() + ".jpg"));
214 				} catch (MalformedURLException e) {
215 					// IGNORING
216 				}
217 			}
218 		}
219 		return cachedImage;
220 	}
221 
222 	@Override
223 	public Image scaledImage() {
224 		if (cachedScaledImage == null) {
225 			cachedScaledImage = Picture.getScaledImage(image());
226 		}
227 		return cachedScaledImage;
228 	}
229 
230 	/***
231 	 * The cached image. Is <code>null</code> while the associated image of this
232 	 * ability is not loaded.
233 	 */
234 	private Image cachedImage;
235 
236 	/***
237 	 * The scaled cached image. Is <code>null</code> while the associated image
238 	 * of this ability is not loaded.
239 	 */
240 	private Image cachedScaledImage;
241 
242 	@Override
243 	public void mouseClicked(MouseEvent e) {
244 		if (Replacement.isRunning) {
245 			Log
246 					.debug("Replacement : considere the mouse click as replacement choice.");
247 			return;
248 		}
249 		StackManager.noReplayToken.take();
250 		try {
251 			if (triggeredAbility.isHidden()) {
252 				throw new InternalError(
253 						"hidden triggered abilities should not be visible in TBZ");
254 			}
255 			Log.debug("mouseClicked triggered ability");
256 			// only if left button is pressed
257 			if (ConnectionManager.isConnected()
258 					&& e.getButton() == MouseEvent.BUTTON1
259 					&& StackManager.idHandedPlayer == 0
260 					&& StackManager.actionManager.clickOn(this)) {
261 				// card has been accepted, we can play it
262 				sendClickToOpponent();
263 				StackManager.actionManager.succeedClickOn(this);
264 			}
265 		} catch (Throwable t) {
266 			t.printStackTrace();
267 		} finally {
268 			StackManager.noReplayToken.release();
269 		}
270 	}
271 
272 	@Override
273 	public void sendClickToOpponent() {
274 		// get position of this card
275 		TriggeredBuffer cont = StackManager.PLAYERS[0].zoneManager.triggeredBuffer;
276 		// get index of this card within it's container
277 		int index = -1;
278 		for (index = cont.getCardCount(); index-- > 0;) {
279 			if (cont.getTriggeredAbility(index) == this) {
280 				// send position of this card
281 				ConnectionManager.send(CoreMessageType.CLICK_TRIGGERED_CARD,
282 						(byte) index);
283 				return;
284 			}
285 		}
286 		Log.fatal("Could not find the triggered card to send");
287 	}
288 
289 	/***
290 	 * This method is invoked when opponent has clicked on this object.
291 	 * 
292 	 * @param data
293 	 *          data sent by opponent.
294 	 */
295 	public static void clickOn(byte[] data) {
296 		// waiting for triggered card information
297 		Log.debug("clickedOn triggeredcard throw input");
298 		TriggeredCard triggered = getTriggeredCard(data);
299 		StackManager.actionManager.clickOn(triggered);
300 		StackManager.actionManager.succeedClickOn(triggered);
301 	}
302 
303 	/***
304 	 * Return the component from information read from opponent.
305 	 * 
306 	 * @param data
307 	 *          data sent by opponent.
308 	 * @return the triggered card read from the input stream
309 	 */
310 	public static TriggeredCard getTriggeredCard(byte[] data) {
311 		// waiting for card information
312 		return StackManager.PLAYERS[1].zoneManager.triggeredBuffer
313 				.getTriggeredAbility(data[0]);
314 	}
315 
316 	@Override
317 	public void moveCard(int newIdPlace, Player newController,
318 			boolean newIsTapped, int idPosition) {
319 		// DEBUG
320 		if (newIdPlace != IdZones.STACK) {
321 			throw new InternalError(
322 					"A wrong destination place has been specified for a triggered:"
323 							+ newIdPlace);
324 		}
325 		StackManager.PLAYERS[newController.idPlayer].zoneManager.triggeredBuffer
326 				.removeTriggered(this);
327 		reverse(false);
328 		setSize(new Dimension(CardFactory.cardWidth, CardFactory.cardHeight));
329 		setPreferredSize(getSize());
330 		ZoneManager.stack.add(this, 0);
331 		// TODO updateTooltipText();
332 	}
333 
334 	@Override
335 	public String getTooltipString() {
336 		StringBuilder toolTip = new StringBuilder(300);
337 
338 		// html header and card name
339 		toolTip.append("<html><b>");
340 		toolTip.append(LanguageManager.getString("card.name"));
341 		toolTip.append(": </b>");
342 		if (isVisibleForYou()) {
343 			// the test added
344 			// for token cards
345 			toolTip.append("??");
346 		} else {
347 			toolTip.append(database.getLocalName());
348 			toolTip.append(CardFactory.ttSource);
349 			toolTip.append(triggeredAbility.getCard().toString());
350 			toolTip.append("<br><b>");
351 			toolTip.append(LanguageManager.getString("triggeredability"));
352 			toolTip.append(": </b>");
353 			toolTip.append(triggeredAbility.toHtmlString(context));
354 
355 			// credits
356 			if (database.getRulesCredit() != null) {
357 				toolTip.append(CardFactory.ttRulesAuthor);
358 				toolTip.append(database.getRulesCredit());
359 			}
360 		}
361 		toolTip.append("</html>");
362 		return toolTip.toString();
363 	}
364 
365 	@Override
366 	public final void mouseEntered(MouseEvent e) {
367 		CardFactory.previewCard.setImage(image(), database.getLocalName());
368 		setToolTipText(getTooltipString());
369 		if (getParent() == ZoneManager.stack) {
370 			// This spell is in the stack
371 			TargetHelper.getInstance().addTargetedBy(StackManager.getContextOf(this));
372 		} else {
373 			// This spell is not yet in the stack, but in the TBZ
374 			TargetHelper.getInstance().addTargetedBy(this);
375 		}
376 	}
377 
378 	@Override
379 	public String toString() {
380 		return ""
381 				+ triggeredAbility
382 				+ ", card="
383 				+ triggeredAbility.getCard()
384 				+ (triggeredAbility.getCard() != null
385 						&& triggeredAbility.getCard() != SystemCard.instance ? "@"
386 						+ Integer.toHexString(triggeredAbility.getCard().hashCode()) : "")
387 				+ (triggeredAbility.isHidden() ? ", hidden=true" : "");
388 	}
389 
390 	@Override
391 	public int getValue(int index) {
392 		throw new InternalError("Triggered Card have no registers");
393 	}
394 
395 	@Override
396 	public void removeModifier(RegisterModifier modifier, int index) {
397 		throw new InternalError("Should not be called");
398 	}
399 
400 	@Override
401 	public void removeModifier(RegisterIndirection indirection, int index) {
402 		throw new InternalError("Should not be called");
403 	}
404 
405 	/***
406 	 * The border will be highligthed to a color identifying it easily as a token
407 	 * component.
408 	 * 
409 	 * @see #STACKABLE_COLOR
410 	 */
411 	public void highlightStackable() {
412 		highLight(STACKABLE_COLOR);
413 	}
414 
415 	@Override
416 	public Target getLastKnownTargetable(int timeStamp) {
417 		throw new InternalError("Should not be called");
418 	}
419 
420 	@Override
421 	public Target getOriginalTargetable() {
422 		throw new InternalError("Should not be called");
423 	}
424 
425 	@Override
426 	public void addTimestampReference() {
427 		throw new InternalError("Should not be called");
428 	}
429 
430 	@Override
431 	public int getTimestamp() {
432 		throw new InternalError("Should not be called");
433 	}
434 
435 	@Override
436 	public void decrementTimestampReference(int timestamp) {
437 		throw new InternalError("Should not be called");
438 	}
439 
440 	public void abortion(AbstractCard card, Ability source) {
441 		throw new InternalError("Should not be called");
442 	}
443 
444 	public Ability getAbortingAbility() {
445 		throw new InternalError("Should not be called");
446 	}
447 
448 	/***
449 	 * Return the delayed card attached to this ability.
450 	 * 
451 	 * @return the delayed card attached to this ability.
452 	 */
453 	public DelayedCard getDelayedCard() {
454 		if (triggeredAbility instanceof TriggeredAbility)
455 			return ((TriggeredAbility) triggeredAbility).getDelayedCard();
456 		return null;
457 	}
458 
459 	/***
460 	 * Triggered ability
461 	 */
462 	public Ability triggeredAbility;
463 
464 	/***
465 	 * Is the ability making this triggered ability to be created.
466 	 */
467 	public long abilityID;
468 
469 	/***
470 	 * Context of triggered ability
471 	 */
472 	protected ContextEventListener context;
473 
474 	/***
475 	 * Height of original card to display
476 	 */
477 	public static int cardHeight;
478 
479 	/***
480 	 * Width of original card to display
481 	 */
482 	public static int cardWidth;
483 
484 	/***
485 	 * The color used to color the stackable component
486 	 */
487 	public static final Color STACKABLE_COLOR = Color.RED;
488 
489 }