mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-11 16:26:22 +00:00
Compare commits
6 Commits
8a8bff26c0
...
convokeImp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f77dfcbf5 | ||
|
|
c75cab4b75 | ||
|
|
9978cccfb2 | ||
|
|
d85407ea70 | ||
|
|
62b10fe79b | ||
|
|
000c5cd889 |
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user