Compare commits

...

6 Commits

12 changed files with 394 additions and 242 deletions

View File

@@ -17,6 +17,7 @@ import forge.game.ability.*;
import forge.game.card.*;
import forge.game.combat.CombatUtil;
import forge.game.cost.*;
import forge.game.keyword.Keyword;
import forge.game.mana.Mana;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.mana.ManaPool;
@@ -1420,7 +1421,7 @@ public class ComputerUtilMana {
}
}
CostAdjustment.adjust(cost, sa, null, test);
CostAdjustment.adjust(cost, sa, test);
int timesMultikicked = card.getKickerMagnitude();
if (timesMultikicked > 0 && sa.hasParam("Announce") && sa.getParam("Announce").startsWith("Multikicker")) {
@@ -1806,6 +1807,34 @@ public class ComputerUtilMana {
return res;
}
// Convoke, Delve, Improvise
public static List<SpellAbility> getAIPlayableSpecialAbilities(Card card, Player ai, SpellAbility saPaidFor, ManaCostBeingPaid manaCost) {
List<SpellAbility> result = new ArrayList<>();
if (saPaidFor.isSpell()) {
Collection<SpellAbility> specialAbilities = Lists.newArrayList();
if (card.isInPlay() && card.isUntapped()) {
if (saPaidFor.getHostCard().hasKeyword(Keyword.CONVOKE) && card.isCreature()) {
specialAbilities.addAll(CardFactoryUtil.buildConvokeAbility(card, manaCost, saPaidFor));
}
if (saPaidFor.getHostCard().hasKeyword(Keyword.IMPROVISE) && card.isArtifact()) {
specialAbilities.add(CardFactoryUtil.buildImproviseAbility(card, manaCost, saPaidFor));
}
}
if (card.isInZone(ZoneType.Graveyard)) {
if (saPaidFor.getHostCard().hasKeyword(Keyword.DELVE)) {
specialAbilities.add(CardFactoryUtil.buildDelveAbility(card, manaCost, saPaidFor));
}
}
// set the activating player
for (final SpellAbility sa : specialAbilities) {
sa.setActivatingPlayer(ai);
result.add(sa);
}
}
return result;
}
private static void handleOfferingsAI(final SpellAbility sa, boolean test, boolean costIsPaid) {
if (sa.isOffering() && sa.getSacrificedAsOffering() != null) {
final Card offering = sa.getSacrificedAsOffering();

View File

@@ -53,7 +53,7 @@ public class TokenDb implements ITokenDatabase {
@Override
public PaperToken getToken(String tokenName, String edition) {
String fullName = String.format("%s_%s", tokenName, edition.toLowerCase());
String fullName = String.format("%s_%s", tokenName, edition.toLowerCase(Locale.ENGLISH));
if (!tokensByName.containsKey(fullName)) {
try {

View File

@@ -291,8 +291,6 @@ public class GameAction {
}
copied.clearDevoured();
copied.clearDelved();
copied.clearConvoked();
copied.clearExploited();
}
@@ -512,8 +510,6 @@ public class GameAction {
if (!c.isRealToken() && !toBattlefield) {
copied.clearDevoured();
copied.clearDelved();
copied.clearConvoked();
copied.clearExploited();
}

View File

@@ -96,7 +96,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
private final KeywordCollection hiddenExtrinsicKeyword = new KeywordCollection();
// cards attached or otherwise linked to this card
private CardCollection hauntedBy, devouredCards, exploitedCards, delvedCards, convokedCards, imprintedCards, encodedCards;
private CardCollection hauntedBy, devouredCards, exploitedCards, imprintedCards, encodedCards;
private CardCollection mustBlockCards, gainControlTargets, chosenCards, blockedThisTurn, blockedByThisTurn;
private CardCollection mergedCards;
@@ -913,31 +913,17 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
public final CardCollectionView getDelved() {
return CardCollection.getView(delvedCards);
}
public final void addDelved(final Card c) {
if (delvedCards == null) {
delvedCards = new CardCollection();
if (getCastSA() == null) {
return CardCollection.EMPTY;
}
delvedCards.add(c);
return getCastSA().getDelved();
}
public final void clearDelved() {
delvedCards = null;
}
public final CardCollectionView getConvoked() {
return CardCollection.getView(convokedCards);
}
public final void addConvoked(final Card c) {
if (convokedCards == null) {
convokedCards = new CardCollection();
if (getCastSA() == null) {
return CardCollection.EMPTY;
}
convokedCards.add(c);
}
public final void clearConvoked() {
convokedCards = null;
return getCastSA().getTappedForConvoke();
}
public MapOfLists<GameEntity, Object> getRememberMap() {
@@ -1136,7 +1122,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
newTop = c;
}
}
if (newTop != null) {
removeMutatedStates();
newTop.mergedCards = mergedCards;
@@ -5474,7 +5460,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
public boolean isMadness() {
if (this.getCastSA() == null) {
if (getCastSA() == null) {
return false;
}
return getCastSA().isMadness();
@@ -5509,12 +5495,12 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final boolean isForetold() {
// in exile and foretold
if (this.isInZone(ZoneType.Exile)) {
return this.foretold;
if (isInZone(ZoneType.Exile)) {
return foretold;
}
// cast as foretold, currently only spells
if (this.getCastSA() != null) {
return this.getCastSA().isForetold();
if (getCastSA() != null) {
return getCastSA().isForetold();
}
return false;
}
@@ -6248,10 +6234,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return false;
}
if (source == null){
if (source == null) {
return true;
}
// can't sacrifice it for mana ability if it is already marked as sacrifice
if (source.isManaAbility() && isUsedToPay()) {
return false;
}
if (isCreature() && source.getActivatingPlayer().hasKeyword("You can't sacrifice creatures to cast spells or activate abilities.")) {
Cost srcCost = source.getPayCosts();
if (srcCost != null) {

View File

@@ -41,6 +41,7 @@ import forge.game.card.CardPredicates.Presets;
import forge.game.cost.Cost;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.phase.PhaseHandler;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
@@ -51,6 +52,7 @@ import forge.game.spellability.*;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.Expressions;
@@ -4991,4 +4993,152 @@ public class CardFactoryUtil {
re.setOverridingAbility(saExile);
card.addReplacementEffect(re);
}
public static SpellAbility buildConvokeAbility(final Card host, final ManaCostShard shard, final ManaCostBeingPaid manaCost, final SpellAbility saPaidFor) {
SpellAbility result = new AbilityStatic(host, Cost.Zero, null) {
@Override
public boolean isConvoke() {
return true;
}
@Override
public boolean isManaAbility() {
return true;
}
public boolean canPlay() {
if (getHostCard().isTapped()) {
return false;
}
if (!manaCost.canDecreaseShard(shard)) {
return false;
}
return super.canPlay();
}
@Override
public boolean undo() {
saPaidFor.removeTappedForConvoke(host);
host.setTapped(false);
manaCost.increaseShard(shard, 1);
return true;
}
@Override
public void resolve() {
host.getGame().getTriggerHandler().suppressMode(TriggerType.Taps);
saPaidFor.addTappedForConvoke(host);
host.setTapped(false);
host.tap();
manaCost.decreaseShard(shard, 1);
host.getGame().getTriggerHandler().clearSuppression(TriggerType.Taps);
}
};
result.setDescription("Convoke " + shard.toString());
result.setUndoable(true);
return result;
}
public static Collection<SpellAbility> buildConvokeAbility(final Card host, final ManaCostBeingPaid manaCost, final SpellAbility saPaidFor) {
List<SpellAbility> result = Lists.newArrayList();
for (byte color : host.determineColor()) {
result.add(buildConvokeAbility(host, ManaCostShard.valueOf(color), manaCost, saPaidFor));
}
result.add(buildConvokeAbility(host, ManaCostShard.GENERIC, manaCost, saPaidFor));
return result;
}
public static SpellAbility buildImproviseAbility(final Card host, final ManaCostBeingPaid manaCost, final SpellAbility saPaidFor) {
SpellAbility result = new AbilityStatic(host, Cost.Zero, null) {
@Override
public boolean isImprovise() {
return true;
}
@Override
public boolean isManaAbility() {
return true;
}
public boolean canPlay() {
if (getHostCard().isTapped()) {
return false;
}
if (!manaCost.canDecreaseShard(ManaCostShard.GENERIC)) {
return false;
}
return super.canPlay();
}
@Override
public boolean undo() {
saPaidFor.removeTappedForImprovise(host);
host.setTapped(false);
manaCost.increaseGenericMana(1);
return true;
}
@Override
public void resolve() {
host.getGame().getTriggerHandler().suppressMode(TriggerType.Taps);
saPaidFor.addTappedForImprovise(host);
host.setTapped(false);
host.tap();
manaCost.decreaseGenericMana(1);
host.getGame().getTriggerHandler().clearSuppression(TriggerType.Taps);
}
};
result.setDescription("Improvise");
result.setUndoable(true);
return result;
}
public static SpellAbility buildDelveAbility(final Card host, final ManaCostBeingPaid manaCost, final SpellAbility saPaidFor) {
SpellAbility result = new AbilityStatic(host, Cost.Zero, null) {
@Override
public boolean isDelve() {
return true;
}
@Override
public boolean isManaAbility() {
return true;
}
public boolean canPlay() {
if (saPaidFor.getDelved().contains(host)) {
return false;
}
if (!manaCost.canDecreaseShard(ManaCostShard.GENERIC)) {
return false;
}
return super.canPlay();
}
@Override
public boolean undo() {
saPaidFor.removeDelved(host);
manaCost.increaseGenericMana(1);
return true;
}
@Override
public void resolve() {
saPaidFor.addDelved(host);
manaCost.decreaseGenericMana(1);
}
};
result.setDescription("Delve");
result.setUndoable(true);
return result;
}
}

View File

@@ -8,7 +8,6 @@ import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
@@ -20,8 +19,6 @@ import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
@@ -148,7 +145,7 @@ 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 final void adjust(ManaCostBeingPaid cost, final SpellAbility sa, CardCollection cardsToDelveOut, boolean test) {
public static final void adjust(ManaCostBeingPaid cost, final SpellAbility sa, boolean test) {
final Game game = sa.getActivatingPlayer().getGame();
final Card originalCard = sa.getHostCard();
@@ -209,36 +206,6 @@ public class CostAdjustment {
applySetCostAbility(stAb, sa, cost);
}
if (sa.isSpell()) {
if (sa.getHostCard().hasKeyword(Keyword.DELVE)) {
sa.getHostCard().clearDelved();
final CardZoneTable table = new CardZoneTable();
final Player pc = sa.getActivatingPlayer();
final CardCollection mutableGrave = new CardCollection(pc.getCardsIn(ZoneType.Graveyard));
final CardCollectionView toExile = pc.getController().chooseCardsToDelve(cost.getUnpaidShards(ManaCostShard.GENERIC), mutableGrave);
for (final Card c : toExile) {
cost.decreaseGenericMana(1);
if (cardsToDelveOut != null) {
cardsToDelveOut.add(c);
} else if (!test) {
sa.getHostCard().addDelved(c);
final Card d = game.getAction().exile(c, null);
d.setExiledWith(sa.getHostCard());
d.setExiledBy(sa.getHostCard().getController());
table.put(ZoneType.Graveyard, d.getZone().getZoneType(), d);
}
}
table.triggerChangesZoneAll(game);
}
if (sa.getHostCard().hasKeyword(Keyword.CONVOKE)) {
adjustCostByConvokeOrImprovise(cost, sa, false, test);
}
if (sa.getHostCard().hasKeyword(Keyword.IMPROVISE)) {
adjustCostByConvokeOrImprovise(cost, sa, true, test);
}
} // isSpell
// Reset card state (if changed)
if (isStateChangeToFaceDown) {
originalCard.setFaceDown(false);
@@ -247,32 +214,6 @@ public class CostAdjustment {
}
// GetSpellCostChange
private static void adjustCostByConvokeOrImprovise(ManaCostBeingPaid cost, final SpellAbility sa, boolean improvise, boolean test) {
CardCollectionView untappedCards = CardLists.filter(sa.getActivatingPlayer().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.UNTAPPED);
if (improvise) {
untappedCards = CardLists.filter(untappedCards, CardPredicates.Presets.ARTIFACTS);
} else {
untappedCards = CardLists.filter(untappedCards, CardPredicates.Presets.CREATURES);
}
Map<Card, ManaCostShard> convokedCards = sa.getActivatingPlayer().getController().chooseCardsForConvokeOrImprovise(sa, cost.toManaCost(), untappedCards, improvise);
// Convoked creats are tapped here, setting up their taps triggers,
// Then again when payment is done(In InputPayManaCost.done()) with suppression of Taps triggers.
// This is to make sure that triggers go off at the right time
// AND that you can't use mana tapabilities of convoked creatures to pay the convoked cost.
for (final Entry<Card, ManaCostShard> conv : convokedCards.entrySet()) {
sa.addTappedForConvoke(conv.getKey());
cost.decreaseShard(conv.getValue(), 1);
if (!test) {
conv.getKey().tap();
if (!improvise) {
sa.getHostCard().addConvoked(conv.getKey());
}
}
}
}
private static void adjustCostByOffering(final ManaCostBeingPaid cost, final SpellAbility sa) {
String offeringType = "";
for (KeywordInterface inst : sa.getHostCard().getKeywords()) {

View File

@@ -6,12 +6,12 @@
* 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 <http://www.gnu.org/licenses/>.
*/
@@ -38,7 +38,7 @@ import org.apache.commons.lang3.StringUtils;
* <p>
* ManaCostBeingPaid class.
* </p>
*
*
* @author Forge
* @version $Id$
*/
@@ -283,6 +283,35 @@ public class ManaCostBeingPaid {
decreaseShard(ManaCostShard.GENERIC, manaToSubtract);
}
public boolean canDecreaseShard(final ManaCostShard shard) {
ShardCount sc = unpaidShards.get(shard);
if (sc != null) {
return true;
}
// only special rules for Mono Color Shards and for Generic
if (!shard.isMonoColor() && shard != ManaCostShard.GENERIC) {
return false;
}
if (shard.isMonoColor()) {
for (Entry<ManaCostShard, ShardCount> e : unpaidShards.entrySet()) {
final ManaCostShard eShard = e.getKey();
if (eShard.isOfKind(shard.getShard()) && (!eShard.isMonoColor() || eShard.isOr2Generic() || eShard.isPhyrexian())) {
return true;
}
}
} else if (shard == ManaCostShard.GENERIC) {
// try to remove 2 generic hybrid shards WITH generic shard
for (Entry<ManaCostShard, ShardCount> e : unpaidShards.entrySet()) {
final ManaCostShard eShard = e.getKey();
if (eShard.isOr2Generic()) {
return true;
}
}
}
return false;
}
public final void decreaseShard(final ManaCostShard shard, final int manaToSubtract) {
if (manaToSubtract <= 0) {
return;
@@ -296,9 +325,9 @@ public class ManaCostBeingPaid {
}
int otherSubtract = manaToSubtract;
List<ManaCostShard> toRemove = Lists.newArrayList();
//TODO move that for parts into extra function if able
// try to remove multicolored hybrid shards
// for that, this shard need to be mono colored
if (shard.isMonoColor()) {
@@ -383,15 +412,15 @@ public class ManaCostBeingPaid {
}
}
}
unpaidShards.keySet().removeAll(toRemove);
//System.out.println("Tried to substract a " + shard.toString() + " shard that is not present in this ManaCostBeingPaid");
return;
}
int difference = manaToSubtract - sc.totalCount;
int difference = manaToSubtract - sc.totalCount;
if (manaToSubtract >= sc.totalCount) {
sc.xCount = 0;
sc.totalCount = 0;
@@ -419,7 +448,7 @@ public class ManaCostBeingPaid {
* <p>
* addMana.
* </p>
*
*
* @param mana
* a {@link java.lang.String} object.
* @return a boolean.
@@ -446,7 +475,7 @@ public class ManaCostBeingPaid {
* <p>
* addMana.
* </p>
*
*
* @param mana
* a {@link forge.game.mana.Mana} object.
* @return a boolean.
@@ -467,7 +496,7 @@ public class ManaCostBeingPaid {
byte outColor = pool.getPossibleColorUses(inColor);
return tryPayMana(inColor, Iterables.filter(unpaidShards.keySet(), predCanBePaid), outColor) != null;
}
public final ManaCostShard payManaViaConvoke(final byte color) {
Predicate<ManaCostShard> predCanBePaid = new Predicate<ManaCostShard>() {
@Override
@@ -592,7 +621,7 @@ public class ManaCostBeingPaid {
/**
* To string.
*
*
* @param addX
* the add x
* @return the string
@@ -629,7 +658,7 @@ public class ManaCostBeingPaid {
if (shard == ManaCostShard.GENERIC) {
continue;
}
final String str = shard.toString();
final int count = unpaidShards.get(shard).totalCount;
for (int i = 0; i < count; i++) {
@@ -650,7 +679,7 @@ public class ManaCostBeingPaid {
* <p>
* getConvertedManaCost.
* </p>
*
*
* @return a int.
*/
public final int getConvertedManaCost() {
@@ -710,7 +739,7 @@ public class ManaCostBeingPaid {
}
return result;
}
public boolean hasAnyKind(int kind) {
for (ManaCostShard s : unpaidShards.keySet()) {
if (s.isOfKind(kind)) {

View File

@@ -138,6 +138,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private List<AbilitySub> chosenList = null;
private CardCollection tappedForConvoke = new CardCollection();
private CardCollection tappedForImprovise = new CardCollection();
private CardCollection delvedCards = new CardCollection();
private Card sacrificedAsOffering = null;
private Card sacrificedAsEmerge = null;
@@ -334,7 +337,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return result;
}
public final boolean isManaAbility() {
public boolean isManaAbility() {
// Check whether spell or ability first
if (isSpell()) {
return false;
@@ -444,6 +447,39 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public boolean isAbility() { return true; }
public boolean isActivatedAbility() { return false; }
public boolean isPayingSpecial() {
for (SpellAbility sa : getPayingManaAbilities()) {
if (sa.isSpecialPayment()) {
return true;
}
}
return false;
}
// special cost for improvise
public boolean isConvoke() { return false; }
public boolean isImprovise() { return false; }
public boolean isDelve() { return false; }
public boolean isSpecialPayment() {
return isConvoke() || isImprovise() || isDelve();
}
public final CardCollectionView getDelved() {
return CardCollection.getView(delvedCards);
}
public final void addDelved(final Card c) {
delvedCards.add(c);
}
public final void removeDelved(final Card c) {
delvedCards.remove(c);
}
public final void clearDelved() {
delvedCards.clear();
}
public boolean isMorphUp() {
return this.hasParam("MorphUp");
}
@@ -1349,15 +1385,26 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return tappedForConvoke;
}
public void addTappedForConvoke(final Card c) {
if (tappedForConvoke == null) {
tappedForConvoke = new CardCollection();
}
tappedForConvoke.add(c);
}
public void removeTappedForConvoke(final Card c) {
tappedForConvoke.remove(c);
}
public void clearTappedForConvoke() {
if (tappedForConvoke != null) {
tappedForConvoke.clear();
}
tappedForConvoke.clear();
}
public CardCollection getTappedForImprovise() {
return tappedForImprovise;
}
public void addTappedForImprovise(final Card c) {
tappedForImprovise.add(c);
}
public void removeTappedForImprovise(final Card c) {
tappedForImprovise.remove(c);
}
public void clearTappedForImprovise() {
tappedForImprovise.clear();
}
public boolean isEmerge() {

View File

@@ -474,6 +474,15 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
return false;
}
}
if (!sa.isSpecialPayment()) {
IndividualCostPaymentInstance i = game.costPaymentStack.peek();
if (i != null) {
SpellAbility saPay = i.getPayment().getAbility();
if (saPay.isPayingSpecial()) {
return false;
}
}
}
}
if (this.getsVarToCheck() != null) {
@@ -508,7 +517,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
* @return a boolean.
*/
public final boolean canPlay(final Card c, final SpellAbility sa) {
if (c.isPhasedOut() || c.isUsedToPay()) {
if (c.isPhasedOut()) {
return false;
}

View File

@@ -50,7 +50,9 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Multimaps;
public class TriggerHandler {
private final List<TriggerType> suppressedModes = Collections.synchronizedList(new ArrayList<>());
private final Set<TriggerType> suppressedModes = Collections.synchronizedSet(EnumSet.noneOf(TriggerType.class));
private boolean allSuppressed = false;
private final List<Trigger> activeTriggers = Collections.synchronizedList(new ArrayList<>());
private final List<Trigger> delayedTriggers = Collections.synchronizedList(new ArrayList<>());
@@ -111,19 +113,17 @@ public class TriggerHandler {
}
public final void setSuppressAllTriggers(final boolean suppress) {
for (TriggerType t : TriggerType.values()) {
if (suppress) {
suppressMode(t);
} else {
clearSuppression(t);
}
}
allSuppressed = suppress;
}
public final void clearSuppression(final TriggerType mode) {
suppressedModes.remove(mode);
}
public boolean isTriggerSuppressed(final TriggerType mode) {
return allSuppressed || suppressedModes.contains(mode);
}
public static Trigger parseTrigger(final String trigParse, final Card host, final boolean intrinsic) {
return parseTrigger(trigParse, host, intrinsic, host);
}
@@ -250,7 +250,7 @@ public class TriggerHandler {
}
public final void runTrigger(final TriggerType mode, final Map<AbilityKey, Object> runParams, boolean holdTrigger) {
if (suppressedModes.contains(mode)) {
if (isTriggerSuppressed(mode)) {
return;
}

View File

@@ -4,6 +4,7 @@ import java.util.*;
import forge.GuiBase;
import forge.game.spellability.SpellAbilityView;
import forge.game.zone.ZoneType;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
@@ -18,6 +19,8 @@ import forge.card.mana.ManaAtom;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.card.Card;
import forge.game.card.CardFactoryUtil;
import forge.game.keyword.Keyword;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
import forge.game.player.PlayerView;
@@ -100,21 +103,46 @@ public abstract class InputPayMana extends InputSyncronizedBase {
protected List<SpellAbility> getAllManaAbilities(Card card) {
List<SpellAbility> result = Lists.newArrayList();
for (SpellAbility sa : card.getManaAbilities()) {
result.add(sa);
result.addAll(GameActionUtil.getAlternativeCosts(sa, player));
}
final Collection<SpellAbility> toRemove = Lists.newArrayListWithCapacity(result.size());
for (final SpellAbility sa : result) {
sa.setActivatingPlayer(player);
// fix things like retrace
// check only if SA can't be cast normally
if (sa.canPlay(true)) {
continue;
if (!saPaidFor.isPayingSpecial()) {
for (SpellAbility sa : card.getManaAbilities()) {
result.add(sa);
result.addAll(GameActionUtil.getAlternativeCosts(sa, player));
}
final Collection<SpellAbility> toRemove = Lists.newArrayListWithCapacity(result.size());
for (final SpellAbility sa : result) {
sa.setActivatingPlayer(player);
// fix things like retrace
// check only if SA can't be cast normally
if (sa.canPlay(true)) {
continue;
}
toRemove.add(sa);
}
result.removeAll(toRemove);
}
if (saPaidFor.isSpell()) {
Collection<SpellAbility> specialAbilities = Lists.newArrayList();
if (card.isInPlay() && card.isUntapped()) {
if (saPaidFor.getHostCard().hasKeyword(Keyword.CONVOKE) && card.isCreature()) {
specialAbilities.addAll(CardFactoryUtil.buildConvokeAbility(card, manaCost, saPaidFor));
}
if (saPaidFor.getHostCard().hasKeyword(Keyword.IMPROVISE) && card.isArtifact()) {
specialAbilities.add(CardFactoryUtil.buildImproviseAbility(card, manaCost, saPaidFor));
}
}
if (card.isInZone(ZoneType.Graveyard)) {
if (saPaidFor.getHostCard().hasKeyword(Keyword.DELVE)) {
specialAbilities.add(CardFactoryUtil.buildDelveAbility(card, manaCost, saPaidFor));
}
}
// set the activating player
for (final SpellAbility sa : specialAbilities) {
sa.setActivatingPlayer(player);
result.add(sa);
}
toRemove.add(sa);
}
result.removeAll(toRemove);
return result;
}
@@ -150,40 +178,6 @@ public abstract class InputPayMana extends InputSyncronizedBase {
return false;
}
@Deprecated
public List<SpellAbility> getUsefulManaAbilities(Card card) {
List<SpellAbility> abilities = new ArrayList<>();
if (card.getController() != player) {
return abilities;
}
byte colorCanUse = 0;
for (final byte color : ManaAtom.MANATYPES) {
if (manaCost.isAnyPartPayableWith(color, player.getManaPool())) {
colorCanUse |= color;
}
}
if (manaCost.isAnyPartPayableWith((byte) ManaAtom.GENERIC, player.getManaPool())) {
colorCanUse |= ManaAtom.GENERIC;
}
if (colorCanUse == 0) { // no mana cost or something
return abilities;
}
final String typeRes = manaCost.getSourceRestriction();
if (StringUtils.isNotBlank(typeRes) && !card.getType().hasStringType(typeRes)) {
return abilities;
}
for (SpellAbility ma : getAllManaAbilities(card)) {
ma.setActivatingPlayer(player);
if (ma.isManaAbilityFor(saPaidFor, colorCanUse))
abilities.add(ma);
}
return abilities;
}
public void useManaFromPool(byte colorCode) {
// find the matching mana in pool.
if (player.getManaPool().tryPayCostWithColor(colorCode, saPaidFor, manaCost)) {
@@ -330,10 +324,15 @@ public abstract class InputPayMana extends InputSyncronizedBase {
@Override
public void run() {
if (HumanPlay.playSpellAbility(getController(), chosen.getActivatingPlayer(), chosen)) {
if (chosen.getManaPart().meetsManaRestrictions(saPaidFor)) {
if (chosen.isSpecialPayment()) {
saPaidFor.getPayingManaAbilities().add(chosen);
// bypass the mana from Ability part
} else if (chosen.getManaPart().meetsManaRestrictions(saPaidFor)) {
player.getManaPool().payManaFromAbility(saPaidFor, InputPayMana.this.manaCost, chosen);
onManaAbilityPaid();
}
onManaAbilityPaid();
onStateChanged();
}
}

View File

@@ -20,7 +20,6 @@ import forge.game.player.Player;
import forge.game.player.PlayerController;
import forge.game.player.PlayerView;
import forge.game.spellability.*;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.match.input.InputPayMana;
import forge.match.input.InputPayManaOfCostPayment;
@@ -596,14 +595,13 @@ public class HumanPlay {
}
private static boolean handleOfferingConvokeAndDelve(final SpellAbility ability, CardCollection cardsToDelve, boolean manaInputCancelled) {
private static boolean handleOfferingConvokeAndDelve(final SpellAbility ability, boolean manaInputCancelled) {
Card hostCard = ability.getHostCard();
final Game game = hostCard.getGame();
final CardZoneTable table = new CardZoneTable();
if (!manaInputCancelled && !cardsToDelve.isEmpty()) {
for (final Card c : cardsToDelve) {
hostCard.addDelved(c);
if (!manaInputCancelled) {
for (final Card c : ability.getDelved()) {
final ZoneType o = c.getZone().getZoneType();
final Card d = game.getAction().exile(c, null);
d.setExiledWith(hostCard);
@@ -627,15 +625,21 @@ public class HumanPlay {
}
ability.resetSacrificedAsEmerge();
}
if (ability.getTappedForConvoke() != null) {
for (final Card c : ability.getTappedForConvoke()) {
c.setTapped(false);
if (!manaInputCancelled) {
c.tap();
}
for (final Card c : ability.getTappedForConvoke()) {
c.setTapped(false);
if (!manaInputCancelled) {
c.tap();
}
ability.clearTappedForConvoke();
}
for (final Card c : ability.getTappedForImprovise()) {
c.setTapped(false);
if (!manaInputCancelled) {
c.tap();
}
}
if (!table.isEmpty() && !manaInputCancelled) {
table.triggerChangesZoneAll(game);
}
@@ -680,75 +684,32 @@ public class HumanPlay {
}
}
CardCollection cardsToDelve = new CardCollection();
if (isActivatedSa) {
CostAdjustment.adjust(toPay, ability, cardsToDelve, false);
CostAdjustment.adjust(toPay, ability, false);
}
Card offering = null;
Card emerge = null;
InputPayMana inpPayment;
if (ability.isOffering()) {
if (ability.getSacrificedAsOffering() == null) {
System.out.println("Sacrifice input for Offering cancelled");
return false;
} else {
offering = ability.getSacrificedAsOffering();
}
if (ability.isOffering() && ability.getSacrificedAsOffering() == null) {
System.out.println("Sacrifice input for Offering cancelled");
return false;
}
if (ability.isEmerge()) {
if (ability.getSacrificedAsEmerge() == null) {
System.out.println("Sacrifice input for Emerge cancelled");
return false;
} else {
emerge = ability.getSacrificedAsEmerge();
}
if (ability.isEmerge() && ability.getSacrificedAsEmerge() == null) {
System.out.println("Sacrifice input for Emerge cancelled");
return false;
}
if (!toPay.isPaid()) {
// Input is somehow clearing out the offering card?
inpPayment = new InputPayManaOfCostPayment(controller, toPay, ability, activator, matrix);
inpPayment.setMessagePrefix(prompt);
inpPayment.showAndWait();
if (!inpPayment.isPaid()) {
return handleOfferingConvokeAndDelve(ability, cardsToDelve, true);
return handleOfferingConvokeAndDelve(ability, true);
}
source.setXManaCostPaidByColor(toPay.getXManaCostPaidByColor());
}
// Handle convoke and offerings
if (ability.isOffering()) {
if (ability.getSacrificedAsOffering() == null && offering != null) {
ability.setSacrificedAsOffering(offering);
}
if (ability.getSacrificedAsOffering() != null) {
System.out.println("Finishing up Offering");
offering.setUsedToPay(false);
activator.getGame().getAction().sacrifice(offering, ability, null);
ability.resetSacrificedAsOffering();
}
}
if (ability.isEmerge()) {
if (ability.getSacrificedAsEmerge() == null && emerge != null) {
ability.setSacrificedAsEmerge(emerge);
}
if (ability.getSacrificedAsEmerge() != null) {
System.out.println("Finishing up Emerge");
emerge.setUsedToPay(false);
activator.getGame().getAction().sacrifice(emerge, ability, null);
ability.resetSacrificedAsEmerge();
}
}
if (ability.getTappedForConvoke() != null) {
activator.getGame().getTriggerHandler().suppressMode(TriggerType.Taps);
for (final Card c : ability.getTappedForConvoke()) {
c.setTapped(false);
c.tap();
}
activator.getGame().getTriggerHandler().clearSuppression(TriggerType.Taps);
ability.clearTappedForConvoke();
}
return handleOfferingConvokeAndDelve(ability, cardsToDelve, false);
return handleOfferingConvokeAndDelve(ability, false);
}
}