1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package net.sf.magicproject.clickable.ability;
22
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.util.ArrayList;
26 import java.util.Collection;
27
28 import net.sf.magicproject.action.Actiontype;
29 import net.sf.magicproject.action.InputChoice;
30 import net.sf.magicproject.action.MAction;
31 import net.sf.magicproject.action.RemoveObject;
32 import net.sf.magicproject.action.Repeat;
33 import net.sf.magicproject.action.target.AbstractTarget;
34 import net.sf.magicproject.action.target.ChoosenTarget;
35 import net.sf.magicproject.clickable.targetable.Targetable;
36 import net.sf.magicproject.clickable.targetable.card.CardCopy;
37 import net.sf.magicproject.clickable.targetable.card.MCard;
38 import net.sf.magicproject.clickable.targetable.card.TriggeredCard;
39 import net.sf.magicproject.clickable.targetable.card.TriggeredCardChoice;
40 import net.sf.magicproject.clickable.targetable.player.Player;
41 import net.sf.magicproject.event.MEventListener;
42 import net.sf.magicproject.event.context.ContextEventListener;
43 import net.sf.magicproject.modifier.Unregisterable;
44 import net.sf.magicproject.stack.ResolveStackHandler;
45 import net.sf.magicproject.stack.StackManager;
46 import net.sf.magicproject.token.IdConst;
47 import net.sf.magicproject.token.IdZones;
48 import net.sf.magicproject.token.TrueFalseAuto;
49 import net.sf.magicproject.tools.MToolKit;
50 import net.sf.magicproject.ui.i18n.LanguageManager;
51
52 import org.apache.commons.lang.StringUtils;
53
54 /***
55 * An ability contains a cost part and an effect part. Each ability is
56 * associated to an event conditionning it's activation.
57 *
58 * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
59 * @since 0.1
60 * @since 0.60 name attribute added
61 * @since 0.86 Object to be removed from a component are checked be enabling an
62 * ability.
63 * @since 0.86 The controller of this ability may be any player.
64 * @since 0.90 linked abilities added.
65 */
66 public abstract class Ability implements ResolveStackHandler, Unregisterable {
67
68 /***
69 * Create an instance of Ability
70 * <ul>
71 * Structure of InputStream : Data[size]
72 * <li>name name [String]</li>
73 * <li>priority [Priority]</li>
74 * <li>optimization [Optimization]</li>
75 * <li>play-as-spell [TrueFalseAuto]</li>
76 * </ul>
77 *
78 * @param inputFile
79 * file containing this ability
80 * @throws IOException
81 * if error occurred during the reading process from the specified
82 * input stream
83 */
84 protected Ability(InputStream inputFile) throws IOException {
85
86 this.name = StringUtils.trimToNull(MToolKit.readString(inputFile).intern());
87
88
89 AbilityFactory.lastInstance = this;
90
91 /***
92 * We read the ability tag. If this ability has 'isHidden' tag, it would be
93 * considered as abstract and no picture would be used to represent it, so
94 * it would be played immediatly without player intervention. If this
95 * ability has this tag and requires player intervention the play would
96 * crash.
97 */
98 priority = Priority.valueOf(inputFile);
99 optimizer = Optimization.valueOf(inputFile);
100 if (isHidden()) {
101 pictureName = null;
102 } else {
103 pictureName = StringUtils.trimToNull(MToolKit.readString(inputFile));
104 }
105 playAsSpell = TrueFalseAuto.deserialize(inputFile);
106 }
107
108 /***
109 * Create an instance of Ability
110 *
111 * @param name
112 * Name of card used to display this ability in a stack
113 * @param optimizer
114 * the optimizer to use.
115 * @param priority
116 * the resolution type.
117 * @param pictureName
118 * the picture name of this ability. If <code>null</code> the card
119 * picture will be used instead.
120 * @param linkedAbilities
121 * the linked abilities. May be null.
122 * @param playAsSpell
123 * play-as-spell.
124 */
125 protected Ability(String name, Optimization optimizer, Priority priority,
126 String pictureName, Collection<Ability> linkedAbilities,
127 TrueFalseAuto playAsSpell) {
128
129 this.name = StringUtils.trimToNull(name);
130 this.optimizer = optimizer;
131 this.priority = priority;
132 this.pictureName = pictureName;
133 this.linkedAbilities = linkedAbilities;
134 this.playAsSpell = playAsSpell;
135 }
136
137 /***
138 * Register this abilty to manager trying to append test on existing ability
139 * with same effects.
140 *
141 * @since 0.82
142 */
143 public void optimizeRegisterToManager() {
144 if (!MEventListener.TRIGGRED_ABILITIES.get(eventComing.getIdEvent())
145 .contains(this)) {
146 MEventListener.TRIGGRED_ABILITIES.get(eventComing.getIdEvent()).add(this);
147 }
148 }
149
150 /***
151 * Return the name of this ability
152 *
153 * @return the new name
154 */
155 public String getName() {
156 return name;
157 }
158
159 /***
160 * Verify in the 'cost' part there is no target action may cause abortion of
161 * this ability.
162 *
163 * @return true if all actions in the 'cost' part can be played.
164 */
165 public boolean checkTargetActions() {
166 for (int i = 0; i < actionList().length; i++) {
167 if (actionList()[i] instanceof ChoosenTarget) {
168 if (i != 0
169 && actionList()[i - 1].getIdAction() == Actiontype.REPEAT_ACTION
170 && !((ChoosenTarget) actionList()[i]).checkTarget(this,
171 ((Repeat) actionList()[i - 1]).getPreemptionTimes(this, this
172 .getCard()))) {
173 return false;
174 }
175 if (!((ChoosenTarget) actionList()[i]).checkTarget(this, 1)) {
176 return false;
177 }
178 } else if (actionList()[i] instanceof InputChoice) {
179 if (!((InputChoice) actionList()[i]).checkTarget(this, i)) {
180 return false;
181 }
182 i += ((InputChoice) actionList()[i]).getSkipHop();
183 }
184 }
185 return true;
186 }
187
188 /***
189 * Checks too the other actions requiring a particular state, such as the
190 * presence of an object.
191 *
192 * @return true if the other actions requiring a particular state, such as the
193 * presence of an object are OK.
194 */
195 public boolean checkObjectActions() {
196 for (int i = 0; i < actionList().length; i++) {
197 if (actionList()[i] instanceof RemoveObject && i != 0) {
198 if (actionList()[i - 1] instanceof Repeat) {
199 if (i > 1
200 && actionList()[i - 2] instanceof AbstractTarget
201 && !((RemoveObject) actionList()[i])
202 .checkObject(this, ((AbstractTarget) actionList()[i - 2])
203 .getAbstractTarget(StackManager.getInstance()
204 .getAbilityContext(), this),
205 ((Repeat) actionList()[i - 1]).getPreemptionTimes(this,
206 null))) {
207 return false;
208 }
209 } else if (actionList()[i - 1] instanceof AbstractTarget
210 && !((RemoveObject) actionList()[i]).checkObject(this,
211 ((AbstractTarget) actionList()[i - 1]).getAbstractTarget(
212 StackManager.getInstance().getAbilityContext(), this), 1)) {
213 return false;
214 }
215 }
216 }
217 return true;
218 }
219
220 /***
221 * Is this ability contains targeting action.
222 *
223 * @return true if this ability contains targeting action.
224 */
225 public boolean recheckTargets() {
226 return StackManager.getInstance().getTargetedList().recheckList(this) > 0;
227 }
228
229 /***
230 * Return card where is this ability. As default, it return null.
231 *
232 * @return true card where is this ability
233 */
234 public abstract MCard getCard();
235
236 /***
237 * Return card where is this ability
238 *
239 * @return true card where is this ability
240 */
241 public Targetable getTargetable() {
242 return getCard();
243 }
244
245 public boolean isAutoResolve() {
246 return priority.isAutoResolve();
247 }
248
249 public boolean isHidden() {
250 return priority.isHidden();
251 }
252
253 /***
254 * Indicates wether this ability is choosen in priority to the others without
255 * this tag.
256 *
257 * @return true if this ability is choosen in priority to the others without
258 * this tag.
259 */
260 public boolean hasHighPriority() {
261 return priority.hasHighPriority();
262 }
263
264 /***
265 * compare the current event to the event activating this ability. If
266 * matching, verify that there enougth mana in the player's mana pool
267 *
268 * @return true if this ability can be played responding the current event
269 */
270 public abstract boolean isMatching();
271
272 /***
273 * Return a card representing this ability.
274 *
275 * @return a card representing this ability
276 */
277 public CardCopy getCardCopy() {
278 return new CardCopy(pictureName, getCard());
279 }
280
281 /***
282 * Return the picture name associated to this ability. Is <code>null</code>
283 * if no picture is used with this ability.
284 *
285 * @return the picture name associated to this ability.
286 */
287 public String getPictureName() {
288 return pictureName;
289 }
290
291 /***
292 * return the amount of mana needed (constant part only) to play this ability
293 *
294 * @param context
295 * the current context of this ability.
296 * @return array of mana needed to play this ability
297 */
298 public int[] manaNeeded(ContextEventListener context) {
299 return IdConst.EMPTY_CODE;
300 }
301
302 /***
303 * Return list of actions to play to cast this ability
304 *
305 * @return list of actions to play to cast this ability
306 */
307 public abstract MAction[] actionList();
308
309 /***
310 * Return list of actions effects of this ability
311 *
312 * @return list of actions effects of this ability
313 */
314 public abstract MAction[] effectList();
315
316 /***
317 * Return matched to activate this ability matched to activate this ability.
318 * As default, return null.
319 *
320 * @return event matched to activate this ability
321 */
322 public MEventListener eventComing() {
323 return eventComing;
324 }
325
326 /***
327 * Set the new event for this ability.
328 *
329 * @param event
330 * the new event for this ability.
331 */
332 public void setEvent(MEventListener event) {
333 this.eventComing = event;
334 }
335
336 /***
337 * return a copy of this ability <br>
338 * TODO remove param container since it is not used in this constructor As
339 * default, return null
340 *
341 * @param container
342 * is not used here
343 * @return copy of this ability
344 */
345 public Ability clone(MCard container) {
346 return null;
347 }
348
349 /***
350 * Return a MTriggeredCard representing this ability. This clone should used
351 * to be added into the triggered buffer zone of player contrilling this
352 * ability.
353 *
354 * @param context
355 * the attached context of this ability
356 * @return a TriggeredCard object built from this and the specified context
357 */
358 public TriggeredCard getTriggeredClone(ContextEventListener context) {
359 return new TriggeredCard(this, context, StackManager.abilityID);
360 }
361
362 /***
363 * Return a MTriggeredCard representing this ability. This clone should used
364 * to be added into the triggered buffer zone of player contrilling this
365 * ability.
366 *
367 * @param context
368 * the attached context of this ability
369 * @return a TriggeredCardChoice object built from this and the specified
370 * context
371 */
372 public TriggeredCardChoice getTriggeredCloneChoice(
373 ContextEventListener context) {
374 return new TriggeredCardChoice(this, context, StackManager.abilityID);
375 }
376
377 public void resolveStack() {
378 if (StackManager.isEmpty()) {
379
380 StackManager.idActivePlayer = StackManager.idCurrentPlayer;
381 StackManager.resolveStack();
382 } else {
383
384 StackManager.activePlayer().waitTriggeredBufferChoice(true);
385 }
386 }
387
388 /***
389 * called when this ability is going to be triggered This method would add
390 * this ability to the triggered zone, or perform another play action
391 *
392 * @param context
393 * the context needed by event activated
394 * @return true if this ability has been added to the triggered buffer zone,
395 * return false otherwise
396 */
397 public boolean triggerIt(ContextEventListener context) {
398 return true;
399 }
400
401 @Override
402 public String toString() {
403 return name == null ? this.getClass().getName() + "-- name = ??"
404 : getName();
405 }
406
407 /***
408 * Return the HTML code representing this ability.
409 *
410 * @param context
411 * the context needed by event activated
412 * @return the HTML code representing this ability.
413 * @since 0.85 Event is displayed
414 */
415 public String toHtmlString(ContextEventListener context) {
416 return toString();
417 }
418
419 /***
420 * Return ability html title. Type of ability and a few other information
421 *
422 * @return ability html title. Type of ability and a few other information
423 */
424 public String getAbilityTitle() {
425 return "<br>" + LanguageManager.getString("cardname") + " : " + getCard();
426 }
427
428 public void removeFromManager() {
429 eventComing().removeFromManager(this);
430 if (linkedAbilities != null) {
431 for (Ability ability : linkedAbilities) {
432 ability.removeFromManager();
433 }
434 }
435 }
436
437 /***
438 * Add this ability to the looked for events. Linked abilities are also
439 * registered.
440 */
441 public void registerToManager() {
442 eventComing().registerToManager(this);
443 if (linkedAbilities != null) {
444 for (Ability ability : linkedAbilities) {
445 ability.registerToManager();
446 }
447 }
448 }
449
450 /***
451 * Return the controller of this ability
452 *
453 * @return the controller of this ability
454 */
455 public Player getController() {
456 return getCard().getController();
457 }
458
459 /***
460 * Add a linked ability.
461 *
462 * @param ability
463 * a linked ability to add.
464 */
465 public final void addLinkedAbility(Ability ability) {
466 if (linkedAbilities == null) {
467 linkedAbilities = new ArrayList<Ability>();
468 }
469 linkedAbilities.add(ability);
470 }
471
472 /***
473 * Compare two abilities the specified ability to the TBZ.
474 *
475 * @param thisContext
476 * the attached context of this ability
477 * @param ability
478 * the ability to add
479 * @param context
480 * the attached context of given ability
481 * @return true if the abilities are functionnaly equal in this context.
482 */
483 public boolean equals(ContextEventListener thisContext, Ability ability,
484 ContextEventListener context) {
485 return context == ability;
486 }
487
488 @Override
489 public int hashCode() {
490 if (name == null)
491 return super.hashCode();
492 return name.hashCode();
493 }
494
495 /***
496 * Is this ability is played as a spell.
497 *
498 * @return <code>true</code> if this ability is played as a spell.
499 */
500 public boolean isPlayAsSpell() {
501 if (playAsSpell == TrueFalseAuto.AUTO)
502 return !getCard().isSameIdZone(IdZones.PLAY);
503
504 return playAsSpell.getValue();
505 }
506
507 /***
508 * Return a String identifying this ability with the name and/or card name.
509 *
510 * @param context
511 * the current context of this ability.
512 * @return a String identifying this ability with the name and/or card name.
513 */
514 public abstract String getLog(ContextEventListener context);
515
516 /***
517 * The event to test to be activated
518 */
519 protected MEventListener eventComing;
520
521 /***
522 * The optimizer to use to manage the 'add' method to the TBZ
523 */
524 public Optimization optimizer;
525
526 /***
527 * The resolution selector choose the right abstract zone where an hidden
528 * ability would be added.
529 */
530 public Priority priority;
531
532 /***
533 * Ability name
534 */
535 protected final String name;
536
537 /***
538 * The ability picture to use. Only if the ability is not hidden.
539 */
540 protected final String pictureName;
541
542 /***
543 * The linked abilities to this ability. Registering/Unregistering this
544 * ability causes the same to these linked abilities.
545 */
546 protected Collection<Ability> linkedAbilities;
547
548 /***
549 * If this ability is played as a copy of card or added to stack
550 */
551 protected final TrueFalseAuto playAsSpell;
552 }