diff --git a/.gitattributes b/.gitattributes
index a4a8337da55..63d2032dc49 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -517,6 +517,7 @@ forge-game/src/main/java/forge/game/cost/CostDamage.java -text
forge-game/src/main/java/forge/game/cost/CostDecisionMakerBase.java -text
forge-game/src/main/java/forge/game/cost/CostDiscard.java -text
forge-game/src/main/java/forge/game/cost/CostDraw.java -text
+forge-game/src/main/java/forge/game/cost/CostExert.java -text
forge-game/src/main/java/forge/game/cost/CostExile.java -text
forge-game/src/main/java/forge/game/cost/CostExileFromStack.java -text
forge-game/src/main/java/forge/game/cost/CostExiledMoveToGrave.java -text
@@ -16959,6 +16960,7 @@ forge-gui/res/cardsfolder/upcoming/hour_of_revelation.txt -text
forge-gui/res/cardsfolder/upcoming/khenra_eternal.txt -text
forge-gui/res/cardsfolder/upcoming/nicol_bolas_god_pharaoh.txt -text
forge-gui/res/cardsfolder/upcoming/nissa_genesis_mage.txt -text
+forge-gui/res/cardsfolder/upcoming/oasis_ritualist.txt -text
forge-gui/res/cardsfolder/upcoming/ramunap_excavator.txt -text
forge-gui/res/cardsfolder/upcoming/samut_the_tested.txt -text
forge-gui/res/cardsfolder/upcoming/steadfast_sentinel.txt -text
diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java
index 945f2dfbebf..b7451254003 100644
--- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java
+++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java
@@ -218,6 +218,36 @@ public class AiCostDecision extends CostDecisionMakerBase {
return chosen.isEmpty() ? null : PaymentDecision.card(chosen);
}
+ @Override
+ public PaymentDecision visit(CostExert cost) {
+ if (cost.payCostFromSource()) {
+ return PaymentDecision.card(source);
+ }
+
+ Integer c = cost.convertAmount();
+ if (c == null) {
+ if (ability.getSVar(cost.getAmount()).equals("XChoice")) {
+ return null;
+ }
+
+ c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
+ }
+
+ final CardCollection typeList = CardLists.getValidCards(player.getGame().getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), player, source, ability);
+
+ if (typeList.size() < c) {
+ return null;
+ }
+
+ CardLists.sortByPowerAsc(typeList);
+ final CardCollection res = new CardCollection();
+
+ for (int i = 0; i < c; i++) {
+ res.add(typeList.get(i));
+ }
+ return res.isEmpty() ? null : PaymentDecision.card(res);
+ }
+
@Override
public PaymentDecision visit(CostFlipCoin cost) {
Integer c = cost.convertAmount();
diff --git a/forge-game/src/main/java/forge/game/cost/Cost.java b/forge-game/src/main/java/forge/game/cost/Cost.java
index c642bebc684..df78ddc9347 100644
--- a/forge-game/src/main/java/forge/game/cost/Cost.java
+++ b/forge-game/src/main/java/forge/game/cost/Cost.java
@@ -436,6 +436,12 @@ public class Cost {
return new CostPutCardToLib(splitStr[0], splitStr[1], splitStr[2], description, ZoneType.Graveyard, true);
}
+ if (parse.startsWith("Exert<")) {
+ final String[] splitStr = abCostParse(parse, 3);
+ final String description = splitStr.length > 2 ? splitStr[2] : null;
+ return new CostExert(splitStr[0], splitStr[1], description);
+ }
+
// These won't show up with multiples
if (parse.equals("Untap") || parse.equals("Q")) {
return new CostUntap();
diff --git a/forge-game/src/main/java/forge/game/cost/CostExert.java b/forge-game/src/main/java/forge/game/cost/CostExert.java
new file mode 100644
index 00000000000..77251717889
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/cost/CostExert.java
@@ -0,0 +1,124 @@
+/*
+ * Forge: Play Magic: the Gathering.
+ * Copyright (C) 2011 Forge Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package forge.game.cost;
+
+import forge.game.card.Card;
+import forge.game.card.CardCollectionView;
+import forge.game.card.CardLists;
+import forge.game.player.Player;
+import forge.game.spellability.SpellAbility;
+import forge.game.zone.ZoneType;
+
+/**
+ * The Class CostExert.
+ */
+public class CostExert extends CostPartWithList {
+
+ /**
+ * Instantiates a new cost Exert.
+ *
+ * @param amount
+ * the amount
+ * @param type
+ * the type
+ * @param description
+ * the description
+ */
+ public CostExert(final String amount, final String type, final String description) {
+ super(amount, type, description);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see forge.card.cost.CostPart#toString()
+ */
+ @Override
+ public final String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("Exert ");
+
+ final Integer i = this.convertAmount();
+
+ if (this.payCostFromSource()) {
+ sb.append(this.getType());
+ } else {
+ final String desc = this.getTypeDescription() == null ? this.getType() : this.getTypeDescription();
+ if (i != null) {
+ sb.append(Cost.convertIntAndTypeToWords(i, desc));
+ } else {
+ sb.append(Cost.convertAmountTypeToWords(this.getAmount(), desc));
+ }
+ }
+ return sb.toString();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility,
+ * forge.Card, forge.Player, forge.card.cost.Cost)
+ */
+ @Override
+ public final boolean canPay(final SpellAbility ability) {
+ final Player activator = ability.getActivatingPlayer();
+ final Card source = ability.getHostCard();
+
+
+ if (!this.payCostFromSource()) {
+ boolean needsAnnoucement = ability.hasParam("Announce") && this.getType().contains(ability.getParam("Announce"));
+
+ CardCollectionView typeList = activator.getCardsIn(ZoneType.Battlefield);
+ typeList = CardLists.getValidCards(typeList, this.getType().split(";"), activator, source, ability);
+ final Integer amount = this.convertAmount();
+
+
+ if (!needsAnnoucement && (amount != null) && (typeList.size() < amount)) {
+ return false;
+ }
+
+ }
+
+ return true;
+ }
+
+ @Override
+ protected Card doPayment(SpellAbility ability, Card targetCard) {
+ targetCard.exert();
+ return targetCard;
+ }
+
+ /* (non-Javadoc)
+ * @see forge.card.cost.CostPartWithList#getHashForList()
+ */
+ @Override
+ public String getHashForLKIList() {
+ return "Exerted";
+ }
+ @Override
+ public String getHashForCardList() {
+ return "ExertedCards";
+ }
+
+ // Inputs
+ public T accept(ICostVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+}
diff --git a/forge-game/src/main/java/forge/game/cost/ICostVisitor.java b/forge-game/src/main/java/forge/game/cost/ICostVisitor.java
index a50e37e02fa..296ede6b058 100644
--- a/forge-game/src/main/java/forge/game/cost/ICostVisitor.java
+++ b/forge-game/src/main/java/forge/game/cost/ICostVisitor.java
@@ -10,6 +10,7 @@ public interface ICostVisitor {
public T visit(CostExile cost);
public T visit(CostExileFromStack cost);
public T visit(CostExiledMoveToGrave cost);
+ public T visit(CostExert cost);
public T visit(CostFlipCoin cost);
public T visit(CostMill cost);
public T visit(CostAddMana cost);
@@ -72,6 +73,11 @@ public interface ICostVisitor {
return null;
}
+ @Override
+ public T visit(CostExert cost) {
+ return null;
+ }
+
@Override
public T visit(CostFlipCoin cost) {
return null;
diff --git a/forge-gui/res/cardsfolder/upcoming/oasis_ritualist.txt b/forge-gui/res/cardsfolder/upcoming/oasis_ritualist.txt
new file mode 100644
index 00000000000..4f9a8e848f4
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/oasis_ritualist.txt
@@ -0,0 +1,8 @@
+Name:Oasis Ritualist
+ManaCost:3 G
+Types:Creature Naga Druid
+PT:2/4
+A:AB$ Mana | Cost$ T | Produced$ Any | SpellDescription$ Add one mana of any color to your mana pool.
+A:AB$ Mana | Cost$ T Exert<1/CARDNAME> | Produced$ Any | Amount$ 2 | SpellDescription$ Add two mana of any one color to your manna pool. (An exerted creature won't untap during your next untap step.)
+SVar:Picture:http://www.wizards.com/global/images/magic/general/oasis_ritualist.jpg
+Oracle:{T}: Add one mana of any color to your mana pool.\n{T}, Exert Oasis Ritualist: Add two mana of any one color to your manna pool. (An exerted creature won't untap during your next untap step.)
diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java
index 3b180f455aa..e94c8c20b1f 100644
--- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java
+++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java
@@ -441,6 +441,49 @@ public class HumanCostDecision extends CostDecisionMakerBase {
return PaymentDecision.card(choice);
}
+ @Override
+ public PaymentDecision visit(final CostExert cost) {
+ final String amount = cost.getAmount();
+ final String type = cost.getType();
+
+ CardCollectionView list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"), player, source, ability);
+
+ if (cost.payCostFromSource()) {
+ if (source.getController() == ability.getActivatingPlayer() && source.isInPlay()) {
+ return player.getController().confirmPayment(cost, "Exert " + source.getName() + "?",ability) ? PaymentDecision.card(source) : null;
+ }
+ else {
+ return null;
+ }
+ }
+
+ Integer c = cost.convertAmount();
+ if (c == null) {
+ // Generalize this
+ if (ability.getSVar(amount).equals("XChoice")) {
+ c = chooseXValue(list.size());
+ } else {
+ c = AbilityUtils.calculateAmount(source, amount, ability);
+ }
+ }
+ if (0 == c.intValue()) {
+ return PaymentDecision.number(0);
+ }
+ if (list.size() < c) {
+ return null;
+ }
+ final InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, c, c, list, ability);
+ inp.setMessage("Select a " + cost.getDescriptiveType() + " to exert (%d left)");
+ inp.setCancelAllowed(true);
+ inp.showAndWait();
+ if (inp.hasCancelled()) {
+ return null;
+ }
+
+ return PaymentDecision.card(inp.getSelected());
+
+ }
+
@Override
public PaymentDecision visit(final CostFlipCoin cost) {
final String amount = cost.getAmount();