Compare commits

...

12 Commits

Author SHA1 Message Date
Hans Mackowiak
9671063e88 Fix Oasis of Renewal ActivationLimit 2025-11-10 16:31:53 +01:00
Hans Mackowiak
d493bd1d86 Fix Multi Trigger with Idris 2025-11-10 16:31:53 +01:00
Hans Mackowiak
dab85f9318 Update CardState.java 2025-11-10 16:31:53 +01:00
Hans Mackowiak
2d5c06f8d1 Update Trigger.java 2025-11-10 16:31:53 +01:00
Hans Mackowiak
0724fc5091 Update CardState.java 2025-11-10 16:31:53 +01:00
Hans Mackowiak
6768fbe1d7 Update CardState.java
copy abilityForTrigger
2025-11-10 16:31:53 +01:00
Hans Mackowiak
28b21f305a Trigger: reuse Execute for multiple Trigger 2025-11-10 16:31:53 +01:00
Fulgur14
40e9480c2c Apostrophes correction (#9130)
* Update ruinous_waterbending.txt

* Update waterbenders_restoration.txt
2025-11-10 14:33:39 +01:00
tool4ever
3db276f323 Secret of Bloodbending and support (#9129) 2025-11-10 12:18:53 +00:00
Chris H
c9a5fe9135 Initial checkin for Waterbending (#9120) 2025-11-10 10:27:44 +00:00
Fulgur14
219e3d6182 Update magmasaur.txt (#9125) 2025-11-10 07:30:19 +01:00
Jacob
ee7670abf6 Fix mana cost for Longshot, Rebel Bowman (#9124) 2025-11-09 16:04:34 +00:00
32 changed files with 262 additions and 89 deletions

View File

@@ -1345,9 +1345,7 @@ public class ComputerUtilMana {
}
}
if (!effect) {
CostAdjustment.adjust(manaCost, sa, null, test);
}
CostAdjustment.adjust(manaCost, sa, null, test, effect);
if ("NumTimes".equals(sa.getParam("Announce"))) { // e.g. the Adversary cycle
ManaCost mkCost = sa.getPayCosts().getTotalMana();
@@ -1773,15 +1771,18 @@ public class ComputerUtilMana {
/**
* Matches list of creatures to shards in mana cost for convoking.
* @param cost cost of convoked ability
* @param list creatures to be evaluated
* @param improvise
*
* @param cost cost of convoked ability
* @param list creatures to be evaluated
* @param artifacts
* @param creatures
* @return map between creatures and shards to convoke
*/
public static Map<Card, ManaCostShard> getConvokeOrImproviseFromList(final ManaCost cost, List<Card> list, boolean improvise) {
public static Map<Card, ManaCostShard> getConvokeOrImproviseFromList(final ManaCost cost, List<Card> list, boolean artifacts, boolean creatures) {
final Map<Card, ManaCostShard> convoke = new HashMap<>();
Card convoked = null;
if (!improvise) {
if (creatures && !artifacts) {
// Run for convoke but not improvise or waterbending
for (ManaCostShard toPay : cost) {
if (toPay.isSnow() || toPay.isColorless()) {
continue;

View File

@@ -1382,7 +1382,7 @@ public class PlayerControllerAi extends PlayerController {
}
@Override
public Map<Card, ManaCostShard> chooseCardsForConvokeOrImprovise(SpellAbility sa, ManaCost manaCost, CardCollectionView untappedCards, boolean improvise) {
public Map<Card, ManaCostShard> chooseCardsForConvokeOrImprovise(SpellAbility sa, ManaCost manaCost, CardCollectionView untappedCards, boolean artifacts, boolean creatures, Integer maxReduction) {
final Player ai = sa.getActivatingPlayer();
final PhaseHandler ph = ai.getGame().getPhaseHandler();
//Filter out mana sources that will interfere with payManaCost()
@@ -1390,9 +1390,10 @@ public class PlayerControllerAi extends PlayerController {
// Filter out creatures if AI hasn't attacked yet
if (ph.isPlayerTurn(ai) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
if (improvise) {
if (!creatures) {
untapped = CardLists.filter(untapped, c -> !c.isCreature());
} else {
// TODO AI needs to learn how to use Convoke or Waterbend
return new HashMap<>();
}
}
@@ -1406,13 +1407,16 @@ public class PlayerControllerAi extends PlayerController {
if (!ai.getGame().getStack().isEmpty()) {
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), null);
for (Card c : blockers) {
if (objects.contains(c) && (!improvise || c.isArtifact())) {
if (objects.contains(c) && (creatures || c.isArtifact())) {
untapped.add(c);
}
if (maxReduction != null && untapped.size() >= maxReduction) {
break;
}
}
}
}
return ComputerUtilMana.getConvokeOrImproviseFromList(manaCost, untapped, improvise);
return ComputerUtilMana.getConvokeOrImproviseFromList(manaCost, untapped, artifacts, creatures);
}
@Override

View File

@@ -75,11 +75,13 @@ public class Game {
private List<Card> activePlanes = null;
public final Phase cleanup;
public final Phase endOfCombat;
public final Phase endOfTurn;
public final Untap untap;
public final Phase upkeep;
public final Phase beginOfCombat;
public final Phase endOfCombat;
public final Phase endOfTurn;
public final Phase cleanup;
// to execute commands for "current" phase each time state based action is checked
public final List<GameCommand> sbaCheckedCommandList;
public final MagicStack stack;
@@ -363,9 +365,10 @@ public class Game {
untap = new Untap(this);
upkeep = new Phase(PhaseType.UPKEEP);
cleanup = new Phase(PhaseType.CLEANUP);
beginOfCombat = new Phase(PhaseType.COMBAT_BEGIN);
endOfCombat = new Phase(PhaseType.COMBAT_END);
endOfTurn = new Phase(PhaseType.END_OF_TURN);
cleanup = new Phase(PhaseType.CLEANUP);
sbaCheckedCommandList = new ArrayList<>();
@@ -428,6 +431,9 @@ public class Game {
public final Phase getUpkeep() {
return upkeep;
}
public final Phase getBeginOfCombat() {
return beginOfCombat;
}
public final Phase getEndOfCombat() {
return endOfCombat;
}

View File

@@ -15,6 +15,7 @@ import forge.game.*;
import forge.game.ability.AbilityFactory.AbilityRecordType;
import forge.game.card.*;
import forge.game.cost.Cost;
import forge.game.cost.CostAdjustment;
import forge.game.cost.IndividualCostPaymentInstance;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
@@ -1527,6 +1528,7 @@ public class AbilityUtils {
else {
cost = new Cost(unlessCost, true);
}
cost = CostAdjustment.adjust(cost, sa, true);
return cost;
}

View File

@@ -2,7 +2,6 @@ package forge.game.ability.effects;
import java.util.List;
import forge.GameCommand;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
@@ -28,10 +27,11 @@ public class ControlPlayerEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) {
final Player controller = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Controller"), sa).get(0);
final Game game = controller.getGame();
final boolean combat = sa.hasParam("Combat");
for (final Player pTarget: getTargetPlayers(sa)) {
// before next untap gain control
game.getCleanup().addUntil(pTarget, (GameCommand) () -> {
(combat ? game.getBeginOfCombat() : game.getCleanup()).addUntil(pTarget, () -> {
// CR 800.4b
if (!controller.isInGame()) {
return;
@@ -41,7 +41,7 @@ public class ControlPlayerEffect extends SpellAbilityEffect {
pTarget.addController(ts, controller);
// after following cleanup release control
game.getCleanup().addUntil((GameCommand) () -> pTarget.removeController(ts));
(combat ? game.getEndOfCombat() : game.getCleanup()).addUntil(() -> pTarget.removeController(ts));
});
}
}

View File

@@ -140,6 +140,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
// stores the card traits created by static abilities
private final Table<StaticAbility, String, SpellAbility> storedSpellAbility = TreeBasedTable.create();
private final Table<StaticAbility, String, Trigger> storedTrigger = TreeBasedTable.create();
private final Table<StaticAbility, SpellAbility, SpellAbility> storedAbilityForTrigger = TreeBasedTable.create();
private final Table<StaticAbility, String, ReplacementEffect> storedReplacementEffect = TreeBasedTable.create();
private final Table<StaticAbility, String, StaticAbility> storedStaticAbility = TreeBasedTable.create();
@@ -4974,7 +4975,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
String str = trig.toString() + trig.getId();
Trigger result = storedTrigger.get(stAb, str);
if (result == null) {
result = trig.copy(this, false);
SpellAbility ab = null;
if (trig.hasParam("Execute") && trig.getOverridingAbility() != null) {
ab = storedAbilityForTrigger.get(stAb, trig.getOverridingAbility());
if (ab == null) {
ab = trig.getOverridingAbility().copy(this, false);
storedAbilityForTrigger.put(stAb, trig.getOverridingAbility(), ab);
}
}
result = trig.copy(this, false, false, ab);
storedTrigger.put(stAb, str, result);
}
return result;

View File

@@ -79,6 +79,7 @@ public class CardState implements GameObject, IHasSVars, ITranslatable {
private FCollection<StaticAbility> staticAbilities = new FCollection<>();
private String imageKey = "";
private Map<String, String> sVars = Maps.newTreeMap();
private Map<String, SpellAbility> abilityForTrigger = Maps.newHashMap();
private KeywordCollection cachedKeywords = new KeywordCollection();
@@ -732,6 +733,11 @@ public class CardState implements GameObject, IHasSVars, ITranslatable {
setFlavorName(source.getFlavorName());
setSVars(source.getSVars());
abilityForTrigger.clear();
for (Map.Entry<String, SpellAbility> e : source.abilityForTrigger.entrySet()) {
abilityForTrigger.put(e.getKey(), e.getValue().copy(card, lki));
}
abilities.clear();
for (SpellAbility sa : source.abilities) {
if (sa.isIntrinsic()) {
@@ -758,7 +764,7 @@ public class CardState implements GameObject, IHasSVars, ITranslatable {
continue;
}
if (tr.isIntrinsic()) {
triggers.add(tr.copy(card, lki));
triggers.add(tr.copy(card, lki, false, tr.hasParam("Execute") ? abilityForTrigger.get(tr.getParam("Execute")) : null));
}
}
ReplacementEffect runRE = null;
@@ -951,6 +957,14 @@ public class CardState implements GameObject, IHasSVars, ITranslatable {
return cloakUp;
}
public SpellAbility getAbilityForTrigger(String svar) {
return abilityForTrigger.computeIfAbsent(svar, s -> AbilityFactory.getAbility(getCard(), s, this));
}
public boolean hasAbilityForTrigger(String svar) {
return abilityForTrigger.containsKey(svar);
}
@Override
public String getTranslationKey() {
String displayName = flavorName == null ? name : flavorName;

View File

@@ -188,6 +188,15 @@ public class Cost implements Serializable {
return this.isAbility;
}
public final String getMaxWaterbend() {
for (CostPart cp : this.costParts) {
if (cp instanceof CostPartMana) {
return ((CostPartMana) cp).getMaxWaterbend();
}
}
return null;
}
private Cost() {
}
@@ -564,6 +573,11 @@ public class Cost implements Serializable {
return new CostRevealChosen(splitStr[0], splitStr.length > 1 ? splitStr[1] : null);
}
if (parse.startsWith("Waterbend<")) {
final String[] splitStr = abCostParse(parse, 1);
return new CostWaterbend(splitStr[0]);
}
if (parse.equals("Forage")) {
return new CostForage();
}
@@ -973,6 +987,7 @@ public class Cost implements Serializable {
} else {
costParts.add(0, new CostPartMana(manaCost.toManaCost(), null));
}
getCostMana().setMaxWaterbend(mPart.getMaxWaterbend());
} else if (part instanceof CostPutCounter || (mergeAdditional && // below usually not desired because they're from different causes
(part instanceof CostDiscard || part instanceof CostDraw ||
part instanceof CostAddMana || part instanceof CostPayLife ||

View File

@@ -31,11 +31,13 @@ import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Predicate;
public class CostAdjustment {
public static Cost adjust(final Cost cost, final SpellAbility sa, boolean effect) {
if (sa.isTrigger() || cost == null || effect) {
sa.setMaxWaterbend(cost);
return cost;
}
@@ -99,6 +101,9 @@ public class CostAdjustment {
host.setState(CardStateName.Original, false);
host.setFaceDown(false);
}
sa.setMaxWaterbend(result);
return result;
}
@@ -171,21 +176,22 @@ public class CostAdjustment {
// If cardsToDelveOut is null, will immediately exile the delved cards and remember them on the host card.
// Otherwise, will return them in cardsToDelveOut and the caller is responsible for doing the above.
public static boolean adjust(ManaCostBeingPaid cost, final SpellAbility sa, CardCollection cardsToDelveOut, boolean test) {
if (sa.isTrigger() || sa.isReplacementAbility()) {
public static boolean adjust(ManaCostBeingPaid cost, final SpellAbility sa, CardCollection cardsToDelveOut, boolean test, boolean effect) {
if (effect) {
adjustCostByWaterbend(cost, sa, test);
}
if (effect || sa.isTrigger() || sa.isReplacementAbility()) {
return true;
}
final Game game = sa.getActivatingPlayer().getGame();
final Card originalCard = sa.getHostCard();
boolean isStateChangeToFaceDown = false;
if (sa.isSpell()) {
if (sa.isCastFaceDown() && !originalCard.isFaceDown()) {
// Turn face down to apply cost modifiers correctly
originalCard.turnFaceDownNoUpdate();
isStateChangeToFaceDown = true;
}
boolean isStateChangeToFaceDown = false;
if (sa.isSpell() && sa.isCastFaceDown() && !originalCard.isFaceDown()) {
// Turn face down to apply cost modifiers correctly
originalCard.turnFaceDownNoUpdate();
isStateChangeToFaceDown = true;
}
CardCollection cardsOnBattlefield = new CardCollection(game.getCardsIn(ZoneType.Battlefield));
@@ -278,17 +284,19 @@ public class CostAdjustment {
table.triggerChangesZoneAll(game, sa);
}
if (sa.getHostCard().hasKeyword(Keyword.CONVOKE)) {
adjustCostByConvokeOrImprovise(cost, sa, false, test);
adjustCostByConvokeOrImprovise(cost, sa, false, true, test);
}
if (sa.getHostCard().hasKeyword(Keyword.IMPROVISE)) {
adjustCostByConvokeOrImprovise(cost, sa, true, test);
adjustCostByConvokeOrImprovise(cost, sa, true, false, test);
}
} // isSpell
if (sa.hasParam("TapCreaturesForMana")) {
adjustCostByConvokeOrImprovise(cost, sa, false, test);
adjustCostByConvokeOrImprovise(cost, sa, false, true, test);
}
adjustCostByWaterbend(cost, sa, test);
// Reset card state (if changed)
if (isStateChangeToFaceDown) {
originalCard.setFaceDown(false);
@@ -299,6 +307,13 @@ public class CostAdjustment {
}
// GetSpellCostChange
private static void adjustCostByWaterbend(ManaCostBeingPaid cost, SpellAbility sa, boolean test) {
Integer maxWaterbend = sa.getMaxWaterbend();
if (maxWaterbend != null && maxWaterbend > 0) {
adjustCostByConvokeOrImprovise(cost, sa, true, true, test);
}
}
private static boolean adjustCostByAssist(ManaCostBeingPaid cost, final SpellAbility sa, boolean test) {
// 702.132a Assist is a static ability that modifies the rules of paying for the spell with assist (see rules 601.2g-h).
// If the total cost to cast a spell with assist includes a generic mana component, before you activate mana abilities while casting it, you may choose another player.
@@ -321,27 +336,33 @@ public class CostAdjustment {
return assistant.getController().helpPayForAssistSpell(cost, sa, genericLeft, requestedAmount);
}
private static void adjustCostByConvokeOrImprovise(ManaCostBeingPaid cost, final SpellAbility sa, boolean improvise, boolean test) {
if (!improvise) {
private static void adjustCostByConvokeOrImprovise(ManaCostBeingPaid cost, final SpellAbility sa, boolean artifacts, boolean creatures, boolean test) {
if (creatures && !artifacts) {
sa.clearTappedForConvoke();
}
final Player activator = sa.getActivatingPlayer();
CardCollectionView untappedCards = CardLists.filter(activator.getCardsIn(ZoneType.Battlefield),
CardPredicates.CAN_TAP);
if (improvise) {
Integer maxReduction = null;
if (artifacts && creatures) {
maxReduction = sa.getMaxWaterbend();
Predicate <Card> isArtifactOrCreature = card -> card.isArtifact() || card.isCreature();
untappedCards = CardLists.filter(untappedCards, isArtifactOrCreature);
} else if (artifacts) {
untappedCards = CardLists.filter(untappedCards, CardPredicates.ARTIFACTS);
} else {
untappedCards = CardLists.filter(untappedCards, CardPredicates.CREATURES);
}
Map<Card, ManaCostShard> convokedCards = activator.getController().chooseCardsForConvokeOrImprovise(sa,
cost.toManaCost(), untappedCards, improvise);
cost.toManaCost(), untappedCards, artifacts, creatures, maxReduction);
CardCollection tapped = new CardCollection();
for (final Entry<Card, ManaCostShard> conv : convokedCards.entrySet()) {
Card c = conv.getKey();
if (!improvise) {
if (creatures && !artifacts) {
sa.addTappedForConvoke(c);
}
cost.decreaseShard(conv.getValue(), 1);

View File

@@ -39,6 +39,8 @@ public class CostPartMana extends CostPart {
private boolean isEnchantedCreatureCost = false;
private boolean isCostPayAnyNumberOfTimes = false;
protected String maxWaterbend;
public int paymentOrder() { return shouldPayLast() ? 200 : 0; }
public boolean shouldPayLast() {
@@ -63,6 +65,13 @@ public class CostPartMana extends CostPart {
this.isEnchantedCreatureCost = enchantedCreatureCost;
}
public String getMaxWaterbend() {
return maxWaterbend;
}
public void setMaxWaterbend(String max) {
maxWaterbend = max;
}
/**
* Gets the mana.
*
@@ -101,7 +110,7 @@ public class CostPartMana extends CostPart {
public boolean isUndoable() { return true; }
@Override
public final String toString() {
public String toString() {
return cost.toString();
}

View File

@@ -0,0 +1,18 @@
package forge.game.cost;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
public class CostWaterbend extends CostPartMana {
public CostWaterbend(final String mana) {
super(new ManaCost(new ManaCostParser(mana)), null);
maxWaterbend = mana;
}
@Override
public final String toString() {
return "Waterbend " + getMana().toString();
}
}

View File

@@ -297,6 +297,7 @@ public class PhaseHandler implements java.io.Serializable {
case COMBAT_BEGIN:
nCombatsThisTurn++;
combat = new Combat(playerTurn);
game.getBeginOfCombat().executeUntil(playerTurn);
//PhaseUtil.verifyCombat();
break;
@@ -756,7 +757,6 @@ public class PhaseHandler implements java.io.Serializable {
}
}
}
// fire blockers declared trigger
final Map<AbilityKey, Object> bdRunParams = AbilityKey.newMap();
bdRunParams.put(AbilityKey.Blockers, declaredBlockers);
bdRunParams.put(AbilityKey.Attackers, blockedAttackers);
@@ -768,7 +768,6 @@ public class PhaseHandler implements java.io.Serializable {
continue;
}
// Run triggers
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Blocker, c1);
runParams.put(AbilityKey.Attackers, combat.getAttackersBlockedBy(c1));

View File

@@ -202,7 +202,7 @@ public abstract class PlayerController {
public abstract CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard);
public abstract CardCollectionView chooseCardsToDelve(int genericAmount, CardCollection grave);
public abstract Map<Card, ManaCostShard> chooseCardsForConvokeOrImprovise(SpellAbility sa, ManaCost manaCost, CardCollectionView untappedCards, boolean improvise);
public abstract Map<Card, ManaCostShard> chooseCardsForConvokeOrImprovise(SpellAbility sa, ManaCost manaCost, CardCollectionView untappedCards, boolean artifacts, boolean creatures, Integer maxReduction);
public abstract List<Card> chooseCardsForSplice(SpellAbility sa, List<Card> cards);
public abstract CardCollectionView chooseCardsToRevealFromHand(int min, int max, CardCollectionView valid);

View File

@@ -144,6 +144,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private final Supplier<CardCollection> tappedForConvoke = Suppliers.memoize(CardCollection::new);
private Card sacrificedAsOffering;
private Card sacrificedAsEmerge;
private Integer maxWaterbend;
private AbilityManaPart manaPart;
@@ -2692,4 +2693,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public void setName(String name) {
this.name = name;
}
public Integer getMaxWaterbend() {
return maxWaterbend;
}
public void setMaxWaterbend(Cost cost) {
if (cost == null || cost.getMaxWaterbend() == null) {
return;
}
maxWaterbend = AbilityUtils.calculateAmount(getHostCard(), cost.getMaxWaterbend(), this);
}
}

View File

@@ -544,14 +544,19 @@ public abstract class Trigger extends TriggerReplacementBase {
}
public final Trigger copy(Card newHost, boolean lki) {
return copy(newHost, lki, false);
return copy(newHost, lki, false, null);
}
public final Trigger copy(Card newHost, boolean lki, boolean keepTextChanges) {
return copy(newHost, lki, keepTextChanges, null);
}
public final Trigger copy(Card newHost, boolean lki, boolean keepTextChanges, SpellAbility spellAbility) {
final Trigger copy = (Trigger) clone();
copyHelper(copy, newHost, lki || keepTextChanges);
if (getOverridingAbility() != null) {
if (spellAbility != null) {
copy.setOverridingAbility(spellAbility);
} else if (getOverridingAbility() != null) {
copy.setOverridingAbility(getOverridingAbility().copy(newHost, lki));
}
@@ -611,7 +616,11 @@ public abstract class Trigger extends TriggerReplacementBase {
public SpellAbility ensureAbility(final IHasSVars sVarHolder) {
SpellAbility sa = getOverridingAbility();
if (sa == null && hasParam("Execute")) {
sa = AbilityFactory.getAbility(getHostCard(), getParam("Execute"), sVarHolder);
if (this.isIntrinsic() && sVarHolder instanceof CardState state) {
sa = state.getAbilityForTrigger(getParam("Execute"));
} else {
sa = AbilityFactory.getAbility(getHostCard(), getParam("Execute"), sVarHolder);
}
setOverridingAbility(sa);
}
return sa;

View File

@@ -428,6 +428,10 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
game.getTriggerHandler().runTrigger(TriggerType.AbilityCast, runParams, true);
}
if (sp.getMaxWaterbend() != null) {
activator.triggerElementalBend(TriggerType.Waterbend);
}
// Run Cycled triggers
if (sp.isCycling()) {
activator.addCycled(sp);

View File

@@ -690,7 +690,7 @@ public class PlayerControllerForTests extends PlayerController {
@Override
public Map<Card, ManaCostShard> chooseCardsForConvokeOrImprovise(SpellAbility sa, ManaCost manaCost,
CardCollectionView untappedCards, boolean improvise) {
CardCollectionView untappedCards, boolean artifacts, boolean creatures, Integer maxReduction) {
// TODO: AI to choose a creature to tap would go here
// Probably along with deciding how many creatures to tap
return new HashMap<>();

View File

@@ -3,6 +3,6 @@ ManaCost:1 G
Types:Sorcery
A:SP$ Pump | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ DBDamage | AILogic$ PowerDmg | StackDescription$ {c:ThisTargetedCard} | SpellDescription$ Target creature you control
SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature | TgtPrompt$ Select another target creature | TargetUnique$ True | AILogic$ PowerDmg | NumDmg$ X | DamageSource$ ParentTarget | ExcessSVar$ Excess | SubAbility$ DBDiscover | StackDescription$ REP another target creature_{c:ThisTargetedCard} | SpellDescription$ deals damage equal to its power to another target creature.
SVar:DBDiscover:DB$ Discover | Num$ Excess | StackDescription$ SpellDescription | SpellDescription$ If excess damage was dealt this way, discover X, where X is that excess damage. (Exile cards from the top of your library until you exile a nonland card with that mana value or less. Cast it without paying its mana cost or put it into your hand. Put the rest on the bottom in a random order.)
SVar:DBDiscover:DB$ Discover | Num$ Excess | ConditionCheckSVar$ Excess | StackDescription$ SpellDescription | SpellDescription$ If excess damage was dealt this way, discover X, where X is that excess damage. (Exile cards from the top of your library until you exile a nonland card with that mana value or less. Cast it without paying its mana cost or put it into your hand. Put the rest on the bottom in a random order.)
SVar:X:ParentTargeted$CardPower
Oracle:Target creature you control deals damage equal to its power to another target creature. If excess damage was dealt this way, discover X, where X is that excess damage. (Exile cards from the top of your library until you exile a nonland card with that mana value or less. Cast it without paying its mana cost or put it into your hand. Put the rest on the bottom in a random order.)

View File

@@ -4,9 +4,8 @@ Types:Creature Elemental Dinosaur
PT:0/0
K:etbCounter:P1P1:5
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ SelfDestruct | TriggerDescription$ At the beginning of your upkeep, you may remove a +1/+1 counter from CARDNAME. If you don't, sacrifice CARDNAME and it deals damage equal to the number of +1/+1 counters on it to each creature without flying and each player.
SVar:SelfDestruct:DB$ Sacrifice | RememberSacrificed$ True | UnlessCost$ SubCounter<1/P1P1> | UnlessPayer$ You | SubAbility$ LetEmHaveIt
SVar:LetEmHaveIt:DB$ DamageAll | ValidCards$ Creature.withoutFlying | ValidPlayers$ Player | NumDmg$ X | ConditionDefined$ Remembered | ConditionPresent$ Card | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:SelfDestruct:DB$ Sacrifice | UnlessCost$ SubCounter<1/P1P1> | UnlessPayer$ You | UnlessResolveSubs$ WhenNotPaid | SubAbility$ LetEmHaveIt
SVar:LetEmHaveIt:DB$ DamageAll | ValidCards$ Creature.withoutFlying | ValidPlayers$ Player | NumDmg$ X
SVar:X:Count$CardCounters.P1P1
AI:RemoveDeck:All
Oracle:Magmasaur enters with five +1/+1 counters on it.\nAt the beginning of your upkeep, you may remove a +1/+1 counter from Magmasaur. If you don't, sacrifice Magmasaur and it deals damage equal to the number of +1/+1 counters on it to each creature without flying and each player.

View File

@@ -1,18 +1,10 @@
Name:Oasis of Renewal
ManaCost:B G U
Types:Legendary Enchantment
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSeekLand | TriggerDescription$ When CARDNAME enters and whenever a land card leaves your graveyard, seek a land card. This ability triggers only once each turn.
T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Any | ValidCard$ Card.Land+YouOwn | TriggerZones$ Battlefield | Execute$ TrigSeekLand | Secondary$ True | CheckSVar$ X | SVarCompare$ LT1 | TriggerDescription$ When CARDNAME enters and whenever a land card leaves your graveyard, seek a land card. This ability triggers only once each turn.
SVar:TrigSeekLand:DB$ Seek | Type$ Card.Land | SubAbility$ DBLogLand
SVar:DBLogLand:DB$ StoreSVar | SVar$ X | Type$ Number | Expression$ 1
SVar:X:Number$0
T:Mode$ Phase | Phase$ Cleanup | TriggerZones$ Battlefield | Execute$ DBLandClean | Static$ True
SVar:DBLandClean:DB$ StoreSVar | SVar$ X | Type$ Number | Expression$ 0
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSeekNonLand | TriggerDescription$ When CARDNAME enters and whenever a nonland card leaves your graveyard, seek a nonland card. This ability triggers only once each turn.
T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Any | ValidCard$ Card.nonLand+YouOwn | TriggerZones$ Battlefield | Execute$ TrigSeekNonLand | Secondary$ True | CheckSVar$ Y | SVarCompare$ LT1 | TriggerDescription$ When CARDNAME enters and whenever a nonland card leaves your graveyard, seek a nonland card. This ability triggers only once each turn.
SVar:TrigSeekNonLand:DB$ Seek | Type$ Card.nonLand | SubAbility$ DBLogNonLand
SVar:DBLogNonLand:DB$ StoreSVar | SVar$ Y | Type$ Number | Expression$ 1
SVar:Y:Number$0
T:Mode$ Phase | Phase$ Cleanup | TriggerZones$ Battlefield | Execute$ DBNonLandClean | Static$ True
SVar:DBNonLandClean:DB$ StoreSVar | SVar$ Y | Type$ Number | Expression$ 0
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | ActivationLimit$ 1 | Execute$ TrigSeekLand | TriggerDescription$ When CARDNAME enters and whenever a land card leaves your graveyard, seek a land card. This ability triggers only once each turn.
T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Any | ValidCard$ Card.Land+YouOwn | TriggerZones$ Battlefield | ActivationLimit$ 1 | Execute$ TrigSeekLand | Secondary$ True | TriggerDescription$ When CARDNAME enters and whenever a land card leaves your graveyard, seek a land card. This ability triggers only once each turn.
SVar:TrigSeekLand:DB$ Seek | Type$ Card.Land
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | ActivationLimit$ 1 | Execute$ TrigSeekNonLand | TriggerDescription$ When CARDNAME enters and whenever a nonland card leaves your graveyard, seek a nonland card. This ability triggers only once each turn.
T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Any | ValidCard$ Card.nonLand+YouOwn | TriggerZones$ Battlefield | ActivationLimit$ 1 | Execute$ TrigSeekNonLand | Secondary$ True | TriggerDescription$ When CARDNAME enters and whenever a nonland card leaves your graveyard, seek a nonland card. This ability triggers only once each turn.
SVar:TrigSeekNonLand:DB$ Seek | Type$ Card.nonLand
Oracle:When Oasis of Renewal enters and whenever a land card leaves your graveyard, seek a land card. This ability triggers only once each turn.\nWhen Oasis of Renewal enters and whenever a nonland card leaves your graveyard, seek a nonland card. This ability triggers only once each turn.

View File

@@ -2,6 +2,6 @@ Name:Of One Mind
ManaCost:2 U
Types:Sorcery
S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ 2 | EffectZone$ All | IsPresent$ Creature.Human+YouCtrl | CheckSVar$ Count$Valid Creature.nonHuman+YouCtrl | Description$ CARDNAME costs {2} less to cast if you control a Human creature and a non-Human creature.
DeckHints:Type$Human
A:SP$ Draw | NumCards$ 2 | SpellDescription$ Draw two cards.
DeckHints:Type$Human
Oracle:This spell costs {2} less to cast if you control a Human creature and a non-Human creature.\nDraw two cards.

View File

@@ -0,0 +1,8 @@
Name:Geyser Leaper
ManaCost:4 U
Types:Creature Human Warrior Ally
PT:4/3
K:Flying
A:AB$ Draw | Cost$ Waterbend<4> | NumCards$ 1 | SubAbility$ DBDiscard | SpellDescription$ Draw a card, then discard a card. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)
SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose
Oracle:Flying\nWaterbend 4: Draw a card, then discard a card.

View File

@@ -1,5 +1,5 @@
Name:Longshot, Rebel Bowman
ManaCost:3 W
ManaCost:3 R
Types:Legendary Creature Human Rebel Ally
PT:3/3
K:Reach

View File

@@ -0,0 +1,9 @@
Name:Ruinous Waterbending
ManaCost:1 B B
Types:Sorcery Lesson
S:Mode$ OptionalCost | EffectZone$ All | ValidCard$ Card.Self | ValidSA$ Spell | Cost$ Waterbend<4> | Description$ As an additional cost to cast this spell, you may waterbend {4}. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)
A:SP$ PumpAll | ValidCards$ Creature | NumAtt$ -2 | NumDef$ -2 | IsCurse$ True | SubAbility$ DBEffect | SpellDescription$ All creatures get -2/-2 until end of turn. If this spell's additional cost was paid, whenever a creature dies this turn, you gain 1 life.
SVar:DBEffect:DB$ Effect | Triggers$ TrigDies | Condition$ OptionalCost | ConditionOptionalPaid$ True
SVar:TrigDies:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature | Execute$ TrigGainLife | TriggerDescription$ Whenever a creature dies this turn, you gain 1 life.
SVar:TrigGainLife:DB$ GainLife | LifeAmount$ 1
Oracle:As an additional cost to cast this spell, you may waterbend {4}. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)\nAll creatures get -2/-2 until end of turn. If this spell's additional cost was paid, whenever a creature dies this turn, you gain 1 life.

View File

@@ -0,0 +1,9 @@
Name:Secret of Bloodbending
ManaCost:U U U U
Types:Sorcery Lesson
S:Mode$ OptionalCost | EffectZone$ All | ValidCard$ Card.Self | ValidSA$ Spell | Cost$ Waterbend<10> | Description$ As an additional cost to cast this spell, you may waterbend {10}.
A:SP$ ControlPlayer | ValidTgts$ Opponent | Condition$ OptionalCost | ConditionOptionalPaid$ False | Combat$ True | SubAbility$ ControlTurn | SpellDescription$ You control target opponent during their next combat phase. If this spells additional cost was paid, you control that player during their next turn instead. Exile CARDNAME.
SVar:ControlTurn:DB$ ControlPlayer | Defined$ Targeted | Condition$ OptionalCost | ConditionOptionalPaid$ True | SubAbility$ DBExile
SVar:DBExile:DB$ ChangeZone | Defined$ Self | Origin$ Stack | Destination$ Exile
SVar:X:Count$xPaid
Oracle:As an additional cost to cast this spell, you may waterbend {10}.\nYou control target opponent during their next combat phase. If this spells additional cost was paid, you control that player during their next turn instead. (You see all cards that player could see and make all decisions for them.)\nExile Secret of Bloodbending.

View File

@@ -4,7 +4,7 @@ Types:Legendary Creature Human Pilot Ally
PT:1/4
K:Flying
T:Mode$ AttackersDeclared | AttackingPlayer$ You | ValidAttackers$ Creature.withFlying | Execute$ TrigDraw | TriggerZones$ Battlefield | TriggerDescription$ Whenever one or more creatures you control with flying attack, draw a card, then discard a card. When you discard a nonland card this way, put a +1/+1 counter on target creature you control.
SVar:DBDraw:DB$ Draw | SubAbility$ DBDiscard | SpellDescription$ Draw a card, then discard a card.
SVar:TrigDraw:DB$ Draw | SubAbility$ DBDiscard
SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | RememberDiscarded$ True | SubAbility$ DBImmediateTrig
SVar:DBImmediateTrig:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card.nonLand | ConditionCompare$ GE1 | Execute$ TrigPutCounter | SubAbility$ DBCleanup | SpellDescription$ When you discard a nonland card this way, put a +1/+1 counter on target creature you control.
SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ P1P1 | CounterNum$ 1

View File

@@ -0,0 +1,10 @@
Name:Waterbender's Restoration
ManaCost:U U
Types:Instant Lesson
S:Mode$ RaiseCost | ValidCard$ Card.Self | Activator$ You | Type$ Spell | Cost$ Waterbend<X> | EffectZone$ All | Description$ As an additional cost to cast this spell, waterbend {X}. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)
A:SP$ ChangeZone | ValidTgts$ Creature.YouCtrl | Announce$ X | TargetMin$ X | TargetMax$ X | Origin$ Battlefield | Destination$ Exile | TgtPrompt$ Select target creature you control | SubAbility$ DelTrig | RememberChanged$ True | SpellDescription$ Exile X target creatures you control. Return those cards to the battlefield under their owner's control at the beginning of the next end step.
SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ TrigReturn | RememberObjects$ RememberedLKI | TriggerDescription$ Return exiled permanent to the battlefield. | SubAbility$ DBCleanup
SVar:TrigReturn:DB$ ChangeZone | Origin$ Exile | Destination$ Battlefield | Defined$ DelayTriggerRememberedLKI
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$xPaid
Oracle:As an additional cost to cast this spell, waterbend {X}. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)\nExile X target creatures you control. Return those cards to the battlefield under their owner's control at the beginning of the next end step.

View File

@@ -0,0 +1,7 @@
Name:Waterbending Lesson
ManaCost:3 U
Types:Sorcery Lesson
A:SP$ Draw | NumCards$ 3 | SubAbility$ DBDiscard | SpellDescription$ Draw three cards. Then discard a card unless you waterbend {2}. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)
SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | UnlessCost$ Waterbend<2> | UnlessPayer$ You
DeckHas:Ability$Discard
Oracle:Draw three cards. Then discard a card unless you waterbend {2}. (While paying a waterbend cost, you can tap your artifacts and creatures to help. Each one pays for {1}.)

View File

@@ -4,12 +4,9 @@ Types:Legendary Creature Human Warlock
PT:3/3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Enchantment.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigSurveil | TriggerDescription$ Eerie — Whenever an enchantment you control enters and whenever you fully unlock a Room, surveil 2 if this is the first time this ability has resolved this turn. If it's the second time, each opponent discards a card. If it's the third time, put a creature card from a graveyard onto the battlefield under your control.
T:Mode$ FullyUnlock | ValidCard$ Card.Room | ValidPlayer$ You | Secondary$ True | Execute$ TrigSurveil | TriggerZones$ Battlefield | TriggerDescription$ Eerie — Whenever an enchantment you control enters and whenever you fully unlock a Room, surveil 2 if this is the first time this ability has resolved this turn. If it's the second time, each opponent discards a card. If it's the third time, put a creature card from a graveyard onto the battlefield under your control.
SVar:TrigSurveil:DB$ Surveil | Amount$ 2 | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ1 | SubAbility$ DBDiscard
SVar:DBDiscard:DB$ Discard | Defined$ Player.Opponent | Mode$ TgtChoose | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ2 | SubAbility$ DBChangeZone
SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Creature | ChangeNum$ 1 | Mandatory$ True | GainControl$ True | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ3 | SelectPrompt$ Select a creature card in a graveyard | Hidden$ True | SubAbility$ DBLog
SVar:DBLog:DB$ StoreSVar | SVar$ X | Type$ CountSVar | Expression$ X/Plus.1
SVar:X:Number$1
T:Mode$ Phase | Phase$ Cleanup | TriggerZones$ Battlefield | Execute$ DBCleanup | Static$ True
SVar:DBCleanup:DB$ StoreSVar | SVar$ X | Type$ Number | Expression$ 1
SVar:TrigSurveil:DB$ Surveil | Amount$ 2 | ConditionCheckSVar$ Resolved | ConditionSVarCompare$ EQ1 | SubAbility$ DBDiscard
SVar:DBDiscard:DB$ Discard | Defined$ Player.Opponent | Mode$ TgtChoose | ConditionCheckSVar$ Resolved | ConditionSVarCompare$ EQ2 | SubAbility$ DBChangeZone
SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Creature | ChangeNum$ 1 | Mandatory$ True | GainControl$ True | ConditionCheckSVar$ Resolved | ConditionSVarCompare$ EQ3 | SelectPrompt$ Select a creature card in a graveyard | Hidden$ True
SVar:Resolved:Count$ResolvedThisTurn
DeckNeeds:Type$Enchantment
Oracle:Eerie — Whenever an enchantment you control enters and whenever you fully unlock a Room, surveil 2 if this is the first time this ability has resolved this turn. If it's the second time, each opponent discards a card. If it's the third time, put a creature card from a graveyard onto the battlefield under your control.

View File

@@ -27,18 +27,33 @@ public final class InputSelectCardsForConvokeOrImprovise extends InputSelectMany
private final ManaCostBeingPaid remainingCost;
private final Player player;
private final CardCollectionView availableCards;
private final boolean improvise;
private final boolean artifacts;
private final boolean creatures;
private final Integer maxSelectable;
private final String cardType;
private final String description;
public InputSelectCardsForConvokeOrImprovise(final PlayerControllerHuman controller, final Player p, final ManaCost cost, final CardCollectionView untapped, boolean impr, final SpellAbility sa) {
public InputSelectCardsForConvokeOrImprovise(final PlayerControllerHuman controller, final Player p, final SpellAbility sa, final ManaCost cost, final CardCollectionView untapped, boolean artifacts, boolean creatures, Integer maxReduction) {
super(controller, 0, Math.min(cost.getCMC(), untapped.size()), sa);
remainingCost = new ManaCostBeingPaid(cost);
player = p;
availableCards = untapped;
improvise = impr;
cardType = impr ? "artifact" : "creature";
description = impr ? "Improvise" : "Convoke";
this.artifacts = artifacts;
this.creatures = creatures;
this.maxSelectable = maxReduction;
if (artifacts && creatures) {
cardType = "artifact or creature";
description = "Waterbend";
} else if (!artifacts && !creatures) {
throw new IllegalArgumentException("At least one of artifacts or creatures must be true");
} else if (creatures) {
cardType = "creature";
description = "Convoke";
} else {
cardType = "artifact";
description = "Improvise";
}
}
@Override
@@ -49,6 +64,10 @@ public final class InputSelectCardsForConvokeOrImprovise extends InputSelectMany
sb.append(sa.getStackDescription()).append("\n");
}
sb.append(TextUtil.concatNoSpace("Choose ", cardType, " to tap for ", description, ".\nRemaining mana cost is ", remainingCost.toString()));
if (maxSelectable != null) {
sb.append(". You may select up to ").append(chosenCards.size() - maxSelectable).append(" more ").append(cardType).append("(s).");
}
return sb.toString();
}
@@ -66,10 +85,17 @@ public final class InputSelectCardsForConvokeOrImprovise extends InputSelectMany
onSelectStateChanged(card, false);
}
else {
if (maxSelectable != null && chosenCards.size() >= maxSelectable) {
// Should show a different message if there's a max selectable
return false;
}
byte chosenColor;
if (improvise) {
if (artifacts) {
// Waterbend/Improvise can be paid with colorless mana from artifacts
chosenColor = ManaCostShard.COLORLESS.getColorMask();
} else {
// Convoke can pay color or generic mana cost from creatures
ColorSet colors = card.getColor();
if (colors.isMulticolor()) {
//if card is multicolor, strip out any colors which can't be paid towards remaining cost
@@ -107,10 +133,6 @@ public final class InputSelectCardsForConvokeOrImprovise extends InputSelectMany
return null;
}
@Override
protected void onPlayerSelected(final Player player, final ITriggerEvent triggerEvent) {
}
public Map<Card, ManaCostShard> getConvokeMap() {
if (hasCancelled()) {
return Maps.newHashMap();

View File

@@ -560,9 +560,7 @@ public class HumanPlay {
}
CardCollection cardsToDelve = new CardCollection();
if (!effect) {
CostAdjustment.adjust(toPay, ability, cardsToDelve, false);
}
CostAdjustment.adjust(toPay, ability, cardsToDelve, false, effect);
Card offering = null;
Card emerge = null;

View File

@@ -2332,9 +2332,9 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
@Override
public Map<Card, ManaCostShard> chooseCardsForConvokeOrImprovise(final SpellAbility sa, final ManaCost manaCost,
final CardCollectionView untappedCards, boolean improvise) {
final CardCollectionView untappedCards, boolean artifacts, boolean creatures, Integer maxReduction) {
final InputSelectCardsForConvokeOrImprovise inp = new InputSelectCardsForConvokeOrImprovise(this, player,
manaCost, untappedCards, improvise, sa);
sa, manaCost, untappedCards, artifacts, creatures, maxReduction);
inp.showAndWait();
return inp.getConvokeMap();
}