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