Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions forge-game/src/main/java/forge/game/ability/AbilityUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import forge.game.cost.CostAdjustment;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.keyword.KeywordWithCost;
import forge.game.keyword.KeywordWithCostAndType;
import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix;
Expand Down Expand Up @@ -3151,6 +3152,65 @@ public static SpellAbility addSpliceEffects(final SpellAbility sa) {
return newSA;
}

/**
* Adds the effects of one reprised card from the caster's graveyard to an
* instant or sorcery spell. Reprise is deliberately limited to one card,
* unlike splice.
*/
public static SpellAbility addRepriseEffect(final SpellAbility sa) {
final Card source = sa.getHostCard();
final Player player = sa.getActivatingPlayer();

if (!sa.isSpell() || source.isCopiedSpell()
|| (!source.isInstant() && !source.isSorcery())) {
return sa;
}

final CardCollection reprises = CardLists.filter(player.getCardsIn(ZoneType.Graveyard),
card -> card.hasKeyword(Keyword.REPRISE));
if (reprises.isEmpty()) {
return sa;
}

final List<Card> chosen = player.getController().chooseCardForReprise(sa, reprises);
if (chosen.isEmpty()) {
return sa;
}

final Card card = chosen.get(0);
final SpellAbility newSA = sa.copy();
addRepriseEffect(newSA, card);
return newSA;
}

public static void addRepriseEffect(final SpellAbility sa, final Card card) {
Cost repriseCost = null;
for (final KeywordInterface inst : card.getKeywords(Keyword.REPRISE)) {
if (inst instanceof KeywordWithCost reprise) {
repriseCost = reprise.getCost();
break;
}
}
if (repriseCost == null) {
return;
}

final SpellAbility firstSpell = card.getFirstSpellAbility();
final Map<String, String> params = Maps.newHashMap(firstSpell.getMapParams());
final ApiType api = AbilityRecordType.getRecordType(params).getApiTypeOf(params);
final AbilitySub subAbility = (AbilitySub) AbilityFactory.getAbility(AbilityRecordType.SubAbility, api, params,
null, card.getCurrentState(), card.getCurrentState());
subAbility.setActivatingPlayer(sa.getActivatingPlayer());
subAbility.setHostCard(sa.getHostCard());
sa.appendSubAbility(subAbility);

sa.setBasicSpell(false);
sa.getPayCosts().add(new Cost("ExileFromGrave<1/Card.CardUID_" + card.getId() + ">", false));
sa.getPayCosts().add(repriseCost);
sa.setDescription(sa.getDescription() + " (Reprising " + card + ")");
sa.addReprisedCard(card);
}

public static void addSpliceEffect(final SpellAbility sa, final Card c) {
Cost spliceCost = null;
// This Function thinks that Splice exist only once on the card
Expand Down
2 changes: 1 addition & 1 deletion forge-game/src/main/java/forge/game/card/CardUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ private CardUtil() { }
public static final ImmutableList<String> modifiableKeywords = ImmutableList.<String>builder().add(
"Enchant", "Protection", "Cumulative upkeep", "Equip", "Buyback",
"Cycling", "Echo", "Kicker", "Flashback", "Madness", "Morph",
"Affinity", "Entwine", "Splice", "Ninjutsu",
"Affinity", "Entwine", "Splice", "Reprise", "Ninjutsu",
"Transmute", "Replicate", "Recover", "Squad", "Suspend", "Aura swap",
"Fortify", "Transfigure", "Champion", "Evoke", "Prowl", "Freerunning",
"Reinforce", "Unearth", "Level up", "Miracle", "Overload", "Cleave",
Expand Down
1 change: 1 addition & 0 deletions forge-game/src/main/java/forge/game/keyword/Keyword.java
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ public enum Keyword {
REINFORCE("Reinforce", KeywordWithCostAndAmount.class, false, "%s, Discard this card: Put {%d:+1/+1 counter} on target creature."),
RENOWN("Renown", KeywordWithAmount.class, false, "When this creature deals combat damage to a player, if it isn't renowned, put {%d:+1/+1 counter} on it and it becomes renowned."),
REPLICATE("Replicate", KeywordWithCost.class, false, "As an additional cost to cast this spell, you may pay %s any number of times. If you do, copy it that many times. You may choose new targets for the copies."),
REPRISE("Reprise", KeywordWithCost.class, false, "As you cast an instant or sorcery spell, you may exile one card with reprise from your graveyard and pay its reprise cost. If you do, add its effects to that spell."),
RETRACE("Retrace", SimpleKeyword.class, false, "You may cast this card from your graveyard by discarding a land card in addition to paying its other costs."),
RIOT("Riot", SimpleKeyword.class, false, "This creature enters with your choice of a +1/+1 counter or haste."),
RIPPLE("Ripple", KeywordWithAmount.class, false, "When you cast this spell, you may reveal the top {%d:card} of your library. You may cast any of those cards with the same name as this spell without paying their mana costs. Put the rest on the bottom of your library in any order."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@ public final boolean playAbility(final boolean mayChooseTargets, final boolean i
}

ability = AbilityUtils.addSpliceEffects(ability);
ability = AbilityUtils.addRepriseEffect(ability);
}

// used to rollback
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@ public CardCollectionView chooseCardsToDiscardFrom(Player playerDiscard, SpellAb
public abstract CardCollectionView chooseCardsToDelve(int genericAmount, CardCollection grave);
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 List<Card> chooseCardForReprise(SpellAbility sa, List<Card> cards) {
final List<Card> chosen = chooseCardsForSplice(sa, cards);
return chosen.isEmpty() ? chosen : List.of(chosen.get(0));
}

public abstract CardCollectionView chooseCardsToRevealFromHand(int min, int max, CardCollectionView valid);
public abstract List<SpellAbility> chooseSaToActivateFromOpeningHand(List<SpellAbility> usableFromOpeningHand);
Expand Down
16 changes: 16 additions & 0 deletions forge-game/src/main/java/forge/game/spellability/SpellAbility.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ public static class EmptySa extends SpellAbility {
private StaticAbility grantorStatic;

private CardCollection splicedCards = null;
private CardCollection reprisedCards = null;

private boolean basicSpell = true;
private Trigger triggerObj;
Expand Down Expand Up @@ -1638,6 +1639,21 @@ public void addSplicedCards(Card splicedCard) {
splicedCards.add(splicedCard);
}

public CardCollection getReprisedCards() {
return reprisedCards;
}

public boolean isReprising() {
return reprisedCards != null && !reprisedCards.isEmpty();
}

public void addReprisedCard(Card reprisedCard) {
if (reprisedCards == null) {
reprisedCards = new CardCollection();
}
reprisedCards.add(reprisedCard);
}

public CardCollection knownDetermineDefined(final String defined) {
final CardCollection ret = new CardCollection();
final CardCollection list = AbilityUtils.getDefinedCards(getHostCard(), defined, this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ public static boolean hasProperty(SpellAbility sa, Player sourceController, Card
return sa.isEternalize();
} else if (property.equals("Flashback")) {
return sa.isFlashback();
} else if (property.equals("Reprise") || property.equals("Reprising")) {
return sa.isReprising();
} else if (property.equals("Harmonize")) {
return sa.isHarmonize();
} else if (property.equals("Jumpstart")) {
Expand Down
17 changes: 17 additions & 0 deletions forge-gui/src/main/java/forge/player/PlayerControllerHuman.java
Original file line number Diff line number Diff line change
Expand Up @@ -3763,6 +3763,23 @@ public List<Card> chooseCardsForSplice(SpellAbility sa, List<Card> cards) {
return chosenCards;
}

@Override
public List<Card> chooseCardForReprise(SpellAbility sa, List<Card> cards) {
GameEntityViewMap<Card, CardView> gameCacheReprise = GameEntityView.getMap(cards);
List<CardView> chosen = getGui().many(
"Choose a card to reprise",
"Chosen card",
0,
1,
gameCacheReprise.getTrackableKeys(),
sa.getHostCard().getView()
);

List<Card> chosenCards = new CardCollection();
gameCacheReprise.addToList(chosen, chosenCards);
return chosenCards;
}

/*
* (non-Javadoc)
*
Expand Down
Loading