Refactor: private fields and performance tweaks (#9625)

1a. Make `costs`, `manaCosts`, and `manaCostsToPay` private in `AbilityImpl` with access through getters/setters 
1b. fix cost adjuster for imprinted cards affected by the above

2a. Lazy instantiation for rarely used `data` field in `TargetPointerImpl`

3a. Pre-allocate certain array sizes in `Modes` and `CostsImpl`

4a. Make `manaTemplate` private in `BasicManaEffect`, copy when passing outside the class 
4b. Don't copy `manaTemplate` in copy constructor since it doesn't change 
4c. Add comments explaining copy usage for `manaTemplate` 
4d. Remove redundant variable assignment and make fields final 

---------

Co-authored-by: xenohedron <xenohedron@users.noreply.github.com>
This commit is contained in:
Alex Vasile 2023-08-27 17:58:19 -04:00 committed by GitHub
parent 53be4f384e
commit a2162ec3e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 262 additions and 196 deletions

View file

@ -153,7 +153,7 @@ public class SimulatedPlayer2 extends ComputerPlayer {
if (newAbility instanceof AbilityImpl) { if (newAbility instanceof AbilityImpl) {
xMultiplier = ((AbilityImpl) newAbility).handleManaXMultiplier(game, xMultiplier); xMultiplier = ((AbilityImpl) newAbility).handleManaXMultiplier(game, xMultiplier);
} }
newAbility.getManaCostsToPay().add(new ManaCostsImpl<>(new StringBuilder("{").append(xAnnounceValue).append('}').toString())); newAbility.addManaCostsToPay(new ManaCostsImpl<>(new StringBuilder("{").append(xAnnounceValue).append('}').toString()));
newAbility.getManaCostsToPay().setX(xAnnounceValue * xMultiplier, xAnnounceValue * xInstancesCount); newAbility.getManaCostsToPay().setX(xAnnounceValue * xMultiplier, xAnnounceValue * xInstancesCount);
if (varCost != null) { if (varCost != null) {
varCost.setPaid(); varCost.setPaid();

View file

@ -91,7 +91,7 @@ public class MCTSPlayer extends ComputerPlayer {
} }
for (int i = start; i < numAvailable; i++) { for (int i = start; i < numAvailable; i++) {
Ability newAbility = ability.copy(); Ability newAbility = ability.copy();
newAbility.getManaCostsToPay().add(new GenericManaCost(i)); newAbility.addManaCostsToPay(new GenericManaCost(i));
options.add(newAbility); options.add(newAbility);
} }
} }

View file

@ -95,7 +95,7 @@ public class SimulatedPlayerMCTS extends MCTSPlayer {
int amount = getAvailableManaProducers(game).size() - ability.getManaCosts().manaValue(); int amount = getAvailableManaProducers(game).size() - ability.getManaCosts().manaValue();
if (amount > 0) { if (amount > 0) {
ability = ability.copy(); ability = ability.copy();
ability.getManaCostsToPay().add(new GenericManaCost(RandomUtil.nextInt(amount))); ability.addManaCostsToPay(new GenericManaCost(RandomUtil.nextInt(amount)));
} }
} }
// check if ability kills player, if not then it's ok to play // check if ability kills player, if not then it's ok to play

View file

@ -119,7 +119,7 @@ public class SimulatedPlayer extends ComputerPlayer {
} }
for (int i = start; i < numAvailable; i++) { for (int i = start; i < numAvailable; i++) {
Ability newAbility = ability.copy(); Ability newAbility = ability.copy();
newAbility.getManaCostsToPay().add(new GenericManaCost(i)); newAbility.addManaCostsToPay(new GenericManaCost(i));
allActions.add(newAbility); allActions.add(newAbility);
} }
} }

View file

@ -66,7 +66,7 @@ enum BargainingTableAdjuster implements CostAdjuster {
handSize = player.getHand().size(); handSize = player.getHand().size();
} }
} }
ability.getManaCostsToPay().clear(); ability.clearManaCostsToPay();
ability.getManaCostsToPay().add(new GenericManaCost(handSize)); ability.addManaCostsToPay(new GenericManaCost(handSize));
} }
} }

View file

@ -122,8 +122,8 @@ class BruenorBattlehammerCostEffect extends CostModificationEffectImpl {
} }
if (applyReduce) { if (applyReduce) {
abilityToModify.getCosts().clear(); abilityToModify.clearCosts();
abilityToModify.getManaCostsToPay().clear(); abilityToModify.clearManaCostsToPay();
return true; return true;
} }

View file

@ -68,14 +68,17 @@ class CadaverousBloomManaEffect extends BasicManaEffect {
if (player != null) { if (player != null) {
int count = player.getHand().size(); int count = player.getHand().size();
if (count > 0) { if (count > 0) {
Mana newManaTemplate = getManaTemplate(); // returns a copy so only copying once for below checks
Mana mana = new Mana( Mana mana = new Mana(
getManaTemplate().getWhite() * count, getManaTemplate().getBlue() * count, getManaTemplate().getBlack() * count, getManaTemplate().getRed() * count, newManaTemplate.getWhite() * count,
getManaTemplate().getGreen() * count, newManaTemplate.getBlue() * count,
getManaTemplate().getGeneric() * count, newManaTemplate.getBlack() * count,
getManaTemplate().getAny() * count, newManaTemplate.getRed() * count,
getManaTemplate().getColorless() * count newManaTemplate.getGreen() * count,
newManaTemplate.getGeneric() * count,
newManaTemplate.getAny() * count,
newManaTemplate.getColorless() * count
); );
netMana.add(mana); netMana.add(mana);
} }
} }

View file

@ -50,7 +50,7 @@ class DefenseGridCostModificationEffect extends CostModificationEffectImpl {
@Override @Override
public boolean apply(Game game, Ability source, Ability abilityToModify) { public boolean apply(Game game, Ability source, Ability abilityToModify) {
SpellAbility spellAbility = (SpellAbility) abilityToModify; SpellAbility spellAbility = (SpellAbility) abilityToModify;
spellAbility.getManaCostsToPay().add(new GenericManaCost(3)); spellAbility.addManaCostsToPay(new GenericManaCost(3));
return true; return true;
} }

View file

@ -75,8 +75,8 @@ enum EliteArcanistAdjuster implements CostAdjuster {
} }
int cmc = imprintedInstant.getManaValue(); int cmc = imprintedInstant.getManaValue();
if (cmc > 0) { if (cmc > 0) {
ability.getManaCostsToPay().clear(); ability.clearManaCostsToPay();
ability.getManaCostsToPay().add(new GenericManaCost(cmc)); ability.addManaCostsToPay(new GenericManaCost(cmc));
} }
} }
} }

View file

@ -47,7 +47,7 @@ enum FireballAdjuster implements CostAdjuster {
public void adjustCosts(Ability ability, Game game) { public void adjustCosts(Ability ability, Game game) {
int numTargets = ability.getTargets().isEmpty() ? 0 : ability.getTargets().get(0).getTargets().size(); int numTargets = ability.getTargets().isEmpty() ? 0 : ability.getTargets().get(0).getTargets().size();
if (numTargets > 1) { if (numTargets > 1) {
ability.getManaCostsToPay().add(new GenericManaCost(numTargets - 1)); ability.addManaCostsToPay(new GenericManaCost(numTargets - 1));
} }
} }
} }

View file

@ -96,9 +96,9 @@ class CyclingZeroCostEffect extends CostModificationEffectImpl {
if (player == null || !player.chooseUse(outcome, "Pay {0} to cycle this card?", source, game)) { if (player == null || !player.chooseUse(outcome, "Pay {0} to cycle this card?", source, game)) {
return true; return true;
} }
abilityToModify.getManaCostsToPay().clear(); abilityToModify.clearManaCostsToPay();
abilityToModify.getCosts().removeIf(cost -> !CyclingDiscardCost.class.isInstance(cost)); abilityToModify.getCosts().removeIf(cost -> !CyclingDiscardCost.class.isInstance(cost));
abilityToModify.getManaCostsToPay().add(new GenericManaCost(0)); abilityToModify.addManaCostsToPay(new GenericManaCost(0));
return true; return true;
} }

View file

@ -70,9 +70,9 @@ class NewPerspectivesCostModificationEffect extends CostModificationEffectImpl {
if (controller != null) { if (controller != null) {
if (game.inCheckPlayableState() if (game.inCheckPlayableState()
|| controller.chooseUse(Outcome.PlayForFree, "Pay {0} to cycle?", source, game)) { || controller.chooseUse(Outcome.PlayForFree, "Pay {0} to cycle?", source, game)) {
abilityToModify.getCosts().clear(); abilityToModify.clearCosts();
abilityToModify.getManaCostsToPay().clear(); abilityToModify.clearManaCostsToPay();
abilityToModify.getCosts().add(new CyclingDiscardCost()); abilityToModify.addCost(new CyclingDiscardCost());
} }
return true; return true;
} }

View file

@ -49,7 +49,7 @@ enum PhyrexianPurgeCostAdjuster implements CostAdjuster {
public void adjustCosts(Ability ability, Game game) { public void adjustCosts(Ability ability, Game game) {
int numTargets = ability.getTargets().get(0).getTargets().size(); int numTargets = ability.getTargets().get(0).getTargets().size();
if (numTargets > 0) { if (numTargets > 0) {
ability.getCosts().add(new PayLifeCost(numTargets * 3)); ability.addCost(new PayLifeCost(numTargets * 3));
} }
} }
} }

View file

@ -69,7 +69,8 @@ enum PrototypePortalAdjuster implements CostAdjuster {
if (!card.getImprinted().isEmpty()) { if (!card.getImprinted().isEmpty()) {
Card imprinted = game.getCard(card.getImprinted().get(0)); Card imprinted = game.getCard(card.getImprinted().get(0));
if (imprinted != null) { if (imprinted != null) {
ability.getManaCostsToPay().add(0, new GenericManaCost(imprinted.getManaValue())); ability.clearManaCostsToPay();
ability.addManaCostsToPay(new GenericManaCost(imprinted.getManaValue()));
} }
} }
} }

View file

@ -80,8 +80,8 @@ class RanarTheEverWatchfulCostReductionEffect extends CostModificationEffectImpl
@Override @Override
public boolean apply(Game game, Ability source, Ability abilityToModify) { public boolean apply(Game game, Ability source, Ability abilityToModify) {
abilityToModify.getManaCostsToPay().clear(); abilityToModify.clearManaCostsToPay();
abilityToModify.getManaCostsToPay().addAll(new ManaCostsImpl<>("{0}")); abilityToModify.addManaCostsToPay(new ManaCostsImpl<>("{0}"));
return true; return true;
} }

View file

@ -69,8 +69,8 @@ enum SoulFoundryAdjuster implements CostAdjuster {
if (!sourcePermanent.getImprinted().isEmpty()) { if (!sourcePermanent.getImprinted().isEmpty()) {
Card imprinted = game.getCard(sourcePermanent.getImprinted().get(0)); Card imprinted = game.getCard(sourcePermanent.getImprinted().get(0));
if (imprinted != null) { if (imprinted != null) {
ability.getManaCostsToPay().clear(); ability.clearManaCostsToPay();
ability.getManaCostsToPay().add(0, new GenericManaCost(imprinted.getManaValue())); ability.addManaCostsToPay(new GenericManaCost(imprinted.getManaValue()));
} }
} }
} }

View file

@ -4,7 +4,6 @@ import mage.abilities.Ability;
import mage.abilities.common.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.BeginningOfEndStepTriggeredAbility;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.condition.InvertCondition;
import mage.abilities.condition.common.SourceTappedCondition; import mage.abilities.condition.common.SourceTappedCondition;
import mage.abilities.costs.CostAdjuster; import mage.abilities.costs.CostAdjuster;
import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapSourceCost;
@ -78,8 +77,8 @@ enum VoodooDollAdjuster implements CostAdjuster {
Permanent sourcePermanent = game.getPermanent(ability.getSourceId()); Permanent sourcePermanent = game.getPermanent(ability.getSourceId());
if (sourcePermanent != null) { if (sourcePermanent != null) {
int pin = sourcePermanent.getCounters(game).getCount(CounterType.PIN); int pin = sourcePermanent.getCounters(game).getCount(CounterType.PIN);
ability.getManaCostsToPay().clear(); ability.clearManaCostsToPay();
ability.getManaCostsToPay().add(0, new GenericManaCost(pin * 2)); ability.addManaCostsToPay(new GenericManaCost(pin * 2));
} }
} }
} }

View file

@ -60,7 +60,7 @@ class WellOfKnowledgeConditionalActivatedAbility extends ActivatedAbilityImpl {
@Override @Override
public ActivationStatus canActivate(UUID playerId, Game game) { public ActivationStatus canActivate(UUID playerId, Game game) {
if (condition.apply(game, this) if (condition.apply(game, this)
&& costs.canPay(this, this, playerId, game) && getCosts().canPay(this, this, playerId, game)
&& game.isActivePlayer(playerId)) { && game.isActivePlayer(playerId)) {
this.activatorId = playerId; this.activatorId = playerId;
return ActivationStatus.getTrue(this, game); return ActivationStatus.getTrue(this, game);

View file

@ -118,8 +118,8 @@ class WordOfCommandEffect extends OneShotEffect {
&& !targetPlayer.playCard(card, game, false, new ApprovingObject(source, game))) { && !targetPlayer.playCard(card, game, false, new ApprovingObject(source, game))) {
SpellAbility spellAbility = card.getSpellAbility(); SpellAbility spellAbility = card.getSpellAbility();
if (spellAbility != null) { if (spellAbility != null) {
spellAbility.getManaCostsToPay().clear(); spellAbility.clearManaCostsToPay();
spellAbility.getManaCostsToPay().addAll(spellAbility.getManaCosts()); spellAbility.addManaCostsToPay(spellAbility.getManaCosts());
((ManaCostsImpl) spellAbility.getManaCostsToPay()).forceManaRollback(game, manaPool); // force rollback if card was deemed playable ((ManaCostsImpl) spellAbility.getManaCostsToPay()).forceManaRollback(game, manaPool); // force rollback if card was deemed playable
canPlay = checkPlayability(card, targetPlayer, game, source); canPlay = checkPlayability(card, targetPlayer, game, source);
} else { } else {

View file

@ -0,0 +1,32 @@
package org.mage.test.performance;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* These tests are used to benchmark the performance of the copying of the state.
* <p>
* Leave the tests comment out when pushing, uncomment only when testing locally.
*
* @author Alex-Vasile
*/
public class StateCopying extends CardTestPlayerBase {
@Test
@Ignore
public void copyingBattlefield() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 10);
addCard(Zone.BATTLEFIELD, playerA, "Sol Ring", 10);
addCard(Zone.BATTLEFIELD, playerA, "Sapphire Medallion", 10);
setStopAt(1, PhaseStep.END_TURN);
execute();
for (int i = 0; i < 100000; i++) {
currentGame.getBattlefield().reset(currentGame);
}
}
}

View file

@ -102,6 +102,18 @@ public interface Ability extends Controllable, Serializable {
*/ */
void setSourceId(UUID sourceID); void setSourceId(UUID sourceID);
default void clearCosts() {
getCosts().clear();
}
default void clearManaCosts() {
getManaCosts().clear();
}
default void clearManaCostsToPay() {
getManaCostsToPay().clear();
}
/** /**
* Gets all {@link Costs} associated with this ability. * Gets all {@link Costs} associated with this ability.
* *
@ -151,6 +163,8 @@ public interface Ability extends Controllable, Serializable {
*/ */
void addManaCost(ManaCost cost); void addManaCost(ManaCost cost);
void addManaCostsToPay(ManaCost manaCost);
/** /**
* Retrieves the effects that are put into the place by the resolution of * Retrieves the effects that are put into the place by the resolution of
* this ability. * this ability.

View file

@ -56,9 +56,9 @@ public abstract class AbilityImpl implements Ability {
protected AbilityType abilityType; protected AbilityType abilityType;
protected UUID controllerId; protected UUID controllerId;
protected UUID sourceId; protected UUID sourceId;
protected ManaCosts<ManaCost> manaCosts; private ManaCosts<ManaCost> manaCosts;
protected ManaCosts<ManaCost> manaCostsToPay; private ManaCosts<ManaCost> manaCostsToPay;
protected Costs<Cost> costs; private Costs<Cost> costs;
protected Modes modes; // access to it by GetModes only (it can be overridden by some abilities) protected Modes modes; // access to it by GetModes only (it can be overridden by some abilities)
protected Zone zone; protected Zone zone;
protected String name; protected String name;
@ -253,14 +253,14 @@ public abstract class AbilityImpl implements Ability {
if (noMana) { if (noMana) {
if (!this.getManaCostsToPay().getVariableCosts().isEmpty()) { if (!this.getManaCostsToPay().getVariableCosts().isEmpty()) {
int xValue = this.getManaCostsToPay().getX(); int xValue = this.getManaCostsToPay().getX();
this.getManaCostsToPay().clear(); this.clearManaCostsToPay();
VariableManaCost xCosts = new VariableManaCost(VariableCostType.ADDITIONAL); VariableManaCost xCosts = new VariableManaCost(VariableCostType.ADDITIONAL);
// no x events - rules from Unbound Flourishing: // no x events - rules from Unbound Flourishing:
// - Spells with additional costs that include X won't be affected by Unbound Flourishing. X must be in the spell's mana cost. // - Spells with additional costs that include X won't be affected by Unbound Flourishing. X must be in the spell's mana cost.
xCosts.setAmount(xValue, xValue, false); xCosts.setAmount(xValue, xValue, false);
this.getManaCostsToPay().add(xCosts); addManaCostsToPay(xCosts);
} else { } else {
this.getManaCostsToPay().clear(); this.clearManaCostsToPay();
} }
} }
@ -379,7 +379,7 @@ public abstract class AbilityImpl implements Ability {
} }
// this is a hack to prevent mana abilities with mana costs from causing endless loops - pay other costs first // this is a hack to prevent mana abilities with mana costs from causing endless loops - pay other costs first
if (this instanceof ActivatedManaAbilityImpl && !costs.pay(this, game, this, controllerId, noMana, null)) { if (this instanceof ActivatedManaAbilityImpl && !getCosts().pay(this, game, this, controllerId, noMana, null)) {
logger.debug("activate mana ability failed - non mana costs"); logger.debug("activate mana ability failed - non mana costs");
return false; return false;
} }
@ -396,12 +396,12 @@ public abstract class AbilityImpl implements Ability {
} }
//20100716 - 601.2f (noMana is not used here, because mana costs were cleared for this ability before adding additional costs and applying cost modification effects) //20100716 - 601.2f (noMana is not used here, because mana costs were cleared for this ability before adding additional costs and applying cost modification effects)
if (!manaCostsToPay.pay(this, game, this, activatorId, false, null)) { if (!getManaCostsToPay().pay(this, game, this, activatorId, false, null)) {
return false; // cancel during mana payment return false; // cancel during mana payment
} }
//20100716 - 601.2g //20100716 - 601.2g
if (!costs.pay(this, game, this, activatorId, noMana, null)) { if (!getCosts().pay(this, game, this, activatorId, noMana, null)) {
logger.debug("activate failed - non mana costs"); logger.debug("activate failed - non mana costs");
return false; return false;
} }
@ -509,13 +509,11 @@ public abstract class AbilityImpl implements Ability {
*/ */
protected String handleOtherXCosts(Game game, Player controller) { protected String handleOtherXCosts(Game game, Player controller) {
StringBuilder announceString = new StringBuilder(); StringBuilder announceString = new StringBuilder();
for (VariableCost variableCost : this.costs.getVariableCosts()) { for (VariableCost variableCost : this.getCosts().getVariableCosts()) {
if (!(variableCost instanceof VariableManaCost) && !((Cost) variableCost).isPaid()) { if (!(variableCost instanceof VariableManaCost) && !((Cost) variableCost).isPaid()) {
int xValue = variableCost.announceXValue(this, game); int xValue = variableCost.announceXValue(this, game);
Cost fixedCost = variableCost.getFixedCostsFromAnnouncedValue(xValue); Cost fixedCost = variableCost.getFixedCostsFromAnnouncedValue(xValue);
if (fixedCost != null) { addCost(fixedCost);
costs.add(fixedCost);
}
// set the xcosts to paid // set the xcosts to paid
// no x events - rules from Unbound Flourishing: // no x events - rules from Unbound Flourishing:
// - Spells with additional costs that include X won't be affected by Unbound Flourishing. X must be in the spell's mana cost. // - Spells with additional costs that include X won't be affected by Unbound Flourishing. X must be in the spell's mana cost.
@ -534,7 +532,7 @@ public abstract class AbilityImpl implements Ability {
* life or the corresponding colored mana cost for each of those symbols. * life or the corresponding colored mana cost for each of those symbols.
*/ */
private void handlePhyrexianManaCosts(Game game, Player controller) { private void handlePhyrexianManaCosts(Game game, Player controller) {
Iterator<ManaCost> costIterator = manaCostsToPay.iterator(); Iterator<ManaCost> costIterator = getManaCostsToPay().iterator();
while (costIterator.hasNext()) { while (costIterator.hasNext()) {
ManaCost cost = costIterator.next(); ManaCost cost = costIterator.next();
@ -545,8 +543,8 @@ public abstract class AbilityImpl implements Ability {
if (payLifeCost.canPay(this, this, controller.getId(), game) if (payLifeCost.canPay(this, this, controller.getId(), game)
&& controller.chooseUse(Outcome.LoseLife, "Pay 2 life instead of " + cost.getText().replace("/P", "") + '?', this, game)) { && controller.chooseUse(Outcome.LoseLife, "Pay 2 life instead of " + cost.getText().replace("/P", "") + '?', this, game)) {
costIterator.remove(); costIterator.remove();
costs.add(payLifeCost); addCost(payLifeCost);
manaCostsToPay.incrPhyrexianPaid(); getManaCostsToPay().incrPhyrexianPaid();
} }
} }
} }
@ -580,7 +578,7 @@ public abstract class AbilityImpl implements Ability {
// TODO: Handle announcing other variable costs here like: RemoveVariableCountersSourceCost // TODO: Handle announcing other variable costs here like: RemoveVariableCountersSourceCost
VariableManaCost variableManaCost = null; VariableManaCost variableManaCost = null;
for (ManaCost cost : manaCostsToPay) { for (ManaCost cost : getManaCostsToPay()) {
if (cost instanceof VariableManaCost) { if (cost instanceof VariableManaCost) {
if (variableManaCost == null) { if (variableManaCost == null) {
variableManaCost = (VariableManaCost) cost; variableManaCost = (VariableManaCost) cost;
@ -625,8 +623,8 @@ public abstract class AbilityImpl implements Ability {
manaString.append('{').append(manaSymbol).append('}'); manaString.append('{').append(manaSymbol).append('}');
} }
} }
manaCostsToPay.add(new ManaCostsImpl<>(manaString.toString())); addManaCostsToPay(new ManaCostsImpl<>(manaString.toString()));
manaCostsToPay.setX(xValue * xValueMultiplier, amountMana); getManaCostsToPay().setX(xValue * xValueMultiplier, amountMana);
} }
variableManaCost.setPaid(); variableManaCost.setPaid();
} }
@ -788,14 +786,14 @@ public abstract class AbilityImpl implements Ability {
public String getRule(boolean all) { public String getRule(boolean all) {
StringBuilder sbRule = threadLocalBuilder.get(); StringBuilder sbRule = threadLocalBuilder.get();
if (all || this.abilityType != AbilityType.SPELL) { // TODO: Why the override for non-spells? if (all || this.abilityType != AbilityType.SPELL) { // TODO: Why the override for non-spells?
if (!manaCosts.isEmpty()) { if (!getManaCosts().isEmpty()) {
sbRule.append(manaCosts.getText()); sbRule.append(getManaCosts().getText());
} }
if (!costs.isEmpty()) { if (!getCosts().isEmpty()) {
if (sbRule.length() > 0) { if (sbRule.length() > 0) {
sbRule.append(", "); sbRule.append(", ");
} }
sbRule.append(costs.getText()); sbRule.append(getCosts().getText());
} }
if (sbRule.length() > 0) { if (sbRule.length() > 0) {
sbRule.append(": "); sbRule.append(": ");
@ -866,19 +864,46 @@ public abstract class AbilityImpl implements Ability {
} else { } else {
// as single cost // as single cost
if (cost instanceof ManaCost) { if (cost instanceof ManaCost) {
this.addManaCost((ManaCost) cost); addManaCost((ManaCost) cost);
} else { } else {
this.costs.add(cost); if (costs == null) {
costs = new CostsImpl<>();
}
costs.add(cost);
} }
} }
} }
@Override @Override
public void addManaCost(ManaCost cost) { public void addManaCostsToPay(ManaCost manaCost) {
if (cost != null) { if (manaCost == null) {
this.manaCosts.add(cost); return;
this.manaCostsToPay.add(cost);
} }
if (manaCostsToPay == null) {
manaCostsToPay = new ManaCostsImpl<>();
}
if (manaCost instanceof ManaCosts) {
manaCostsToPay.addAll((ManaCosts) manaCost);
} else {
manaCostsToPay.add(manaCost);
}
}
@Override
public void addManaCost(ManaCost manaCost) {
if (manaCost == null) {
return;
}
if (manaCosts == null) {
manaCosts = new ManaCostsImpl<>();
}
if (manaCostsToPay == null) {
manaCostsToPay = new ManaCostsImpl<>();
}
manaCosts.add(manaCost);
manaCostsToPay.add(manaCost);
} }
@Override @Override

View file

@ -193,7 +193,7 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
} }
// targets and costs check // targets and costs check
if (!costs.canPay(this, this, playerId, game) if (!getCosts().canPay(this, this, playerId, game)
|| !canChooseTarget(game, playerId)) { || !canChooseTarget(game, playerId)) {
return ActivationStatus.getFalse(); return ActivationStatus.getFalse();
} }

View file

@ -70,7 +70,7 @@ public class LoyaltyAbility extends ActivatedAbilityImpl {
// cost modification support only 1 cost item // cost modification support only 1 cost item
int staticCount = 0; int staticCount = 0;
for (Cost cost : costs) { for (Cost cost : getCosts()) {
if (cost instanceof PayLoyaltyCost) { if (cost instanceof PayLoyaltyCost) {
// static cost // static cost
PayLoyaltyCost staticCost = (PayLoyaltyCost) cost; PayLoyaltyCost staticCost = (PayLoyaltyCost) cost;

View file

@ -139,7 +139,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
public List<UUID> getSelectedModes() { public List<UUID> getSelectedModes() {
// modes can be selected in any order by user, but execution must be in rule's order // modes can be selected in any order by user, but execution must be in rule's order
List<UUID> res = new ArrayList<>(); List<UUID> res = new ArrayList<>(this.size());
for (Mode mode : this.values()) { for (Mode mode : this.values()) {
for (UUID selectedId : this.selectedModes) { for (UUID selectedId : this.selectedModes) {
// selectedModes contains original mode and 2+ selected as duplicates (new modes) // selectedModes contains original mode and 2+ selected as duplicates (new modes)

View file

@ -147,7 +147,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
} }
// can pay all costs and choose targets // can pay all costs and choose targets
if (costs.canPay(this, this, playerId, game)) { if (getCosts().canPay(this, this, playerId, game)) {
if (getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) { if (getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) {
SplitCard splitCard = (SplitCard) game.getCard(getSourceId()); SplitCard splitCard = (SplitCard) game.getCard(getSourceId());
if (splitCard != null) { if (splitCard != null) {
@ -180,12 +180,6 @@ public class SpellAbility extends ActivatedAbilityImpl {
return super.getRule(false); return super.getRule(false);
} }
public void clear() {
getTargets().clearChosen();
this.manaCosts.clearPaid();
this.costs.clearPaid();
}
public String getName() { public String getName() {
return this.name; return this.name;
} }

View file

@ -67,7 +67,7 @@ class StriveCostIncreasingEffect extends CostModificationEffectImpl {
sb.append(striveCosts.getText()); sb.append(striveCosts.getText());
} }
String finalCost = ManaUtil.condenseManaCostString(sb.toString()); String finalCost = ManaUtil.condenseManaCostString(sb.toString());
abilityToModify.getManaCostsToPay().add(new ManaCostsImpl<>(finalCost)); abilityToModify.addManaCostsToPay(new ManaCostsImpl<>(finalCost));
return true; return true;
} }
} }

View file

@ -146,19 +146,19 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
CardUtil.reduceCost((SpellAbility) ability, ability.getManaCosts()); CardUtil.reduceCost((SpellAbility) ability, ability.getManaCosts());
} else { } else {
ability.getManaCostsToPay().clear(); ability.clearManaCostsToPay();
} }
if (!onlyMana) { if (!onlyMana) {
ability.getCosts().clear(); ability.clearCosts();
} }
for (AlternativeCost alternateCost : alternativeCostsToCheck) { for (AlternativeCost alternateCost : alternativeCostsToCheck) {
alternateCost.activate(); alternateCost.activate();
for (Iterator it = ((Costs) alternateCost).iterator(); it.hasNext(); ) { for (Iterator it = ((Costs) alternateCost).iterator(); it.hasNext(); ) {
Cost costDetailed = (Cost) it.next(); Cost costDetailed = (Cost) it.next();
if (costDetailed instanceof ManaCost) { if (costDetailed instanceof ManaCost) {
ability.getManaCostsToPay().add((ManaCost) costDetailed.copy()); ability.addManaCostsToPay((ManaCost) costDetailed.copy());
} else if (costDetailed != null) { } else if (costDetailed != null) {
ability.getCosts().add(costDetailed.copy()); ability.addCost(costDetailed.copy());
} }
} }
} }

View file

@ -67,14 +67,14 @@ public abstract class AlternativeSourceCostsImpl extends StaticAbility implement
throw new IllegalArgumentException("source card not found"); throw new IllegalArgumentException("source card not found");
} }
} }
ability.getManaCostsToPay().clear(); ability.clearManaCostsToPay();
ability.getCosts().clear(); ability.clearCosts();
for (Iterator<Cost> it = ((Costs<Cost>) alternativeCost).iterator(); it.hasNext(); ) { for (Iterator<Cost> it = ((Costs<Cost>) alternativeCost).iterator(); it.hasNext(); ) {
Cost cost = it.next(); Cost cost = it.next();
if (cost instanceof ManaCost) { if (cost instanceof ManaCost) {
ability.getManaCostsToPay().add((ManaCost) cost.copy()); ability.addManaCostsToPay((ManaCost) cost.copy());
} else { } else {
ability.getCosts().add(cost.copy()); ability.addCost(cost.copy());
} }
} }
return true; return true;

View file

@ -22,6 +22,7 @@ public class CostsImpl<T extends Cost> extends ArrayList<T> implements Costs<T>
} }
public CostsImpl(final CostsImpl<T> costs) { public CostsImpl(final CostsImpl<T> costs) {
this.ensureCapacity(costs.size());
for (Cost cost : costs) { for (Cost cost : costs) {
this.add((T) cost.copy()); this.add((T) cost.copy());
} }

View file

@ -74,7 +74,7 @@ public class ExileFromHandCost extends CostImpl {
// TODO: wtf, look at setXFromCMC usage -- it used in cards with alternative costs, not additional... need to fix? // TODO: wtf, look at setXFromCMC usage -- it used in cards with alternative costs, not additional... need to fix?
vmc.setAmount(cmc, cmc, false); vmc.setAmount(cmc, cmc, false);
vmc.setPaid(); vmc.setPaid();
ability.getManaCostsToPay().add(vmc); ability.addManaCostsToPay(vmc);
} }
} }
return paid; return paid;

View file

@ -17,16 +17,15 @@ import java.util.List;
*/ */
public class ConditionalManaEffect extends ManaEffect { public class ConditionalManaEffect extends ManaEffect {
private BasicManaEffect effect; private final BasicManaEffect effect;
private BasicManaEffect otherwiseEffect; private final BasicManaEffect otherwiseEffect;
private Condition condition; private final Condition condition;
public ConditionalManaEffect(BasicManaEffect effect, Condition condition, String text) {
this(effect, null, condition, text);
}
public ConditionalManaEffect(BasicManaEffect effect, BasicManaEffect otherwiseEffect, Condition condition, String text) { public ConditionalManaEffect(BasicManaEffect effect, BasicManaEffect otherwiseEffect, Condition condition, String text) {
super(); super();
if (effect == null || otherwiseEffect == null) {
throw new IllegalArgumentException("Wrong code usage: mana effect must not be null");
}
this.effect = effect; this.effect = effect;
this.otherwiseEffect = otherwiseEffect; this.otherwiseEffect = otherwiseEffect;
this.condition = condition; this.condition = condition;
@ -36,9 +35,7 @@ public class ConditionalManaEffect extends ManaEffect {
public ConditionalManaEffect(ConditionalManaEffect effect) { public ConditionalManaEffect(ConditionalManaEffect effect) {
super(effect); super(effect);
this.effect = effect.effect.copy(); this.effect = effect.effect.copy();
if (effect.otherwiseEffect != null) {
this.otherwiseEffect = effect.otherwiseEffect.copy(); this.otherwiseEffect = effect.otherwiseEffect.copy();
}
this.condition = effect.condition; this.condition = effect.condition;
} }
@ -67,9 +64,9 @@ public class ConditionalManaEffect extends ManaEffect {
return mana; return mana;
} }
if (condition.apply(game, source)) { if (condition.apply(game, source)) {
mana = effect.getManaTemplate().copy(); mana = effect.getManaTemplate(); // getManaTemplate returns a copy already
} else if (otherwiseEffect != null) { } else {
mana = otherwiseEffect.getManaTemplate().copy(); mana = otherwiseEffect.getManaTemplate(); // getManaTemplate returns a copy already
} }
if (mana.getAny() > 0) { if (mana.getAny() > 0) {
int amount = mana.getAny(); int amount = mana.getAny();

View file

@ -71,9 +71,6 @@ public interface ContinuousEffect extends Effect {
boolean isYourNextUpkeepStep(Game game); boolean isYourNextUpkeepStep(Game game);
@Override
void newId();
@Override @Override
ContinuousEffect copy(); ContinuousEffect copy();

View file

@ -129,13 +129,6 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
this.order = order; this.order = order;
} }
@Override
public void newId() {
if (!(this instanceof MageSingleton)) {
this.id = UUID.randomUUID();
}
}
@Override @Override
public boolean hasLayer(Layer layer) { public boolean hasLayer(Layer layer) {
return this.layer == layer; return this.layer == layer;

View file

@ -12,12 +12,11 @@ import mage.game.Game;
public class BasicManaEffect extends ManaEffect { public class BasicManaEffect extends ManaEffect {
protected Mana manaTemplate; private final Mana manaTemplate; // This field must not become directly accessible outside this class
private final DynamicValue netAmount; private final DynamicValue netAmount;
public BasicManaEffect(Mana mana) { public BasicManaEffect(Mana mana) {
this(mana, null); this(mana, null);
this.manaTemplate = mana;
} }
public BasicManaEffect(Mana mana, DynamicValue netAmount) { public BasicManaEffect(Mana mana, DynamicValue netAmount) {
@ -40,9 +39,8 @@ public class BasicManaEffect extends ManaEffect {
protected BasicManaEffect(final BasicManaEffect effect) { protected BasicManaEffect(final BasicManaEffect effect) {
super(effect); super(effect);
this.manaTemplate = effect.manaTemplate.copy(); this.manaTemplate = effect.manaTemplate; // Not copying for performance reasons. Never modified within the class.
this.netAmount = effect.netAmount; this.netAmount = effect.netAmount;
} }
@Override @Override
@ -88,7 +86,7 @@ public class BasicManaEffect extends ManaEffect {
} }
public Mana getManaTemplate() { public Mana getManaTemplate() {
return manaTemplate; return manaTemplate.copy(); // Copy is needed here to prevent unintentional modification
} }
@Override @Override

View file

@ -43,8 +43,8 @@ public class AwakenAbility extends SpellAbility {
zone = Zone.HAND; zone = Zone.HAND;
spellAbilityType = SpellAbilityType.BASE_ALTERNATE; spellAbilityType = SpellAbilityType.BASE_ALTERNATE;
this.getManaCosts().clear(); this.clearManaCosts();
this.getManaCostsToPay().clear(); this.clearManaCostsToPay();
this.addManaCost(new ManaCostsImpl<>(awakenCosts)); this.addManaCost(new ManaCostsImpl<>(awakenCosts));
this.addTarget(new TargetControlledPermanent(new FilterControlledLandPermanent(filterMessage))); this.addTarget(new TargetControlledPermanent(new FilterControlledLandPermanent(filterMessage)));

View file

@ -58,15 +58,15 @@ public class BlitzAbility extends SpellAbility {
@Override @Override
public String getRule() { public String getRule() {
StringBuilder sb = new StringBuilder("Blitz"); StringBuilder sb = new StringBuilder("Blitz");
if (costs.isEmpty()) { if (getCosts().isEmpty()) {
sb.append(' '); sb.append(' ');
} else { } else {
sb.append("&mdash;"); sb.append("&mdash;");
} }
sb.append(manaCosts.getText()); sb.append(getManaCosts().getText());
if (!costs.isEmpty()) { if (!getCosts().isEmpty()) {
sb.append(", "); sb.append(", ");
sb.append(costs.getText()); sb.append(getCosts().getText());
sb.append('.'); sb.append('.');
} }
sb.append(" <i>(If you cast this spell for its blitz cost, it gains haste "); sb.append(" <i>(If you cast this spell for its blitz cost, it gains haste ");

View file

@ -144,9 +144,9 @@ public class BuybackAbility extends StaticAbility implements OptionalAdditionalS
for (Iterator it = ((Costs) buybackCost).iterator(); it.hasNext(); ) { for (Iterator it = ((Costs) buybackCost).iterator(); it.hasNext(); ) {
Cost cost = (Cost) it.next(); Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) { if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy()); ability.addManaCostsToPay((ManaCostsImpl) cost.copy());
} else { } else {
ability.getCosts().add(cost.copy()); ability.addCost(cost.copy());
} }
} }
} }

View file

@ -84,7 +84,7 @@ public class CasualtyAbility extends StaticAbility implements OptionalAdditional
additionalCost.activate(); additionalCost.activate();
for (Cost cost : ((Costs<Cost>) additionalCost)) { for (Cost cost : ((Costs<Cost>) additionalCost)) {
ability.getCosts().add(cost.copy()); ability.addCost(cost.copy());
} }
game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility( game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility(
new CopySourceSpellEffect(), false, "when you do, copy this spell" new CopySourceSpellEffect(), false, "when you do, copy this spell"

View file

@ -133,9 +133,9 @@ public class ConspireAbility extends StaticAbility implements OptionalAdditional
ability.getAllEffects().setValue("ConspireActivation" + conspireId + addedById, true); ability.getAllEffects().setValue("ConspireActivation" + conspireId + addedById, true);
for (Cost cost : (Costs<Cost>) conspireCost) { for (Cost cost : (Costs<Cost>) conspireCost) {
if (cost instanceof ManaCostsImpl) { if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl<?>) cost.copy()); ability.addManaCostsToPay((ManaCostsImpl<?>) cost.copy());
} else { } else {
ability.getCosts().add(cost.copy()); ability.addCost(cost.copy());
} }
} }
} }

View file

@ -41,8 +41,8 @@ public class DisturbAbility extends SpellAbility {
this.setSpellAbilityCastMode(SpellAbilityCastMode.DISTURB); this.setSpellAbilityCastMode(SpellAbilityCastMode.DISTURB);
this.manaCost = manaCost; this.manaCost = manaCost;
this.getManaCosts().clear(); this.clearManaCosts();
this.getManaCostsToPay().clear(); this.clearManaCostsToPay();
this.addManaCost(new ManaCostsImpl<>(manaCost)); this.addManaCost(new ManaCostsImpl<>(manaCost));
this.addSubAbility(new TransformAbility()); this.addSubAbility(new TransformAbility());
} }

View file

@ -37,7 +37,7 @@ public class EchoAbility extends TriggeredAbilityImpl {
super(Zone.BATTLEFIELD, new EchoEffect(amount), false); super(Zone.BATTLEFIELD, new EchoEffect(amount), false);
this.amount = amount; this.amount = amount;
this.echoPaid = false; this.echoPaid = false;
this.echoCosts.add(costs); this.echoCosts.add(getCosts());
this.lastController = null; this.lastController = null;
this.manaEcho = true; this.manaEcho = true;
this.rule = rule; this.rule = rule;

View file

@ -34,8 +34,8 @@ public class EmergeAbility extends SpellAbility {
zone = Zone.HAND; zone = Zone.HAND;
spellAbilityType = SpellAbilityType.BASE_ALTERNATE; spellAbilityType = SpellAbilityType.BASE_ALTERNATE;
this.getManaCosts().clear(); this.clearManaCosts();
this.getManaCostsToPay().clear(); this.clearManaCostsToPay();
this.addManaCost(emergeCost.copy()); this.addManaCost(emergeCost.copy());
this.setRuleAtTheTop(true); this.setRuleAtTheTop(true);

View file

@ -92,9 +92,9 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM
for (Iterator it = ((Costs) entwineCost).iterator(); it.hasNext(); ) { for (Iterator it = ((Costs) entwineCost).iterator(); it.hasNext(); ) {
Cost cost = (Cost) it.next(); Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) { if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy()); ability.addManaCostsToPay((ManaCostsImpl) cost.copy());
} else { } else {
ability.getCosts().add(cost.copy()); ability.addCost(cost.copy());
} }
} }
activateEntwine(game, ability); activateEntwine(game, ability);

View file

@ -63,19 +63,19 @@ public class EquipAbility extends ActivatedAbilityImpl {
@Override @Override
public String getRule() { public String getRule() {
String targetText = getTargets().get(0) != null ? getTargets().get(0).getFilter().getMessage() : "creature"; String targetText = getTargets().get(0) != null ? getTargets().get(0).getFilter().getMessage() : "creature";
String reminderText = " <i>(" + manaCosts.getText() + ": Attach to target " + targetText + ". Equip only as a sorcery.)</i>"; String reminderText = " <i>(" + getManaCosts().getText() + ": Attach to target " + targetText + ". Equip only as a sorcery.)</i>";
StringBuilder sb = new StringBuilder("Equip"); StringBuilder sb = new StringBuilder("Equip");
if (!targetText.equals("creature you control")) { if (!targetText.equals("creature you control")) {
sb.append(' ').append(targetText); sb.append(' ').append(targetText);
} }
String costText = costs.getText(); String costText = getCosts().getText();
if (costText != null && !costText.isEmpty()) { if (costText != null && !costText.isEmpty()) {
sb.append("&mdash;").append(costText).append('.'); sb.append("&mdash;").append(costText).append('.');
} else { } else {
sb.append(' '); sb.append(' ');
} }
sb.append(manaCosts.getText()); sb.append(getManaCosts().getText());
if (costReduceText != null && !costReduceText.isEmpty()) { if (costReduceText != null && !costReduceText.isEmpty()) {
sb.append(". "); sb.append(". ");
sb.append(costReduceText); sb.append(costReduceText);

View file

@ -38,8 +38,8 @@ public class EscapeAbility extends SpellAbility {
this.manaCost = manaCost; this.manaCost = manaCost;
this.exileCount = exileCount; this.exileCount = exileCount;
this.getManaCosts().clear(); this.clearManaCosts();
this.getManaCostsToPay().clear(); this.clearManaCostsToPay();
this.addManaCost(new ManaCostsImpl<>(manaCost)); this.addManaCost(new ManaCostsImpl<>(manaCost));
this.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard(exileCount, filter), "")); // hide additional cost text from rules this.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard(exileCount, filter), "")); // hide additional cost text from rules
} }

View file

@ -113,9 +113,9 @@ public class FlashbackAbility extends SpellAbility {
return null; return null;
} }
spellAbilityCopy.setId(this.getId()); spellAbilityCopy.setId(this.getId());
spellAbilityCopy.getManaCosts().clear(); spellAbilityCopy.clearManaCosts();
spellAbilityCopy.getManaCostsToPay().clear(); spellAbilityCopy.clearManaCostsToPay();
spellAbilityCopy.getCosts().addAll(this.getCosts().copy()); spellAbilityCopy.addCost(this.getCosts().copy());
spellAbilityCopy.addCost(this.getManaCosts().copy()); spellAbilityCopy.addCost(this.getManaCosts().copy());
spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode()); spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode());
spellAbilityToResolve = spellAbilityCopy; spellAbilityToResolve = spellAbilityCopy;
@ -148,19 +148,19 @@ public class FlashbackAbility extends SpellAbility {
@Override @Override
public String getRule() { public String getRule() {
StringBuilder sbRule = new StringBuilder("Flashback"); StringBuilder sbRule = new StringBuilder("Flashback");
if (!costs.isEmpty()) { if (!getCosts().isEmpty()) {
sbRule.append("&mdash;"); sbRule.append("&mdash;");
} else { } else {
sbRule.append(' '); sbRule.append(' ');
} }
if (!manaCosts.isEmpty()) { if (!getManaCosts().isEmpty()) {
sbRule.append(manaCosts.getText()); sbRule.append(getManaCosts().getText());
} }
if (!costs.isEmpty()) { if (!getCosts().isEmpty()) {
if (!manaCosts.isEmpty()) { if (!getManaCosts().isEmpty()) {
sbRule.append(", "); sbRule.append(", ");
} }
sbRule.append(costs.getText()); sbRule.append(getCosts().getText());
sbRule.append('.'); sbRule.append('.');
} }
if (abilityName != null) { if (abilityName != null) {

View file

@ -404,9 +404,9 @@ public class ForetellAbility extends SpecialAction {
return null; return null;
} }
spellAbilityCopy.setId(this.getId()); spellAbilityCopy.setId(this.getId());
spellAbilityCopy.getManaCosts().clear(); spellAbilityCopy.clearManaCosts();
spellAbilityCopy.getManaCostsToPay().clear(); spellAbilityCopy.clearManaCostsToPay();
spellAbilityCopy.getCosts().addAll(this.getCosts().copy()); spellAbilityCopy.addCost(this.getCosts().copy());
spellAbilityCopy.addCost(this.getManaCosts().copy()); spellAbilityCopy.addCost(this.getManaCosts().copy());
spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode()); spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode());
spellAbilityToResolve = spellAbilityCopy; spellAbilityToResolve = spellAbilityCopy;
@ -431,19 +431,19 @@ public class ForetellAbility extends SpecialAction {
@Override @Override
public String getRule(boolean all) { public String getRule(boolean all) {
StringBuilder sbRule = new StringBuilder("Foretell"); StringBuilder sbRule = new StringBuilder("Foretell");
if (!costs.isEmpty()) { if (!getCosts().isEmpty()) {
sbRule.append("&mdash;"); sbRule.append("&mdash;");
} else { } else {
sbRule.append(' '); sbRule.append(' ');
} }
if (!manaCosts.isEmpty()) { if (!getManaCosts().isEmpty()) {
sbRule.append(manaCosts.getText()); sbRule.append(getManaCosts().getText());
} }
if (!costs.isEmpty()) { if (!getCosts().isEmpty()) {
if (!manaCosts.isEmpty()) { if (!getManaCosts().isEmpty()) {
sbRule.append(", "); sbRule.append(", ");
} }
sbRule.append(costs.getText()); sbRule.append(getCosts().getText());
sbRule.append('.'); sbRule.append('.');
} }
if (abilityName != null) { if (abilityName != null) {

View file

@ -44,6 +44,6 @@ public class FortifyAbility extends ActivatedAbilityImpl {
@Override @Override
public String getRule() { public String getRule() {
return "Fortify " + costs.getText() + manaCosts.getText() + " (" + manaCosts.getText() + ": <i>Attach to target land you control. Fortify only as a sorcery.)</i>"; return "Fortify " + getCosts().getText() + getManaCosts().getText() + " (" + getManaCosts().getText() + ": <i>Attach to target land you control. Fortify only as a sorcery.)</i>";
} }
} }

View file

@ -282,9 +282,9 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
// can contain multiple costs from multikicker ability // can contain multiple costs from multikicker ability
// must be additional cost type // must be additional cost type
if (cost instanceof ManaCostsImpl) { if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy()); ability.addManaCostsToPay((ManaCostsImpl) cost.copy());
} else { } else {
ability.getCosts().add(cost.copy()); ability.addCost(cost.copy());
} }
} }

View file

@ -30,7 +30,7 @@ public class LevelUpAbility extends ActivatedAbilityImpl {
@Override @Override
public String getRule() { public String getRule() {
return new StringBuilder("Level up ").append(manaCostsToPay.getText()) return new StringBuilder("Level up ").append(getManaCostsToPay().getText())
.append(" <i>(").append(manaCostsToPay.getText()).append(": Put a level counter on this. Level up only as a sorcery.)</i>").toString(); .append(" <i>(").append(getManaCostsToPay().getText()).append(": Put a level counter on this. Level up only as a sorcery.)</i>").toString();
} }
} }

View file

@ -267,7 +267,7 @@ class MadnessCastEffect extends OneShotEffect {
ManaCosts<ManaCost> costRef = castByMadness.getManaCostsToPay(); ManaCosts<ManaCost> costRef = castByMadness.getManaCostsToPay();
castByMadness.setSpellAbilityType(SpellAbilityType.BASE_ALTERNATE); castByMadness.setSpellAbilityType(SpellAbilityType.BASE_ALTERNATE);
castByMadness.setSpellAbilityCastMode(SpellAbilityCastMode.MADNESS); castByMadness.setSpellAbilityCastMode(SpellAbilityCastMode.MADNESS);
castByMadness.getCosts().clear(); castByMadness.clearCosts();
castByMadness.addCost(new PayLifeCost(this.lifeCost)); castByMadness.addCost(new PayLifeCost(this.lifeCost));
costRef.clear(); costRef.clear();
costRef.add(madnessCost); costRef.add(madnessCost);

View file

@ -36,7 +36,7 @@ public class MeditateAbility extends ActivatedAbilityImpl {
@Override @Override
public String getRule() { public String getRule() {
StringBuilder sb = new StringBuilder("Meditate ").append(manaCosts.getText()); StringBuilder sb = new StringBuilder("Meditate ").append(getManaCosts().getText());
sb.append(" <i>(Return this creature to its owner's hand. Meditate only as a sorcery.)</i>"); sb.append(" <i>(Return this creature to its owner's hand. Meditate only as a sorcery.)</i>");
return sb.toString(); return sb.toString();
} }

View file

@ -28,8 +28,8 @@ public class MoreThanMeetsTheEyeAbility extends SpellAbility {
this.setSpellAbilityCastMode(SpellAbilityCastMode.MORE_THAN_MEETS_THE_EYE); this.setSpellAbilityCastMode(SpellAbilityCastMode.MORE_THAN_MEETS_THE_EYE);
this.manaCost = manaCost; this.manaCost = manaCost;
this.getManaCosts().clear(); this.clearManaCosts();
this.getManaCostsToPay().clear(); this.clearManaCostsToPay();
this.addManaCost(new ManaCostsImpl<>(manaCost)); this.addManaCost(new ManaCostsImpl<>(manaCost));
this.addSubAbility(new TransformAbility()); this.addSubAbility(new TransformAbility());
} }

View file

@ -32,7 +32,7 @@ public class OutlastAbility extends ActivatedAbilityImpl {
@Override @Override
public String getRule() { public String getRule() {
StringBuilder sb = new StringBuilder("Outlast ").append(manaCosts.getText()); StringBuilder sb = new StringBuilder("Outlast ").append(getManaCosts().getText());
sb.append(" <i>(").append(getManaCosts().getText()).append(", ").append(getCosts().getText()).append(": Put a +1/+1 counter on this creature. Outlast only as a sorcery.)</i>"); sb.append(" <i>(").append(getManaCosts().getText()).append(", ").append(getCosts().getText()).append(": Put a +1/+1 counter on this creature. Outlast only as a sorcery.)</i>");
return sb.toString(); return sb.toString();
} }

View file

@ -106,9 +106,9 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona
for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext(); ) { for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext(); ) {
Cost cost = (Cost) it.next(); Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) { if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy()); ability.addManaCostsToPay((ManaCostsImpl) cost.copy());
} else { } else {
ability.getCosts().add(cost.copy()); ability.addCost(cost.copy());
} }
} }
} }

View file

@ -29,8 +29,8 @@ public class SpectacleAbility extends SpellAbility {
zone = Zone.HAND; zone = Zone.HAND;
spellAbilityType = SpellAbilityType.BASE_ALTERNATE; spellAbilityType = SpellAbilityType.BASE_ALTERNATE;
this.getManaCosts().clear(); this.clearManaCosts();
this.getManaCostsToPay().clear(); this.clearManaCostsToPay();
this.addManaCost(spectacleCosts.copy()); this.addManaCost(spectacleCosts.copy());
this.setRuleAtTheTop(true); this.setRuleAtTheTop(true);

View file

@ -174,9 +174,9 @@ class SpliceCardEffectImpl extends ContinuousEffectImpl implements SpliceCardEff
for (Iterator it = ((SpliceAbility) source).getSpliceCosts().iterator(); it.hasNext();) { for (Iterator it = ((SpliceAbility) source).getSpliceCosts().iterator(); it.hasNext();) {
Cost cost = (Cost) it.next(); Cost cost = (Cost) it.next();
if (cost instanceof ManaCost) { if (cost instanceof ManaCost) {
spell.getSpellAbility().getManaCostsToPay().add((ManaCost) cost.copy()); spell.getSpellAbility().addManaCostsToPay((ManaCost) cost.copy());
} else { } else {
spell.getSpellAbility().getCosts().add(cost.copy()); spell.getSpellAbility().addCost(cost.copy());
} }
} }
} }

View file

@ -29,8 +29,8 @@ public class SurgeAbility extends SpellAbility {
zone = Zone.HAND; zone = Zone.HAND;
spellAbilityType = SpellAbilityType.BASE_ALTERNATE; spellAbilityType = SpellAbilityType.BASE_ALTERNATE;
this.getManaCosts().clear(); this.clearManaCosts();
this.getManaCostsToPay().clear(); this.clearManaCostsToPay();
this.addManaCost(new ManaCostsImpl<>(surgeCosts)); this.addManaCost(new ManaCostsImpl<>(surgeCosts));
this.setRuleAtTheTop(true); this.setRuleAtTheTop(true);

View file

@ -142,7 +142,7 @@ class AdventureCardSpellAbility extends SpellAbility {
StringBuilder sbRule = new StringBuilder(); StringBuilder sbRule = new StringBuilder();
sbRule.append(this.nameFull); sbRule.append(this.nameFull);
sbRule.append(" "); sbRule.append(" ");
sbRule.append(manaCosts.getText()); sbRule.append(getManaCosts().getText());
sbRule.append(" &mdash; "); sbRule.append(" &mdash; ");
Modes modes = this.getModes(); Modes modes = this.getModes();
if (modes.size() <= 1) { if (modes.size() <= 1) {

View file

@ -210,7 +210,7 @@ public interface Card extends MageObject {
CommanderPlaysCountWatcher watcher = game.getState().getWatcher(CommanderPlaysCountWatcher.class); CommanderPlaysCountWatcher watcher = game.getState().getWatcher(CommanderPlaysCountWatcher.class);
int castCount = watcher.getPlaysCount(getMainCard().getId()); int castCount = watcher.getPlaysCount(getMainCard().getId());
if (castCount > 0) { if (castCount > 0) {
abilityToModify.getManaCostsToPay().add(ManaUtil.createManaCost(2 * castCount, false)); abilityToModify.addManaCostsToPay(ManaUtil.createManaCost(2 * castCount, false));
} }
return true; return true;
} }

View file

@ -394,7 +394,13 @@ public class StackAbility extends StackObjectImpl implements Ability {
} }
@Override @Override
public void addManaCost(ManaCost cost) { public void addManaCost(ManaCost manaCost) {
// Do nothing
}
@Override
public void addManaCostsToPay(ManaCost manaCost) {
// Do nothing
} }
@Override @Override

View file

@ -1194,15 +1194,12 @@ public abstract class PlayerImpl implements Player, Serializable {
if (alternateCosts == null) { if (alternateCosts == null) {
noMana = true; noMana = true;
} else { } else {
spellAbility.getManaCosts().clear(); spellAbility.clearManaCosts();
spellAbility.getManaCostsToPay().clear(); spellAbility.clearManaCostsToPay();
spellAbility.getManaCosts().add(alternateCosts.copy()); spellAbility.addManaCost(alternateCosts.copy());
spellAbility.getManaCostsToPay().add(alternateCosts.copy());
}
spellAbility.getCosts().clear();
if (costs != null) {
spellAbility.getCosts().addAll(costs);
} }
spellAbility.clearCosts();
spellAbility.addCost(costs);
} }
clearCastSourceIdManaCosts(); // TODO: test multiple alternative cost for different cards as same time clearCastSourceIdManaCosts(); // TODO: test multiple alternative cost for different cards as same time
@ -3620,8 +3617,8 @@ public abstract class PlayerImpl implements Player, Serializable {
// alternative cost reduce // alternative cost reduce
copyAbility = ability.copy(); copyAbility = ability.copy();
copyAbility.getManaCostsToPay().clear(); copyAbility.clearManaCostsToPay();
copyAbility.getManaCostsToPay().addAll(manaCosts.copy()); copyAbility.addManaCostsToPay(manaCosts.copy());
copyAbility.adjustCosts(game); copyAbility.adjustCosts(game);
game.getContinuousEffects().costModification(copyAbility, game); game.getContinuousEffects().costModification(copyAbility, game);
@ -3686,12 +3683,12 @@ public abstract class PlayerImpl implements Player, Serializable {
// alternative cost reduce // alternative cost reduce
copyAbility = ability.copy(); copyAbility = ability.copy();
copyAbility.getManaCostsToPay().clear(); copyAbility.clearManaCostsToPay();
// TODO: IDE warning: // TODO: IDE warning:
// Unchecked assignment: 'mage.abilities.costs.mana.ManaCosts' to // Unchecked assignment: 'mage.abilities.costs.mana.ManaCosts' to
// 'java.util.Collection<? extends mage.abilities.costs.mana.ManaCost>'. // 'java.util.Collection<? extends mage.abilities.costs.mana.ManaCost>'.
// Reason: 'manaCosts' has raw type, so result of copy is erased // Reason: 'manaCosts' has raw type, so result of copy is erased
copyAbility.getManaCostsToPay().addAll(manaCosts.copy()); copyAbility.addManaCostsToPay(manaCosts.copy());
copyAbility.adjustCosts(game); copyAbility.adjustCosts(game);
game.getContinuousEffects().costModification(copyAbility, game); game.getContinuousEffects().costModification(copyAbility, game);

View file

@ -9,7 +9,7 @@ import java.util.Map;
public abstract class TargetPointerImpl implements TargetPointer { public abstract class TargetPointerImpl implements TargetPointer {
// Store custom data here. Use it to keep unique values for ability instances on stack (example: Gruul Ragebeast) // Store custom data here. Use it to keep unique values for ability instances on stack (example: Gruul Ragebeast)
Map<String, String> data = new HashMap<>(); private Map<String, String> data;
public TargetPointerImpl() { public TargetPointerImpl() {
super(); super();
@ -17,17 +17,26 @@ public abstract class TargetPointerImpl implements TargetPointer {
protected TargetPointerImpl(final TargetPointerImpl targetPointer) { protected TargetPointerImpl(final TargetPointerImpl targetPointer) {
super(); super();
if (targetPointer.data != null) {
this.data = new HashMap<>();
this.data.putAll(targetPointer.data); this.data.putAll(targetPointer.data);
} }
}
@Override @Override
public String getData(String key) { public String getData(String key) {
return this.data.getOrDefault(key, ""); if (data == null) {
return "";
}
return data.getOrDefault(key, "");
} }
@Override @Override
public TargetPointer withData(String key, String value) { public TargetPointer withData(String key, String value) {
this.data.put(key, value); if (data == null) {
data = new HashMap<>();
}
data.put(key, value);
return this; return this;
} }
} }

View file

@ -141,8 +141,8 @@ public final class CardUtil {
*/ */
private static void adjustAbilityCost(Ability ability, int reduceCount) { private static void adjustAbilityCost(Ability ability, int reduceCount) {
ManaCosts<ManaCost> adjustedCost = adjustCost(ability.getManaCostsToPay(), reduceCount); ManaCosts<ManaCost> adjustedCost = adjustCost(ability.getManaCostsToPay(), reduceCount);
ability.getManaCostsToPay().clear(); ability.clearManaCostsToPay();
ability.getManaCostsToPay().addAll(adjustedCost); ability.addManaCostsToPay(adjustedCost);
} }
private static ManaCosts<ManaCost> adjustCost(ManaCosts<ManaCost> manaCosts, int reduceCount) { private static ManaCosts<ManaCost> adjustCost(ManaCosts<ManaCost> manaCosts, int reduceCount) {
@ -304,8 +304,8 @@ public final class CardUtil {
increasedCost.add(manaCost.copy()); increasedCost.add(manaCost.copy());
} }
spellAbility.getManaCostsToPay().clear(); spellAbility.clearManaCostsToPay();
spellAbility.getManaCostsToPay().addAll(increasedCost); spellAbility.addManaCostsToPay(increasedCost);
} }
/** /**
@ -468,8 +468,8 @@ public final class CardUtil {
adjustedCost.add(new GenericManaCost(0)); // neede to check if cost was reduced to 0 adjustedCost.add(new GenericManaCost(0)); // neede to check if cost was reduced to 0
} }
adjustedCost.setSourceFilter(previousCost.getSourceFilter()); // keep mana source restrictions adjustedCost.setSourceFilter(previousCost.getSourceFilter()); // keep mana source restrictions
spellAbility.getManaCostsToPay().clear(); spellAbility.clearManaCostsToPay();
spellAbility.getManaCostsToPay().addAll(adjustedCost); spellAbility.addManaCostsToPay(adjustedCost);
} }
/** /**