1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.sf.firemox.action;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.util.List;
24
25 import net.sf.firemox.action.context.ActionContextWrapper;
26 import net.sf.firemox.action.context.ManaCost;
27 import net.sf.firemox.action.handler.ChosenAction;
28 import net.sf.firemox.action.handler.InitAction;
29 import net.sf.firemox.action.handler.RollBackAction;
30 import net.sf.firemox.action.listener.WaitingAbility;
31 import net.sf.firemox.action.listener.WaitingMana;
32 import net.sf.firemox.clickable.ability.Ability;
33 import net.sf.firemox.clickable.mana.Mana;
34 import net.sf.firemox.clickable.target.card.MCard;
35 import net.sf.firemox.clickable.target.player.Player;
36 import net.sf.firemox.event.context.ContextEventListener;
37 import net.sf.firemox.expression.Expression;
38 import net.sf.firemox.expression.ExpressionFactory;
39 import net.sf.firemox.expression.RegisterAccess;
40 import net.sf.firemox.network.ConnectionManager;
41 import net.sf.firemox.network.message.CoreMessageType;
42 import net.sf.firemox.stack.StackManager;
43 import net.sf.firemox.test.TestOn;
44 import net.sf.firemox.token.IdCardColors;
45 import net.sf.firemox.token.IdCommonToken;
46 import net.sf.firemox.token.IdConst;
47 import net.sf.firemox.token.IdTokens;
48 import net.sf.firemox.token.MCommonVars;
49 import net.sf.firemox.tools.Log;
50 import net.sf.firemox.tools.MToolKit;
51 import net.sf.firemox.ui.MagicUIComponents;
52 import net.sf.firemox.ui.i18n.LanguageManager;
53 import net.sf.firemox.zone.MZone;
54
55 /***
56 * Used to pay mana, remove directly mana from the mana pool <br>
57 *
58 * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
59 * @since 0.54
60 * @since 0.70 'auto use colored mana as colorless mana' AI has been upgraded to
61 * manage case where only one colored mana can be used as colorless mana
62 * @since 0.71 'auto use colored mana as colorless mana' AI has been upgraded to
63 * prevent the opponent to be informed you are using this option.
64 * @since 0.85 Mana pay can be aborted.
65 * @see net.sf.firemox.action.Actiontype#PAY_MANA
66 */
67 public class PayMana extends UserAction implements ChosenAction, InitAction,
68 WaitingMana, WaitingAbility, RollBackAction {
69
70 /***
71 * If true, mana operation are nor ignored.
72 */
73 public static boolean useMana;
74
75 /***
76 * The maximum number of mana symbols displayed in context menu.
77 */
78 public static int thresholdColored;
79
80 /***
81 * This method is invoked when opponent has clicked on manas.
82 *
83 * @param data
84 * data sent by opponent.
85 */
86 public static void clickOn(byte[] data) {
87
88 if (!(StackManager.actionManager.currentAction instanceof PayMana)) {
89 Log.fatal("Current action is not 'pay mana' but '"
90 + StackManager.actionManager.currentAction.getClass().getName());
91 }
92
93 final int color = data[0];
94 final PayMana action = (PayMana) StackManager.actionManager.currentAction;
95 StackManager.actionManager.succeedClickOn(action.controller.getPlayer(
96 StackManager.currentAbility, null).mana.manaButtons[color]);
97 }
98
99 /***
100 * Return Html string corresponding to the given mana pool. Colors with no
101 * mana in the mana pool are ignored. If a color appear more than
102 * THRESHOLD_COLORED, the <code>{color} x {amount}</code> will be used. If
103 * the given code is empty the <code>{0}</code> value will be returned.
104 *
105 * @param manaPool
106 * the amount of mana to pay
107 * @return Html string corresponding to the given mana pool.
108 */
109 public static String toHtmlString(int[] manaPool) {
110 String res = null;
111
112 if (manaPool[0] != 0) {
113 res = MToolKit.getHtmlMana(0, manaPool[0]);
114 }
115
116 for (int j = IdCommonToken.COLOR_NAMES.length; j-- > 1;) {
117 if (manaPool[j] != 0) {
118 if (res == null) {
119 res = "";
120 }
121 if (manaPool[j] > PayMana.thresholdColored) {
122 res += MToolKit.getHtmlMana(j, 1) + "x" + manaPool[j];
123 } else {
124 res += MToolKit.getHtmlMana(j, manaPool[j]);
125 }
126 }
127 }
128 if (res == null) {
129 return " " + MToolKit.getHtmlMana(0, 0);
130 }
131 return res;
132 }
133
134 /***
135 * The test manager
136 */
137 private TestOn on;
138
139 /***
140 * The complex expression to use for the right value. Is null if the IdToken
141 * number is not a complex expression.
142 */
143 private Expression[] codeExpr = null;
144
145 /***
146 * The player paying this mana
147 */
148 public TestOn controller;
149
150 /***
151 * Create an instance of PayMana by reading a file Offset's file must pointing
152 * on the first byte of this action <br>
153 * <ul>
154 * Structure of InputStream : Data[size]
155 * <li>the player paying this mana [TestOn]</li>
156 * <li>mode IdTokens#MANA_POOL[2]
157 * <li>[on [Expression]]</li>
158 * <li>COLORLESS [Expression]</li>
159 * <li>BLACK [Expression]</li>
160 * <li>BLUE [Expression]</li>
161 * <li>GREEN [Expression]</li>
162 * <li>RED [Expression]</li>
163 * <li>WHITE [Expression]</li>
164 * </ul>
165 *
166 * @param inputFile
167 * file containing this action
168 * @throws IOException
169 * if error occurred during the reading process from the specified
170 * input stream
171 */
172 PayMana(InputStream inputFile) throws IOException {
173 super(inputFile);
174 controller = TestOn.deserialize(inputFile);
175 int code0 = MToolKit.readInt16(inputFile);
176 if (code0 == IdTokens.MANA_POOL) {
177 on = TestOn.deserialize(inputFile);
178 } else {
179 codeExpr = new Expression[IdCommonToken.COLOR_NAMES.length];
180 for (int i = 0; i < codeExpr.length; i++) {
181 codeExpr[i] = ExpressionFactory.readNextExpression(inputFile);
182 }
183 }
184 }
185
186 public List<Ability> abilitiesOf(MCard card) {
187 return WaitChosenActionChoice.getInstance().abilitiesOf(card);
188 }
189
190 public List<Ability> advancedAbilitiesOf(MCard card) {
191 return WaitChosenActionChoice.getInstance().advancedAbilitiesOf(card);
192 }
193
194 public boolean choose(ActionContextWrapper actionContext,
195 ContextEventListener context, Ability ability) {
196 if (!useMana) {
197 return true;
198 }
199
200 final Player controller = this.controller.getPlayer(ability, context, null);
201 final ManaCost manaContext = (ManaCost) actionContext.actionContext;
202
203 if (manaContext.isNullRequired()) {
204
205 return true;
206 }
207
208
209 for (int idColor = IdCardColors.CARD_COLOR_NAMES.length; idColor-- > 0;) {
210 if (controller.mana.getMana(idColor, ability) > 0
211 && (manaContext.requiredMana[idColor] > 0 || manaContext.requiredMana[0] > 0)) {
212 controller.mana.manaButtons[idColor].highLight(null);
213
214 } else {
215 controller.mana.manaButtons[idColor].disHighLight();
216 }
217 }
218 controller.mana.repaint();
219
220
221 MagicUIComponents.magicForm.moreInfoLbl.setText("<html>"
222 + LanguageManager.getString("manatopay",
223 toHtmlString(manaContext.manaPaid),
224 toHtmlString(manaContext.requiredMana)));
225 actionContext.refreshText(ability, context);
226
227
228 WaitChosenActionChoice.getInstance().play(controller);
229
230 if (controller.isYou() && MCommonVars.autoMana) {
231
232 int remainingColoredMana = 0;
233 int uniqueColored = -1;
234
235 for (int i = IdCardColors.CARD_COLOR_NAMES.length; i-- > 1;) {
236 if (manaContext.requiredMana[i] > controller.mana.getMana(i, ability)) {
237
238 return false;
239 }
240
241
242 if (remainingColoredMana != 0) {
243 uniqueColored = -1;
244 } else {
245 uniqueColored = i;
246 }
247 remainingColoredMana += controller.mana.getMana(i, ability)
248 - manaContext.requiredMana[i];
249 }
250
251 if (manaContext.requiredMana[0] <= controller.mana.getMana(0, ability)) {
252
253 validateCode(manaContext.requiredMana, controller);
254 } else if (manaContext.requiredMana[0] == controller.mana.getMana(0,
255 ability)
256 + remainingColoredMana) {
257
258 final int[] validCode = new int[IdCardColors.CARD_COLOR_NAMES.length];
259 for (int i = validCode.length; i-- > 0;) {
260 validCode[i] = controller.mana.getMana(i, ability);
261 }
262 validateCode(validCode, controller);
263 } else if (manaContext.requiredMana[0] < controller.mana.getMana(0)
264 + remainingColoredMana) {
265
266 if (uniqueColored != -1) {
267
268 final int[] validCode = new int[6];
269 validCode[0] = controller.mana.getMana(0, ability);
270 for (int i = validCode.length; i-- > 1;) {
271 validCode[i] = manaContext.requiredMana[i];
272 }
273 validCode[uniqueColored] += manaContext.requiredMana[0]
274 - validCode[0];
275 validateCode(validCode, controller);
276 } else {
277
278 final int[] coloredHand = new int[IdCardColors.CARD_COLOR_NAMES.length];
279 final MZone hand = controller.zoneManager.hand;
280
281 for (int i = hand.getComponentCount(); i-- > 0;) {
282 final int[] colors = hand.getCard(i).cachedRegisters;
283 for (int color = coloredHand.length; color-- > 1;) {
284 if (colors[color] > coloredHand[color - 1]) {
285 coloredHand[color] = colors[color];
286 }
287 }
288 }
289
290 int colorlessToPay = manaContext.requiredMana[0]
291 - controller.mana.getMana(0, ability);
292 int totalColored = 0;
293 final int[] manaPaid = new int[IdCardColors.CARD_COLOR_NAMES.length];
294 for (int color = manaPaid.length; color-- > 1;) {
295 manaPaid[color] = manaContext.requiredMana[color];
296 totalColored = 1 + coloredHand[color];
297 }
298 manaPaid[0] = controller.mana.getMana(0, ability);
299 final int ratioRemove = totalColored < colorlessToPay ? 1
300 : totalColored / colorlessToPay;
301
302 while (colorlessToPay-- > 0) {
303 int miniColor = -1;
304 int mini = 0;
305 for (int color = manaPaid.length; color-- > 1;) {
306 if ((miniColor == -1 || mini > coloredHand[color])
307 && controller.mana.getMana(color, ability) - manaPaid[color] > 0) {
308
309 miniColor = color;
310 mini = coloredHand[color];
311 }
312 }
313 if (miniColor == -1) {
314 throw new InternalError("couldn't find the color to use");
315 }
316 coloredHand[miniColor] -= ratioRemove;
317 manaPaid[miniColor]++;
318 }
319 validateCode(manaPaid, controller);
320 }
321 }
322 return false;
323 }
324
325 return false;
326 }
327
328 public boolean clickOn(Ability ability) {
329 return WaitChosenActionChoice.getInstance().clickOn(ability);
330 }
331
332 public boolean clickOn(Mana mana) {
333 final Player controller = this.controller.getPlayer(
334 StackManager.currentAbility, null);
335 final ManaCost manaContext = (ManaCost) StackManager.actionManager
336 .getActionContext().actionContext;
337
338 if (controller.isYou() && mana.getMana() > 0) {
339 if (mana.color == 0) {
340 return manaContext.requiredMana[0] > 0;
341 }
342 return manaContext.requiredMana[mana.color] > 0
343 || manaContext.requiredMana[0] > 0;
344 }
345 return false;
346 }
347
348 /***
349 * No generated event. Unset this action as current one.
350 *
351 * @param actionContext
352 * the context containing data saved by this action during the
353 * 'choose" process.
354 * @param ability
355 * is the ability owning this test. The card component of this
356 * ability should correspond to the card owning this test too.
357 * @param context
358 * is the context attached to this action.
359 */
360 public void disactivate(ActionContextWrapper actionContext,
361 ContextEventListener context, Ability ability) {
362
363 controller.getPlayer(ability, context, null).mana.disHighLight();
364 }
365
366 /***
367 * Called when this action is finished (aborted or completed). No stack
368 * operation should be done here.<br>
369 * For this action, all mana are dishilighted.
370 */
371 public void finished() {
372 final Player controller = this.controller.getPlayer(
373 StackManager.currentAbility, null);
374 for (Mana mana : controller.mana.manaButtons) {
375 mana.disHighLight();
376 }
377 WaitChosenActionChoice.getInstance().finished();
378 }
379
380 private int[] getCode(Ability ability, ContextEventListener context) {
381 if (on != null) {
382 return on.getCard(ability, null).cachedRegisters;
383 }
384
385 final int[] code = new int[codeExpr.length];
386 for (int i = 6; i-- > 0;) {
387 final Expression expr = codeExpr[i];
388 if (expr instanceof RegisterAccess && ((RegisterAccess) expr).isXvalue()) {
389 code[i] = -1;
390 } else {
391 code[i] = codeExpr[i].getValue(ability, null, context);
392 }
393 }
394 return code;
395 }
396
397 @Override
398 public Actiontype getIdAction() {
399 return Actiontype.PAY_MANA;
400 }
401
402 public boolean init(ActionContextWrapper actionContext,
403 ContextEventListener context, Ability ability) {
404
405 if (actionContext.actionContext == null) {
406 actionContext.actionContext = new ManaCost(manaNeeded(ability, context));
407 }
408 return true;
409 }
410
411 /***
412 * Return the amount of mana needed (constant part only) to play this ability
413 * As default, we return an empty number for all manas.
414 *
415 * @param ability
416 * is the ability owning this test. The card component of this
417 * ability should correspond to the card owning this test too.
418 * @param context
419 * the context of playing ability.
420 * @return array of mana needed to play this ability
421 */
422 public int[] manaNeeded(Ability ability, ContextEventListener context) {
423 if (!useMana) {
424 return IdConst.EMPTY_CODE;
425 }
426
427
428 if (on != null) {
429 return on.getCard(ability, null).cachedRegisters;
430 }
431 final int[] realCode = new int[codeExpr.length];
432 for (int i = realCode.length; i-- > 0;) {
433 realCode[i] = codeExpr[i].getValue(ability, null, context);
434 }
435 return realCode;
436 }
437
438 public boolean manualSkip() {
439
440 StackManager.cancel();
441 return false;
442 }
443
444 public boolean replay(ActionContextWrapper actionContext,
445 ContextEventListener context, Ability ability) {
446 assert ((ManaCost) actionContext.actionContext).isNullRequired();
447
448 final int[] manaPaid = ((ManaCost) actionContext.actionContext).manaPaid;
449 final Player controller = this.controller.getPlayer(ability, context, null);
450 for (int idColor = controller.mana.manaButtons.length; idColor-- > 0;) {
451 controller.mana.removeMana(idColor, manaPaid[idColor], ability);
452 }
453 return true;
454 }
455
456 /***
457 * No generated event. Rollback an action.
458 *
459 * @param actionContext
460 * the context containing data saved by this action during the
461 * 'choose" process.
462 * @param ability
463 * is the ability owning this test. The card component of this
464 * ability should correspond to the card owning this test too.
465 * @param context
466 * is the context attached to this action.
467 */
468 public void rollback(ActionContextWrapper actionContext,
469 ContextEventListener context, Ability ability) {
470
471 final Player controller = this.controller.getPlayer(
472 StackManager.currentAbility, context, null);
473 ((ManaCost) actionContext.actionContext).restoreMana(controller.mana);
474 }
475
476 public boolean succeedClickOn(Ability ability) {
477 finished();
478 StackManager.newSpell(ability, ability.isMatching());
479 return true;
480 }
481
482 public boolean succeedClickOn(Mana mana) {
483 finished();
484 final Player controller = this.controller.getPlayer(
485 StackManager.currentAbility, null);
486
487 if (mana.getMana() == 0) {
488 Log.fatal("Player has not this mana : " + mana);
489 }
490
491 final ManaCost manaCost = (ManaCost) StackManager.actionManager
492 .getActionContext().actionContext;
493 if (mana.color == 0) {
494 if (manaCost.requiredMana[mana.color] > 0
495 && controller.mana.getMana(0) > 0) {
496 manaCost.payMana(0, 0, 1, controller.mana);
497 } else {
498 Log.fatal("No colorless mana can be paid this way : " + mana);
499 }
500 } else {
501 if (manaCost.requiredMana[mana.color] > 0) {
502
503 manaCost.payMana(mana.color, mana.color, 1, controller.mana);
504 } else if (manaCost.requiredMana[0] > 0) {
505
506 manaCost.payMana(0, mana.color, 1, controller.mana);
507 } else {
508 Log.fatal("This colored mana is not required : " + mana);
509 }
510 }
511
512
513 if (controller.isYou()) {
514 ConnectionManager.send(CoreMessageType.CLICK_PAY_MANA, (byte) mana.color);
515 }
516
517
518 return choose(StackManager.actionManager.getActionContext(), StackManager
519 .getInstance().getAbilityContext(), StackManager.currentAbility);
520 }
521
522 @Override
523 public String toHtmlString(Ability ability, ContextEventListener context) {
524 return toHtmlString(getCode(ability, context));
525 }
526
527 public String toHtmlString(Ability ability, ContextEventListener context,
528 ActionContextWrapper actionContext) {
529 final ManaCost manaCost = (ManaCost) actionContext.actionContext;
530 for (int color = manaCost.requiredMana.length; color-- > 0;) {
531 if (manaCost.requiredMana[color] > 0) {
532
533 return toHtmlString(manaCost.manaCost) + ", required "
534 + toHtmlString(manaCost.requiredMana);
535 }
536 }
537
538 return toHtmlString(manaCost.manaCost);
539 }
540
541 @Override
542 public String toString(Ability ability) {
543 String res = null;
544
545
546 final int[] code = getCode(ability, null);
547
548 if (code[0] != 0) {
549 res = "" + code[0];
550 }
551
552 for (int j = IdCommonToken.COLOR_NAMES.length; j-- > 1;) {
553 if (code[j] != 0) {
554 if (res == null) {
555 res = "";
556 }
557 res += IdCommonToken.COLOR_NAMES[j] + "x" + code[j] + ",";
558 }
559 }
560 if (res == null) {
561 return MToolKit.getHtmlMana(0, 0);
562 }
563 return res;
564 }
565
566 private void validateCode(int[] valideMana, Player controller) {
567 for (int i = controller.mana.manaButtons.length; i-- > 0;) {
568 if (valideMana[i] > 0) {
569 StackManager.actionManager
570 .succeedClickOn(controller.mana.manaButtons[i]);
571 return;
572 }
573 }
574 }
575 }