Merge pull request #2253 from tool4ever/costTweaks

Cost tweaks
This commit is contained in:
Anthony Calosa
2023-01-18 18:11:43 +08:00
committed by GitHub
10 changed files with 44 additions and 42 deletions

View File

@@ -782,15 +782,9 @@ public class AiController {
return AiPlayDecision.CantAfford; return AiPlayDecision.CantAfford;
} }
} }
if (wardCost.hasSpecificCostType(CostPayLife.class)) { SpellAbilityAi topAI = new SpellAbilityAi() {};
int lifeToPay = wardCost.getCostPartByType(CostPayLife.class).convertAmount(); if (!topAI.willPayCosts(player, sa , wardCost, host)) {
if (lifeToPay > player.getLife() || (lifeToPay == player.getLife() && !player.cantLoseForZeroOrLessLife())) { return AiPlayDecision.CostNotAcceptable;
return AiPlayDecision.CantAfford;
}
}
if (wardCost.hasSpecificCostType(CostDiscard.class)
&& wardCost.getCostPartByType(CostDiscard.class).convertAmount() > player.getCardsIn(ZoneType.Hand).size()) {
return AiPlayDecision.CantAfford;
} }
} }
} }

View File

@@ -620,6 +620,9 @@ public abstract class GameState {
game.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated game.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
// prevent interactions with objects from old state
game.copyLastState();
// Set negative or zero life after state effects if need be, important for some puzzles that rely on // Set negative or zero life after state effects if need be, important for some puzzles that rely on
// pre-setting negative life (e.g. PS_NEO4). // pre-setting negative life (e.g. PS_NEO4).
for (int i = 0; i < playerStates.size(); i++) { for (int i = 0; i < playerStates.size(); i++) {

View File

@@ -64,16 +64,13 @@ public class SacrificeEffect extends SpellAbilityEffect {
table.replaceCounterEffect(game, sa, true); table.replaceCounterEffect(game, sa, true);
Cost cumCost = new Cost(sa.getParam("CumulativeUpkeep"), true);
Cost payCost = new Cost(ManaCost.ZERO, true); Cost payCost = new Cost(ManaCost.ZERO, true);
int n = card.getCounters(CounterEnumType.AGE); int n = card.getCounters(CounterEnumType.AGE);
if (n > 0) {
// multiply cost Cost cumCost = new Cost(sa.getParam("CumulativeUpkeep"), true);
for (int i = 0; i < n; ++i) { payCost.mergeTo(cumCost, n);
payCost.add(cumCost);
} }
sa.setCumulativeupkeep(true);
game.updateLastStateForCard(card); game.updateLastStateForCard(card);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();

View File

@@ -883,7 +883,24 @@ public class Cost implements Serializable {
return sb.toString(); return sb.toString();
} }
public void mergeTo(Cost source, int amt) {
// multiply to create the full cost
if (amt > 1) {
// to double itself we need to work on a copy
Cost sourceCpy = source.copy();
for (int i = 1; i < amt; ++i) {
// in theory setAmount could be used instead but it depends on the cost complexity (probably not worth trying to determine that first)
source.add(sourceCpy);
}
}
// combine costs (these shouldn't mix together)
this.add(source, false);
}
public Cost add(Cost cost1) { public Cost add(Cost cost1) {
return add(cost1, true);
}
public Cost add(Cost cost1, boolean mergeAdditional) {
CostPartMana costPart2 = this.getCostMana(); CostPartMana costPart2 = this.getCostMana();
List<CostPart> toRemove = Lists.newArrayList(); List<CostPart> toRemove = Lists.newArrayList();
for (final CostPart part : cost1.getCostParts()) { for (final CostPart part : cost1.getCostParts()) {
@@ -906,10 +923,10 @@ public class Cost implements Serializable {
} else { } else {
costParts.add(0, new CostPartMana(oldManaCost.toManaCost(), r)); costParts.add(0, new CostPartMana(oldManaCost.toManaCost(), r));
} }
} else if (part instanceof CostDiscard || part instanceof CostDraw } else if (part instanceof CostPutCounter || (mergeAdditional && // below usually not desired because they're from different causes
|| part instanceof CostAddMana || part instanceof CostPayLife (part instanceof CostDiscard || part instanceof CostDraw ||
|| part instanceof CostPutCounter || part instanceof CostTapType part instanceof CostAddMana || part instanceof CostPayLife ||
|| part instanceof CostExile) { part instanceof CostSacrifice || part instanceof CostTapType))) {
boolean alreadyAdded = false; boolean alreadyAdded = false;
for (final CostPart other : costParts) { for (final CostPart other : costParts) {
if ((other.getClass().equals(part.getClass()) || (part instanceof CostPutCounter && ((CostPutCounter)part).getCounter().is(CounterEnumType.LOYALTY))) && if ((other.getClass().equals(part.getClass()) || (part instanceof CostPutCounter && ((CostPutCounter)part).getCounter().is(CounterEnumType.LOYALTY))) &&
@@ -917,7 +934,7 @@ public class Cost implements Serializable {
StringUtils.isNumeric(part.getAmount()) && StringUtils.isNumeric(part.getAmount()) &&
StringUtils.isNumeric(other.getAmount())) { StringUtils.isNumeric(other.getAmount())) {
String amount = String.valueOf(part.convertAmount() + other.convertAmount()); String amount = String.valueOf(part.convertAmount() + other.convertAmount());
if (part instanceof CostPutCounter) { // path for Carth & Cumulative Upkeep if (part instanceof CostPutCounter) { // CR 606.5 path for Carth
if (other instanceof CostPutCounter && ((CostPutCounter)other).getCounter().equals(((CostPutCounter) part).getCounter())) { if (other instanceof CostPutCounter && ((CostPutCounter)other).getCounter().equals(((CostPutCounter) part).getCounter())) {
costParts.add(new CostPutCounter(amount, ((CostPutCounter) part).getCounter(), part.getType(), part.getTypeDescription())); costParts.add(new CostPutCounter(amount, ((CostPutCounter) part).getCounter(), part.getType(), part.getTypeDescription()));
} else if (other instanceof CostRemoveCounter && ((CostRemoveCounter)other).counter.is(CounterEnumType.LOYALTY)) { } else if (other instanceof CostRemoveCounter && ((CostRemoveCounter)other).counter.is(CounterEnumType.LOYALTY)) {
@@ -931,6 +948,8 @@ public class Cost implements Serializable {
} else { } else {
continue; continue;
} }
} else if (part instanceof CostSacrifice) {
costParts.add(new CostSacrifice(amount, part.getType(), part.getTypeDescription()));
} else if (part instanceof CostDiscard) { } else if (part instanceof CostDiscard) {
costParts.add(new CostDiscard(amount, part.getType(), part.getTypeDescription())); costParts.add(new CostDiscard(amount, part.getType(), part.getTypeDescription()));
} else if (part instanceof CostDraw) { } else if (part instanceof CostDraw) {
@@ -942,12 +961,6 @@ public class Cost implements Serializable {
costParts.add(new CostAddMana(amount, part.getType(), part.getTypeDescription())); costParts.add(new CostAddMana(amount, part.getType(), part.getTypeDescription()));
} else if (part instanceof CostPayLife) { } else if (part instanceof CostPayLife) {
costParts.add(new CostPayLife(amount, part.getTypeDescription())); costParts.add(new CostPayLife(amount, part.getTypeDescription()));
} else if (part instanceof CostExile) {
ZoneType z = ((CostExile) part).getFrom();
if (((CostExile) other).getFrom() != z) {
continue;
}
costParts.add(new CostExile(amount, part.getType(), part.getTypeDescription(), z));
} }
toRemove.add(other); toRemove.add(other);
alreadyAdded = true; alreadyAdded = true;

View File

@@ -112,7 +112,6 @@ public class CostAdjustment {
} }
final String scost = st.getParamOrDefault("Cost", "1"); final String scost = st.getParamOrDefault("Cost", "1");
Cost part = new Cost(scost, sa.isAbility());
int count = 0; int count = 0;
if (st.hasParam("ForEachShard")) { if (st.hasParam("ForEachShard")) {
@@ -155,8 +154,9 @@ public class CostAdjustment {
// Amount 1 as default // Amount 1 as default
count = 1; count = 1;
} }
for (int i = 0; i < count; ++i) { if (count > 0) {
cost.add(part); Cost part = new Cost(scost, sa.isAbility());
cost.mergeTo(part, count);
} }
} }
@@ -177,7 +177,7 @@ public class CostAdjustment {
originalCard.turnFaceDownNoUpdate(); originalCard.turnFaceDownNoUpdate();
isStateChangeToFaceDown = true; isStateChangeToFaceDown = true;
} }
} // isSpell }
CardCollection cardsOnBattlefield = new CardCollection(game.getCardsIn(ZoneType.Battlefield)); CardCollection cardsOnBattlefield = new CardCollection(game.getCardsIn(ZoneType.Battlefield));
cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Stack)); cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Stack));

View File

@@ -127,7 +127,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private boolean aftermath = false; private boolean aftermath = false;
private boolean cumulativeupkeep = false;
private boolean blessing = false; private boolean blessing = false;
private Integer chapter = null; private Integer chapter = null;
private boolean lastChapter = false; private boolean lastChapter = false;
@@ -516,6 +515,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return this.hasParam("Ninjutsu"); return this.hasParam("Ninjutsu");
} }
public boolean isCumulativeupkeep() {
return hasParam("CumulativeUpkeep");
}
public boolean isEpic() { public boolean isEpic() {
AbilitySub sub = this.getSubAbility(); AbilitySub sub = this.getSubAbility();
while (sub != null && !sub.hasParam("Epic")) { while (sub != null && !sub.hasParam("Epic")) {
@@ -2140,13 +2143,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return ForgeScript.spellAbilityHasProperty(this, property, sourceController, source, spellAbility); return ForgeScript.spellAbilityHasProperty(this, property, sourceController, source, spellAbility);
} }
public boolean isCumulativeupkeep() {
return cumulativeupkeep;
}
public void setCumulativeupkeep(boolean cumulativeupkeep0) {
cumulativeupkeep = cumulativeupkeep0;
}
// Return whether this spell tracks what color mana is spent to cast it for the sake of the effect // Return whether this spell tracks what color mana is spent to cast it for the sake of the effect
public boolean tracksManaSpent() { public boolean tracksManaSpent() {
if (hostCard == null || hostCard.getRules() == null) { return false; } if (hostCard == null || hostCard.getRules() == null) { return false; }

View File

@@ -72,7 +72,7 @@ public class StaticAbilityCantGainLosePayLife {
} }
if (!stAb.matchesValidParam("ValidCause", cause)) { if (!stAb.matchesValidParam("ValidCause", cause)) {
return false; continue;
} }
if (applyCommonAbility(stAb, player)) { if (applyCommonAbility(stAb, player)) {

View File

@@ -1,7 +1,7 @@
Name:Land Grant Name:Land Grant
ManaCost:1 G ManaCost:1 G
Types:Sorcery Types:Sorcery
S:Mode$ Continuous | CharacteristicDefining$ True | AddKeyword$ Alternative Cost:Reveal<1/Hand> | CheckSVar$ X | SVarCompare$ EQ0 | Description$ If you have no land cards in hand, you may reveal your hand rather than pay Land Grant's mana cost. S:Mode$ Continuous | CharacteristicDefining$ True | AddKeyword$ Alternative Cost:Reveal<1/Hand> | CheckSVar$ X | SVarCompare$ EQ0 | Description$ If you have no land cards in hand, you may reveal your hand rather than pay this spell's mana cost.
SVar:X:Count$TypeInYourHand.Land SVar:X:Count$TypeInYourHand.Land
A:SP$ ChangeZone | Cost$ 1 G | Origin$ Library | Destination$ Hand | ChangeType$ Forest | ChangeNum$ 1 | SpellDescription$ Search your library for a Forest card, reveal that card, put it into your hand, then shuffle. A:SP$ ChangeZone | Cost$ 1 G | Origin$ Library | Destination$ Hand | ChangeType$ Forest | ChangeNum$ 1 | SpellDescription$ Search your library for a Forest card, reveal that card, put it into your hand, then shuffle.
Oracle:If you have no land cards in hand, you may reveal your hand rather than pay this spell's mana cost.\nSearch your library for a Forest card, reveal that card, put it into your hand, then shuffle. Oracle:If you have no land cards in hand, you may reveal your hand rather than pay this spell's mana cost.\nSearch your library for a Forest card, reveal that card, put it into your hand, then shuffle.

View File

@@ -2,7 +2,7 @@ Name:The Lady of Otaria
ManaCost:3 R G ManaCost:3 R G
Types:Legendary Creature Avatar Types:Legendary Creature Avatar
PT:5/5 PT:5/5
SVar:AltCost:Cost$ tapXType<3/Creature.Dwarf> | Description$ You may tap three untapped Dwarves you control rather than pay this spell's mana cost. SVar:AltCost:Cost$ tapXType<3/Dwarf> | Description$ You may tap three untapped Dwarves you control rather than pay this spell's mana cost.
T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | Execute$ TrigDig | CheckSVar$ X | TriggerDescription$ At the beginning of each end step, if a land you controlled was put into your graveyard from the battlefield this turn, reveal the top four cards of your library. Put any number of Dwarf cards from among them into your hand and the rest on the bottom of your library in a random order. T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | Execute$ TrigDig | CheckSVar$ X | TriggerDescription$ At the beginning of each end step, if a land you controlled was put into your graveyard from the battlefield this turn, reveal the top four cards of your library. Put any number of Dwarf cards from among them into your hand and the rest on the bottom of your library in a random order.
SVar:TrigDig:DB$ Dig | DigNum$ 4 | ChangeValid$ Dwarf | DestinationZone$ Hand | RestRandomOrder$ True | AnyNumber$ True SVar:TrigDig:DB$ Dig | DigNum$ 4 | ChangeValid$ Dwarf | DestinationZone$ Hand | RestRandomOrder$ True | AnyNumber$ True
SVar:X:Count$ThisTurnEntered_Graveyard_from_Battlefield_Land.YouCtrl+YouOwn SVar:X:Count$ThisTurnEntered_Graveyard_from_Battlefield_Land.YouCtrl+YouOwn

View File

@@ -452,7 +452,6 @@ public class HumanPlay {
return false; return false;
} }
} }
return true;
} }
else if (part instanceof CostGainControl) { else if (part instanceof CostGainControl) {
int amount = Integer.parseInt(part.getAmount()); int amount = Integer.parseInt(part.getAmount());