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) {
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);
if (varCost != null) {
varCost.setPaid();

View file

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

View file

@ -95,7 +95,7 @@ public class SimulatedPlayerMCTS extends MCTSPlayer {
int amount = getAvailableManaProducers(game).size() - ability.getManaCosts().manaValue();
if (amount > 0) {
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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -47,7 +47,7 @@ enum FireballAdjuster implements CostAdjuster {
public void adjustCosts(Ability ability, Game game) {
int numTargets = ability.getTargets().isEmpty() ? 0 : ability.getTargets().get(0).getTargets().size();
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)) {
return true;
}
abilityToModify.getManaCostsToPay().clear();
abilityToModify.clearManaCostsToPay();
abilityToModify.getCosts().removeIf(cost -> !CyclingDiscardCost.class.isInstance(cost));
abilityToModify.getManaCostsToPay().add(new GenericManaCost(0));
abilityToModify.addManaCostsToPay(new GenericManaCost(0));
return true;
}

View file

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

View file

@ -49,7 +49,7 @@ enum PhyrexianPurgeCostAdjuster implements CostAdjuster {
public void adjustCosts(Ability ability, Game game) {
int numTargets = ability.getTargets().get(0).getTargets().size();
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()) {
Card imprinted = game.getCard(card.getImprinted().get(0));
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
public boolean apply(Game game, Ability source, Ability abilityToModify) {
abilityToModify.getManaCostsToPay().clear();
abilityToModify.getManaCostsToPay().addAll(new ManaCostsImpl<>("{0}"));
abilityToModify.clearManaCostsToPay();
abilityToModify.addManaCostsToPay(new ManaCostsImpl<>("{0}"));
return true;
}

View file

@ -69,8 +69,8 @@ enum SoulFoundryAdjuster implements CostAdjuster {
if (!sourcePermanent.getImprinted().isEmpty()) {
Card imprinted = game.getCard(sourcePermanent.getImprinted().get(0));
if (imprinted != null) {
ability.getManaCostsToPay().clear();
ability.getManaCostsToPay().add(0, new GenericManaCost(imprinted.getManaValue()));
ability.clearManaCostsToPay();
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.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.condition.InvertCondition;
import mage.abilities.condition.common.SourceTappedCondition;
import mage.abilities.costs.CostAdjuster;
import mage.abilities.costs.common.TapSourceCost;
@ -78,8 +77,8 @@ enum VoodooDollAdjuster implements CostAdjuster {
Permanent sourcePermanent = game.getPermanent(ability.getSourceId());
if (sourcePermanent != null) {
int pin = sourcePermanent.getCounters(game).getCount(CounterType.PIN);
ability.getManaCostsToPay().clear();
ability.getManaCostsToPay().add(0, new GenericManaCost(pin * 2));
ability.clearManaCostsToPay();
ability.addManaCostsToPay(new GenericManaCost(pin * 2));
}
}
}

View file

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

View file

@ -118,8 +118,8 @@ class WordOfCommandEffect extends OneShotEffect {
&& !targetPlayer.playCard(card, game, false, new ApprovingObject(source, game))) {
SpellAbility spellAbility = card.getSpellAbility();
if (spellAbility != null) {
spellAbility.getManaCostsToPay().clear();
spellAbility.getManaCostsToPay().addAll(spellAbility.getManaCosts());
spellAbility.clearManaCostsToPay();
spellAbility.addManaCostsToPay(spellAbility.getManaCosts());
((ManaCostsImpl) spellAbility.getManaCostsToPay()).forceManaRollback(game, manaPool); // force rollback if card was deemed playable
canPlay = checkPlayability(card, targetPlayer, game, source);
} 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);
default void clearCosts() {
getCosts().clear();
}
default void clearManaCosts() {
getManaCosts().clear();
}
default void clearManaCostsToPay() {
getManaCostsToPay().clear();
}
/**
* Gets all {@link Costs} associated with this ability.
*
@ -151,6 +163,8 @@ public interface Ability extends Controllable, Serializable {
*/
void addManaCost(ManaCost cost);
void addManaCostsToPay(ManaCost manaCost);
/**
* Retrieves the effects that are put into the place by the resolution of
* this ability.

View file

@ -56,9 +56,9 @@ public abstract class AbilityImpl implements Ability {
protected AbilityType abilityType;
protected UUID controllerId;
protected UUID sourceId;
protected ManaCosts<ManaCost> manaCosts;
protected ManaCosts<ManaCost> manaCostsToPay;
protected Costs<Cost> costs;
private ManaCosts<ManaCost> manaCosts;
private ManaCosts<ManaCost> manaCostsToPay;
private Costs<Cost> costs;
protected Modes modes; // access to it by GetModes only (it can be overridden by some abilities)
protected Zone zone;
protected String name;
@ -253,14 +253,14 @@ public abstract class AbilityImpl implements Ability {
if (noMana) {
if (!this.getManaCostsToPay().getVariableCosts().isEmpty()) {
int xValue = this.getManaCostsToPay().getX();
this.getManaCostsToPay().clear();
this.clearManaCostsToPay();
VariableManaCost xCosts = new VariableManaCost(VariableCostType.ADDITIONAL);
// 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.
xCosts.setAmount(xValue, xValue, false);
this.getManaCostsToPay().add(xCosts);
addManaCostsToPay(xCosts);
} 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
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");
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)
if (!manaCostsToPay.pay(this, game, this, activatorId, false, null)) {
if (!getManaCostsToPay().pay(this, game, this, activatorId, false, null)) {
return false; // cancel during mana payment
}
//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");
return false;
}
@ -509,13 +509,11 @@ public abstract class AbilityImpl implements Ability {
*/
protected String handleOtherXCosts(Game game, Player controller) {
StringBuilder announceString = new StringBuilder();
for (VariableCost variableCost : this.costs.getVariableCosts()) {
for (VariableCost variableCost : this.getCosts().getVariableCosts()) {
if (!(variableCost instanceof VariableManaCost) && !((Cost) variableCost).isPaid()) {
int xValue = variableCost.announceXValue(this, game);
Cost fixedCost = variableCost.getFixedCostsFromAnnouncedValue(xValue);
if (fixedCost != null) {
costs.add(fixedCost);
}
addCost(fixedCost);
// set the xcosts to paid
// 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.
@ -534,7 +532,7 @@ public abstract class AbilityImpl implements Ability {
* life or the corresponding colored mana cost for each of those symbols.
*/
private void handlePhyrexianManaCosts(Game game, Player controller) {
Iterator<ManaCost> costIterator = manaCostsToPay.iterator();
Iterator<ManaCost> costIterator = getManaCostsToPay().iterator();
while (costIterator.hasNext()) {
ManaCost cost = costIterator.next();
@ -545,8 +543,8 @@ public abstract class AbilityImpl implements Ability {
if (payLifeCost.canPay(this, this, controller.getId(), game)
&& controller.chooseUse(Outcome.LoseLife, "Pay 2 life instead of " + cost.getText().replace("/P", "") + '?', this, game)) {
costIterator.remove();
costs.add(payLifeCost);
manaCostsToPay.incrPhyrexianPaid();
addCost(payLifeCost);
getManaCostsToPay().incrPhyrexianPaid();
}
}
}
@ -580,7 +578,7 @@ public abstract class AbilityImpl implements Ability {
// TODO: Handle announcing other variable costs here like: RemoveVariableCountersSourceCost
VariableManaCost variableManaCost = null;
for (ManaCost cost : manaCostsToPay) {
for (ManaCost cost : getManaCostsToPay()) {
if (cost instanceof VariableManaCost) {
if (variableManaCost == null) {
variableManaCost = (VariableManaCost) cost;
@ -625,8 +623,8 @@ public abstract class AbilityImpl implements Ability {
manaString.append('{').append(manaSymbol).append('}');
}
}
manaCostsToPay.add(new ManaCostsImpl<>(manaString.toString()));
manaCostsToPay.setX(xValue * xValueMultiplier, amountMana);
addManaCostsToPay(new ManaCostsImpl<>(manaString.toString()));
getManaCostsToPay().setX(xValue * xValueMultiplier, amountMana);
}
variableManaCost.setPaid();
}
@ -788,14 +786,14 @@ public abstract class AbilityImpl implements Ability {
public String getRule(boolean all) {
StringBuilder sbRule = threadLocalBuilder.get();
if (all || this.abilityType != AbilityType.SPELL) { // TODO: Why the override for non-spells?
if (!manaCosts.isEmpty()) {
sbRule.append(manaCosts.getText());
if (!getManaCosts().isEmpty()) {
sbRule.append(getManaCosts().getText());
}
if (!costs.isEmpty()) {
if (!getCosts().isEmpty()) {
if (sbRule.length() > 0) {
sbRule.append(", ");
}
sbRule.append(costs.getText());
sbRule.append(getCosts().getText());
}
if (sbRule.length() > 0) {
sbRule.append(": ");
@ -866,19 +864,46 @@ public abstract class AbilityImpl implements Ability {
} else {
// as single cost
if (cost instanceof ManaCost) {
this.addManaCost((ManaCost) cost);
addManaCost((ManaCost) cost);
} else {
this.costs.add(cost);
if (costs == null) {
costs = new CostsImpl<>();
}
costs.add(cost);
}
}
}
@Override
public void addManaCost(ManaCost cost) {
if (cost != null) {
this.manaCosts.add(cost);
this.manaCostsToPay.add(cost);
public void addManaCostsToPay(ManaCost manaCost) {
if (manaCost == null) {
return;
}
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

View file

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

View file

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

View file

@ -139,7 +139,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
public List<UUID> getSelectedModes() {
// 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 (UUID selectedId : this.selectedModes) {
// 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
if (costs.canPay(this, this, playerId, game)) {
if (getCosts().canPay(this, this, playerId, game)) {
if (getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) {
SplitCard splitCard = (SplitCard) game.getCard(getSourceId());
if (splitCard != null) {
@ -180,12 +180,6 @@ public class SpellAbility extends ActivatedAbilityImpl {
return super.getRule(false);
}
public void clear() {
getTargets().clearChosen();
this.manaCosts.clearPaid();
this.costs.clearPaid();
}
public String getName() {
return this.name;
}

View file

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

View file

@ -146,19 +146,19 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
CardUtil.reduceCost((SpellAbility) ability, ability.getManaCosts());
} else {
ability.getManaCostsToPay().clear();
ability.clearManaCostsToPay();
}
if (!onlyMana) {
ability.getCosts().clear();
ability.clearCosts();
}
for (AlternativeCost alternateCost : alternativeCostsToCheck) {
alternateCost.activate();
for (Iterator it = ((Costs) alternateCost).iterator(); it.hasNext(); ) {
Cost costDetailed = (Cost) it.next();
if (costDetailed instanceof ManaCost) {
ability.getManaCostsToPay().add((ManaCost) costDetailed.copy());
ability.addManaCostsToPay((ManaCost) costDetailed.copy());
} 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");
}
}
ability.getManaCostsToPay().clear();
ability.getCosts().clear();
ability.clearManaCostsToPay();
ability.clearCosts();
for (Iterator<Cost> it = ((Costs<Cost>) alternativeCost).iterator(); it.hasNext(); ) {
Cost cost = it.next();
if (cost instanceof ManaCost) {
ability.getManaCostsToPay().add((ManaCost) cost.copy());
ability.addManaCostsToPay((ManaCost) cost.copy());
} else {
ability.getCosts().add(cost.copy());
ability.addCost(cost.copy());
}
}
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) {
this.ensureCapacity(costs.size());
for (Cost cost : costs) {
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?
vmc.setAmount(cmc, cmc, false);
vmc.setPaid();
ability.getManaCostsToPay().add(vmc);
ability.addManaCostsToPay(vmc);
}
}
return paid;

View file

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

View file

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

View file

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

View file

@ -12,12 +12,11 @@ import mage.game.Game;
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;
public BasicManaEffect(Mana mana) {
this(mana, null);
this.manaTemplate = mana;
}
public BasicManaEffect(Mana mana, DynamicValue netAmount) {
@ -40,9 +39,8 @@ public class BasicManaEffect extends ManaEffect {
protected BasicManaEffect(final BasicManaEffect 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;
}
@Override
@ -88,7 +86,7 @@ public class BasicManaEffect extends ManaEffect {
}
public Mana getManaTemplate() {
return manaTemplate;
return manaTemplate.copy(); // Copy is needed here to prevent unintentional modification
}
@Override

View file

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

View file

@ -58,15 +58,15 @@ public class BlitzAbility extends SpellAbility {
@Override
public String getRule() {
StringBuilder sb = new StringBuilder("Blitz");
if (costs.isEmpty()) {
if (getCosts().isEmpty()) {
sb.append(' ');
} else {
sb.append("&mdash;");
}
sb.append(manaCosts.getText());
if (!costs.isEmpty()) {
sb.append(getManaCosts().getText());
if (!getCosts().isEmpty()) {
sb.append(", ");
sb.append(costs.getText());
sb.append(getCosts().getText());
sb.append('.');
}
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(); ) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
ability.addManaCostsToPay((ManaCostsImpl) cost.copy());
} 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();
for (Cost cost : ((Costs<Cost>) additionalCost)) {
ability.getCosts().add(cost.copy());
ability.addCost(cost.copy());
}
game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility(
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);
for (Cost cost : (Costs<Cost>) conspireCost) {
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl<?>) cost.copy());
ability.addManaCostsToPay((ManaCostsImpl<?>) cost.copy());
} 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.manaCost = manaCost;
this.getManaCosts().clear();
this.getManaCostsToPay().clear();
this.clearManaCosts();
this.clearManaCostsToPay();
this.addManaCost(new ManaCostsImpl<>(manaCost));
this.addSubAbility(new TransformAbility());
}

View file

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

View file

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

View file

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

View file

@ -63,19 +63,19 @@ public class EquipAbility extends ActivatedAbilityImpl {
@Override
public String getRule() {
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");
if (!targetText.equals("creature you control")) {
sb.append(' ').append(targetText);
}
String costText = costs.getText();
String costText = getCosts().getText();
if (costText != null && !costText.isEmpty()) {
sb.append("&mdash;").append(costText).append('.');
} else {
sb.append(' ');
}
sb.append(manaCosts.getText());
sb.append(getManaCosts().getText());
if (costReduceText != null && !costReduceText.isEmpty()) {
sb.append(". ");
sb.append(costReduceText);

View file

@ -38,8 +38,8 @@ public class EscapeAbility extends SpellAbility {
this.manaCost = manaCost;
this.exileCount = exileCount;
this.getManaCosts().clear();
this.getManaCostsToPay().clear();
this.clearManaCosts();
this.clearManaCostsToPay();
this.addManaCost(new ManaCostsImpl<>(manaCost));
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;
}
spellAbilityCopy.setId(this.getId());
spellAbilityCopy.getManaCosts().clear();
spellAbilityCopy.getManaCostsToPay().clear();
spellAbilityCopy.getCosts().addAll(this.getCosts().copy());
spellAbilityCopy.clearManaCosts();
spellAbilityCopy.clearManaCostsToPay();
spellAbilityCopy.addCost(this.getCosts().copy());
spellAbilityCopy.addCost(this.getManaCosts().copy());
spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode());
spellAbilityToResolve = spellAbilityCopy;
@ -148,19 +148,19 @@ public class FlashbackAbility extends SpellAbility {
@Override
public String getRule() {
StringBuilder sbRule = new StringBuilder("Flashback");
if (!costs.isEmpty()) {
if (!getCosts().isEmpty()) {
sbRule.append("&mdash;");
} else {
sbRule.append(' ');
}
if (!manaCosts.isEmpty()) {
sbRule.append(manaCosts.getText());
if (!getManaCosts().isEmpty()) {
sbRule.append(getManaCosts().getText());
}
if (!costs.isEmpty()) {
if (!manaCosts.isEmpty()) {
if (!getCosts().isEmpty()) {
if (!getManaCosts().isEmpty()) {
sbRule.append(", ");
}
sbRule.append(costs.getText());
sbRule.append(getCosts().getText());
sbRule.append('.');
}
if (abilityName != null) {

View file

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

View file

@ -44,6 +44,6 @@ public class FortifyAbility extends ActivatedAbilityImpl {
@Override
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
// must be additional cost type
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
ability.addManaCostsToPay((ManaCostsImpl) cost.copy());
} else {
ability.getCosts().add(cost.copy());
ability.addCost(cost.copy());
}
}

View file

@ -30,7 +30,7 @@ public class LevelUpAbility extends ActivatedAbilityImpl {
@Override
public String getRule() {
return new StringBuilder("Level up ").append(manaCostsToPay.getText())
.append(" <i>(").append(manaCostsToPay.getText()).append(": Put a level counter on this. Level up only as a sorcery.)</i>").toString();
return new StringBuilder("Level up ").append(getManaCostsToPay().getText())
.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();
castByMadness.setSpellAbilityType(SpellAbilityType.BASE_ALTERNATE);
castByMadness.setSpellAbilityCastMode(SpellAbilityCastMode.MADNESS);
castByMadness.getCosts().clear();
castByMadness.clearCosts();
castByMadness.addCost(new PayLifeCost(this.lifeCost));
costRef.clear();
costRef.add(madnessCost);

View file

@ -36,7 +36,7 @@ public class MeditateAbility extends ActivatedAbilityImpl {
@Override
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>");
return sb.toString();
}

View file

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

View file

@ -32,7 +32,7 @@ public class OutlastAbility extends ActivatedAbilityImpl {
@Override
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>");
return sb.toString();
}

View file

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

View file

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

View file

@ -174,9 +174,9 @@ class SpliceCardEffectImpl extends ContinuousEffectImpl implements SpliceCardEff
for (Iterator it = ((SpliceAbility) source).getSpliceCosts().iterator(); it.hasNext();) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCost) {
spell.getSpellAbility().getManaCostsToPay().add((ManaCost) cost.copy());
spell.getSpellAbility().addManaCostsToPay((ManaCost) cost.copy());
} 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;
spellAbilityType = SpellAbilityType.BASE_ALTERNATE;
this.getManaCosts().clear();
this.getManaCostsToPay().clear();
this.clearManaCosts();
this.clearManaCostsToPay();
this.addManaCost(new ManaCostsImpl<>(surgeCosts));
this.setRuleAtTheTop(true);

View file

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

View file

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

View file

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

View file

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

View file

@ -9,7 +9,7 @@ import java.util.Map;
public abstract class TargetPointerImpl implements TargetPointer {
// 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() {
super();
@ -17,17 +17,26 @@ public abstract class TargetPointerImpl implements TargetPointer {
protected TargetPointerImpl(final TargetPointerImpl targetPointer) {
super();
this.data.putAll(targetPointer.data);
if (targetPointer.data != null) {
this.data = new HashMap<>();
this.data.putAll(targetPointer.data);
}
}
@Override
public String getData(String key) {
return this.data.getOrDefault(key, "");
if (data == null) {
return "";
}
return data.getOrDefault(key, "");
}
@Override
public TargetPointer withData(String key, String value) {
this.data.put(key, value);
if (data == null) {
data = new HashMap<>();
}
data.put(key, value);
return this;
}
}

View file

@ -141,8 +141,8 @@ public final class CardUtil {
*/
private static void adjustAbilityCost(Ability ability, int reduceCount) {
ManaCosts<ManaCost> adjustedCost = adjustCost(ability.getManaCostsToPay(), reduceCount);
ability.getManaCostsToPay().clear();
ability.getManaCostsToPay().addAll(adjustedCost);
ability.clearManaCostsToPay();
ability.addManaCostsToPay(adjustedCost);
}
private static ManaCosts<ManaCost> adjustCost(ManaCosts<ManaCost> manaCosts, int reduceCount) {
@ -304,8 +304,8 @@ public final class CardUtil {
increasedCost.add(manaCost.copy());
}
spellAbility.getManaCostsToPay().clear();
spellAbility.getManaCostsToPay().addAll(increasedCost);
spellAbility.clearManaCostsToPay();
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.setSourceFilter(previousCost.getSourceFilter()); // keep mana source restrictions
spellAbility.getManaCostsToPay().clear();
spellAbility.getManaCostsToPay().addAll(adjustedCost);
spellAbility.clearManaCostsToPay();
spellAbility.addManaCostsToPay(adjustedCost);
}
/**