mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
Add Forage Cost (#5633)
* Add Forage Cost * ~ fix PaymentDecision for now * TriggerForage: add Forage trigger * CostForage: Add First Part of Human Sacrifice Food Logic * Human ready, first Forage card * AiCostDecision: Basic AI for Forage
This commit is contained in:
@@ -264,6 +264,20 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
return PaymentDecision.number(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(final CostForage cost) {
|
||||
CardCollection food = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Food"), CardPredicates.canBeSacrificedBy(ability, isEffect()));
|
||||
CardCollection exile = CardLists.filter(player.getCardsIn(ZoneType.Graveyard), CardPredicates.canExiledBy(ability, isEffect()));
|
||||
if (!food.isEmpty()) {
|
||||
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
||||
CardCollectionView list = aic.chooseSacrificeType("Food", ability, isEffect(), 1, null);
|
||||
return list == null ? null : PaymentDecision.card(list);
|
||||
} else {
|
||||
CardCollectionView chosen = ComputerUtil.chooseExileFromList(player, exile, source, 3, ability, isEffect());
|
||||
return null == chosen ? null : PaymentDecision.card(chosen);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostRollDice cost) {
|
||||
int c = cost.getAbilityAmount(ability);
|
||||
|
||||
@@ -708,6 +708,11 @@ public class ComputerUtil {
|
||||
typeList = new CardCollection(ai.getCardsIn(cost.from));
|
||||
}
|
||||
typeList = CardLists.getValidCards(typeList, cost.getType().split(";"), activate.getController(), activate, sa);
|
||||
|
||||
return chooseExileFromList(ai, typeList, activate, amount, sa, effect);
|
||||
}
|
||||
|
||||
public static CardCollection chooseExileFromList(final Player ai, CardCollection typeList, final Card activate, final int amount, SpellAbility sa, final boolean effect) {
|
||||
typeList = CardLists.filter(typeList, CardPredicates.canExiledBy(sa, effect));
|
||||
|
||||
// don't exile the card we're pumping
|
||||
|
||||
@@ -553,6 +553,10 @@ public class Cost implements Serializable {
|
||||
return new CostRevealChosen(splitStr[0], splitStr.length > 1 ? splitStr[1] : null);
|
||||
}
|
||||
|
||||
if (parse.equals("Forage")) {
|
||||
return new CostForage();
|
||||
}
|
||||
|
||||
// These won't show up with multiples
|
||||
if (parse.equals("Untap") || parse.equals("Q")) {
|
||||
return new CostUntap();
|
||||
|
||||
91
forge-game/src/main/java/forge/game/cost/CostForage.java
Normal file
91
forge-game/src/main/java/forge/game/cost/CostForage.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package forge.game.cost;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class CostForage extends CostPartWithList {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public boolean canPay(SpellAbility ability, Player payer, boolean effect) {
|
||||
CardCollection graveyard = CardLists.filter(payer.getCardsIn(ZoneType.Graveyard), CardPredicates.canExiledBy(ability, effect));
|
||||
if (graveyard.size() >= 3) {
|
||||
return true;
|
||||
}
|
||||
|
||||
CardCollection food = CardLists.filter(payer.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Food"), CardPredicates.canBeSacrificedBy(ability, effect));
|
||||
if (!food.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Forage";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Card doPayment(Player payer, SpellAbility ability, Card targetCard, final boolean effect) { return null; }
|
||||
@Override
|
||||
protected boolean canPayListAtOnce() { return true; }
|
||||
@Override
|
||||
protected CardCollectionView doListPayment(Player payer, SpellAbility ability, CardCollectionView targetCards, final boolean effect) {
|
||||
final Game game = payer.getGame();
|
||||
if (targetCards.size() == 3) {
|
||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
AbilityKey.addCardZoneTableParams(moveParams, table);
|
||||
CardCollection result = new CardCollection();
|
||||
for (Card targetCard : targetCards) {
|
||||
Card newCard = game.getAction().exile(targetCard, null, moveParams);
|
||||
result.add(newCard);
|
||||
SpellAbilityEffect.handleExiledWith(newCard, ability);
|
||||
}
|
||||
triggerForage(payer);
|
||||
return result;
|
||||
} else if (targetCards.size() == 1) {
|
||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
AbilityKey.addCardZoneTableParams(moveParams, table);
|
||||
CardCollection result = new CardCollection(game.getAction().sacrifice(targetCards.getFirst(), ability, effect, moveParams));
|
||||
triggerForage(payer);
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void triggerForage(Player payer) {
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(payer);
|
||||
payer.getGame().getTriggerHandler().runTrigger(TriggerType.Forage, runParams, false);
|
||||
}
|
||||
|
||||
public static final String HashLKIListKey = "Foraged";
|
||||
public static final String HashCardListKey = "ForagedCards";
|
||||
|
||||
@Override
|
||||
public String getHashForLKIList() {
|
||||
return HashLKIListKey;
|
||||
}
|
||||
@Override
|
||||
public String getHashForCardList() {
|
||||
return HashCardListKey;
|
||||
}
|
||||
|
||||
public <T> T accept(ICostVisitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ public interface ICostVisitor<T> {
|
||||
T visit(CostExert cost);
|
||||
T visit(CostEnlist cost);
|
||||
T visit(CostFlipCoin cost);
|
||||
T visit(CostForage cost);
|
||||
T visit(CostRollDice cost);
|
||||
T visit(CostMill cost);
|
||||
T visit(CostAddMana cost);
|
||||
@@ -103,6 +104,10 @@ public interface ICostVisitor<T> {
|
||||
public T visit(CostFlipCoin cost) {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public T visit(CostForage cost) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visit(CostRollDice cost) {
|
||||
@@ -209,5 +214,4 @@ public interface ICostVisitor<T> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package forge.game.trigger;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Localizer;
|
||||
|
||||
public class TriggerForage extends Trigger {
|
||||
|
||||
public TriggerForage(Map<String, String> params, Card host, boolean intrinsic) {
|
||||
super(params, host, intrinsic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performTest(Map<AbilityKey, Object> runParams) {
|
||||
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Player))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTriggeringObjects(SpellAbility sa, Map<AbilityKey, Object> runParams) {
|
||||
sa.setTriggeringObjectsFrom(runParams, AbilityKey.Player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImportantStackObjects(SpellAbility sa) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(Localizer.getInstance().getMessage("lblPlayer")).append(": ").append(sa.getTriggeringObject(AbilityKey.Player));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -83,6 +83,7 @@ public enum TriggerType {
|
||||
Fight(TriggerFight.class),
|
||||
FightOnce(TriggerFightOnce.class),
|
||||
FlippedCoin(TriggerFlippedCoin.class),
|
||||
Forage(TriggerForage.class),
|
||||
Foretell(TriggerForetell.class),
|
||||
Immediate(TriggerImmediate.class),
|
||||
Investigated(TriggerInvestigated.class),
|
||||
|
||||
9
forge-gui/res/cardsfolder/upcoming/treetop_sentries.txt
Normal file
9
forge-gui/res/cardsfolder/upcoming/treetop_sentries.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Name:Treetop Sentries
|
||||
ManaCost:3 G
|
||||
Types:Creature Squirrel Archer
|
||||
PT:2/4
|
||||
K:Reach
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters, you may forage. If you do, draw a card. (To forage, exile three cards from your graveyard or sacrifice a Food.)
|
||||
SVar:TrigDraw:AB$ Draw | Cost$ Forage | NumCards$ 1
|
||||
Oracle:Reach\nWhen Treetop Sentries enters, you may forage. If you do, draw a card. (To forage, exile three cards from your graveyard or sacrifice a Food.)
|
||||
|
||||
@@ -572,6 +572,36 @@ public class HumanCostDecision extends CostDecisionMakerBase {
|
||||
return PaymentDecision.number(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(final CostForage cost) {
|
||||
CardCollection food = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Food"), CardPredicates.canBeSacrificedBy(ability, isEffect()));
|
||||
CardCollection exile = CardLists.filter(player.getCardsIn(ZoneType.Graveyard), CardPredicates.canExiledBy(ability, isEffect()));
|
||||
if (!food.isEmpty() && confirmAction(cost, "Sacrifice Food")) {
|
||||
// Sacrifice Food logic
|
||||
final InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, 1, 1, food, ability);
|
||||
inp.setMessage(Localizer.getInstance().getMessage("lblSelectATargetToSacrifice", "Food", "%d"));
|
||||
inp.setCancelAllowed(!mandatory);
|
||||
inp.showAndWait();
|
||||
if (inp.hasCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return PaymentDecision.card(inp.getSelected());
|
||||
} if (exile.size() >= 3) {
|
||||
// Sacrifice Food logic
|
||||
final InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, 3, 3, exile, ability);
|
||||
inp.setMessage(Localizer.getInstance().getMessage("lblSelectToExile", 3));
|
||||
inp.setCancelAllowed(!mandatory);
|
||||
inp.showAndWait();
|
||||
if (inp.hasCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return PaymentDecision.card(inp.getSelected());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(final CostRollDice cost) {
|
||||
int c = cost.getAbilityAmount(ability);
|
||||
|
||||
Reference in New Issue
Block a user