View Javadoc

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