mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-11 16:26:22 +00:00
Initial checkin for Waterbending (#9120)
This commit is contained in:
@@ -1345,9 +1345,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!effect) {
|
CostAdjustment.adjust(manaCost, sa, null, test, effect);
|
||||||
CostAdjustment.adjust(manaCost, sa, null, test);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("NumTimes".equals(sa.getParam("Announce"))) { // e.g. the Adversary cycle
|
if ("NumTimes".equals(sa.getParam("Announce"))) { // e.g. the Adversary cycle
|
||||||
ManaCost mkCost = sa.getPayCosts().getTotalMana();
|
ManaCost mkCost = sa.getPayCosts().getTotalMana();
|
||||||
@@ -1773,15 +1771,18 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Matches list of creatures to shards in mana cost for convoking.
|
* Matches list of creatures to shards in mana cost for convoking.
|
||||||
* @param cost cost of convoked ability
|
*
|
||||||
* @param list creatures to be evaluated
|
* @param cost cost of convoked ability
|
||||||
* @param improvise
|
* @param list creatures to be evaluated
|
||||||
|
* @param artifacts
|
||||||
|
* @param creatures
|
||||||
* @return map between creatures and shards to convoke
|
* @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<>();
|
final Map<Card, ManaCostShard> convoke = new HashMap<>();
|
||||||
Card convoked = null;
|
Card convoked = null;
|
||||||
if (!improvise) {
|
if (creatures && !artifacts) {
|
||||||
|
// Run for convoke but not improvise or waterbending
|
||||||
for (ManaCostShard toPay : cost) {
|
for (ManaCostShard toPay : cost) {
|
||||||
if (toPay.isSnow() || toPay.isColorless()) {
|
if (toPay.isSnow() || toPay.isColorless()) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -1382,7 +1382,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 Player ai = sa.getActivatingPlayer();
|
||||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
//Filter out mana sources that will interfere with payManaCost()
|
//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
|
// Filter out creatures if AI hasn't attacked yet
|
||||||
if (ph.isPlayerTurn(ai) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
if (ph.isPlayerTurn(ai) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
if (improvise) {
|
if (!creatures) {
|
||||||
untapped = CardLists.filter(untapped, c -> !c.isCreature());
|
untapped = CardLists.filter(untapped, c -> !c.isCreature());
|
||||||
} else {
|
} else {
|
||||||
|
// TODO AI needs to learn how to use Convoke or Waterbend
|
||||||
return new HashMap<>();
|
return new HashMap<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1406,13 +1407,16 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
if (!ai.getGame().getStack().isEmpty()) {
|
if (!ai.getGame().getStack().isEmpty()) {
|
||||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), null);
|
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), null);
|
||||||
for (Card c : blockers) {
|
for (Card c : blockers) {
|
||||||
if (objects.contains(c) && (!improvise || c.isArtifact())) {
|
if (objects.contains(c) && (creatures || c.isArtifact())) {
|
||||||
untapped.add(c);
|
untapped.add(c);
|
||||||
}
|
}
|
||||||
|
if (maxReduction != null && untapped.size() >= maxReduction) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ComputerUtilMana.getConvokeOrImproviseFromList(manaCost, untapped, improvise);
|
return ComputerUtilMana.getConvokeOrImproviseFromList(manaCost, untapped, artifacts, creatures);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import forge.game.*;
|
|||||||
import forge.game.ability.AbilityFactory.AbilityRecordType;
|
import forge.game.ability.AbilityFactory.AbilityRecordType;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.cost.CostAdjustment;
|
||||||
import forge.game.cost.IndividualCostPaymentInstance;
|
import forge.game.cost.IndividualCostPaymentInstance;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.keyword.KeywordInterface;
|
import forge.game.keyword.KeywordInterface;
|
||||||
@@ -1527,6 +1528,7 @@ public class AbilityUtils {
|
|||||||
else {
|
else {
|
||||||
cost = new Cost(unlessCost, true);
|
cost = new Cost(unlessCost, true);
|
||||||
}
|
}
|
||||||
|
cost = CostAdjustment.adjust(cost, sa, true);
|
||||||
return cost;
|
return cost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -188,6 +188,15 @@ public class Cost implements Serializable {
|
|||||||
return this.isAbility;
|
return this.isAbility;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final String getMaxWaterbend() {
|
||||||
|
for (CostPart cp : this.costParts) {
|
||||||
|
if (cp instanceof CostPartMana) {
|
||||||
|
return ((CostPartMana) cp).getMaxWaterbend();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private Cost() {
|
private Cost() {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -564,6 +573,11 @@ public class Cost implements Serializable {
|
|||||||
return new CostRevealChosen(splitStr[0], splitStr.length > 1 ? splitStr[1] : null);
|
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")) {
|
if (parse.equals("Forage")) {
|
||||||
return new CostForage();
|
return new CostForage();
|
||||||
}
|
}
|
||||||
@@ -973,6 +987,7 @@ public class Cost implements Serializable {
|
|||||||
} else {
|
} else {
|
||||||
costParts.add(0, new CostPartMana(manaCost.toManaCost(), null));
|
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
|
} else if (part instanceof CostPutCounter || (mergeAdditional && // below usually not desired because they're from different causes
|
||||||
(part instanceof CostDiscard || part instanceof CostDraw ||
|
(part instanceof CostDiscard || part instanceof CostDraw ||
|
||||||
part instanceof CostAddMana || part instanceof CostPayLife ||
|
part instanceof CostAddMana || part instanceof CostPayLife ||
|
||||||
|
|||||||
@@ -31,11 +31,13 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
public class CostAdjustment {
|
public class CostAdjustment {
|
||||||
|
|
||||||
public static Cost adjust(final Cost cost, final SpellAbility sa, boolean effect) {
|
public static Cost adjust(final Cost cost, final SpellAbility sa, boolean effect) {
|
||||||
if (sa.isTrigger() || cost == null || effect) {
|
if (sa.isTrigger() || cost == null || effect) {
|
||||||
|
sa.setMaxWaterbend(cost);
|
||||||
return cost;
|
return cost;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +101,9 @@ public class CostAdjustment {
|
|||||||
host.setState(CardStateName.Original, false);
|
host.setState(CardStateName.Original, false);
|
||||||
host.setFaceDown(false);
|
host.setFaceDown(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sa.setMaxWaterbend(result);
|
||||||
|
|
||||||
return 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.
|
// 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.
|
// 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) {
|
public static boolean adjust(ManaCostBeingPaid cost, final SpellAbility sa, CardCollection cardsToDelveOut, boolean test, boolean effect) {
|
||||||
if (sa.isTrigger() || sa.isReplacementAbility()) {
|
if (effect) {
|
||||||
|
adjustCostByWaterbend(cost, sa, test);
|
||||||
|
}
|
||||||
|
if (effect || sa.isTrigger() || sa.isReplacementAbility()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Game game = sa.getActivatingPlayer().getGame();
|
final Game game = sa.getActivatingPlayer().getGame();
|
||||||
final Card originalCard = sa.getHostCard();
|
final Card originalCard = sa.getHostCard();
|
||||||
boolean isStateChangeToFaceDown = false;
|
|
||||||
|
|
||||||
if (sa.isSpell()) {
|
boolean isStateChangeToFaceDown = false;
|
||||||
if (sa.isCastFaceDown() && !originalCard.isFaceDown()) {
|
if (sa.isSpell() && sa.isCastFaceDown() && !originalCard.isFaceDown()) {
|
||||||
// Turn face down to apply cost modifiers correctly
|
// Turn face down to apply cost modifiers correctly
|
||||||
originalCard.turnFaceDownNoUpdate();
|
originalCard.turnFaceDownNoUpdate();
|
||||||
isStateChangeToFaceDown = true;
|
isStateChangeToFaceDown = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection cardsOnBattlefield = new CardCollection(game.getCardsIn(ZoneType.Battlefield));
|
CardCollection cardsOnBattlefield = new CardCollection(game.getCardsIn(ZoneType.Battlefield));
|
||||||
@@ -278,17 +284,19 @@ public class CostAdjustment {
|
|||||||
table.triggerChangesZoneAll(game, sa);
|
table.triggerChangesZoneAll(game, sa);
|
||||||
}
|
}
|
||||||
if (sa.getHostCard().hasKeyword(Keyword.CONVOKE)) {
|
if (sa.getHostCard().hasKeyword(Keyword.CONVOKE)) {
|
||||||
adjustCostByConvokeOrImprovise(cost, sa, false, test);
|
adjustCostByConvokeOrImprovise(cost, sa, false, true, test);
|
||||||
}
|
}
|
||||||
if (sa.getHostCard().hasKeyword(Keyword.IMPROVISE)) {
|
if (sa.getHostCard().hasKeyword(Keyword.IMPROVISE)) {
|
||||||
adjustCostByConvokeOrImprovise(cost, sa, true, test);
|
adjustCostByConvokeOrImprovise(cost, sa, true, false, test);
|
||||||
}
|
}
|
||||||
} // isSpell
|
} // isSpell
|
||||||
|
|
||||||
if (sa.hasParam("TapCreaturesForMana")) {
|
if (sa.hasParam("TapCreaturesForMana")) {
|
||||||
adjustCostByConvokeOrImprovise(cost, sa, false, test);
|
adjustCostByConvokeOrImprovise(cost, sa, false, true, test);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
adjustCostByWaterbend(cost, sa, test);
|
||||||
|
|
||||||
// Reset card state (if changed)
|
// Reset card state (if changed)
|
||||||
if (isStateChangeToFaceDown) {
|
if (isStateChangeToFaceDown) {
|
||||||
originalCard.setFaceDown(false);
|
originalCard.setFaceDown(false);
|
||||||
@@ -299,6 +307,13 @@ public class CostAdjustment {
|
|||||||
}
|
}
|
||||||
// GetSpellCostChange
|
// 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) {
|
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).
|
// 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.
|
// 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);
|
return assistant.getController().helpPayForAssistSpell(cost, sa, genericLeft, requestedAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void adjustCostByConvokeOrImprovise(ManaCostBeingPaid cost, final SpellAbility sa, boolean improvise, boolean test) {
|
private static void adjustCostByConvokeOrImprovise(ManaCostBeingPaid cost, final SpellAbility sa, boolean artifacts, boolean creatures, boolean test) {
|
||||||
if (!improvise) {
|
if (creatures && !artifacts) {
|
||||||
sa.clearTappedForConvoke();
|
sa.clearTappedForConvoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
final Player activator = sa.getActivatingPlayer();
|
final Player activator = sa.getActivatingPlayer();
|
||||||
CardCollectionView untappedCards = CardLists.filter(activator.getCardsIn(ZoneType.Battlefield),
|
CardCollectionView untappedCards = CardLists.filter(activator.getCardsIn(ZoneType.Battlefield),
|
||||||
CardPredicates.CAN_TAP);
|
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);
|
untappedCards = CardLists.filter(untappedCards, CardPredicates.ARTIFACTS);
|
||||||
} else {
|
} else {
|
||||||
untappedCards = CardLists.filter(untappedCards, CardPredicates.CREATURES);
|
untappedCards = CardLists.filter(untappedCards, CardPredicates.CREATURES);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<Card, ManaCostShard> convokedCards = activator.getController().chooseCardsForConvokeOrImprovise(sa,
|
Map<Card, ManaCostShard> convokedCards = activator.getController().chooseCardsForConvokeOrImprovise(sa,
|
||||||
cost.toManaCost(), untappedCards, improvise);
|
cost.toManaCost(), untappedCards, artifacts, creatures, maxReduction);
|
||||||
|
|
||||||
CardCollection tapped = new CardCollection();
|
CardCollection tapped = new CardCollection();
|
||||||
for (final Entry<Card, ManaCostShard> conv : convokedCards.entrySet()) {
|
for (final Entry<Card, ManaCostShard> conv : convokedCards.entrySet()) {
|
||||||
Card c = conv.getKey();
|
Card c = conv.getKey();
|
||||||
if (!improvise) {
|
if (creatures && !artifacts) {
|
||||||
sa.addTappedForConvoke(c);
|
sa.addTappedForConvoke(c);
|
||||||
}
|
}
|
||||||
cost.decreaseShard(conv.getValue(), 1);
|
cost.decreaseShard(conv.getValue(), 1);
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ public class CostPartMana extends CostPart {
|
|||||||
private boolean isEnchantedCreatureCost = false;
|
private boolean isEnchantedCreatureCost = false;
|
||||||
private boolean isCostPayAnyNumberOfTimes = false;
|
private boolean isCostPayAnyNumberOfTimes = false;
|
||||||
|
|
||||||
|
protected String maxWaterbend;
|
||||||
|
|
||||||
public int paymentOrder() { return shouldPayLast() ? 200 : 0; }
|
public int paymentOrder() { return shouldPayLast() ? 200 : 0; }
|
||||||
|
|
||||||
public boolean shouldPayLast() {
|
public boolean shouldPayLast() {
|
||||||
@@ -63,6 +65,13 @@ public class CostPartMana extends CostPart {
|
|||||||
this.isEnchantedCreatureCost = enchantedCreatureCost;
|
this.isEnchantedCreatureCost = enchantedCreatureCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getMaxWaterbend() {
|
||||||
|
return maxWaterbend;
|
||||||
|
}
|
||||||
|
public void setMaxWaterbend(String max) {
|
||||||
|
maxWaterbend = max;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the mana.
|
* Gets the mana.
|
||||||
*
|
*
|
||||||
@@ -101,7 +110,7 @@ public class CostPartMana extends CostPart {
|
|||||||
public boolean isUndoable() { return true; }
|
public boolean isUndoable() { return true; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final String toString() {
|
public String toString() {
|
||||||
return cost.toString();
|
return cost.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
forge-game/src/main/java/forge/game/cost/CostWaterbend.java
Normal file
18
forge-game/src/main/java/forge/game/cost/CostWaterbend.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -202,7 +202,7 @@ public abstract class PlayerController {
|
|||||||
public abstract CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard);
|
public abstract CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard);
|
||||||
|
|
||||||
public abstract CardCollectionView chooseCardsToDelve(int genericAmount, CardCollection grave);
|
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 List<Card> chooseCardsForSplice(SpellAbility sa, List<Card> cards);
|
||||||
|
|
||||||
public abstract CardCollectionView chooseCardsToRevealFromHand(int min, int max, CardCollectionView valid);
|
public abstract CardCollectionView chooseCardsToRevealFromHand(int min, int max, CardCollectionView valid);
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
private final Supplier<CardCollection> tappedForConvoke = Suppliers.memoize(CardCollection::new);
|
private final Supplier<CardCollection> tappedForConvoke = Suppliers.memoize(CardCollection::new);
|
||||||
private Card sacrificedAsOffering;
|
private Card sacrificedAsOffering;
|
||||||
private Card sacrificedAsEmerge;
|
private Card sacrificedAsEmerge;
|
||||||
|
private Integer maxWaterbend;
|
||||||
|
|
||||||
private AbilityManaPart manaPart;
|
private AbilityManaPart manaPart;
|
||||||
|
|
||||||
@@ -2692,4 +2693,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
this.name = 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -428,6 +428,10 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
|||||||
game.getTriggerHandler().runTrigger(TriggerType.AbilityCast, runParams, true);
|
game.getTriggerHandler().runTrigger(TriggerType.AbilityCast, runParams, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sp.getPayCosts() != null && sp.getMaxWaterbend() != null) {
|
||||||
|
activator.triggerElementalBend(TriggerType.Waterbend);
|
||||||
|
}
|
||||||
|
|
||||||
// Run Cycled triggers
|
// Run Cycled triggers
|
||||||
if (sp.isCycling()) {
|
if (sp.isCycling()) {
|
||||||
activator.addCycled(sp);
|
activator.addCycled(sp);
|
||||||
|
|||||||
@@ -690,7 +690,7 @@ public class PlayerControllerForTests extends PlayerController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<Card, ManaCostShard> chooseCardsForConvokeOrImprovise(SpellAbility sa, ManaCost manaCost,
|
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
|
// TODO: AI to choose a creature to tap would go here
|
||||||
// Probably along with deciding how many creatures to tap
|
// Probably along with deciding how many creatures to tap
|
||||||
return new HashMap<>();
|
return new HashMap<>();
|
||||||
|
|||||||
8
forge-gui/res/cardsfolder/upcoming/geyser_leaper.txt
Normal file
8
forge-gui/res/cardsfolder/upcoming/geyser_leaper.txt
Normal 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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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}.)
|
||||||
@@ -27,18 +27,33 @@ public final class InputSelectCardsForConvokeOrImprovise extends InputSelectMany
|
|||||||
private final ManaCostBeingPaid remainingCost;
|
private final ManaCostBeingPaid remainingCost;
|
||||||
private final Player player;
|
private final Player player;
|
||||||
private final CardCollectionView availableCards;
|
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 cardType;
|
||||||
private final String description;
|
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);
|
super(controller, 0, Math.min(cost.getCMC(), untapped.size()), sa);
|
||||||
remainingCost = new ManaCostBeingPaid(cost);
|
remainingCost = new ManaCostBeingPaid(cost);
|
||||||
player = p;
|
player = p;
|
||||||
availableCards = untapped;
|
availableCards = untapped;
|
||||||
improvise = impr;
|
this.artifacts = artifacts;
|
||||||
cardType = impr ? "artifact" : "creature";
|
this.creatures = creatures;
|
||||||
description = impr ? "Improvise" : "Convoke";
|
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
|
@Override
|
||||||
@@ -49,6 +64,10 @@ public final class InputSelectCardsForConvokeOrImprovise extends InputSelectMany
|
|||||||
sb.append(sa.getStackDescription()).append("\n");
|
sb.append(sa.getStackDescription()).append("\n");
|
||||||
}
|
}
|
||||||
sb.append(TextUtil.concatNoSpace("Choose ", cardType, " to tap for ", description, ".\nRemaining mana cost is ", remainingCost.toString()));
|
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();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,10 +85,17 @@ public final class InputSelectCardsForConvokeOrImprovise extends InputSelectMany
|
|||||||
onSelectStateChanged(card, false);
|
onSelectStateChanged(card, false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (maxSelectable != null && chosenCards.size() >= maxSelectable) {
|
||||||
|
// Should show a different message if there's a max selectable
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
byte chosenColor;
|
byte chosenColor;
|
||||||
if (improvise) {
|
if (artifacts) {
|
||||||
|
// Waterbend/Improvise can be paid with colorless mana from artifacts
|
||||||
chosenColor = ManaCostShard.COLORLESS.getColorMask();
|
chosenColor = ManaCostShard.COLORLESS.getColorMask();
|
||||||
} else {
|
} else {
|
||||||
|
// Convoke can pay color or generic mana cost from creatures
|
||||||
ColorSet colors = card.getColor();
|
ColorSet colors = card.getColor();
|
||||||
if (colors.isMulticolor()) {
|
if (colors.isMulticolor()) {
|
||||||
//if card is multicolor, strip out any colors which can't be paid towards remaining cost
|
//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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPlayerSelected(final Player player, final ITriggerEvent triggerEvent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<Card, ManaCostShard> getConvokeMap() {
|
public Map<Card, ManaCostShard> getConvokeMap() {
|
||||||
if (hasCancelled()) {
|
if (hasCancelled()) {
|
||||||
return Maps.newHashMap();
|
return Maps.newHashMap();
|
||||||
|
|||||||
@@ -560,9 +560,7 @@ public class HumanPlay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CardCollection cardsToDelve = new CardCollection();
|
CardCollection cardsToDelve = new CardCollection();
|
||||||
if (!effect) {
|
CostAdjustment.adjust(toPay, ability, cardsToDelve, false, effect);
|
||||||
CostAdjustment.adjust(toPay, ability, cardsToDelve, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Card offering = null;
|
Card offering = null;
|
||||||
Card emerge = null;
|
Card emerge = null;
|
||||||
|
|||||||
@@ -2332,9 +2332,9 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<Card, ManaCostShard> chooseCardsForConvokeOrImprovise(final SpellAbility sa, final ManaCost manaCost,
|
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,
|
final InputSelectCardsForConvokeOrImprovise inp = new InputSelectCardsForConvokeOrImprovise(this, player,
|
||||||
manaCost, untappedCards, improvise, sa);
|
sa, manaCost, untappedCards, artifacts, creatures, maxReduction);
|
||||||
inp.showAndWait();
|
inp.showAndWait();
|
||||||
return inp.getConvokeMap();
|
return inp.getConvokeMap();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user