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