Merge remote-tracking branch 'upstream/master' into collector-number-in-card-list-and-card-db-refactoring

This commit is contained in:
leriomaggio
2021-07-21 17:32:02 +01:00
488 changed files with 2679 additions and 1017 deletions

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.43-SNAPSHOT</version> <version>1.6.44-SNAPSHOT</version>
</parent> </parent>
<artifactId>forge-ai</artifactId> <artifactId>forge-ai</artifactId>

View File

@@ -510,7 +510,6 @@ public class AiController {
landList = unreflectedLands; landList = unreflectedLands;
} }
//try to skip lands that enter the battlefield tapped //try to skip lands that enter the battlefield tapped
if (!nonLandsInHand.isEmpty()) { if (!nonLandsInHand.isEmpty()) {
CardCollection nonTappedLands = new CardCollection(); CardCollection nonTappedLands = new CardCollection();
@@ -534,6 +533,7 @@ public class AiController {
} }
} }
// TODO if this is the only source for a color we need badly prioritize it instead
if (foundTapped) { if (foundTapped) {
continue; continue;
} }
@@ -813,7 +813,7 @@ public class AiController {
} }
else { else {
Cost payCosts = sa.getPayCosts(); Cost payCosts = sa.getPayCosts();
if(payCosts != null) { if (payCosts != null) {
ManaCost mana = payCosts.getTotalMana(); ManaCost mana = payCosts.getTotalMana();
if (mana != null) { if (mana != null) {
if (mana.countX() > 0) { if (mana.countX() > 0) {
@@ -879,7 +879,7 @@ public class AiController {
public boolean isNonDisabledCardInPlay(final String cardName) { public boolean isNonDisabledCardInPlay(final String cardName) {
for (Card card : player.getCardsIn(ZoneType.Battlefield)) { for (Card card : player.getCardsIn(ZoneType.Battlefield)) {
if (card.getName().equals(cardName)) { if (card.getName().equals(cardName)) {
// TODO - Better logic to detemine if a permanent is disabled by local effects // TODO - Better logic to determine if a permanent is disabled by local effects
// currently assuming any permanent enchanted by another player // currently assuming any permanent enchanted by another player
// is disabled and a second copy is necessary // is disabled and a second copy is necessary
// will need actual logic that determines if the enchantment is able // will need actual logic that determines if the enchantment is able
@@ -1916,7 +1916,7 @@ public class AiController {
if (sa.hasParam("AIMaxAmount")) { if (sa.hasParam("AIMaxAmount")) {
max = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("AIMaxAmount"), sa); max = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("AIMaxAmount"), sa);
} }
switch(sa.getApi()) { switch (sa.getApi()) {
case TwoPiles: case TwoPiles:
// TODO: improve AI // TODO: improve AI
Card biggest = null; Card biggest = null;
@@ -2046,11 +2046,6 @@ public class AiController {
} }
} }
// check
for (int i = 0; i < library.size(); i++) {
System.out.println(library.get(i));
}
return library; return library;
} // smoothComputerManaCurve() } // smoothComputerManaCurve()

View File

@@ -78,15 +78,13 @@ public class AiCostDecision extends CostDecisionMakerBase {
return null; return null;
} }
return PaymentDecision.card(player.getLastDrawnCard()); return PaymentDecision.card(player.getLastDrawnCard());
} } else if (cost.payCostFromSource()) {
else if (cost.payCostFromSource()) {
if (!hand.contains(source)) { if (!hand.contains(source)) {
return null; return null;
} }
return PaymentDecision.card(source); return PaymentDecision.card(source);
} } else if (type.equals("Hand")) {
else if (type.equals("Hand")) {
if (hand.size() > 1 && ability.getActivatingPlayer() != null) { if (hand.size() > 1 && ability.getActivatingPlayer() != null) {
hand = ability.getActivatingPlayer().getController().orderMoveToZoneList(hand, ZoneType.Graveyard, ability); hand = ability.getActivatingPlayer().getController().orderMoveToZoneList(hand, ZoneType.Graveyard, ability);
} }
@@ -107,8 +105,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
randomSubset = ability.getActivatingPlayer().getController().orderMoveToZoneList(randomSubset, ZoneType.Graveyard, ability); randomSubset = ability.getActivatingPlayer().getController().orderMoveToZoneList(randomSubset, ZoneType.Graveyard, ability);
} }
return PaymentDecision.card(randomSubset); return PaymentDecision.card(randomSubset);
} } else if (type.equals("DifferentNames")) {
else if (type.equals("DifferentNames")) {
CardCollection differentNames = new CardCollection(); CardCollection differentNames = new CardCollection();
CardCollection discardMe = CardLists.filter(hand, CardPredicates.hasSVar("DiscardMe")); CardCollection discardMe = CardLists.filter(hand, CardPredicates.hasSVar("DiscardMe"));
while (c > 0) { while (c > 0) {
@@ -125,8 +122,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
c--; c--;
} }
return PaymentDecision.card(differentNames); return PaymentDecision.card(differentNames);
} } else {
else {
final AiController aic = ((PlayerControllerAi)player.getController()).getAi(); final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
CardCollection result = aic.getCardsToDiscard(c, type.split(";"), ability, discarded); CardCollection result = aic.getCardsToDiscard(c, type.split(";"), ability, discarded);
@@ -183,8 +179,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
else if (cost.sameZone) { else if (cost.sameZone) {
// TODO Determine exile from same zone for AI // TODO Determine exile from same zone for AI
return null; return null;
} } else {
else {
CardCollectionView chosen = ComputerUtil.chooseExileFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c, ability); CardCollectionView chosen = ComputerUtil.chooseExileFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c, ability);
return null == chosen ? null : PaymentDecision.card(chosen); return null == chosen ? null : PaymentDecision.card(chosen);
} }
@@ -267,6 +262,15 @@ public class AiCostDecision extends CostDecisionMakerBase {
return PaymentDecision.number(c); return PaymentDecision.number(c);
} }
@Override
public PaymentDecision visit(CostRollDice cost) {
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
return PaymentDecision.number(c);
}
@Override @Override
public PaymentDecision visit(CostGainControl cost) { public PaymentDecision visit(CostGainControl cost) {
if (cost.payCostFromSource()) { if (cost.payCostFromSource()) {
@@ -366,8 +370,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
if (cost.isSameZone()) { if (cost.isSameZone()) {
list = new CardCollection(game.getCardsIn(cost.getFrom())); list = new CardCollection(game.getCardsIn(cost.getFrom()));
} } else {
else {
list = new CardCollection(player.getCardsIn(cost.getFrom())); list = new CardCollection(player.getCardsIn(cost.getFrom()));
} }
@@ -862,4 +865,3 @@ public class AiCostDecision extends CostDecisionMakerBase {
return false; return false;
} }
} }

View File

@@ -2832,7 +2832,6 @@ public class ComputerUtil {
} }
public static boolean lifegainPositive(final Player player, final Card source) { public static boolean lifegainPositive(final Player player, final Card source) {
if (!player.canGainLife()) { if (!player.canGainLife()) {
return false; return false;
} }

View File

@@ -716,6 +716,7 @@ public class ComputerUtilCard {
int bigCMC = -1; int bigCMC = -1;
for (final Card card : all) { for (final Card card : all) {
// TODO when PlayAi can consider MDFC this should also look at the back face (if not on stack or battlefield)
int curCMC = card.getCMC(); int curCMC = card.getCMC();
// Add all cost of all auras with the same controller // Add all cost of all auras with the same controller

View File

@@ -1899,8 +1899,7 @@ public class ComputerUtilMana {
if (!res.contains(a)) { if (!res.contains(a)) {
if (cost.isReusuableResource()) { if (cost.isReusuableResource()) {
res.add(0, a); res.add(0, a);
} } else {
else {
res.add(res.size(), a); res.add(res.size(), a);
} }
} }

View File

@@ -143,6 +143,7 @@ public enum SpellApiToAi {
.put(ApiType.ReplaceEffect, AlwaysPlayAi.class) .put(ApiType.ReplaceEffect, AlwaysPlayAi.class)
.put(ApiType.ReplaceDamage, AlwaysPlayAi.class) .put(ApiType.ReplaceDamage, AlwaysPlayAi.class)
.put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class) .put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class)
.put(ApiType.ReplaceToken, AlwaysPlayAi.class)
.put(ApiType.RestartGame, RestartGameAi.class) .put(ApiType.RestartGame, RestartGameAi.class)
.put(ApiType.Reveal, RevealAi.class) .put(ApiType.Reveal, RevealAi.class)
.put(ApiType.RevealHand, RevealHandAi.class) .put(ApiType.RevealHand, RevealHandAi.class)

View File

@@ -34,7 +34,7 @@ public class AmassAi extends SpellAbilityAi {
final String tokenScript = "b_0_0_zombie_army"; final String tokenScript = "b_0_0_zombie_army";
final int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Num", "1"), sa); final int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Num", "1"), sa);
Card token = TokenInfo.getProtoType(tokenScript, sa, false); Card token = TokenInfo.getProtoType(tokenScript, sa, ai, false);
if (token == null) { if (token == null) {
return false; return false;

View File

@@ -34,6 +34,7 @@ public class ChooseTypeAi extends SpellAbilityAi {
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) { if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
return doMirrorEntityLogic(aiPlayer, sa); return doMirrorEntityLogic(aiPlayer, sa);
} }
return !chooseType(sa, aiPlayer.getCardsIn(ZoneType.Battlefield)).isEmpty();
} else if ("MostProminentOppControls".equals(sa.getParam("AILogic"))) { } else if ("MostProminentOppControls".equals(sa.getParam("AILogic"))) {
return !chooseType(sa, aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield)).isEmpty(); return !chooseType(sa, aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield)).isEmpty();
} }

View File

@@ -1,42 +1,27 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import forge.ai.*;
import forge.ai.AiController;
import forge.ai.AiPlayDecision;
import forge.ai.AiProps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.card.CardStateName; import forge.card.CardStateName;
import forge.card.CardTypeView; import forge.card.CardTypeView;
import forge.card.mana.ManaCost;
import forge.game.Game; import forge.game.Game;
import forge.game.GameType; import forge.game.GameType;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.*;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.Spell; import forge.game.spellability.*;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.spellability.SpellPermanent;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.MyRandom; import forge.util.MyRandom;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class PlayAi extends SpellAbilityAi { public class PlayAi extends SpellAbilityAi {
@Override @Override
@@ -70,13 +55,12 @@ public class PlayAi extends SpellAbilityAi {
} }
} }
if (sa.hasParam("ValidSA")) { if (cards != null & sa.hasParam("ValidSA")) {
final String valid[] = {sa.getParam("ValidSA")}; final String valid[] = {sa.getParam("ValidSA")};
final Iterator<Card> itr = cards.iterator(); final Iterator<Card> itr = cards.iterator();
while (itr.hasNext()) { while (itr.hasNext()) {
final Card c = itr.next(); final Card c = itr.next();
final List<SpellAbility> validSA = Lists.newArrayList(Iterables.filter(AbilityUtils.getBasicSpellsFromPlayEffect(c, ai), SpellAbilityPredicates.isValid(valid, ai , c, sa))); if (!Iterables.any(AbilityUtils.getBasicSpellsFromPlayEffect(c, ai), SpellAbilityPredicates.isValid(valid, ai , c, sa))) {
if (validSA.size() == 0) {
itr.remove(); itr.remove();
} }
} }
@@ -120,6 +104,23 @@ public class PlayAi extends SpellAbilityAi {
} }
validOpts = CardLists.filter(validOpts, CardPredicates.greaterCMC(minCMC)); validOpts = CardLists.filter(validOpts, CardPredicates.greaterCMC(minCMC));
return chooseSingleCard(ai, sa, validOpts, sa.hasParam("Optional"), null, null) != null; return chooseSingleCard(ai, sa, validOpts, sa.hasParam("Optional"), null, null) != null;
} else if ("WithTotalCMC".equals(logic)) {
// Try to play only when there are more than three playable cards.
if (cards.size() < 3)
return false;
ManaCost mana = sa.getPayCosts().getTotalMana();
if (mana.countX() > 0) {
int amount = ComputerUtilCost.getMaxXValue(sa, ai);
if (amount < ComputerUtilCard.getBestAI(cards).getCMC())
return false;
int totalCMC = 0;
for (Card c : cards) {
totalCMC += c.getCMC();
}
if (amount > totalCMC)
amount = totalCMC;
sa.setXManaCostPaid(amount);
}
} }
if (source != null && source.hasKeyword(Keyword.HIDEAWAY) && source.hasRemembered()) { if (source != null && source.hasKeyword(Keyword.HIDEAWAY) && source.hasRemembered()) {
@@ -173,12 +174,19 @@ public class PlayAi extends SpellAbilityAi {
List<Card> tgtCards = CardLists.filter(options, new Predicate<Card>() { List<Card> tgtCards = CardLists.filter(options, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
// TODO needs to be aligned for MDFC along with getAbilityToPlay so the knowledge
// of which spell was the reason for the choice can be used there
for (SpellAbility s : c.getBasicSpells(c.getState(CardStateName.Original))) { for (SpellAbility s : c.getBasicSpells(c.getState(CardStateName.Original))) {
Spell spell = (Spell) s; Spell spell = (Spell) s;
s.setActivatingPlayer(ai); s.setActivatingPlayer(ai);
// timing restrictions still apply // timing restrictions still apply
if (!s.getRestrictions().checkTimingRestrictions(c, s)) if (!s.getRestrictions().checkTimingRestrictions(c, s))
continue; continue;
if (params != null && params.containsKey("CMCLimit")) {
Integer cmcLimit = (Integer) params.get("CMCLimit");
if (spell.getPayCosts().getTotalMana().getCMC() > cmcLimit)
continue;
}
if (sa.hasParam("WithoutManaCost")) { if (sa.hasParam("WithoutManaCost")) {
// Try to avoid casting instants and sorceries with X in their cost, since X will be assumed to be 0. // Try to avoid casting instants and sorceries with X in their cost, since X will be assumed to be 0.
if (!(spell instanceof SpellPermanent)) { if (!(spell instanceof SpellPermanent)) {

View File

@@ -95,7 +95,7 @@ public class TokenAi extends SpellAbilityAi {
if (sa.getSVar("X").equals("Count$xPaid")) { if (sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value. // Set PayX here to maximum value.
x = ComputerUtilCost.getMaxXValue(sa, ai); x = ComputerUtilCost.getMaxXValue(sa, ai);
sa.setXManaCostPaid(x); sa.getRootAbility().setXManaCostPaid(x);
} }
if (x <= 0) { if (x <= 0) {
return false; // 0 tokens or 0 toughness token(s) return false; // 0 tokens or 0 toughness token(s)
@@ -358,7 +358,7 @@ public class TokenAi extends SpellAbilityAi {
if (!sa.hasParam("TokenScript")) { if (!sa.hasParam("TokenScript")) {
throw new RuntimeException("Spell Ability has no TokenScript: " + sa); throw new RuntimeException("Spell Ability has no TokenScript: " + sa);
} }
Card result = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa); Card result = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa, ai);
if (result == null) { if (result == null) {
throw new RuntimeException("don't find Token for TokenScript: " + sa.getParam("TokenScript")); throw new RuntimeException("don't find Token for TokenScript: " + sa.getParam("TokenScript"));

View File

@@ -229,12 +229,12 @@ public class GameCopier {
private static final boolean USE_FROM_PAPER_CARD = true; private static final boolean USE_FROM_PAPER_CARD = true;
private Card createCardCopy(Game newGame, Player newOwner, Card c) { private Card createCardCopy(Game newGame, Player newOwner, Card c) {
if (c.isToken() && !c.isEmblem()) { if (c.isToken() && !c.isImmutable()) {
Card result = new TokenInfo(c).makeOneToken(newOwner); Card result = new TokenInfo(c).makeOneToken(newOwner);
CardFactory.copyCopiableCharacteristics(c, result); CardFactory.copyCopiableCharacteristics(c, result);
return result; return result;
} }
if (USE_FROM_PAPER_CARD && !c.isEmblem() && c.getPaperCard() != null) { if (USE_FROM_PAPER_CARD && !c.isImmutable() && c.getPaperCard() != null) {
Card newCard = Card.fromPaperCard(c.getPaperCard(), newOwner); Card newCard = Card.fromPaperCard(c.getPaperCard(), newOwner);
newCard.setCommander(c.isCommander()); newCard.setCommander(c.isCommander());
return newCard; return newCard;

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.43-SNAPSHOT</version> <version>1.6.44-SNAPSHOT</version>
</parent> </parent>
<artifactId>forge-core</artifactId> <artifactId>forge-core</artifactId>

View File

@@ -424,9 +424,9 @@ public final class CardRules implements ICardCharacteristics {
value = colonPos > 0 ? value.substring(1+colonPos) : null; value = colonPos > 0 ? value.substring(1+colonPos) : null;
if ( "RemoveDeck".equals(variable) ) { if ( "RemoveDeck".equals(variable) ) {
this.removedFromAIDecks = "All".equalsIgnoreCase(value); this.removedFromAIDecks |= "All".equalsIgnoreCase(value);
this.removedFromRandomDecks = "Random".equalsIgnoreCase(value); this.removedFromRandomDecks |= "Random".equalsIgnoreCase(value);
this.removedFromNonCommanderDecks = "NonCommander".equalsIgnoreCase(value); this.removedFromNonCommanderDecks |= "NonCommander".equalsIgnoreCase(value);
} }
} else if ("AlternateMode".equals(key)) { } else if ("AlternateMode".equals(key)) {
this.altMode = CardSplitType.smartValueOf(value); this.altMode = CardSplitType.smartValueOf(value);

View File

@@ -58,7 +58,6 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
Conspiracy(false, "conspiracies"), Conspiracy(false, "conspiracies"),
Creature(true, "creatures"), Creature(true, "creatures"),
Dungeon(false, "dungeons"), Dungeon(false, "dungeons"),
Emblem(false, "emblems"),
Enchantment(true, "enchantments"), Enchantment(true, "enchantments"),
Instant(false, "instants"), Instant(false, "instants"),
Land(true, "lands"), Land(true, "lands"),
@@ -437,11 +436,6 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
return coreTypes.contains(CoreType.Phenomenon); return coreTypes.contains(CoreType.Phenomenon);
} }
@Override
public boolean isEmblem() {
return coreTypes.contains(CoreType.Emblem);
}
@Override @Override
public boolean isTribal() { public boolean isTribal() {
return coreTypes.contains(CoreType.Tribal); return coreTypes.contains(CoreType.Tribal);
@@ -547,7 +541,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
if (!isInstant() && !isSorcery()) { if (!isInstant() && !isSorcery()) {
Iterables.removeIf(subtypes, Predicates.IS_SPELL_TYPE); Iterables.removeIf(subtypes, Predicates.IS_SPELL_TYPE);
} }
if (!isPlaneswalker() && !isEmblem()) { if (!isPlaneswalker()) {
Iterables.removeIf(subtypes, Predicates.IS_WALKER_TYPE); Iterables.removeIf(subtypes, Predicates.IS_WALKER_TYPE);
} }
} }

View File

@@ -42,7 +42,6 @@ public interface CardTypeView extends Iterable<String>, Serializable {
boolean isBasicLand(); boolean isBasicLand();
boolean isPlane(); boolean isPlane();
boolean isPhenomenon(); boolean isPhenomenon();
boolean isEmblem();
boolean isTribal(); boolean isTribal();
boolean isDungeon(); boolean isDungeon();
CardTypeView getTypeWithChanges(Iterable<CardChangedType> changedCardTypes); CardTypeView getTypeWithChanges(Iterable<CardChangedType> changedCardTypes);

View File

@@ -87,7 +87,6 @@ public class DeckGeneratorMonoColor extends DeckGeneratorBase {
} }
} }
@Override @Override
public final CardPool getDeck(final int size, final boolean forAi) { public final CardPool getDeck(final int size, final boolean forAi) {
addCreaturesAndSpells(size, cmcLevels, forAi); addCreaturesAndSpells(size, cmcLevels, forAi);

View File

@@ -61,7 +61,6 @@ import forge.util.TextUtil;
*/ */
public class BoosterGenerator { public class BoosterGenerator {
private final static Map<String, PrintSheet> cachedSheets = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); private final static Map<String, PrintSheet> cachedSheets = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private static synchronized PrintSheet getPrintSheet(String key) { private static synchronized PrintSheet getPrintSheet(String key) {
if( !cachedSheets.containsKey(key) ) if( !cachedSheets.containsKey(key) )

View File

@@ -31,7 +31,6 @@ public class UnOpenedProduct implements IUnOpenedProduct {
this.poolLimited = considerNumbersInPool; // TODO: Add 0 to parameter's name. this.poolLimited = considerNumbersInPool; // TODO: Add 0 to parameter's name.
} }
// Means to select from all unique cards (from base game, ie. no schemes or avatars) // Means to select from all unique cards (from base game, ie. no schemes or avatars)
public UnOpenedProduct(SealedProduct.Template template) { public UnOpenedProduct(SealedProduct.Template template) {
tpl = template; tpl = template;

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.43-SNAPSHOT</version> <version>1.6.44-SNAPSHOT</version>
</parent> </parent>
<artifactId>forge-game</artifactId> <artifactId>forge-game</artifactId>

View File

@@ -705,17 +705,20 @@ public class GameAction {
} }
} }
if (zoneTo.is(ZoneType.Stack)) {
// zoneFrom maybe null if the spell is cast from "Ouside the game", ex. ability of Garth One-Eye
if (zoneFrom == null) { if (zoneFrom == null) {
c.setCastFrom(null); c.setCastFrom(null);
c.setCastSA(null); } else {
} else if (zoneTo.is(ZoneType.Stack)) {
c.setCastFrom(zoneFrom.getZoneType()); c.setCastFrom(zoneFrom.getZoneType());
}
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard()) && !c.isCopiedSpell()) { if (cause != null && cause.isSpell() && c.equals(cause.getHostCard()) && !c.isCopiedSpell()) {
c.setCastSA(cause); c.setCastSA(cause);
} else { } else {
c.setCastSA(null); c.setCastSA(null);
} }
} else if (!(zoneTo.is(ZoneType.Battlefield) && zoneFrom.is(ZoneType.Stack))) { } else if (zoneFrom == null || !(zoneFrom.is(ZoneType.Stack) &&
(zoneTo.is(ZoneType.Battlefield) || zoneTo.is(ZoneType.Merged)))) {
c.setCastFrom(null); c.setCastFrom(null);
c.setCastSA(null); c.setCastSA(null);
} }

View File

@@ -153,8 +153,7 @@ public final class GameActionUtil {
final StringBuilder sb = new StringBuilder(sa.getDescription()); final StringBuilder sb = new StringBuilder(sa.getDescription());
if (!source.equals(host)) { if (!source.equals(host)) {
sb.append(" by "); sb.append(" by ");
if ((host.isEmblem() || host.getType().hasSubtype("Effect")) if ((host.isImmutable()) && host.getEffectSource() != null) {
&& host.getEffectSource() != null) {
sb.append(host.getEffectSource()); sb.append(host.getEffectSource());
} else { } else {
sb.append(host); sb.append(host);
@@ -542,7 +541,6 @@ public final class GameActionUtil {
final Card eff = new Card(game.nextCardId(), game); final Card eff = new Card(game.nextCardId(), game);
eff.setTimestamp(game.getNextTimestamp()); eff.setTimestamp(game.getNextTimestamp());
eff.setName(sourceCard.getName() + "'s Effect"); eff.setName(sourceCard.getName() + "'s Effect");
eff.addType("Effect");
eff.setOwner(controller); eff.setOwner(controller);
eff.setImageKey(sourceCard.getImageKey()); eff.setImageKey(sourceCard.getImageKey());

View File

@@ -1,5 +1,6 @@
package forge.game; package forge.game;
import java.util.Arrays;
import java.util.Map; import java.util.Map;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@@ -37,11 +38,23 @@ public enum PlanarDice {
trigRes = Chaos; trigRes = Chaos;
} }
final Map<AbilityKey, Object> runParams = AbilityKey.newMap(); Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Player, roller); runParams.put(AbilityKey.Player, roller);
runParams.put(AbilityKey.Result, trigRes); runParams.put(AbilityKey.Result, trigRes);
roller.getGame().getTriggerHandler().runTrigger(TriggerType.PlanarDice, runParams,false); roller.getGame().getTriggerHandler().runTrigger(TriggerType.PlanarDice, runParams,false);
// Also run normal RolledDie and RolledDieOnce triggers
runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Player, roller);
runParams.put(AbilityKey.Sides, 6);
runParams.put(AbilityKey.Result, 0);
roller.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDie, runParams, false);
runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Player, roller);
runParams.put(AbilityKey.Sides, 6);
runParams.put(AbilityKey.Result, Arrays.asList(0));
roller.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDieOnce, runParams, false);
return res; return res;
} }

View File

@@ -31,6 +31,7 @@ import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser; import forge.card.mana.ManaCostParser;
import forge.card.mana.ManaCostShard; import forge.card.mana.ManaCostShard;
import forge.game.CardTraitBase; import forge.game.CardTraitBase;
import forge.game.Direction;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameObject; import forge.game.GameObject;
@@ -111,9 +112,9 @@ public class AbilityUtils {
// Probably will move to One function solution sometime in the future // Probably will move to One function solution sometime in the future
public static CardCollection getDefinedCards(final Card hostCard, final String def, final CardTraitBase sa) { public static CardCollection getDefinedCards(final Card hostCard, final String def, final CardTraitBase sa) {
CardCollection cards = new CardCollection(); CardCollection cards = new CardCollection();
String defined = (def == null) ? "Self" : applyAbilityTextChangeEffects(def, sa); // default to Self String changedDef = (def == null) ? "Self" : applyAbilityTextChangeEffects(def, sa); // default to Self
final String[] incR = defined.split("\\.", 2); final String[] incR = changedDef.split("\\.", 2);
defined = incR[0]; String defined = incR[0];
final Game game = hostCard.getGame(); final Game game = hostCard.getGame();
Card c = null; Card c = null;
@@ -131,7 +132,7 @@ public class AbilityUtils {
} }
} }
else if (defined.equals("EffectSource")) { else if (defined.equals("EffectSource")) {
if (hostCard.isEmblem() || hostCard.getType().hasSubtype("Effect")) { if (hostCard.isImmutable()) {
c = findEffectRoot(hostCard); c = findEffectRoot(hostCard);
} }
} }
@@ -313,6 +314,10 @@ public class AbilityUtils {
for (final Card imprint : hostCard.getImprintedCards()) { for (final Card imprint : hostCard.getImprintedCards()) {
cards.add(game.getCardState(imprint)); cards.add(game.getCardState(imprint));
} }
} else if (defined.equals("UntilLeavesBattlefield")) {
for (final Card ulb : hostCard.getUntilLeavesBattlefield()) {
cards.add(game.getCardState(ulb));
}
} else if (defined.startsWith("ThisTurnEntered")) { } else if (defined.startsWith("ThisTurnEntered")) {
final String[] workingCopy = defined.split("_"); final String[] workingCopy = defined.split("_");
ZoneType destination, origin; ZoneType destination, origin;
@@ -345,6 +350,23 @@ public class AbilityUtils {
cards.add(game.getCardState(cardByID)); cards.add(game.getCardState(cardByID));
} }
} }
} else if (defined.startsWith("Valid")) {
Iterable<Card> candidates;
String validDefined;
if (defined.startsWith("Valid ")) {
candidates = game.getCardsIn(ZoneType.Battlefield);
validDefined = changedDef.substring("Valid ".length());
} else if (defined.startsWith("ValidAll ")) {
candidates = game.getCardsInGame();
validDefined = changedDef.substring("ValidAll ".length());
} else {
String[] s = changedDef.split(" ", 2);
String zone = s[0].substring("Valid".length());
candidates = game.getCardsIn(ZoneType.smartValueOf(zone));
validDefined = s[1];
}
cards.addAll(CardLists.getValidCards(candidates, validDefined.split(","), hostCard.getController(), hostCard, sa));
return cards;
} else { } else {
CardCollection list = null; CardCollection list = null;
if (sa instanceof SpellAbility) { if (sa instanceof SpellAbility) {
@@ -376,19 +398,6 @@ public class AbilityUtils {
} }
} }
if (defined.startsWith("Valid ")) {
String validDefined = defined.substring("Valid ".length());
list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), validDefined.split(","), hostCard.getController(), hostCard, sa);
} else if (defined.startsWith("ValidAll ")) {
String validDefined = defined.substring("ValidAll ".length());
list = CardLists.getValidCards(game.getCardsInGame(), validDefined.split(","), hostCard.getController(), hostCard, sa);
} else if (defined.startsWith("Valid")) {
String[] s = defined.split(" ");
String zone = s[0].substring("Valid".length());
String validDefined = s[1];
list = CardLists.getValidCards(game.getCardsIn(ZoneType.smartValueOf(zone)), validDefined.split(","), hostCard.getController(), hostCard, sa);
}
if (list != null) { if (list != null) {
cards.addAll(list); cards.addAll(list);
} }
@@ -420,7 +429,7 @@ public class AbilityUtils {
private static Card findEffectRoot(Card startCard) { private static Card findEffectRoot(Card startCard) {
Card cc = startCard.getEffectSource(); Card cc = startCard.getEffectSource();
if (cc != null) { if (cc != null) {
if (cc.isEmblem() || cc.getType().hasSubtype("Effect")) { if (cc.isImmutable()) {
return findEffectRoot(cc); return findEffectRoot(cc);
} }
return cc; return cc;
@@ -1268,6 +1277,9 @@ public class AbilityUtils {
} }
else if (defined.equals("Opponent")) { else if (defined.equals("Opponent")) {
players.addAll(player.getOpponents()); players.addAll(player.getOpponents());
} else if (defined.startsWith("NextPlayerToYour")) {
Direction dir = defined.substring(16).equals("Left") ? Direction.Left : Direction.Right;
players.add(game.getNextPlayerAfter(player, dir));
} }
else { else {
for (Player p : game.getPlayersInTurnOrder()) { for (Player p : game.getPlayersInTurnOrder()) {

View File

@@ -142,6 +142,7 @@ public enum ApiType {
ReplaceEffect (ReplaceEffect.class), ReplaceEffect (ReplaceEffect.class),
ReplaceMana (ReplaceManaEffect.class), ReplaceMana (ReplaceManaEffect.class),
ReplaceDamage (ReplaceDamageEffect.class), ReplaceDamage (ReplaceDamageEffect.class),
ReplaceToken (ReplaceTokenEffect.class),
ReplaceSplitDamage (ReplaceSplitDamageEffect.class), ReplaceSplitDamage (ReplaceSplitDamageEffect.class),
RestartGame (RestartGameEffect.class), RestartGame (RestartGameEffect.class),
Reveal (RevealEffect.class), Reveal (RevealEffect.class),

View File

@@ -13,7 +13,6 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Table; import com.google.common.collect.Table;
import forge.GameCommand; import forge.GameCommand;
import forge.card.CardType;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
@@ -457,25 +456,19 @@ public abstract class SpellAbilityEffect {
final Card eff = new Card(game.nextCardId(), game); final Card eff = new Card(game.nextCardId(), game);
eff.setTimestamp(game.getNextTimestamp()); eff.setTimestamp(game.getNextTimestamp());
eff.setName(name); eff.setName(name);
eff.setColor(hostCard.determineColor().getColor());
// if name includes emblem then it should be one // if name includes emblem then it should be one
eff.addType(name.startsWith("Emblem") ? "Emblem" : "Effect"); if (name.startsWith("Emblem")) {
// add Planeswalker types into Emblem for fun eff.setEmblem(true);
if (name.startsWith("Emblem") && hostCard.isPlaneswalker()) { // Emblem needs to be colorless
for (final String type : hostCard.getType().getSubtypes()) { eff.setColor(MagicColor.COLORLESS);
if (CardType.isAPlaneswalkerType(type)) {
eff.addType(type);
}
}
} }
eff.setOwner(controller); eff.setOwner(controller);
eff.setSVars(sa.getSVars()); eff.setSVars(sa.getSVars());
eff.setImageKey(image); eff.setImageKey(image);
if (eff.getType().hasType(CardType.CoreType.Emblem)) {
eff.setColor(MagicColor.COLORLESS);
} else {
eff.setColor(hostCard.determineColor().getColor());
}
eff.setImmutable(true); eff.setImmutable(true);
eff.setEffectSource(sa); eff.setEffectSource(sa);

View File

@@ -16,7 +16,6 @@ import forge.game.card.CardPredicates;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.card.CounterEnumType; import forge.game.card.CounterEnumType;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.card.token.TokenInfo;
import forge.game.event.GameEventCombatChanged; import forge.game.event.GameEventCombatChanged;
import forge.game.event.GameEventTokenCreated; import forge.game.event.GameEventTokenCreated;
import forge.game.player.Player; import forge.game.player.Player;
@@ -53,37 +52,24 @@ public class AmassEffect extends TokenEffectBase {
final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa); final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa);
final boolean remember = sa.hasParam("RememberAmass"); final boolean remember = sa.hasParam("RememberAmass");
boolean useZoneTable = true;
CardZoneTable triggerList = sa.getChangeZoneTable();
if (triggerList == null) {
triggerList = new CardZoneTable();
useZoneTable = false;
}
if (sa.hasParam("ChangeZoneTable")) {
sa.setChangeZoneTable(triggerList);
useZoneTable = true;
}
MutableBoolean combatChanged = new MutableBoolean(false);
// create army token if needed // create army token if needed
if (CardLists.count(activator.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Army")) == 0) { if (CardLists.count(activator.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Army")) == 0) {
final String tokenScript = "b_0_0_zombie_army"; CardZoneTable triggerList = new CardZoneTable();
MutableBoolean combatChanged = new MutableBoolean(false);
final Card prototype = TokenInfo.getProtoType(tokenScript, sa, false); makeTokenTable(makeTokenTableInternal(activator, "b_0_0_zombie_army", 1, sa), false, triggerList, combatChanged, sa);
makeTokens(prototype, activator, sa, 1, true, false, triggerList, combatChanged);
if (!useZoneTable) {
triggerList.triggerChangesZoneAll(game, sa); triggerList.triggerChangesZoneAll(game, sa);
triggerList.clear(); triggerList.clear();
}
game.fireEvent(new GameEventTokenCreated()); game.fireEvent(new GameEventTokenCreated());
}
if (combatChanged.isTrue()) { if (combatChanged.isTrue()) {
game.updateCombatForView(); game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged()); game.fireEvent(new GameEventCombatChanged());
} }
}
Map<String, Object> params = Maps.newHashMap(); Map<String, Object> params = Maps.newHashMap();
params.put("CounterType", CounterType.get(CounterEnumType.P1P1)); params.put("CounterType", CounterType.get(CounterEnumType.P1P1));
params.put("Amount", 1); params.put("Amount", 1);

View File

@@ -190,11 +190,6 @@ public class AnimateEffect extends AnimateEffectBase {
} }
} }
// Restore immutable to effect
if (sa.hasParam("Immutable")) {
c.setImmutable(true);
}
game.fireEvent(new GameEventCardStatsChanged(c)); game.fireEvent(new GameEventCardStatsChanged(c));
} }

View File

@@ -164,14 +164,6 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), c, false, sa); final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), c, false, sa);
addedTriggers.add(parsedTrigger); addedTriggers.add(parsedTrigger);
} }
if (sa.hasParam("GainsTriggeredAbilitiesOf")) {
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("GainsTriggeredAbilitiesOf"), sa);
for (final Card card : cards) {
for (Trigger t : card.getTriggers()) {
addedTriggers.add(t.copy(c, false));
}
}
}
// give replacement effects // give replacement effects
final List<ReplacementEffect> addedReplacements = Lists.newArrayList(); final List<ReplacementEffect> addedReplacements = Lists.newArrayList();

View File

@@ -119,6 +119,12 @@ public class ChooseCardEffect extends SpellAbilityEffect {
Aggregates.random(choices, validAmount, chosen); Aggregates.random(choices, validAmount, chosen);
} else { } else {
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseaCard") + " "; String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseaCard") + " ";
if (sa.hasParam ("ChoiceTitleAppendDefined")) {
String defined = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("ChoiceTitleAppendDefined"), sa).toString();
final StringBuilder sb = new StringBuilder();
sb.append(title).append(" ").append(defined);
title = sb.toString();
}
chosen.addAll(p.getController().chooseCardsForEffect(choices, sa, title, minAmount, validAmount, !sa.hasParam("Mandatory"), null)); chosen.addAll(p.getController().chooseCardsForEffect(choices, sa, title, minAmount, validAmount, !sa.hasParam("Mandatory"), null));
} }
} }

View File

@@ -72,8 +72,7 @@ public class ChooseTypeEffect extends SpellAbilityEffect {
} }
} }
} }
} } else {
else {
throw new InvalidParameterException(sa.getHostCard() + "'s ability resulted in no types to choose from"); throw new InvalidParameterException(sa.getHostCard() + "'s ability resulted in no types to choose from");
} }
} }

View File

@@ -67,7 +67,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
if (tapOnLose) { if (tapOnLose) {
c.tap(); c.tap();
} }
} // if }
host.removeGainControlTargets(c); host.removeGainControlTargets(c);
} }
@@ -93,6 +93,11 @@ public class ControlGainEffect extends SpellAbilityEffect {
tgtCards = CardLists.filterControlledBy(tgtCards, getTargetPlayers(sa)); tgtCards = CardLists.filterControlledBy(tgtCards, getTargetPlayers(sa));
} }
// in case source was LKI or still resolving
if (source.isLKI() || source.getZone().is(ZoneType.Stack)) {
source = game.getCardState(source);
}
// check for lose control criteria right away // check for lose control criteria right away
if (lose != null && lose.contains("LeavesPlay") && !source.isInZone(ZoneType.Battlefield)) { if (lose != null && lose.contains("LeavesPlay") && !source.isInZone(ZoneType.Battlefield)) {
return; return;
@@ -103,7 +108,6 @@ public class ControlGainEffect extends SpellAbilityEffect {
boolean combatChanged = false; boolean combatChanged = false;
for (Card tgtC : tgtCards) { for (Card tgtC : tgtCards) {
if (!tgtC.isInPlay() || !tgtC.canBeControlledBy(newController)) { if (!tgtC.isInPlay() || !tgtC.canBeControlledBy(newController)) {
continue; continue;
} }

View File

@@ -20,6 +20,7 @@ import forge.game.card.CardFactory;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.card.TokenCreateTable;
import forge.game.event.GameEventCombatChanged; import forge.game.event.GameEventCombatChanged;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerActionConfirmMode;
@@ -177,15 +178,18 @@ public class CopyPermanentEffect extends TokenEffectBase {
} }
MutableBoolean combatChanged = new MutableBoolean(false); MutableBoolean combatChanged = new MutableBoolean(false);
TokenCreateTable tokenTable = new TokenCreateTable();
for (final Card c : tgtCards) { for (final Card c : tgtCards) {
// if it only targets player, it already got all needed cards from defined // if it only targets player, it already got all needed cards from defined
if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer() && !c.canBeTargetedBy(sa)) { if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer() && !c.canBeTargetedBy(sa)) {
continue; continue;
} }
tokenTable.put(controller, getProtoType(sa, c, controller), numCopies);
makeTokens(getProtoType(sa, c), controller, sa, numCopies, true, true, triggerList, combatChanged);
} // end foreach Card } // end foreach Card
makeTokenTable(tokenTable, true, triggerList, combatChanged, sa);
if (!useZoneTable) { if (!useZoneTable) {
triggerList.triggerChangesZoneAll(game, sa); triggerList.triggerChangesZoneAll(game, sa);
triggerList.clear(); triggerList.clear();
@@ -196,9 +200,8 @@ public class CopyPermanentEffect extends TokenEffectBase {
} }
} // end resolve } // end resolve
private Card getProtoType(final SpellAbility sa, final Card original) { private Card getProtoType(final SpellAbility sa, final Card original, final Player newOwner) {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
final Player newOwner = sa.getActivatingPlayer();
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId(); int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
final Card copy = new Card(id, original.getPaperCard(), host.getGame()); final Card copy = new Card(id, original.getPaperCard(), host.getGame());
copy.setOwner(newOwner); copy.setOwner(newOwner);

View File

@@ -12,7 +12,6 @@ public class DetachedCardEffect extends Card {
card = card0; card = card0;
setName(name0); setName(name0);
addType("Effect");
setOwner(card0.getOwner()); setOwner(card0.getOwner());
setImmutable(true); setImmutable(true);

View File

@@ -140,10 +140,6 @@ public class EffectEffect extends SpellAbilityEffect {
final Card eff = createEffect(sa, controller, name, image); final Card eff = createEffect(sa, controller, name, image);
eff.setSetCode(sa.getHostCard().getSetCode()); eff.setSetCode(sa.getHostCard().getSetCode());
eff.setRarity(sa.getHostCard().getRarity()); eff.setRarity(sa.getHostCard().getRarity());
// For Raging River effect to add attacker "left" or "right" pile later
if (sa.hasParam("Mutable")) {
eff.setImmutable(false);
}
// Abilities and triggers work the same as they do for Token // Abilities and triggers work the same as they do for Token
// Grant abilities // Grant abilities

View File

@@ -38,7 +38,6 @@ public class FightEffect extends DamageBaseEffect {
return sb.toString(); return sb.toString();
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.game.ability.SpellAbilityEffect#resolve(forge.game.spellability.SpellAbility) * @see forge.game.ability.SpellAbilityEffect#resolve(forge.game.spellability.SpellAbility)
*/ */
@@ -77,6 +76,7 @@ public class FightEffect extends DamageBaseEffect {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap(); final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Fighters, fighters); runParams.put(AbilityKey.Fighters, fighters);
game.getTriggerHandler().runTrigger(TriggerType.FightOnce, runParams, false);
} }
private static List<Card> getFighters(SpellAbility sa) { private static List<Card> getFighters(SpellAbility sa) {

View File

@@ -6,7 +6,6 @@ import forge.game.Game;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.card.token.TokenInfo;
import forge.game.event.GameEventCombatChanged; import forge.game.event.GameEventCombatChanged;
import forge.game.event.GameEventTokenCreated; import forge.game.event.GameEventTokenCreated;
import forge.game.player.Player; import forge.game.player.Player;
@@ -36,14 +35,13 @@ public class InvestigateEffect extends TokenEffectBase {
final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa); final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa);
final String tokenScript = "c_a_clue_draw"; // Investigate in Sequence
final Card prototype = TokenInfo.getProtoType(tokenScript, sa, false);
for (final Player p : getTargetPlayers(sa)) { for (final Player p : getTargetPlayers(sa)) {
for (int i = 0; i < amount; i++) { for (int i = 0; i < amount; i++) {
CardZoneTable triggerList = new CardZoneTable(); CardZoneTable triggerList = new CardZoneTable();
MutableBoolean combatChanged = new MutableBoolean(false); MutableBoolean combatChanged = new MutableBoolean(false);
makeTokens(prototype, p, sa, 1, true, false, triggerList, combatChanged);
makeTokenTable(makeTokenTableInternal(p, "c_a_clue_draw", 1, sa), false, triggerList, combatChanged, sa);
triggerList.triggerChangesZoneAll(game, sa); triggerList.triggerChangesZoneAll(game, sa);
p.addInvestigatedThisTurn(); p.addInvestigatedThisTurn();

View File

@@ -1,7 +1,10 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -69,6 +72,8 @@ public class PlayEffect extends SpellAbilityEffect {
final boolean optional = sa.hasParam("Optional"); final boolean optional = sa.hasParam("Optional");
boolean remember = sa.hasParam("RememberPlayed"); boolean remember = sa.hasParam("RememberPlayed");
int amount = 1; int amount = 1;
boolean hasTotalCMCLimit = sa.hasParam("WithTotalCMC");
int totalCMCLimit = Integer.MAX_VALUE;
if (sa.hasParam("Amount") && !sa.getParam("Amount").equals("All")) { if (sa.hasParam("Amount") && !sa.getParam("Amount").equals("All")) {
amount = AbilityUtils.calculateAmount(source, sa.getParam("Amount"), sa); amount = AbilityUtils.calculateAmount(source, sa.getParam("Amount"), sa);
} }
@@ -163,13 +168,15 @@ public class PlayEffect extends SpellAbilityEffect {
if (sa.hasParam("ValidSA")) { if (sa.hasParam("ValidSA")) {
final String valid[] = {sa.getParam("ValidSA")}; final String valid[] = {sa.getParam("ValidSA")};
List<Card> toRemove = Lists.newArrayList(); Iterator<Card> it = tgtCards.iterator();
for (Card c : tgtCards) { while (it.hasNext()) {
Card c = it.next();
if (!Iterables.any(AbilityUtils.getBasicSpellsFromPlayEffect(c, controller), SpellAbilityPredicates.isValid(valid, controller , c, sa))) { if (!Iterables.any(AbilityUtils.getBasicSpellsFromPlayEffect(c, controller), SpellAbilityPredicates.isValid(valid, controller , c, sa))) {
toRemove.add(c); // it.remove will only remove item from the list part of CardCollection
tgtCards.asSet().remove(c);
it.remove();
} }
} }
tgtCards.removeAll(toRemove);
if (tgtCards.isEmpty()) { if (tgtCards.isEmpty()) {
return; return;
} }
@@ -179,15 +186,37 @@ public class PlayEffect extends SpellAbilityEffect {
amount = tgtCards.size(); amount = tgtCards.size();
} }
if (hasTotalCMCLimit) {
totalCMCLimit = AbilityUtils.calculateAmount(source, sa.getParam("WithTotalCMC"), sa);
}
if (controlledByPlayer != null) { if (controlledByPlayer != null) {
activator.addController(controlledByTimeStamp, controlledByPlayer); activator.addController(controlledByTimeStamp, controlledByPlayer);
} }
boolean singleOption = tgtCards.size() == 1 && amount == 1 && optional; boolean singleOption = tgtCards.size() == 1 && amount == 1 && optional;
Map<String, Object> params = hasTotalCMCLimit ? new HashMap<>() : null;
while (!tgtCards.isEmpty() && amount > 0 && totalCMCLimit >= 0) {
if (hasTotalCMCLimit) {
// filter out cards with mana value greater than limit
Iterator<Card> it = tgtCards.iterator();
final String [] valid = {"Spell.cmcLE"+totalCMCLimit};
while (it.hasNext()) {
Card c = it.next();
if (!Iterables.any(AbilityUtils.getBasicSpellsFromPlayEffect(c, controller), SpellAbilityPredicates.isValid(valid, controller , c, sa))) {
// it.remove will only remove item from the list part of CardCollection
tgtCards.asSet().remove(c);
it.remove();
}
}
if (tgtCards.isEmpty())
break;
params.put("CMCLimit", totalCMCLimit);
}
while (!tgtCards.isEmpty() && amount > 0) {
activator.getController().tempShowCards(showCards); activator.getController().tempShowCards(showCards);
Card tgtCard = controller.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblSelectCardToPlay"), !singleOption && optional, null); Card tgtCard = controller.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblSelectCardToPlay"), !singleOption && optional, params);
activator.getController().endTempShowCards(); activator.getController().endTempShowCards();
if (tgtCard == null) { if (tgtCard == null) {
break; break;
@@ -248,6 +277,14 @@ public class PlayEffect extends SpellAbilityEffect {
final String valid[] = {sa.getParam("ValidSA")}; final String valid[] = {sa.getParam("ValidSA")};
sas = Lists.newArrayList(Iterables.filter(sas, SpellAbilityPredicates.isValid(valid, controller , source, sa))); sas = Lists.newArrayList(Iterables.filter(sas, SpellAbilityPredicates.isValid(valid, controller , source, sa)));
} }
if (hasTotalCMCLimit) {
Iterator<SpellAbility> it = sas.iterator();
while (it.hasNext()) {
SpellAbility s = it.next();
if (s.getPayCosts().getTotalMana().getCMC() > totalCMCLimit)
it.remove();
}
}
if (sas.isEmpty()) { if (sas.isEmpty()) {
continue; continue;
@@ -276,6 +313,8 @@ public class PlayEffect extends SpellAbilityEffect {
continue; continue;
} }
final int tgtCMC = tgtSA.getPayCosts().getTotalMana().getCMC();
if (sa.hasParam("WithoutManaCost")) { if (sa.hasParam("WithoutManaCost")) {
tgtSA = tgtSA.copyWithNoManaCost(); tgtSA = tgtSA.copyWithNoManaCost();
} else if (sa.hasParam("PlayCost")) { } else if (sa.hasParam("PlayCost")) {
@@ -341,6 +380,7 @@ public class PlayEffect extends SpellAbilityEffect {
} }
amount--; amount--;
totalCMCLimit -= tgtCMC;
} }
// Remove controlled by player if any // Remove controlled by player if any

View File

@@ -121,7 +121,7 @@ public class PumpEffect extends SpellAbilityEffect {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
//if host is not on the battlefield don't apply //if host is not on the battlefield don't apply
// Suspend should does Affect the Stack // Suspend should does Affect the Stack
if ((sa.hasParam("UntilLoseControlOfHost") || sa.hasParam("UntilHostLeavesPlay")) if (("UntilHostLeavesPlay".equals(sa.getParam("Duration")) || "UntilLoseControlOfHost".equals(sa.getParam("Duration")))
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) { && !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
return; return;
} }
@@ -158,7 +158,6 @@ public class PumpEffect extends SpellAbilityEffect {
} }
if (tgts.size() > 0) { if (tgts.size() > 0) {
for (final GameEntity c : tgts) { for (final GameEntity c : tgts) {
sb.append(c).append(" "); sb.append(c).append(" ");
} }

View File

@@ -37,7 +37,7 @@ public class RegenerationEffect extends SpellAbilityEffect {
// Play the Regen sound // Play the Regen sound
game.fireEvent(new GameEventCardRegenerated()); game.fireEvent(new GameEventCardRegenerated());
if (host.getType().hasStringType("Effect")) { if (host.isImmutable()) {
c.subtractShield(host); c.subtractShield(host);
host.removeRemembered(c); host.removeRemembered(c);
} }

View File

@@ -49,7 +49,7 @@ public class RepeatEffect extends SpellAbilityEffect {
// TODO Replace Infinite Loop Break with a game draw. Here are the scenarios that can cause this: // TODO Replace Infinite Loop Break with a game draw. Here are the scenarios that can cause this:
// Helm of Obedience vs Graveyard to Library replacement effect // Helm of Obedience vs Graveyard to Library replacement effect
if(source.getName().equals("Helm of Obedience")) { if (source.getName().equals("Helm of Obedience")) {
StringBuilder infLoop = new StringBuilder(sa.getHostCard().toString()); StringBuilder infLoop = new StringBuilder(sa.getHostCard().toString());
infLoop.append(" - To avoid an infinite loop, this repeat has been broken "); infLoop.append(" - To avoid an infinite loop, this repeat has been broken ");
infLoop.append(" and the game will now continue in the current state, ending the loop early. "); infLoop.append(" and the game will now continue in the current state, ending the loop early. ");

View File

@@ -47,7 +47,7 @@ public class ReplaceDamageEffect extends SpellAbilityEffect {
prevent -= n; prevent -= n;
if (!StringUtils.isNumeric(varValue) && card.getSVar(varValue).startsWith("Number$")) { if (!StringUtils.isNumeric(varValue) && card.getSVar(varValue).startsWith("Number$")) {
if (card.getType().hasStringType("Effect") && prevent <= 0) { if (card.isImmutable() && prevent <= 0) {
game.getAction().exile(card, null); game.getAction().exile(card, null);
} else { } else {
card.setSVar(varValue, "Number$" + prevent); card.setSVar(varValue, "Number$" + prevent);

View File

@@ -3,12 +3,6 @@ package forge.game.ability.effects;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.GameLogEntryType;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
@@ -17,25 +11,21 @@ import forge.game.card.Card;
import forge.game.card.token.TokenInfo; import forge.game.card.token.TokenInfo;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.util.TextUtil;
public class ReplaceEffect extends SpellAbilityEffect { public class ReplaceEffect extends SpellAbilityEffect {
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Card card = sa.getHostCard(); final Card card = sa.getHostCard();
final Game game = card.getGame();
final AbilityKey varName = AbilityKey.fromString(sa.getParam("VarName")); final AbilityKey varName = AbilityKey.fromString(sa.getParam("VarName"));
final String varValue = sa.getParam("VarValue"); final String varValue = sa.getParam("VarValue");
final String type = sa.getParamOrDefault("VarType", "amount"); final String type = sa.getParamOrDefault("VarType", "amount");
final ReplacementType retype = sa.getReplacementEffect().getMode();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams); Map<AbilityKey, Object> params = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
Map<AbilityKey, Object> params = Maps.newHashMap(originalParams);
if ("Card".equals(type)) { if ("Card".equals(type)) {
List<Card> list = AbilityUtils.getDefinedCards(card, varValue, sa); List<Card> list = AbilityUtils.getDefinedCards(card, varValue, sa);
@@ -53,7 +43,7 @@ public class ReplaceEffect extends SpellAbilityEffect {
params.put(varName, list.get(0)); params.put(varName, list.get(0));
} }
} else if ("TokenScript".equals(type)) { } else if ("TokenScript".equals(type)) {
final Card protoType = TokenInfo.getProtoType(varValue, sa); final Card protoType = TokenInfo.getProtoType(varValue, sa, sa.getActivatingPlayer());
if (protoType != null) { if (protoType != null) {
params.put(varName, protoType); params.put(varName, protoType);
} }
@@ -61,42 +51,7 @@ public class ReplaceEffect extends SpellAbilityEffect {
params.put(varName, AbilityUtils.calculateAmount(card, varValue, sa)); params.put(varName, AbilityUtils.calculateAmount(card, varValue, sa));
} }
if (params.containsKey(AbilityKey.EffectOnly)) { params.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
params.put(AbilityKey.EffectOnly, true);
}
if (retype == ReplacementType.DamageDone) {
for (Map.Entry<AbilityKey, Object> e : params.entrySet()) {
originalParams.put(e.getKey(), e.getValue());
}
originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
return;
}
// need to log Updated events there, or the log is wrong order
String message = sa.getReplacementEffect().toString();
if ( !StringUtils.isEmpty(message)) {
message = TextUtil.fastReplace(message, "CARDNAME", card.getName());
game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
}
//try to call replacementHandler with new Params
ReplacementResult result = game.getReplacementHandler().run(retype, params);
switch (result) {
case NotReplaced:
case Updated: {
for (Map.Entry<AbilityKey, Object> e : params.entrySet()) {
originalParams.put(e.getKey(), e.getValue());
}
// effect was updated
originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
break;
}
default:
// effect was replaced with something else
originalParams.put(AbilityKey.ReplacementResult, result);
break;
}
} }
} }

View File

@@ -4,20 +4,15 @@ import java.util.Map;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Maps;
import forge.card.ColorSet; import forge.card.ColorSet;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.Game;
import forge.game.GameLogEntryType;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.util.TextUtil;
public class ReplaceManaEffect extends SpellAbilityEffect { public class ReplaceManaEffect extends SpellAbilityEffect {
@@ -25,17 +20,14 @@ public class ReplaceManaEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Card card = sa.getHostCard(); final Card card = sa.getHostCard();
final Player player = sa.getActivatingPlayer(); final Player player = sa.getActivatingPlayer();
final Game game = card.getGame();
// outside of Replacement Effect, unwanted result // outside of Replacement Effect, unwanted result
if (!sa.isReplacementAbility()) { if (!sa.isReplacementAbility()) {
return; return;
} }
final ReplacementType event = sa.getReplacementEffect().getMode();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams); Map<AbilityKey, Object> params = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
Map<AbilityKey, Object> params = Maps.newHashMap(originalParams);
String replaced = (String)sa.getReplacingObject(AbilityKey.Mana); String replaced = (String)sa.getReplacingObject(AbilityKey.Mana);
if (sa.hasParam("ReplaceMana")) { if (sa.hasParam("ReplaceMana")) {
@@ -79,31 +71,8 @@ public class ReplaceManaEffect extends SpellAbilityEffect {
replaced = StringUtils.repeat(replaced, " ", Integer.valueOf(sa.getParam("ReplaceAmount"))); replaced = StringUtils.repeat(replaced, " ", Integer.valueOf(sa.getParam("ReplaceAmount")));
} }
params.put(AbilityKey.Mana, replaced); params.put(AbilityKey.Mana, replaced);
// need to log Updated events there, or the log is wrong order
String message = sa.getReplacementEffect().toString();
if (!StringUtils.isEmpty(message)) {
message = TextUtil.fastReplace(message, "CARDNAME", card.getName());
game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
}
//try to call replacementHandler with new Params
ReplacementResult result = game.getReplacementHandler().run(event, params);
switch (result) {
case NotReplaced:
case Updated: {
for (Map.Entry<AbilityKey, Object> e : params.entrySet()) {
originalParams.put(e.getKey(), e.getValue());
}
// effect was updated // effect was updated
originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated); params.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
break;
}
default:
// effect was replaced with something else
originalParams.put(AbilityKey.ReplacementResult, result);
break;
}
} }
} }

View File

@@ -45,7 +45,7 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect {
dmg -= n; dmg -= n;
prevent -= n; prevent -= n;
if (card.getType().hasStringType("Effect") && prevent <= 0) { if (card.isImmutable() && prevent <= 0) {
game.getAction().exile(card, null); game.getAction().exile(card, null);
} else if (!StringUtils.isNumeric(varValue)) { } else if (!StringUtils.isNumeric(varValue)) {
sa.setSVar(varValue, "Number$" + prevent); sa.setSVar(varValue, "Number$" + prevent);

View File

@@ -0,0 +1,131 @@
package forge.game.ability.effects;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.ObjectUtils;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.TokenCreateTable;
import forge.game.card.token.TokenInfo;
import forge.game.player.Player;
import forge.game.replacement.ReplacementResult;
import forge.game.spellability.SpellAbility;
public class ReplaceTokenEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final Card card = sa.getHostCard();
final Player p = sa.getActivatingPlayer();
final Game game = card.getGame();
// ReplaceToken Effect only applies to one Player
Player affected = (Player) sa.getReplacingObject(AbilityKey.Player);
TokenCreateTable table = (TokenCreateTable) sa.getReplacingObject(AbilityKey.Token);
@SuppressWarnings("unchecked")
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa
.getReplacingObject(AbilityKey.OriginalParams);
// currently the only ones that changes the amount does double it
if ("Amount".equals(sa.getParam("Type"))) {
for (Map.Entry<Card, Integer> e : table.row(affected).entrySet()) {
if (!sa.matchesValidParam("ValidCard", e.getKey())) {
continue;
}
// currently the amount is only doubled
table.put(affected, e.getKey(), e.getValue() * 2);
}
} else if ("AddToken".equals(sa.getParam("Type"))) {
long timestamp = game.getNextTimestamp();
Map<Player, Integer> byController = Maps.newHashMap();
for (Map.Entry<Card, Integer> e : table.row(affected).entrySet()) {
if (!sa.matchesValidParam("ValidCard", e.getKey())) {
continue;
}
Player contoller = e.getKey().getController();
int old = ObjectUtils.defaultIfNull(byController.get(contoller), 0);
byController.put(contoller, old + e.getValue());
}
if (!byController.isEmpty()) {
// for Xorn, might matter if you could somehow create Treasure under multiple players control
if (sa.hasParam("Amount")) {
int i = AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa);
for (Map.Entry<Player, Integer> e : byController.entrySet()) {
e.setValue(i);
}
}
for (Map.Entry<Player, Integer> e : byController.entrySet()) {
for (String script : sa.getParam("TokenScript").split(",")) {
final Card token = TokenInfo.getProtoType(script, sa, p);
if (token == null) {
throw new RuntimeException("don't find Token for TokenScript: " + script);
}
token.setController(e.getKey(), timestamp);
table.put(p, token, e.getValue());
}
}
}
} else if ("ReplaceToken".equals(sa.getParam("Type"))) {
long timestamp = game.getNextTimestamp();
Map<Player, Integer> toInsertMap = Maps.newHashMap();
Set<Card> toRemoveSet = Sets.newHashSet();
for (Map.Entry<Card, Integer> e : table.row(affected).entrySet()) {
if (!sa.matchesValidParam("ValidCard", e.getKey())) {
continue;
}
Player controller = e.getKey().getController();
int old = ObjectUtils.defaultIfNull(toInsertMap.get(controller), 0);
toInsertMap.put(controller, old + e.getValue());
toRemoveSet.add(e.getKey());
}
// remove replaced tokens
table.row(affected).keySet().removeAll(toRemoveSet);
// insert new tokens
for (Map.Entry<Player, Integer> pe : toInsertMap.entrySet()) {
if (pe.getValue() <= 0) {
continue;
}
for (String script : sa.getParam("TokenScript").split(",")) {
final Card token = TokenInfo.getProtoType(script, sa, pe.getKey());
if (token == null) {
throw new RuntimeException("don't find Token for TokenScript: " + script);
}
token.setController(pe.getKey(), timestamp);
table.put(affected, token, pe.getValue());
}
}
} else if ("ReplaceController".equals(sa.getParam("Type"))) {
long timestamp = game.getNextTimestamp();
Player newController = sa.getActivatingPlayer();
if (sa.hasParam("NewController")) {
newController = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("NewController"), sa).get(0);
}
for (Map.Entry<Card, Integer> c : table.row(affected).entrySet()) {
if (!sa.matchesValidParam("ValidCard", c.getKey())) {
continue;
}
c.getKey().setController(newController, timestamp);
}
}
// effect was updated
originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
}
}

View File

@@ -23,10 +23,8 @@ import forge.game.Game;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.card.token.TokenInfo;
import forge.game.event.GameEventCombatChanged; import forge.game.event.GameEventCombatChanged;
import forge.game.event.GameEventTokenCreated; import forge.game.event.GameEventTokenCreated;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
public class TokenEffect extends TokenEffectBase { public class TokenEffect extends TokenEffectBase {
@@ -36,20 +34,6 @@ public class TokenEffect extends TokenEffectBase {
return sa.getDescription(); return sa.getDescription();
} }
public Card loadTokenPrototype(SpellAbility sa) {
if (!sa.hasParam("TokenScript")) {
return null;
}
final Card result = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa);
if (result == null) {
throw new RuntimeException("don't find Token for TokenScript: " + sa.getParam("TokenScript"));
}
return result;
}
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
@@ -67,9 +51,8 @@ public class TokenEffect extends TokenEffectBase {
} }
} }
Card prototype = loadTokenPrototype(sa);
final int finalAmount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("TokenAmount", "1"), sa); final int finalAmount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("TokenAmount", "1"), sa);
MutableBoolean combatChanged = new MutableBoolean(false);
boolean useZoneTable = true; boolean useZoneTable = true;
CardZoneTable triggerList = sa.getChangeZoneTable(); CardZoneTable triggerList = sa.getChangeZoneTable();
@@ -82,10 +65,8 @@ public class TokenEffect extends TokenEffectBase {
useZoneTable = true; useZoneTable = true;
} }
MutableBoolean combatChanged = new MutableBoolean(false); makeTokenTable(AbilityUtils.getDefinedPlayers(host, sa.getParamOrDefault("TokenOwner", "You"), sa),
for (final Player owner : AbilityUtils.getDefinedPlayers(host, sa.getParamOrDefault("TokenOwner", "You"), sa)) { sa.getParam("TokenScript").split(","), finalAmount, false, triggerList, combatChanged, sa);
makeTokens(prototype, owner, sa, finalAmount, true, false, triggerList, combatChanged);
}
if (!useZoneTable) { if (!useZoneTable) {
triggerList.triggerChangesZoneAll(game, sa); triggerList.triggerChangesZoneAll(game, sa);

View File

@@ -2,45 +2,125 @@ package forge.game.ability.effects;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.mutable.MutableBoolean; import org.apache.commons.lang3.mutable.MutableBoolean;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import forge.GameCommand; import forge.GameCommand;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardFactory;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.card.TokenCreateTable;
import forge.game.card.token.TokenInfo; import forge.game.card.token.TokenInfo;
import forge.game.event.GameEventCardStatsChanged; import forge.game.event.GameEventCardStatsChanged;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
public abstract class TokenEffectBase extends SpellAbilityEffect { public abstract class TokenEffectBase extends SpellAbilityEffect {
protected List<Card> makeTokens(final Card prototype, final Player creator, final SpellAbility sa, int finalAmount, protected TokenCreateTable createTokenTable(Iterable<Player> players, String[] tokenScripts, final int finalAmount, final SpellAbility sa) {
boolean applyMultiplier, boolean clone, CardZoneTable triggerList, MutableBoolean combatChanged) {
TokenCreateTable tokenTable = new TokenCreateTable();
for (final Player owner : players) {
for (String script : tokenScripts) {
final Card result = TokenInfo.getProtoType(script, sa, owner);
if (result == null) {
throw new RuntimeException("don't find Token for TokenScript: " + script);
}
// set owner
result.setOwner(owner);
tokenTable.put(owner, result, finalAmount);
}
}
return tokenTable;
}
protected TokenCreateTable makeTokenTableInternal(Player owner, String script, final int finalAmount, final SpellAbility sa) {
TokenCreateTable tokenTable = new TokenCreateTable();
final Card result = TokenInfo.getProtoType(script, sa, owner, false);
if (result == null) {
throw new RuntimeException("don't find Token for TokenScript: " + script);
}
// set owner
result.setOwner(owner);
tokenTable.put(owner, result, finalAmount);
return tokenTable;
}
protected TokenCreateTable makeTokenTable(Iterable<Player> players, String[] tokenScripts, final int finalAmount, final boolean clone,
CardZoneTable triggerList, MutableBoolean combatChanged, final SpellAbility sa) {
return makeTokenTable(createTokenTable(players, tokenScripts, finalAmount, sa), clone, triggerList, combatChanged, sa);
}
protected TokenCreateTable makeTokenTable(TokenCreateTable tokenTable, final boolean clone, CardZoneTable triggerList, MutableBoolean combatChanged, final SpellAbility sa) {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
final Game game = host.getGame(); final Game game = host.getGame();
final long timestamp = game.getNextTimestamp(); long timestamp = game.getNextTimestamp();
// support PlayerCollection for affected
Set<Player> toRemove = Sets.newHashSet();
for (Player p : tokenTable.rowKeySet()) {
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(p);
repParams.put(AbilityKey.Token, tokenTable);
repParams.put(AbilityKey.EffectOnly, true); // currently only effects can create tokens?
switch (game.getReplacementHandler().run(ReplacementType.CreateToken, repParams)) {
case NotReplaced:
break;
case Updated: {
tokenTable = (TokenCreateTable) repParams.get(AbilityKey.Token);
break;
}
default:
toRemove.add(p);
}
}
tokenTable.rowKeySet().removeAll(toRemove);
final List<String> pumpKeywords = Lists.newArrayList(); final List<String> pumpKeywords = Lists.newArrayList();
if (sa.hasParam("PumpKeywords")) { if (sa.hasParam("PumpKeywords")) {
pumpKeywords.addAll(Arrays.asList(sa.getParam("PumpKeywords").split(" & "))); pumpKeywords.addAll(Arrays.asList(sa.getParam("PumpKeywords").split(" & ")));
} }
List<Card> allTokens = Lists.newArrayList(); List<Card> allTokens = Lists.newArrayList();
for (Card tok : TokenInfo.makeTokensFromPrototype(prototype, creator, finalAmount, applyMultiplier)) { for (final Table.Cell<Player, Card, Integer> c : tokenTable.cellSet()) {
Card prototype = c.getColumnKey();
Player creator = c.getRowKey();
Player controller = prototype.getController();
int cellAmount = c.getValue();
for (int i = 0; i < cellAmount; i++) {
Card tok = CardFactory.copyCard(prototype, true);
// Crafty Cutpurse would change under which control it does enter,
// but it shouldn't change who creates the token
tok.setOwner(creator);
if (creator != controller) {
tok.setController(controller, timestamp);
}
tok.setTimestamp(timestamp);
tok.setToken(true);
// do effect stuff with the token
if (sa.hasParam("TokenTapped")) { if (sa.hasParam("TokenTapped")) {
tok.setTapped(true); tok.setTapped(true);
} }
@@ -48,7 +128,6 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
if (!sa.hasParam("AttachAfter") && sa.hasParam("AttachedTo") && !attachTokenTo(tok, sa)) { if (!sa.hasParam("AttachAfter") && sa.hasParam("AttachedTo") && !attachTokenTo(tok, sa)) {
continue; continue;
} }
if (sa.hasParam("WithCounters")) { if (sa.hasParam("WithCounters")) {
String[] parse = sa.getParam("WithCounters").split("_"); String[] parse = sa.getParam("WithCounters").split("_");
tok.addEtbCounter(CounterType.getType(parse[0]), Integer.parseInt(parse[1]), creator); tok.addEtbCounter(CounterType.getType(parse[0]), Integer.parseInt(parse[1]), creator);
@@ -60,34 +139,43 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
tok.addEtbCounter(cType, cAmount, creator); tok.addEtbCounter(cType, cAmount, creator);
} }
if (sa.hasParam("AddTriggersFrom")) {
final List<Card> cards = AbilityUtils.getDefinedCards(host, sa.getParam("AddTriggersFrom"), sa);
for (final Card card : cards) {
for (final Trigger trig : card.getTriggers()) {
tok.addTrigger(trig.copy(tok, false));
}
}
}
if (clone) { if (clone) {
tok.setCopiedPermanent(prototype); tok.setCopiedPermanent(prototype);
} }
// Should this be catching the Card that's returned? // Should this be catching the Card that's returned?
Card c = game.getAction().moveToPlay(tok, sa); Card moved = game.getAction().moveToPlay(tok, sa);
if (c == null || c.getZone() == null) { if (moved == null || moved.getZone() == null) {
// in case token can't enter the battlefield, it isn't created // in case token can't enter the battlefield, it isn't created
triggerList.put(ZoneType.None, ZoneType.None, c); triggerList.put(ZoneType.None, ZoneType.None, moved);
continue; continue;
} }
triggerList.put(ZoneType.None, c.getZone().getZoneType(), c); triggerList.put(ZoneType.None, moved.getZone().getZoneType(), moved);
creator.addTokensCreatedThisTurn(); creator.addTokensCreatedThisTurn();
if (clone) { if (clone) {
c.setCloneOrigin(host); moved.setCloneOrigin(host);
} }
if (!pumpKeywords.isEmpty()) { if (!pumpKeywords.isEmpty()) {
c.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, false, timestamp); moved.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, false, timestamp);
addPumpUntil(sa, c, timestamp); addPumpUntil(sa, moved, timestamp);
} }
if (sa.hasParam("AtEOTTrig")) { if (sa.hasParam("AtEOTTrig")) {
addSelfTrigger(sa, sa.getParam("AtEOTTrig"), c); addSelfTrigger(sa, sa.getParam("AtEOTTrig"), moved);
} }
if (addToCombat(c, tok.getController(), sa, "TokenAttacking", "TokenBlocking")) { if (addToCombat(moved, tok.getController(), sa, "TokenAttacking", "TokenBlocking")) {
combatChanged.setTrue(); combatChanged.setTrue();
} }
@@ -95,30 +183,31 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
attachTokenTo(tok, sa); attachTokenTo(tok, sa);
} }
c.updateStateForView(); moved.updateStateForView();
if (sa.hasParam("RememberTokens")) { if (sa.hasParam("RememberTokens")) {
host.addRemembered(c); host.addRemembered(moved);
} }
if (sa.hasParam("ImprintTokens")) { if (sa.hasParam("ImprintTokens")) {
host.addImprintedCard(c); host.addImprintedCard(moved);
} }
if (sa.hasParam("RememberSource")) { if (sa.hasParam("RememberSource")) {
c.addRemembered(host); moved.addRemembered(host);
} }
if (sa.hasParam("TokenRemembered")) { if (sa.hasParam("TokenRemembered")) {
final String remembered = sa.getParam("TokenRemembered"); final String remembered = sa.getParam("TokenRemembered");
for (final Object o : AbilityUtils.getDefinedObjects(host, remembered, sa)) { for (final Object o : AbilityUtils.getDefinedObjects(host, remembered, sa)) {
c.addRemembered(o); moved.addRemembered(o);
} }
} }
allTokens.add(c); allTokens.add(moved);
}
} }
if (sa.hasParam("AtEOT")) { if (sa.hasParam("AtEOT")) {
registerDelayedTrigger(sa, sa.getParam("AtEOT"), allTokens); registerDelayedTrigger(sa, sa.getParam("AtEOT"), allTokens);
} }
return allTokens; return tokenTable;
} }
private boolean attachTokenTo(Card tok, SpellAbility sa) { private boolean attachTokenTo(Card tok, SpellAbility sa) {

View File

@@ -206,6 +206,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
// for Vanguard / Manapool / Emblems etc. // for Vanguard / Manapool / Emblems etc.
private boolean isImmutable = false; private boolean isImmutable = false;
private boolean isEmblem = false;
private int exertThisTurn = 0; private int exertThisTurn = 0;
private PlayerCollection exertedByPlayer = new PlayerCollection(); private PlayerCollection exertedByPlayer = new PlayerCollection();
@@ -1629,7 +1630,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return exiledWith; return exiledWith;
} }
public final void setExiledWith(final Card e) { public final void setExiledWith(final Card e) {
exiledWith = e; exiledWith = view.setCard(exiledWith, e, TrackableProperty.ExiledWith);
} }
public final void cleanupExiledWith() { public final void cleanupExiledWith() {
@@ -1738,6 +1739,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public void setCurrentRoom(String room) { public void setCurrentRoom(String room) {
currentRoom = room; currentRoom = room;
view.updateCurrentRoom(this); view.updateCurrentRoom(this);
view.getCurrentState().updateAbilityText(this, getCurrentState());
} }
public boolean isInLastRoom() { public boolean isInLastRoom() {
for (final Trigger t : getTriggers()) { for (final Trigger t : getTriggers()) {
@@ -2154,6 +2156,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
public String getAbilityText(final CardState state) { public String getAbilityText(final CardState state) {
final String linebreak = "\r\n\r\n";
final String grayTag = "<span style=\"color:gray;\">";
final String endTag = "</span>";
final CardTypeView type = state.getType(); final CardTypeView type = state.getType();
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
@@ -2199,9 +2204,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
// Get original description since text might be translated // Get original description since text might be translated
if (replacementEffect.hasParam("Description") && if (replacementEffect.hasParam("Description") &&
replacementEffect.getParam("Description").contains("enters the battlefield")) { replacementEffect.getParam("Description").contains("enters the battlefield")) {
sb.append(text).append("\r\n"); sb.append(text).append(linebreak);
} else { } else {
replacementEffects.append(text).append("\r\n"); replacementEffects.append(text).append(linebreak);
} }
} }
} }
@@ -2213,7 +2218,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
Cost cost = first.getPayCosts(); Cost cost = first.getPayCosts();
if (cost != null && !cost.isOnlyManaCost()) { if (cost != null && !cost.isOnlyManaCost()) {
sb.append(cost.toString()); sb.append(cost.toString());
sb.append("\r\n"); sb.append(linebreak);
} }
} }
} }
@@ -2233,15 +2238,22 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
// Give spellText line breaks for easier reading // Give spellText line breaks for easier reading
sb.append("\r\n");
sb.append(text.replaceAll("\\\\r\\\\n", "\r\n")); sb.append(text.replaceAll("\\\\r\\\\n", "\r\n"));
sb.append("\r\n"); sb.append(linebreak);
// Triggered abilities // Triggered abilities
for (final Trigger trig : state.getTriggers()) { for (final Trigger trig : state.getTriggers()) {
if (!trig.isSecondary() && !trig.isClassAbility()) { if (!trig.isSecondary() && !trig.isClassAbility()) {
boolean disabled = false;
// Disable text of other rooms
if (type.isDungeon() && !trig.getOverridingAbility().getParam("RoomName").equals(getCurrentRoom())) {
disabled = true;
}
String trigStr = trig.replaceAbilityText(trig.toString(), state); String trigStr = trig.replaceAbilityText(trig.toString(), state);
sb.append(trigStr.replaceAll("\\\\r\\\\n", "\r\n")).append("\r\n"); if (disabled) sb.append(grayTag);
sb.append(trigStr.replaceAll("\\\\r\\\\n", "\r\n"));
if (disabled) sb.append(endTag);
sb.append(linebreak);
} }
} }
@@ -2253,7 +2265,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
if (!stAb.isSecondary() && !stAb.isClassAbility()) { if (!stAb.isSecondary() && !stAb.isClassAbility()) {
final String stAbD = stAb.toString(); final String stAbD = stAb.toString();
if (!stAbD.equals("")) { if (!stAbD.equals("")) {
sb.append(stAbD).append("\r\n"); sb.append(stAbD).append(linebreak);
} }
} }
} }
@@ -2326,7 +2338,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", host.getEffectSource().getName()); desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", host.getEffectSource().getName());
} }
sb.append(desc); sb.append(desc);
sb.append("\r\n"); sb.append(linebreak);
} }
} }
} }
@@ -2334,13 +2346,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
// Class Abilities // Class Abilities
if (isClassCard()) { if (isClassCard()) {
String linebreak = "\r\n\r\n";
sb.append(linebreak); sb.append(linebreak);
// Currently the maximum levels of all Class cards are all 3 // Currently the maximum levels of all Class cards are all 3
for (int level = 1; level <= 3; ++level) { for (int level = 1; level <= 3; ++level) {
boolean disabled = level > getClassLevel() && isInZone(ZoneType.Battlefield); boolean disabled = level > getClassLevel() && isInZone(ZoneType.Battlefield);
final String grayTag = "<span style=\"color:gray;\">";
final String endTag = "</span>";
for (final Trigger trig : state.getTriggers()) { for (final Trigger trig : state.getTriggers()) {
if (trig.isClassLevelNAbility(level) && !trig.isSecondary()) { if (trig.isClassLevelNAbility(level) && !trig.isSecondary()) {
if (disabled) sb.append(grayTag); if (disabled) sb.append(grayTag);
@@ -4532,15 +4541,13 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
public final boolean isPermanent() { public final boolean isPermanent() {
return !isImmutable && (isInZone(ZoneType.Battlefield) || getType().isPermanent()); return !isImmutable() && (isInZone(ZoneType.Battlefield) || getType().isPermanent());
} }
public final boolean isSpell() { public final boolean isSpell() {
return (isInstant() || isSorcery() || (isAura() && !isInZone((ZoneType.Battlefield)))); return (isInstant() || isSorcery() || (isAura() && !isInZone((ZoneType.Battlefield))));
} }
public final boolean isEmblem() { return getType().isEmblem(); }
public final boolean isLand() { return getType().isLand(); } public final boolean isLand() { return getType().isLand(); }
public final boolean isBasicLand() { return getType().isBasicLand(); } public final boolean isBasicLand() { return getType().isBasicLand(); }
public final boolean isSnow() { return getType().isSnow(); } public final boolean isSnow() { return getType().isSnow(); }
@@ -4799,11 +4806,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
// Takes one argument like Permanent.Blue+withFlying // Takes one argument like Permanent.Blue+withFlying
@Override @Override
public final boolean isValid(final String restriction, final Player sourceController, final Card source, CardTraitBase spellAbility) { public final boolean isValid(final String restriction, final Player sourceController, final Card source, CardTraitBase spellAbility) {
if (isImmutable() && source != null && !source.isRemembered(this) &&
!(restriction.startsWith("Emblem") || restriction.startsWith("Effect"))) { // special case exclusion
return false;
}
// Inclusive restrictions are Card types // Inclusive restrictions are Card types
final String[] incR = restriction.split("\\.", 2); final String[] incR = restriction.split("\\.", 2);
@@ -4813,14 +4815,27 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
incR[0] = incR[0].substring(1); // consume negation sign incR[0] = incR[0].substring(1); // consume negation sign
} }
if (incR[0].equals("Spell") && !isSpell()) { if (incR[0].equals("Spell")) {
if (!isSpell()) {
return testFailed; return testFailed;
} }
if (incR[0].equals("Permanent") && !isPermanent()) { } else if (incR[0].equals("Permanent")) {
if (!isPermanent()) {
return testFailed; return testFailed;
} }
if (!incR[0].equals("card") && !incR[0].equals("Card") && !incR[0].equals("Spell") } else if (incR[0].equals("Effect")) {
&& !incR[0].equals("Permanent") && !getType().hasStringType(incR[0])) { if (!isImmutable()) {
return testFailed;
}
} else if (incR[0].equals("Emblem")) {
if (!isEmblem()) {
return testFailed;
}
} else if (incR[0].equals("card") || incR[0].equals("Card")) {
if (isImmutable()) {
return testFailed;
}
} else if (!getType().hasStringType(incR[0])) {
return testFailed; // Check for wrong type return testFailed; // Check for wrong type
} }
@@ -4828,24 +4843,20 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
final String excR = incR[1]; final String excR = incR[1];
final String[] exRs = excR.split("\\+"); // Exclusive Restrictions are ... final String[] exRs = excR.split("\\+"); // Exclusive Restrictions are ...
for (String exR : exRs) { for (String exR : exRs) {
if (exR.startsWith("!")) {
exR = exR.substring(1);
if (hasProperty(exR, sourceController, source, spellAbility)) {
return testFailed;
}
} else {
if (!hasProperty(exR, sourceController, source, spellAbility)) { if (!hasProperty(exR, sourceController, source, spellAbility)) {
return testFailed; return testFailed;
} }
} }
} }
}
return !testFailed; return !testFailed;
} }
// Takes arguments like Blue or withFlying // Takes arguments like Blue or withFlying
@Override @Override
public boolean hasProperty(final String property, final Player sourceController, final Card source, CardTraitBase spellAbility) { public boolean hasProperty(final String property, final Player sourceController, final Card source, CardTraitBase spellAbility) {
if (property.startsWith("!")) {
return !CardProperty.cardHasProperty(this, property.substring(1), sourceController, source, spellAbility);
}
return CardProperty.cardHasProperty(this, property, sourceController, source, spellAbility); return CardProperty.cardHasProperty(this, property, sourceController, source, spellAbility);
} }
@@ -4854,6 +4865,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
public final void setImmutable(final boolean isImmutable0) { public final void setImmutable(final boolean isImmutable0) {
isImmutable = isImmutable0; isImmutable = isImmutable0;
view.updateImmutable(this);
}
public final boolean isEmblem() {
return isEmblem;
}
public final void setEmblem(final boolean isEmblem0) {
isEmblem = isEmblem0;
view.updateEmblem(this);
} }
/* /*
@@ -5969,6 +5989,19 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public void setCastFrom(final ZoneType castFrom0) { public void setCastFrom(final ZoneType castFrom0) {
castFrom = castFrom0; castFrom = castFrom0;
} }
public boolean wasCast() {
if (hasMergedCard()) {
boolean wasCast = false;
for (Card c : getMergedCards()) {
if (null != c.getCastFrom()) {
wasCast = true;
break;
}
}
return wasCast;
}
return getCastFrom() != null;
}
public SpellAbility getCastSA() { public SpellAbility getCastSA() {
return castSA; return castSA;

View File

@@ -350,7 +350,7 @@ public class CardProperty {
return false; return false;
} }
} else if (property.equals("EffectSource")) { } else if (property.equals("EffectSource")) {
if (!source.isEmblem() && !source.getType().hasSubtype("Effect")) { if (!source.isImmutable()) {
return false; return false;
} }
@@ -446,6 +446,9 @@ public class CardProperty {
} }
} else if (property.startsWith("CanEnchant")) { } else if (property.startsWith("CanEnchant")) {
final String restriction = property.substring(10); final String restriction = property.substring(10);
if (restriction.equals("EquippedBy")) {
if (!source.getEquipping().canBeAttached(card)) return false;
}
if (restriction.equals("Remembered")) { if (restriction.equals("Remembered")) {
for (final Object rem : source.getRemembered()) { for (final Object rem : source.getRemembered()) {
if (!(rem instanceof Card) || !((Card) rem).canBeAttached(card)) if (!(rem instanceof Card) || !((Card) rem).canBeAttached(card))
@@ -1658,6 +1661,10 @@ public class CardProperty {
if (source.hasImprintedCard(card)) { if (source.hasImprintedCard(card)) {
return false; return false;
} }
} else if (property.equals("IsGoaded")) {
if (!card.isGoaded()) {
return false;
}
} else if (property.equals("NoAbilities")) { } else if (property.equals("NoAbilities")) {
if (!card.hasNoAbilities()) { if (!card.hasNoAbilities()) {
return false; return false;
@@ -1680,16 +1687,15 @@ public class CardProperty {
return false; return false;
} }
} else if (property.equals("wasCast")) { } else if (property.equals("wasCast")) {
if (null == card.getCastFrom()) { if (!card.wasCast()) {
return false; return false;
} }
} else if (property.equals("wasNotCast")) { } else if (property.equals("wasNotCast")) {
if (null != card.getCastFrom()) { if (card.wasCast()) {
return false; return false;
} }
} else if (property.startsWith("wasCastFrom")) { } else if (property.startsWith("wasCastFrom")) {
// How are we getting in here with a comma? final String strZone = property.substring(11);
final String strZone = property.split(",")[0].substring(11);
final ZoneType realZone = ZoneType.smartValueOf(strZone); final ZoneType realZone = ZoneType.smartValueOf(strZone);
if (realZone != card.getCastFrom()) { if (realZone != card.getCastFrom()) {
return false; return false;

View File

@@ -120,7 +120,7 @@ public final class CardUtil {
List<Card> res = Lists.newArrayList(); List<Card> res = Lists.newArrayList();
final Game game = src.getGame(); final Game game = src.getGame();
if (to != ZoneType.Stack) { if (to != ZoneType.Stack) {
for (Player p : game.getPlayers()) { for (Player p : game.getRegisteredPlayers()) {
res.addAll(p.getZone(to).getCardsAddedThisTurn(from)); res.addAll(p.getZone(to).getCardsAddedThisTurn(from));
} }
} }
@@ -242,6 +242,7 @@ public final class CardUtil {
newCopy.setToken(in.isToken()); newCopy.setToken(in.isToken());
newCopy.setCopiedSpell(in.isCopiedSpell()); newCopy.setCopiedSpell(in.isCopiedSpell());
newCopy.setImmutable(in.isImmutable()); newCopy.setImmutable(in.isImmutable());
newCopy.setEmblem(in.isEmblem());
// lock in the current P/T // lock in the current P/T
newCopy.setBasePower(in.getCurrentPower()); newCopy.setBasePower(in.getCurrentPower());
@@ -530,6 +531,7 @@ public final class CardUtil {
// however, due to the changes necessary for SA_Requirements this is much // however, due to the changes necessary for SA_Requirements this is much
// different than the original // different than the original
public static List<Card> getValidCardsToTarget(TargetRestrictions tgt, SpellAbility ability) { public static List<Card> getValidCardsToTarget(TargetRestrictions tgt, SpellAbility ability) {
Card activatingCard = ability.getHostCard();
final Game game = ability.getActivatingPlayer().getGame(); final Game game = ability.getActivatingPlayer().getGame();
final List<ZoneType> zone = tgt.getZone(); final List<ZoneType> zone = tgt.getZone();
@@ -539,7 +541,6 @@ public final class CardUtil {
if (canTgtStack) { if (canTgtStack) {
// Since getTargetableCards doesn't have additional checks if one of the Zones is stack // Since getTargetableCards doesn't have additional checks if one of the Zones is stack
// Remove the activating card from targeting itself if its on the Stack // Remove the activating card from targeting itself if its on the Stack
Card activatingCard = ability.getHostCard();
if (activatingCard.isInZone(ZoneType.Stack)) { if (activatingCard.isInZone(ZoneType.Stack)) {
choices.remove(ability.getHostCard()); choices.remove(ability.getHostCard());
} }
@@ -561,7 +562,7 @@ public final class CardUtil {
final List<Card> choicesCopy = Lists.newArrayList(choices); final List<Card> choicesCopy = Lists.newArrayList(choices);
for (final Card c : choicesCopy) { for (final Card c : choicesCopy) {
if (c.getCMC() > tgt.getMaxTotalCMC(c, ability) - totalCMCTargeted) { if (c.getCMC() > tgt.getMaxTotalCMC(activatingCard, ability) - totalCMCTargeted) {
choices.remove(c); choices.remove(c);
} }
} }
@@ -576,7 +577,7 @@ public final class CardUtil {
final List<Card> choicesCopy = Lists.newArrayList(choices); final List<Card> choicesCopy = Lists.newArrayList(choices);
for (final Card c : choicesCopy) { for (final Card c : choicesCopy) {
if (c.getNetPower() > tgt.getMaxTotalPower(c, ability) - totalPowerTargeted) { if (c.getNetPower() > tgt.getMaxTotalPower(activatingCard, ability) - totalPowerTargeted) {
choices.remove(c); choices.remove(c);
} }
} }

View File

@@ -233,6 +233,20 @@ public class CardView extends GameEntityView {
set(TrackableProperty.Token, c.isToken()); set(TrackableProperty.Token, c.isToken());
} }
public boolean isImmutable() {
return get(TrackableProperty.IsImmutable);
}
public void updateImmutable(Card c) {
set(TrackableProperty.IsImmutable, c.isImmutable());
}
public boolean isEmblem() {
return get(TrackableProperty.IsEmblem);
}
public void updateEmblem(Card c) {
set(TrackableProperty.IsEmblem, c.isEmblem());
}
public boolean isTokenCard() { return get(TrackableProperty.TokenCard); } public boolean isTokenCard() { return get(TrackableProperty.TokenCard); }
void updateTokenCard(Card c) { set(TrackableProperty.TokenCard, c.isTokenCard()); } void updateTokenCard(Card c) { set(TrackableProperty.TokenCard, c.isTokenCard()); }
@@ -585,6 +599,10 @@ public class CardView extends GameEntityView {
return get(TrackableProperty.CloneOrigin); return get(TrackableProperty.CloneOrigin);
} }
public CardView getExiledWith() {
return get(TrackableProperty.ExiledWith);
}
public FCollectionView<CardView> getImprintedCards() { public FCollectionView<CardView> getImprintedCards() {
return get(TrackableProperty.ImprintedCards); return get(TrackableProperty.ImprintedCards);
} }

View File

@@ -65,6 +65,8 @@ public enum CounterEnumType {
COIN("COIN",255,215,0), COIN("COIN",255,215,0),
COMPONENT("COMPN", 224, 160, 48),
CORPSE("CRPSE", 230, 186, 209), CORPSE("CRPSE", 230, 186, 209),
CORRUPTION("CRPTN", 210, 121, 210), CORRUPTION("CRPTN", 210, 121, 210),

View File

@@ -0,0 +1,95 @@
package forge.game.card;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.ObjectUtils;
import com.google.common.collect.ForwardingTable;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Table;
import forge.game.CardTraitBase;
import forge.game.GameObjectPredicates;
import forge.game.player.Player;
public class TokenCreateTable extends ForwardingTable<Player, Card, Integer> {
Table<Player, Card, Integer> dataMap = HashBasedTable.create();
public TokenCreateTable() {
}
@Override
protected Table<Player, Card, Integer> delegate() {
return dataMap;
}
public int add(Player p, Card c, int i) {
int old = ObjectUtils.defaultIfNull(this.get(p, c), 0);
int newValue = old + i;
this.put(p, c, newValue);
return newValue;
}
public int getFilterAmount(String validOwner, String validToken, final CardTraitBase ctb) {
final Card host = ctb.getHostCard();
int result = 0;
List<Card> filteredCards = null;
List<Player> filteredPlayer = null;
if (validOwner == null && validToken == null) {
for (Integer i : values()) {
result += i;
}
return result;
}
if (validOwner != null) {
filteredPlayer = Lists.newArrayList(Iterables.filter(rowKeySet(),
GameObjectPredicates.restriction(validOwner.split(","), host.getController(), host, ctb)));
if (filteredPlayer.isEmpty()) {
return 0;
}
}
if (validToken != null) {
filteredCards = CardLists.getValidCardsAsList(columnKeySet(), validToken, host.getController(), host, ctb);
if (filteredCards.isEmpty()) {
return 0;
}
}
if (filteredPlayer == null) {
for (Map.Entry<Card, Map<Player, Integer>> e : columnMap().entrySet()) {
for (Integer i : e.getValue().values()) {
result += i;
}
}
return result;
}
if (filteredCards == null) {
for (Map.Entry<Player, Map<Card, Integer>> e : rowMap().entrySet()) {
for (Integer i : e.getValue().values()) {
result += i;
}
}
return result;
}
for (Table.Cell<Player, Card, Integer> c : this.cellSet()) {
if (!filteredPlayer.contains(c.getRowKey())) {
continue;
}
if (!filteredCards.contains(c.getColumnKey())) {
continue;
}
result += c.getValue();
}
return result;
}
}

View File

@@ -15,15 +15,12 @@ import forge.StaticData;
import forge.card.CardType; import forge.card.CardType;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardFactory;
import forge.game.card.CardFactoryUtil; import forge.game.card.CardFactoryUtil;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.item.PaperToken; import forge.item.PaperToken;
@@ -140,59 +137,6 @@ public class TokenInfo {
return sb.toString(); return sb.toString();
} }
public static List<Card> makeToken(final Card prototype, final Player owner,
final boolean applyMultiplier, final int num) {
final List<Card> list = Lists.newArrayList();
final Game game = owner.getGame();
int multiplier = num;
Player player = owner;
Card proto = prototype;
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(player);
repParams.put(AbilityKey.Token, prototype);
repParams.put(AbilityKey.TokenNum, multiplier);
repParams.put(AbilityKey.EffectOnly, applyMultiplier);
switch (game.getReplacementHandler().run(ReplacementType.CreateToken, repParams)) {
case NotReplaced:
break;
case Updated: {
multiplier = (int) repParams.get(AbilityKey.TokenNum);
player = (Player) repParams.get(AbilityKey.Affected);
proto = (Card) repParams.get(AbilityKey.Token);
break;
}
default:
multiplier = 0;
break;
}
if (multiplier <= 0) {
return list;
}
long timestamp = game.getNextTimestamp();
for (int i = 0; i < multiplier; i++) {
// need to set owner or copyCard will fail with assign new ID
proto.setOwner(owner);
Card copy = CardFactory.copyCard(proto, true);
// need to assign player after token is copied
if (player != owner) {
copy.setController(player, timestamp);
}
copy.setTimestamp(timestamp);
copy.setToken(true);
list.add(copy);
}
return list;
}
static public List<Card> makeTokensFromPrototype(Card prototype, final Player owner, int amount, final boolean applyMultiplier) {
return makeToken(prototype, owner, applyMultiplier, amount);
}
public Card makeOneToken(final Player controller) { public Card makeOneToken(final Player controller) {
final Game game = controller.getGame(); final Game game = controller.getGame();
final Card c = toCard(game); final Card c = toCard(game);
@@ -321,10 +265,10 @@ public class TokenInfo {
result.getCurrentState().changeTextIntrinsic(colorMap, typeMap); result.getCurrentState().changeTextIntrinsic(colorMap, typeMap);
} }
static public Card getProtoType(final String script, final SpellAbility sa) { static public Card getProtoType(final String script, final SpellAbility sa, final Player owner) {
return getProtoType(script, sa, true); return getProtoType(script, sa, owner, true);
} }
static public Card getProtoType(final String script, final SpellAbility sa, boolean applyTextChange) { static public Card getProtoType(final String script, final SpellAbility sa, final Player owner, boolean applyTextChange) {
// script might be null, or sa might be null // script might be null, or sa might be null
if (script == null || sa == null) { if (script == null || sa == null) {
return null; return null;
@@ -338,7 +282,7 @@ public class TokenInfo {
if (token == null) { if (token == null) {
return null; return null;
} }
final Card result = Card.fromPaperCard(token, null, game); final Card result = Card.fromPaperCard(token, owner, game);
if (sa.hasParam("TokenPower")) { if (sa.hasParam("TokenPower")) {
String str = sa.getParam("TokenPower"); String str = sa.getParam("TokenPower");

View File

@@ -1080,7 +1080,7 @@ public class CombatUtil {
return canAttackerBeBlockedWithAmount(attacker, amount, combat != null ? combat.getDefenderPlayerByAttacker(attacker) : null); return canAttackerBeBlockedWithAmount(attacker, amount, combat != null ? combat.getDefenderPlayerByAttacker(attacker) : null);
} }
public static boolean canAttackerBeBlockedWithAmount(Card attacker, int amount, Player defender) { public static boolean canAttackerBeBlockedWithAmount(Card attacker, int amount, Player defender) {
if(amount == 0 ) if (amount == 0)
return false; // no block return false; // no block
List<String> restrictions = Lists.newArrayList(); List<String> restrictions = Lists.newArrayList();
@@ -1093,7 +1093,7 @@ public class CombatUtil {
restrictions.add("LT2"); restrictions.add("LT2");
} }
} }
for ( String res : restrictions ) { for (String res : restrictions) {
int operand = Integer.parseInt(res.substring(2)); int operand = Integer.parseInt(res.substring(2));
String operator = res.substring(0,2); String operator = res.substring(0,2);
if (Expressions.compare(amount, operator, operand) ) if (Expressions.compare(amount, operator, operand) )
@@ -1118,7 +1118,7 @@ public class CombatUtil {
} }
} }
int minBlockers = 1; int minBlockers = 1;
for ( String res : restrictions ) { for (String res : restrictions) {
int operand = Integer.parseInt(res.substring(2)); int operand = Integer.parseInt(res.substring(2));
String operator = res.substring(0, 2); String operator = res.substring(0, 2);
if (operator.equals("LT") || operator.equals("GE")) { if (operator.equals("LT") || operator.equals("GE")) {

View File

@@ -363,6 +363,13 @@ public class Cost implements Serializable {
return new CostFlipCoin(splitStr[0]); return new CostFlipCoin(splitStr[0]);
} }
if (parse.startsWith("RollDice<")) {
// RollDice<NumDice/Sides/ResultSVar>
final String[] splitStr = abCostParse(parse, 4);
final String description = splitStr.length > 3 ? splitStr[3] : null;
return new CostRollDice(splitStr[0], splitStr[1], splitStr[2], description);
}
if (parse.startsWith("Discard<")) { if (parse.startsWith("Discard<")) {
// Discard<NumCards/Type> // Discard<NumCards/Type>
final String[] splitStr = abCostParse(parse, 3); final String[] splitStr = abCostParse(parse, 3);

View File

@@ -211,7 +211,7 @@ public class CostPayment extends ManaConversionMatrix {
return false; return false;
} }
// abilities care what was used to pay for them // abilities care what was used to pay for them
if( part instanceof CostPartWithList ) { if (part instanceof CostPartWithList) {
((CostPartWithList) part).resetLists(); ((CostPartWithList) part).resetLists();
} }

View File

@@ -0,0 +1,68 @@
package forge.game.cost;
import forge.game.ability.effects.RollDiceEffect;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* This is for the "RollDice" Cost
*/
public class CostRollDice extends CostPart {
/**
* Serializables need a version ID.
*/
private static final long serialVersionUID = 1L;
private final String resultSVar;
/**
* Instantiates a new cost RollDice.
*
* @param amount
* the amount
*/
public CostRollDice(final String amount, final String sides, final String resultSVar, final String description) {
super(amount, sides, description);
this.resultSVar = resultSVar;
}
/*
* (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 payer) {
return true;
}
@Override
public final String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("Roll ").append(getAmount());
if (this.getTypeDescription() == null) {
sb.append("d").append(getType());
} else {
sb.append(" ").append(this.getTypeDescription());
}
return sb.toString();
}
@Override
public boolean payAsDecided(Player payer, PaymentDecision pd, SpellAbility sa) {
int sides = Integer.parseInt(getType());
int result = RollDiceEffect.rollDiceForPlayer(sa, payer, pd.c, sides);
sa.setSVar(resultSVar, Integer.toString(result));
return true;
}
public <T> T accept(ICostVisitor<T> visitor) {
return visitor.visit(this);
}
}

View File

@@ -12,6 +12,7 @@ public interface ICostVisitor<T> {
T visit(CostExiledMoveToGrave cost); T visit(CostExiledMoveToGrave cost);
T visit(CostExert cost); T visit(CostExert cost);
T visit(CostFlipCoin cost); T visit(CostFlipCoin cost);
T visit(CostRollDice cost);
T visit(CostMill cost); T visit(CostMill cost);
T visit(CostAddMana cost); T visit(CostAddMana cost);
T visit(CostPayLife cost); T visit(CostPayLife cost);
@@ -84,6 +85,11 @@ public interface ICostVisitor<T> {
return null; return null;
} }
@Override
public T visit(CostRollDice cost) {
return null;
}
@Override @Override
public T visit(CostMill cost) { public T visit(CostMill cost) {
return null; return null;

View File

@@ -2217,6 +2217,9 @@ public class Player extends GameEntity implements Comparable<Player> {
@Override @Override
public final boolean hasProperty(final String property, final Player sourceController, final Card source, CardTraitBase spellAbility) { public final boolean hasProperty(final String property, final Player sourceController, final Card source, CardTraitBase spellAbility) {
if (property.startsWith("!")) {
return !PlayerProperty.playerHasProperty(this, property.substring(1), sourceController, source, spellAbility);
}
return PlayerProperty.playerHasProperty(this, property, sourceController, source, spellAbility); return PlayerProperty.playerHasProperty(this, property, sourceController, source, spellAbility);
} }
@@ -3173,6 +3176,7 @@ public class Player extends GameEntity implements Comparable<Player> {
if (monarchEffect == null) { if (monarchEffect == null) {
monarchEffect = new Card(game.nextCardId(), null, game); monarchEffect = new Card(game.nextCardId(), null, game);
monarchEffect.setOwner(this); monarchEffect.setOwner(this);
monarchEffect.setImmutable(true);
if (set != null) { if (set != null) {
monarchEffect.setImageKey("t:monarch_" + set.toLowerCase()); monarchEffect.setImageKey("t:monarch_" + set.toLowerCase());
monarchEffect.setSetCode(set); monarchEffect.setSetCode(set);
@@ -3180,7 +3184,6 @@ public class Player extends GameEntity implements Comparable<Player> {
monarchEffect.setImageKey("t:monarch"); monarchEffect.setImageKey("t:monarch");
} }
monarchEffect.setName("The Monarch"); monarchEffect.setName("The Monarch");
monarchEffect.addType("Effect");
{ {
final String drawTrig = "Mode$ Phase | Phase$ End of Turn | TriggerZones$ Command | " + final String drawTrig = "Mode$ Phase | Phase$ End of Turn | TriggerZones$ Command | " +
@@ -3277,8 +3280,7 @@ public class Player extends GameEntity implements Comparable<Player> {
blessingEffect.setOwner(this); blessingEffect.setOwner(this);
blessingEffect.setImageKey("t:blessing"); blessingEffect.setImageKey("t:blessing");
blessingEffect.setName("City's Blessing"); blessingEffect.setName("City's Blessing");
blessingEffect.addType("Effect"); blessingEffect.setImmutable(true);
blessingEffect.updateStateForView(); blessingEffect.updateStateForView();
@@ -3350,7 +3352,6 @@ public class Player extends GameEntity implements Comparable<Player> {
keywordEffect.setOwner(this); keywordEffect.setOwner(this);
keywordEffect.setName("Keyword Effects"); keywordEffect.setName("Keyword Effects");
keywordEffect.setImageKey(ImageKeys.HIDDEN_CARD); keywordEffect.setImageKey(ImageKeys.HIDDEN_CARD);
keywordEffect.addType("Effect");
keywordEffect.updateStateForView(); keywordEffect.updateStateForView();

View File

@@ -4,6 +4,7 @@ import java.util.Map;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.TokenCreateTable;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
/** /**
@@ -27,9 +28,11 @@ public class ReplaceToken extends ReplacementEffect {
*/ */
@Override @Override
public boolean canReplace(Map<AbilityKey, Object> runParams) { public boolean canReplace(Map<AbilityKey, Object> runParams) {
/*
if (((int) runParams.get(AbilityKey.TokenNum)) <= 0) { if (((int) runParams.get(AbilityKey.TokenNum)) <= 0) {
return false; return false;
} }
//*/
if (hasParam("EffectOnly")) { if (hasParam("EffectOnly")) {
final Boolean effectOnly = (Boolean) runParams.get(AbilityKey.EffectOnly); final Boolean effectOnly = (Boolean) runParams.get(AbilityKey.EffectOnly);
@@ -41,10 +44,16 @@ public class ReplaceToken extends ReplacementEffect {
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) { if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) {
return false; return false;
} }
/*/
if (!matchesValidParam("ValidToken", runParams.get(AbilityKey.Token))) { if (!matchesValidParam("ValidToken", runParams.get(AbilityKey.Token))) {
return false; return false;
} }
//*/
if (filterAmount((TokenCreateTable) runParams.get(AbilityKey.Token)) <= 0) {
return false;
}
return true; return true;
} }
@@ -54,8 +63,13 @@ public class ReplaceToken extends ReplacementEffect {
*/ */
@Override @Override
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) { public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
sa.setReplacingObject(AbilityKey.TokenNum, runParams.get(AbilityKey.TokenNum)); sa.setReplacingObject(AbilityKey.TokenNum, filterAmount((TokenCreateTable) runParams.get(AbilityKey.Token)));
sa.setReplacingObject(AbilityKey.Token, runParams.get(AbilityKey.Token));
sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Affected)); sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Affected));
} }
public int filterAmount(final TokenCreateTable table) {
return table.getFilterAmount(getParamOrDefault("ValidPlayer", null), getParamOrDefault("ValidToken", null), this);
}
} }

View File

@@ -220,7 +220,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
if (hasParam("Description") && !this.isSuppressed()) { if (hasParam("Description") && !this.isSuppressed()) {
String desc = AbilityUtils.applyDescriptionTextChangeEffects(getParam("Description"), this); String desc = AbilityUtils.applyDescriptionTextChangeEffects(getParam("Description"), this);
String currentName; String currentName;
if (this.isIntrinsic() && !this.getHostCard().isMutated() && cardState != null) { if (this.isIntrinsic() && cardState != null && cardState.getCard() == getHostCard()) {
currentName = cardState.getName(); currentName = cardState.getName();
} }
else { else {

View File

@@ -28,6 +28,7 @@ import java.util.Set;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import forge.game.CardTraitBase; import forge.game.CardTraitBase;
@@ -274,20 +275,45 @@ public class ReplacementHandler {
chosenRE.setOtherChoices(null); chosenRE.setOtherChoices(null);
return res; return res;
} }
chosenRE.setHasRun(false);
hasRun.remove(chosenRE);
chosenRE.setOtherChoices(null);
// Updated Replacements need to be logged elsewhere because its otherwise in the wrong order // Log there
if (res != ReplacementResult.Updated) {
String message = chosenRE.getDescription(); String message = chosenRE.getDescription();
if (!StringUtils.isEmpty(message)) if (!StringUtils.isEmpty(message)) {
if (chosenRE.getHostCard() != null) { if (chosenRE.getHostCard() != null) {
message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName()); message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName());
} }
game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
} }
// if its updated, try to call event again
if (res == ReplacementResult.Updated) {
Map<AbilityKey, Object> params = Maps.newHashMap(runParams);
if (params.containsKey(AbilityKey.EffectOnly)) {
params.put(AbilityKey.EffectOnly, true);
}
ReplacementResult result = run(event, params);
switch (result) {
case NotReplaced:
case Updated: {
for (Map.Entry<AbilityKey, Object> e : params.entrySet()) {
runParams.put(e.getKey(), e.getValue());
}
// effect was updated
runParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
break;
}
default:
// effect was replaced with something else
runParams.put(AbilityKey.ReplacementResult, result);
break;
}
}
chosenRE.setHasRun(false);
hasRun.remove(chosenRE);
chosenRE.setOtherChoices(null);
return res; return res;
} }
@@ -300,7 +326,6 @@ public class ReplacementHandler {
*/ */
private ReplacementResult executeReplacement(final Map<AbilityKey, Object> runParams, private ReplacementResult executeReplacement(final Map<AbilityKey, Object> runParams,
final ReplacementEffect replacementEffect, final Player decider, final Game game) { final ReplacementEffect replacementEffect, final Player decider, final Game game) {
final Map<String, String> mapParams = replacementEffect.getMapParams();
SpellAbility effectSA = null; SpellAbility effectSA = null;
@@ -311,10 +336,9 @@ public class ReplacementHandler {
host = game.getCardState(host); host = game.getCardState(host);
} }
if (replacementEffect.getOverridingAbility() == null && mapParams.containsKey("ReplaceWith")) { if (replacementEffect.getOverridingAbility() == null && replacementEffect.hasParam("ReplaceWith")) {
final String effectSVar = mapParams.get("ReplaceWith");
// TODO: the source of replacement effect should be the source of the original effect // TODO: the source of replacement effect should be the source of the original effect
effectSA = AbilityFactory.getAbility(host, effectSVar, replacementEffect); effectSA = AbilityFactory.getAbility(host, replacementEffect.getParam("ReplaceWith"), replacementEffect);
//replacementEffect.setOverridingAbility(effectSA); //replacementEffect.setOverridingAbility(effectSA);
//effectSA.setTrigger(true); //effectSA.setTrigger(true);
} else if (replacementEffect.getOverridingAbility() != null) { } else if (replacementEffect.getOverridingAbility() != null) {
@@ -342,10 +366,10 @@ public class ReplacementHandler {
// Decider gets to choose whether or not to apply the replacement. // Decider gets to choose whether or not to apply the replacement.
if (replacementEffect.hasParam("Optional")) { if (replacementEffect.hasParam("Optional")) {
Player optDecider = decider; Player optDecider = decider;
if (mapParams.containsKey("OptionalDecider") && (effectSA != null)) { if (replacementEffect.hasParam("OptionalDecider") && (effectSA != null)) {
effectSA.setActivatingPlayer(host.getController()); effectSA.setActivatingPlayer(host.getController());
optDecider = AbilityUtils.getDefinedPlayers(host, optDecider = AbilityUtils.getDefinedPlayers(host,
mapParams.get("OptionalDecider"), effectSA).get(0); replacementEffect.getParam("OptionalDecider"), effectSA).get(0);
} }
Card cardForUi = host.getCardForUi(); Card cardForUi = host.getCardForUi();
@@ -360,12 +384,12 @@ public class ReplacementHandler {
} }
} }
boolean isPrevent = mapParams.containsKey("Prevent") && mapParams.get("Prevent").equals("True"); boolean isPrevent = "True".equals(replacementEffect.getParam("Prevent"));
if (isPrevent || mapParams.containsKey("PreventionEffect")) { if (isPrevent || replacementEffect.hasParam("PreventionEffect")) {
if (Boolean.TRUE.equals(runParams.get(AbilityKey.NoPreventDamage))) { if (Boolean.TRUE.equals(runParams.get(AbilityKey.NoPreventDamage))) {
// If can't prevent damage, result is not replaced // If can't prevent damage, result is not replaced
// But still put "prevented" amount for buffered SA // But still put "prevented" amount for buffered SA
if (mapParams.containsKey("AlwaysReplace")) { if (replacementEffect.hasParam("AlwaysReplace")) {
runParams.put(AbilityKey.PreventedAmount, runParams.get(AbilityKey.DamageAmount)); runParams.put(AbilityKey.PreventedAmount, runParams.get(AbilityKey.DamageAmount));
} else { } else {
runParams.put(AbilityKey.PreventedAmount, 0); runParams.put(AbilityKey.PreventedAmount, 0);
@@ -377,11 +401,9 @@ public class ReplacementHandler {
} }
} }
if (mapParams.containsKey("Skip")) { if ("True".equals(replacementEffect.getParam("Skip"))) {
if (mapParams.get("Skip").equals("True")) {
return ReplacementResult.Skipped; // Event is skipped. return ReplacementResult.Skipped; // Event is skipped.
} }
}
Player player = host.getController(); Player player = host.getController();
@@ -394,6 +416,11 @@ public class ReplacementHandler {
// The SA if buffered, but replacement result should be set to Replaced // The SA if buffered, but replacement result should be set to Replaced
runParams.put(AbilityKey.ReplacementResult, ReplacementResult.Replaced); runParams.put(AbilityKey.ReplacementResult, ReplacementResult.Replaced);
} }
// these ones are special for updating
if (apiType == ApiType.ReplaceToken || apiType == ApiType.ReplaceEffect || apiType == ApiType.ReplaceMana) {
runParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
}
} }
// if the spellability is a replace effect then its some new logic // if the spellability is a replace effect then its some new logic

View File

@@ -95,8 +95,7 @@ public class LandAbility extends Ability {
Card source = sta.getHostCard(); Card source = sta.getHostCard();
if (!source.equals(getHostCard())) { if (!source.equals(getHostCard())) {
sb.append(" by "); sb.append(" by ");
if ((source.isEmblem() || source.getType().hasSubtype("Effect")) if (source.isImmutable() && source.getEffectSource() != null) {
&& source.getEffectSource() != null) {
sb.append(source.getEffectSource()); sb.append(source.getEffectSource());
} else { } else {
sb.append(source); sb.append(source);

View File

@@ -854,7 +854,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
if (node.getHostCard() != null) { if (node.getHostCard() != null) {
String currentName; String currentName;
// if alternate state is viewed while card uses original // if alternate state is viewed while card uses original
if (node.isIntrinsic() && !node.getHostCard().isMutated() && node.cardState != null) { if (node.isIntrinsic() && node.cardState != null && node.cardState.getCard() == node.getHostCard()) {
currentName = node.cardState.getName(); currentName = node.cardState.getName();
} }
else { else {

View File

@@ -209,7 +209,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
public final String toString() { public final String toString() {
if (hasParam("Description") && !this.isSuppressed()) { if (hasParam("Description") && !this.isSuppressed()) {
String currentName; String currentName;
if (this.isIntrinsic() && !this.getHostCard().isMutated() && cardState != null) { if (this.isIntrinsic() && cardState != null && cardState.getCard() == getHostCard()) {
currentName = cardState.getName(); currentName = cardState.getName();
} }
else { else {

View File

@@ -92,7 +92,7 @@ public class StaticAbilityCantAttackBlock {
} }
/** /**
* returns true if attacker can be blocked by blocker * returns true if attacker can't be blocked by blocker
* @param stAb * @param stAb
* @param attacker * @param attacker
* @param blocker * @param blocker
@@ -104,9 +104,10 @@ public class StaticAbilityCantAttackBlock {
return false; return false;
} }
if (stAb.hasParam("ValidBlocker")) { if (stAb.hasParam("ValidBlocker")) {
boolean stillblock = true;
for (final String v : stAb.getParam("ValidBlocker").split(",")) { for (final String v : stAb.getParam("ValidBlocker").split(",")) {
if (blocker.isValid(v, host.getController(), host, stAb)) { if (blocker.isValid(v, host.getController(), host, stAb)) {
boolean stillblock = false; stillblock = false;
//Dragon Hunter check //Dragon Hunter check
if (v.contains("withoutReach") && blocker.hasStartOfKeyword("IfReach")) { if (v.contains("withoutReach") && blocker.hasStartOfKeyword("IfReach")) {
for (KeywordInterface inst : blocker.getKeywords()) { for (KeywordInterface inst : blocker.getKeywords()) {
@@ -120,13 +121,14 @@ public class StaticAbilityCantAttackBlock {
} }
} }
} }
if (!stillblock) {
break;
}
}
}
if (stillblock) { if (stillblock) {
return false; return false;
} }
} else {
return false;
}
}
} }
// relative valid relative to each other // relative valid relative to each other
if (!stAb.matchesValidParam("ValidAttackerRelative", attacker, blocker)) { if (!stAb.matchesValidParam("ValidAttackerRelative", attacker, blocker)) {

View File

@@ -132,7 +132,7 @@ public abstract class Trigger extends TriggerReplacementBase {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
String currentName; String currentName;
if (this.isIntrinsic() && !this.getHostCard().isMutated() && cardState != null) { if (this.isIntrinsic() && cardState != null && cardState.getCard() == getHostCard()) {
currentName = cardState.getName(); currentName = cardState.getName();
} }
else { else {

View File

@@ -31,8 +31,6 @@ import forge.game.IHasSVars;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.CharmEffect;
import forge.game.card.*; import forge.game.card.*;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub; import forge.game.spellability.AbilitySub;
@@ -346,8 +344,7 @@ public class TriggerHandler {
} }
} }
private boolean runNonStaticTriggersForPlayer(final Player player, final TriggerWaiting wt, final List<Trigger> delayedTriggersWorkingCopy ) { private boolean runNonStaticTriggersForPlayer(final Player player, final TriggerWaiting wt, final List<Trigger> delayedTriggersWorkingCopy) {
final TriggerType mode = wt.getMode(); final TriggerType mode = wt.getMode();
final Map<AbilityKey, Object> runParams = wt.getParams(); final Map<AbilityKey, Object> runParams = wt.getParams();
final List<Trigger> triggers = wt.getTriggers() != null ? wt.getTriggers() : activeTriggers; final List<Trigger> triggers = wt.getTriggers() != null ? wt.getTriggers() : activeTriggers;
@@ -503,7 +500,6 @@ public class TriggerHandler {
// runs it if so. // runs it if so.
// Return true if the trigger went off, false otherwise. // Return true if the trigger went off, false otherwise.
private void runSingleTriggerInternal(final Trigger regtrig, final Map<AbilityKey, Object> runParams) { private void runSingleTriggerInternal(final Trigger regtrig, final Map<AbilityKey, Object> runParams) {
// All tests passed, execute ability. // All tests passed, execute ability.
if (regtrig instanceof TriggerTapsForMana) { if (regtrig instanceof TriggerTapsForMana) {
final SpellAbility abMana = (SpellAbility) runParams.get(AbilityKey.AbilityMana); final SpellAbility abMana = (SpellAbility) runParams.get(AbilityKey.AbilityMana);
@@ -519,8 +515,7 @@ public class TriggerHandler {
if (sa == null) { if (sa == null) {
if (!regtrig.hasParam("Execute")) { if (!regtrig.hasParam("Execute")) {
sa = new SpellAbility.EmptySa(host); sa = new SpellAbility.EmptySa(host);
} } else {
else {
String name = regtrig.getParam("Execute"); String name = regtrig.getParam("Execute");
if (!host.getCurrentState().hasSVar(name)) { if (!host.getCurrentState().hasSVar(name)) {
System.err.println("Warning: tried to run a trigger for card " + host + " referencing a SVar " + name + " not present on the current state " + host.getCurrentState() + ". Aborting trigger execution to prevent a crash."); System.err.println("Warning: tried to run a trigger for card " + host + " referencing a SVar " + name + " not present on the current state " + host.getCurrentState() + ". Aborting trigger execution to prevent a crash.");
@@ -577,12 +572,6 @@ public class TriggerHandler {
} }
sa.setStackDescription(sa.toString()); sa.setStackDescription(sa.toString());
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
if (!CharmEffect.makeChoices(sa)) {
// 603.3c If no mode is chosen, the ability is removed from the stack.
return;
}
}
Player decider = null; Player decider = null;
boolean isMandatory = false; boolean isMandatory = false;
@@ -592,8 +581,7 @@ public class TriggerHandler {
} }
else if (sa instanceof AbilitySub || !sa.hasParam("Cost") || sa.getParam("Cost").equals("0")) { else if (sa instanceof AbilitySub || !sa.hasParam("Cost") || sa.getParam("Cost").equals("0")) {
isMandatory = true; isMandatory = true;
} } else { // triggers with a cost can't be mandatory
else { // triggers with a cost can't be mandatory
sa.setOptionalTrigger(true); sa.setOptionalTrigger(true);
decider = sa.getActivatingPlayer(); decider = sa.getActivatingPlayer();
} }
@@ -605,8 +593,7 @@ public class TriggerHandler {
wrapperAbility.setLastStateBattlefield(game.getLastStateBattlefield()); wrapperAbility.setLastStateBattlefield(game.getLastStateBattlefield());
if (regtrig.isStatic()) { if (regtrig.isStatic()) {
wrapperAbility.getActivatingPlayer().getController().playTrigger(host, wrapperAbility, isMandatory); wrapperAbility.getActivatingPlayer().getController().playTrigger(host, wrapperAbility, isMandatory);
} } else {
else {
game.getStack().addSimultaneousStackEntry(wrapperAbility); game.getStack().addSimultaneousStackEntry(wrapperAbility);
} }

View File

@@ -44,6 +44,11 @@ public class TriggerRolledDie extends Trigger {
} }
return false; return false;
} }
if (hasParam("ValidSides")) {
final int validSides = Integer.parseInt(getParam("ValidSides"));
final int sides = (int) runParams.get(AbilityKey.Sides);
if (sides == validSides) return true;
}
return true; return true;
} }

View File

@@ -232,8 +232,8 @@ public class WrappedAbility extends Ability {
if (regtrig == null) return ""; if (regtrig == null) return "";
final StringBuilder sb = new StringBuilder(regtrig.replaceAbilityText(regtrig.toString(true), this)); final StringBuilder sb = new StringBuilder(regtrig.replaceAbilityText(regtrig.toString(true), this));
List<TargetChoices> allTargets = sa.getAllTargetChoices(); List<TargetChoices> allTargets = sa.getAllTargetChoices();
if (!allTargets.isEmpty()) { if (!allTargets.isEmpty() && !ApiType.Charm.equals(sa.getApi())) {
sb.append(" (Targeting "); sb.append(" (Targeting: ");
sb.append(allTargets); sb.append(allTargets);
sb.append(")"); sb.append(")");
} }
@@ -549,7 +549,6 @@ public class WrappedAbility extends Ability {
sa.setXManaCostPaid(n); sa.setXManaCostPaid(n);
} }
public CardState getCardState() { public CardState getCardState() {
return sa.getCardState(); return sa.getCardState();
} }

View File

@@ -40,6 +40,7 @@ import forge.game.GameObject;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.ability.effects.CharmEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
@@ -224,8 +225,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
undoStackOwner = activator; undoStackOwner = activator;
} }
undoStack.push(sp); undoStack.push(sp);
} } else {
else {
clearUndoStack(); clearUndoStack();
} }
@@ -628,8 +628,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
if (sa.isZeroTargets()) { if (sa.isZeroTargets()) {
// Nothing targeted, and nothing needs to be targeted. // Nothing targeted, and nothing needs to be targeted.
} } else {
else {
// Some targets were chosen, fizzling for this subability is now possible // Some targets were chosen, fizzling for this subability is now possible
//fizzle = true; //fizzle = true;
// With multi-targets, as long as one target is still legal, // With multi-targets, as long as one target is still legal,
@@ -675,8 +674,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
} }
else if (sa.getTargetCard() != null) { else if (sa.getTargetCard() != null) {
fizzle = !sa.canTarget(sa.getTargetCard()); fizzle = !sa.canTarget(sa.getTargetCard());
} } else {
else {
// Set fizzle to the same as the parent if there's no target info // Set fizzle to the same as the parent if there's no target info
fizzle = parentFizzled; fizzle = parentFizzled;
} }
@@ -799,7 +797,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
result |= chooseOrderOfSimultaneousStackEntry(whoAddsToStack); result |= chooseOrderOfSimultaneousStackEntry(whoAddsToStack);
// 2014-08-10 Fix infinite loop when a player dies during a multiplayer game during their turn // 2014-08-10 Fix infinite loop when a player dies during a multiplayer game during their turn
whoAddsToStack = game.getNextPlayerAfter(whoAddsToStack); whoAddsToStack = game.getNextPlayerAfter(whoAddsToStack);
} while( whoAddsToStack != null && whoAddsToStack != playerTurn); } while (whoAddsToStack != null && whoAddsToStack != playerTurn);
return result; return result;
} }
@@ -809,9 +807,19 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
} }
final List<SpellAbility> activePlayerSAs = Lists.newArrayList(); final List<SpellAbility> activePlayerSAs = Lists.newArrayList();
final List<SpellAbility> failedSAs = Lists.newArrayList();
for (int i = 0; i < simultaneousStackEntryList.size(); i++) { for (int i = 0; i < simultaneousStackEntryList.size(); i++) {
SpellAbility sa = simultaneousStackEntryList.get(i); SpellAbility sa = simultaneousStackEntryList.get(i);
Player activator = sa.getActivatingPlayer(); Player activator = sa.getActivatingPlayer();
if (sa.getApi() == ApiType.Charm) {
if (!CharmEffect.makeChoices(sa)) {
// 603.3c If no mode is chosen, the ability is removed from the stack.
failedSAs.add(sa);
continue;
}
}
if (activator == null) { if (activator == null) {
if (sa.getHostCard().getController().equals(activePlayer)) { if (sa.getHostCard().getController().equals(activePlayer)) {
activePlayerSAs.add(sa); activePlayerSAs.add(sa);
@@ -823,6 +831,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
} }
} }
simultaneousStackEntryList.removeAll(activePlayerSAs); simultaneousStackEntryList.removeAll(activePlayerSAs);
simultaneousStackEntryList.removeAll(failedSAs);
if (activePlayerSAs.isEmpty()) { if (activePlayerSAs.isEmpty()) {
return false; return false;
@@ -875,8 +884,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
public final void addCastCommand(final String valid, final GameCommand c) { public final void addCastCommand(final String valid, final GameCommand c) {
if (commandList.containsKey(valid)) { if (commandList.containsKey(valid)) {
commandList.get(valid).add(0, c); commandList.get(valid).add(0, c);
} } else {
else {
commandList.put(valid, Lists.newArrayList(c)); commandList.put(valid, Lists.newArrayList(c));
} }
} }

View File

@@ -24,6 +24,9 @@ public enum TrackableProperty {
Controller(TrackableTypes.PlayerViewType), Controller(TrackableTypes.PlayerViewType),
Zone(TrackableTypes.EnumType(ZoneType.class)), Zone(TrackableTypes.EnumType(ZoneType.class)),
IsImmutable(TrackableTypes.BooleanType),
IsEmblem(TrackableTypes.BooleanType),
Flipped(TrackableTypes.BooleanType), Flipped(TrackableTypes.BooleanType),
Facedown(TrackableTypes.BooleanType), Facedown(TrackableTypes.BooleanType),
Foretold(TrackableTypes.BooleanType), Foretold(TrackableTypes.BooleanType),
@@ -74,6 +77,7 @@ public enum TrackableProperty {
UntilLeavesBattlefield(TrackableTypes.CardViewCollectionType), UntilLeavesBattlefield(TrackableTypes.CardViewCollectionType),
GainControlTargets(TrackableTypes.CardViewCollectionType), GainControlTargets(TrackableTypes.CardViewCollectionType),
CloneOrigin(TrackableTypes.CardViewType), CloneOrigin(TrackableTypes.CardViewType),
ExiledWith(TrackableTypes.CardViewType),
ImprintedCards(TrackableTypes.CardViewCollectionType), ImprintedCards(TrackableTypes.CardViewCollectionType),
HauntedBy(TrackableTypes.CardViewCollectionType), HauntedBy(TrackableTypes.CardViewCollectionType),

View File

@@ -53,6 +53,7 @@ public class MessageUtil {
case Protection: case Protection:
return Localizer.getInstance().getMessage("lblPlayerChooseValue", choser, value); return Localizer.getInstance().getMessage("lblPlayerChooseValue", choser, value);
case RollDice: case RollDice:
case PutCounter:// For Clay Golem cost text
return value; return value;
case Vote: case Vote:
String chooser = StringUtils.capitalize(mayBeYou(player, target)); String chooser = StringUtils.capitalize(mayBeYou(player, target));

View File

@@ -6,7 +6,7 @@
<packaging.type>jar</packaging.type> <packaging.type>jar</packaging.type>
<build.min.memory>-Xms1024m</build.min.memory> <build.min.memory>-Xms1024m</build.min.memory>
<build.max.memory>-Xmx1536m</build.max.memory> <build.max.memory>-Xmx1536m</build.max.memory>
<alpha-version>1.6.42.001</alpha-version> <alpha-version>1.6.43.001</alpha-version>
<sign.keystore>keystore</sign.keystore> <sign.keystore>keystore</sign.keystore>
<sign.alias>alias</sign.alias> <sign.alias>alias</sign.alias>
<sign.storepass>storepass</sign.storepass> <sign.storepass>storepass</sign.storepass>
@@ -19,7 +19,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.43-SNAPSHOT</version> <version>1.6.44-SNAPSHOT</version>
</parent> </parent>
<artifactId>forge-gui-android</artifactId> <artifactId>forge-gui-android</artifactId>

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.43-SNAPSHOT</version> <version>1.6.44-SNAPSHOT</version>
</parent> </parent>
<artifactId>forge-gui-desktop</artifactId> <artifactId>forge-gui-desktop</artifactId>

View File

@@ -414,4 +414,3 @@ public enum FControl implements KeyEventDispatcher {
return GamePlayerUtil.getGuiPlayer(); return GamePlayerUtil.getGuiPlayer();
} }
} }

View File

@@ -194,7 +194,6 @@ public final class DeckManager extends ItemManager<DeckProxy> implements IHasGam
} }
@Override @Override
protected void buildAddFilterMenu(final JMenu menu) { protected void buildAddFilterMenu(final JMenu menu) {
GuiUtils.addSeparator(menu); //separate from current search item GuiUtils.addSeparator(menu); //separate from current search item
@@ -228,7 +227,6 @@ public final class DeckManager extends ItemManager<DeckProxy> implements IHasGam
} }
menu.add(fmt); menu.add(fmt);
GuiUtils.addMenuItem(menu, localizer.getMessage("lblFormats") + "...", null, new Runnable() { GuiUtils.addMenuItem(menu, localizer.getMessage("lblFormats") + "...", null, new Runnable() {
@Override public void run() { @Override public void run() {
final DeckFormatFilter existingFilter = getFilter(DeckFormatFilter.class); final DeckFormatFilter existingFilter = getFilter(DeckFormatFilter.class);

View File

@@ -340,4 +340,3 @@ public enum CDeckEditorUI implements ICDoc {
@Override @Override
public void update() { } public void update() { }
} }

View File

@@ -288,8 +288,7 @@ public class DeckController<T extends DeckBase> {
final String name = getModelName(); final String name = getModelName();
if (name.isEmpty()) { if (name.isEmpty()) {
newModel(); newModel();
} } else {
else {
load(name); load(name);
} }
} }
@@ -297,8 +296,7 @@ public class DeckController<T extends DeckBase> {
public void load(final String path, final String name) { public void load(final String path, final String name) {
if (StringUtils.isBlank(path)) { if (StringUtils.isBlank(path)) {
currentFolder = rootFolder; currentFolder = rootFolder;
} } else {
else {
currentFolder = rootFolder.tryGetFolder(path); currentFolder = rootFolder.tryGetFolder(path);
} }
modelPath = path; modelPath = path;
@@ -380,8 +378,7 @@ public class DeckController<T extends DeckBase> {
public void refreshModel() { public void refreshModel() {
if (model == null) { if (model == null) {
newModel(); newModel();
} } else {
else {
setModel(model, modelInStorage); setModel(model, modelInStorage);
} }
} }

View File

@@ -501,7 +501,7 @@ public final class CMatchUI
@Override @Override
public void hideZones(final PlayerView controller, final Iterable<PlayerZoneUpdate> zonesToUpdate) { public void hideZones(final PlayerView controller, final Iterable<PlayerZoneUpdate> zonesToUpdate) {
if ( zonesToUpdate != null ) { if (zonesToUpdate != null) {
for (final PlayerZoneUpdate update : zonesToUpdate) { for (final PlayerZoneUpdate update : zonesToUpdate) {
final PlayerView player = update.getPlayer(); final PlayerView player = update.getPlayer();
for (final ZoneType zone : update.getZones()) { for (final ZoneType zone : update.getZones()) {
@@ -532,7 +532,6 @@ public final class CMatchUI
for (final PlayerView p : manaPoolUpdate) { for (final PlayerView p : manaPoolUpdate) {
getFieldViewFor(p).updateManaPool(); getFieldViewFor(p).updateManaPool();
} }
} }
// Player's lives and poison counters // Player's lives and poison counters
@@ -580,10 +579,10 @@ public final class CMatchUI
FThreads.invokeInEdtNowOrLater(new Runnable() { FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public final void run() { @Override public final void run() {
for (final PlayerView p : getGameView().getPlayers()) { for (final PlayerView p : getGameView().getPlayers()) {
if ( p.getCards(ZoneType.Battlefield) != null ) { if (p.getCards(ZoneType.Battlefield) != null) {
updateCards(p.getCards(ZoneType.Battlefield)); updateCards(p.getCards(ZoneType.Battlefield));
} }
if ( p.getCards(ZoneType.Hand) != null ) { if (p.getCards(ZoneType.Hand) != null) {
updateCards(p.getCards(ZoneType.Hand)); updateCards(p.getCards(ZoneType.Hand));
} }
} }
@@ -599,10 +598,10 @@ public final class CMatchUI
FThreads.invokeInEdtNowOrLater(new Runnable() { FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public final void run() { @Override public final void run() {
for (final PlayerView p : getGameView().getPlayers()) { for (final PlayerView p : getGameView().getPlayers()) {
if ( p.getCards(ZoneType.Battlefield) != null ) { if (p.getCards(ZoneType.Battlefield) != null) {
updateCards(p.getCards(ZoneType.Battlefield)); updateCards(p.getCards(ZoneType.Battlefield));
} }
if ( p.getCards(ZoneType.Hand) != null ) { if (p.getCards(ZoneType.Hand) != null) {
updateCards(p.getCards(ZoneType.Hand)); updateCards(p.getCards(ZoneType.Hand));
} }
} }
@@ -617,7 +616,7 @@ public final class CMatchUI
FThreads.invokeInEdtNowOrLater(new Runnable() { FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public final void run() { @Override public final void run() {
for (final PlayerView p : getGameView().getPlayers()) { for (final PlayerView p : getGameView().getPlayers()) {
if ( p.getCards(ZoneType.Battlefield) != null ) { if (p.getCards(ZoneType.Battlefield) != null) {
updateCards(p.getCards(ZoneType.Battlefield)); updateCards(p.getCards(ZoneType.Battlefield));
} }
} }
@@ -787,7 +786,7 @@ public final class CMatchUI
final PhaseType ph = getGameView().getPhase(); final PhaseType ph = getGameView().getPhase();
// this should never happen, but I've seen it periodically... so, need to get to the bottom of it // this should never happen, but I've seen it periodically... so, need to get to the bottom of it
PhaseLabel lbl = null; PhaseLabel lbl = null;
if (ph != null ) { if (ph != null) {
lbl = p == null ? null : getFieldViewFor(p).getPhaseIndicator().getLabelFor(ph); lbl = p == null ? null : getFieldViewFor(p).getPhaseIndicator().getLabelFor(ph);
} else { } else {
// not sure what debugging information would help here, log for now // not sure what debugging information would help here, log for now
@@ -1295,8 +1294,8 @@ public final class CMatchUI
boolean isAi = sa.getActivatingPlayer().isAI(); boolean isAi = sa.getActivatingPlayer().isAI();
boolean isTrigger = sa.isTrigger(); boolean isTrigger = sa.isTrigger();
int stackIndex = event.stackIndex; int stackIndex = event.stackIndex;
if(stackIndex == nextNotifiableStackIndex) { if (stackIndex == nextNotifiableStackIndex) {
if(ForgeConstants.STACK_EFFECT_NOTIFICATION_ALWAYS.equals(stackNotificationPolicy) || (ForgeConstants.STACK_EFFECT_NOTIFICATION_AI_AND_TRIGGERED.equals(stackNotificationPolicy) && (isAi || isTrigger))) { if (ForgeConstants.STACK_EFFECT_NOTIFICATION_ALWAYS.equals(stackNotificationPolicy) || (ForgeConstants.STACK_EFFECT_NOTIFICATION_AI_AND_TRIGGERED.equals(stackNotificationPolicy) && (isAi || isTrigger))) {
// We can go and show the modal // We can go and show the modal
SpellAbilityStackInstance si = event.si; SpellAbilityStackInstance si = event.si;
@@ -1319,9 +1318,9 @@ public final class CMatchUI
// If current effect is a triggered/activated ability of an enchantment card, I want to show the enchanted card // If current effect is a triggered/activated ability of an enchantment card, I want to show the enchanted card
GameEntityView enchantedEntityView = null; GameEntityView enchantedEntityView = null;
Card hostCard = sa.getHostCard(); Card hostCard = sa.getHostCard();
if(hostCard.isEnchantment()) { if (hostCard.isEnchantment()) {
GameEntity enchantedEntity = hostCard.getEntityAttachedTo(); GameEntity enchantedEntity = hostCard.getEntityAttachedTo();
if(enchantedEntity != null) { if (enchantedEntity != null) {
enchantedEntityView = enchantedEntity.getView(); enchantedEntityView = enchantedEntity.getView();
numSmallImages++; numSmallImages++;
} else if ((sa.getRootAbility() != null) } else if ((sa.getRootAbility() != null)
@@ -1329,7 +1328,7 @@ public final class CMatchUI
&& !sa.getRootAbility().getPaidList("Sacrificed").isEmpty()) { && !sa.getRootAbility().getPaidList("Sacrificed").isEmpty()) {
// If the player activated its ability by sacrificing the enchantment, the enchantment has not anything attached anymore and the ex-enchanted card has to be searched in other ways.. for example, the green enchantment "Carapace" // If the player activated its ability by sacrificing the enchantment, the enchantment has not anything attached anymore and the ex-enchanted card has to be searched in other ways.. for example, the green enchantment "Carapace"
enchantedEntity = sa.getRootAbility().getPaidList("Sacrificed").get(0).getEnchantingCard(); enchantedEntity = sa.getRootAbility().getPaidList("Sacrificed").get(0).getEnchantingCard();
if(enchantedEntity != null) { if (enchantedEntity != null) {
enchantedEntityView = enchantedEntity.getView(); enchantedEntityView = enchantedEntity.getView();
numSmallImages++; numSmallImages++;
} }
@@ -1339,7 +1338,7 @@ public final class CMatchUI
// If current effect is a triggered ability, I want to show the triggering card if present // If current effect is a triggered ability, I want to show the triggering card if present
SpellAbility sourceSA = (SpellAbility) si.getTriggeringObject(AbilityKey.SourceSA); SpellAbility sourceSA = (SpellAbility) si.getTriggeringObject(AbilityKey.SourceSA);
CardView sourceCardView = null; CardView sourceCardView = null;
if(sourceSA != null) { if (sourceSA != null) {
sourceCardView = sourceSA.getHostCard().getView(); sourceCardView = sourceSA.getHostCard().getView();
numSmallImages++; numSmallImages++;
} }
@@ -1349,13 +1348,13 @@ public final class CMatchUI
numSmallImages = numSmallImages + targets.size(); numSmallImages = numSmallImages + targets.size();
// Now I know how many small images - on to render them // Now I know how many small images - on to render them
if(enchantedEntityView != null) { if (enchantedEntityView != null) {
addSmallImageToStackModalPanel(enchantedEntityView,mainPanel,numSmallImages); addSmallImageToStackModalPanel(enchantedEntityView,mainPanel,numSmallImages);
} }
if(sourceCardView != null) { if (sourceCardView != null) {
addSmallImageToStackModalPanel(sourceCardView,mainPanel,numSmallImages); addSmallImageToStackModalPanel(sourceCardView,mainPanel,numSmallImages);
} }
for(GameEntityView gev : targets) { for (GameEntityView gev : targets) {
addSmallImageToStackModalPanel(gev, mainPanel, numSmallImages); addSmallImageToStackModalPanel(gev, mainPanel, numSmallImages);
} }
@@ -1367,7 +1366,6 @@ public final class CMatchUI
nextNotifiableStackIndex++; nextNotifiableStackIndex++;
} else { } else {
// Not yet time to show the modal - schedule the method again, and try again later // Not yet time to show the modal - schedule the method again, and try again later
Runnable tryAgainThread = new Runnable() { Runnable tryAgainThread = new Runnable() {
@Override @Override
@@ -1381,21 +1379,21 @@ public final class CMatchUI
} }
private List<GameEntityView> getTargets(SpellAbilityStackInstance si, List<GameEntityView> result){ private List<GameEntityView> getTargets(SpellAbilityStackInstance si, List<GameEntityView> result){
if(si == null) { if (si == null) {
return result; return result;
} }
FCollectionView<CardView> targetCards = CardView.getCollection(si.getTargetChoices().getTargetCards()); FCollectionView<CardView> targetCards = CardView.getCollection(si.getTargetChoices().getTargetCards());
for(CardView currCardView: targetCards) { for (CardView currCardView: targetCards) {
result.add(currCardView); result.add(currCardView);
} }
for(SpellAbility currSA : si.getTargetChoices().getTargetSpells()) { for (SpellAbility currSA : si.getTargetChoices().getTargetSpells()) {
CardView currCardView = currSA.getCardView(); CardView currCardView = currSA.getCardView();
result.add(currCardView); result.add(currCardView);
} }
FCollectionView<PlayerView> targetPlayers = PlayerView.getCollection(si.getTargetChoices().getTargetPlayers()); FCollectionView<PlayerView> targetPlayers = PlayerView.getCollection(si.getTargetChoices().getTargetPlayers());
for(PlayerView currPlayerView : targetPlayers) { for (PlayerView currPlayerView : targetPlayers) {
result.add(currPlayerView); result.add(currPlayerView);
} }
@@ -1443,7 +1441,7 @@ public final class CMatchUI
} }
private void addSmallImageToStackModalPanel(GameEntityView gameEntityView, JPanel mainPanel, int numTarget) { private void addSmallImageToStackModalPanel(GameEntityView gameEntityView, JPanel mainPanel, int numTarget) {
if(gameEntityView instanceof CardView) { if (gameEntityView instanceof CardView) {
CardView cardView = (CardView) gameEntityView; CardView cardView = (CardView) gameEntityView;
int currRotation = getRotation(cardView); int currRotation = getRotation(cardView);
FImagePanel targetPanel = new FImagePanel(); FImagePanel targetPanel = new FImagePanel();
@@ -1454,7 +1452,7 @@ public final class CMatchUI
Dimension targetPanelDimension = new Dimension(imageWidth,imageHeight); Dimension targetPanelDimension = new Dimension(imageWidth,imageHeight);
targetPanel.setMinimumSize(targetPanelDimension); targetPanel.setMinimumSize(targetPanelDimension);
mainPanel.add(targetPanel, "cell 1 1, split " + numTarget+ ", aligny bottom"); mainPanel.add(targetPanel, "cell 1 1, split " + numTarget+ ", aligny bottom");
} else if(gameEntityView instanceof PlayerView) { } else if (gameEntityView instanceof PlayerView) {
PlayerView playerView = (PlayerView) gameEntityView; PlayerView playerView = (PlayerView) gameEntityView;
SkinImage playerAvatar = getPlayerAvatar(playerView, 0); SkinImage playerAvatar = getPlayerAvatar(playerView, 0);
final FLabel lblIcon = new FLabel.Builder().icon(playerAvatar).build(); final FLabel lblIcon = new FLabel.Builder().icon(playerAvatar).build();
@@ -1499,11 +1497,10 @@ public final class CMatchUI
} }
private void createLandPopupPanel(Card land) { private void createLandPopupPanel(Card land) {
String landPlayedNotificationPolicy = FModel.getPreferences().getPref(FPref.UI_LAND_PLAYED_NOTIFICATION_POLICY); String landPlayedNotificationPolicy = FModel.getPreferences().getPref(FPref.UI_LAND_PLAYED_NOTIFICATION_POLICY);
Player cardController = land.getController(); Player cardController = land.getController();
boolean isAi = cardController.isAI(); boolean isAi = cardController.isAI();
if(ForgeConstants.LAND_PLAYED_NOTIFICATION_ALWAYS.equals(landPlayedNotificationPolicy) if (ForgeConstants.LAND_PLAYED_NOTIFICATION_ALWAYS.equals(landPlayedNotificationPolicy)
|| (ForgeConstants.LAND_PLAYED_NOTIFICATION_AI.equals(landPlayedNotificationPolicy) && (isAi)) || (ForgeConstants.LAND_PLAYED_NOTIFICATION_AI.equals(landPlayedNotificationPolicy) && (isAi))
|| (ForgeConstants.LAND_PLAYED_NOTIFICATION_ALWAYS_FOR_NONBASIC_LANDS.equals(landPlayedNotificationPolicy) && !land.isBasicLand()) || (ForgeConstants.LAND_PLAYED_NOTIFICATION_ALWAYS_FOR_NONBASIC_LANDS.equals(landPlayedNotificationPolicy) && !land.isBasicLand())
|| (ForgeConstants.LAND_PLAYED_NOTIFICATION_AI_FOR_NONBASIC_LANDS.equals(landPlayedNotificationPolicy) && !land.isBasicLand()) && (isAi)) { || (ForgeConstants.LAND_PLAYED_NOTIFICATION_AI_FOR_NONBASIC_LANDS.equals(landPlayedNotificationPolicy) && !land.isBasicLand()) && (isAi)) {

View File

@@ -121,7 +121,6 @@ public class GameLogPanel extends JPanel {
} }
public void addLogEntry(final String text) { public void addLogEntry(final String text) {
final boolean useAlternateBackColor = (scrollablePanel.getComponents().length % 2 == 0); final boolean useAlternateBackColor = (scrollablePanel.getComponents().length % 2 == 0);
final JTextArea tar = createNewLogEntryJTextArea(text, useAlternateBackColor); final JTextArea tar = createNewLogEntryJTextArea(text, useAlternateBackColor);
@@ -138,7 +137,6 @@ public class GameLogPanel extends JPanel {
} }
forceVerticalScrollbarToMax(); forceVerticalScrollbarToMax();
} }
public void setTextFont(final SkinFont newFont) { public void setTextFont(final SkinFont newFont) {

View File

@@ -107,8 +107,8 @@ public class VAssignCombatDamage {
private boolean canAssignTo(final CardView card) { private boolean canAssignTo(final CardView card) {
for (DamageTarget dt : defenders) { for (DamageTarget dt : defenders) {
if (dt.card == card ) return true; if (dt.card == card) return true;
if (getDamageToKill(dt.card) > dt.damage ) if (getDamageToKill(dt.card) > dt.damage)
return false; return false;
} }
throw new RuntimeException("Asking to assign damage to object which is not present in defenders list"); throw new RuntimeException("Asking to assign damage to object which is not present in defenders list");
@@ -145,7 +145,7 @@ public class VAssignCombatDamage {
boolean isLMB = SwingUtilities.isLeftMouseButton(evt); boolean isLMB = SwingUtilities.isLeftMouseButton(evt);
boolean isRMB = SwingUtilities.isRightMouseButton(evt); boolean isRMB = SwingUtilities.isRightMouseButton(evt);
if ( isLMB || isRMB) if (isLMB || isRMB)
assignDamageTo(source, meta, isLMB); assignDamageTo(source, meta, isLMB);
} }
}; };
@@ -305,7 +305,7 @@ public class VAssignCombatDamage {
int leftToAssign = getRemainingDamage(); int leftToAssign = getRemainingDamage();
// Left click adds damage, right click substracts damage. // Left click adds damage, right click substracts damage.
// Hold Ctrl to assign lethal damage, Ctrl-click again on a creature with lethal damage to assign all available damage to it // Hold Ctrl to assign lethal damage, Ctrl-click again on a creature with lethal damage to assign all available damage to it
if ( meta ) { if (meta) {
if (isAdding) { if (isAdding) {
damageToAdd = leftToKill > 0 ? leftToKill : leftToAssign; damageToAdd = leftToKill > 0 ? leftToKill : leftToAssign;
} else { } else {
@@ -313,7 +313,7 @@ public class VAssignCombatDamage {
} }
} }
if ( damageToAdd > leftToAssign ) if (damageToAdd > leftToAssign)
damageToAdd = leftToAssign; damageToAdd = leftToAssign;
// cannot assign first blocker less than lethal damage except when overriding order // cannot assign first blocker less than lethal damage except when overriding order
@@ -321,7 +321,7 @@ public class VAssignCombatDamage {
if (!overrideCombatantOrder && isFirstBlocker && damageToAdd + damageItHad < lethalDamage ) if (!overrideCombatantOrder && isFirstBlocker && damageToAdd + damageItHad < lethalDamage )
return; return;
if ( 0 == damageToAdd || damageToAdd + damageItHad < 0) if (0 == damageToAdd || damageToAdd + damageItHad < 0)
return; return;
addDamage(source, damageToAdd); addDamage(source, damageToAdd);
@@ -335,12 +335,12 @@ public class VAssignCombatDamage {
} }
// Clear out any Damage that shouldn't be assigned to other combatants // Clear out any Damage that shouldn't be assigned to other combatants
boolean hasAliveEnemy = false; boolean hasAliveEnemy = false;
for(DamageTarget dt : defenders) { for (DamageTarget dt : defenders) {
int lethal = getDamageToKill(dt.card); int lethal = getDamageToKill(dt.card);
int damage = dt.damage; int damage = dt.damage;
// If overriding combatant order, make sure everything has lethal if defender has damage assigned to it // If overriding combatant order, make sure everything has lethal if defender has damage assigned to it
// Otherwise, follow normal combatant order // Otherwise, follow normal combatant order
if ( hasAliveEnemy && (!overrideCombatantOrder || dt.card == null || dt.card == defender)) if (hasAliveEnemy && (!overrideCombatantOrder || dt.card == null || dt.card == defender))
dt.damage = 0; dt.damage = 0;
else else
hasAliveEnemy |= damage < lethal; hasAliveEnemy |= damage < lethal;
@@ -357,15 +357,15 @@ public class VAssignCombatDamage {
int dmgLeft = totalDamageToAssign; int dmgLeft = totalDamageToAssign;
DamageTarget dtLast = null; DamageTarget dtLast = null;
for(DamageTarget dt : defenders) { // MUST NOT RUN WITH EMPTY collection for (DamageTarget dt : defenders) { // MUST NOT RUN WITH EMPTY collection
int lethal = getDamageToKill(dt.card); int lethal = getDamageToKill(dt.card);
int damage = Math.min(lethal, dmgLeft); int damage = Math.min(lethal, dmgLeft);
addDamage(dt.card, damage); addDamage(dt.card, damage);
dmgLeft -= damage; dmgLeft -= damage;
dtLast = dt; dtLast = dt;
if ( dmgLeft <= 0 || !toAllBlockers ) break; if (dmgLeft <= 0 || !toAllBlockers) break;
} }
if ( dmgLeft < 0 ) if (dmgLeft < 0)
throw new RuntimeException("initialAssignDamage managed to assign more damage than it could"); throw new RuntimeException("initialAssignDamage managed to assign more damage than it could");
if (toAllBlockers && dmgLeft > 0) { if (toAllBlockers && dmgLeft > 0) {
// flush the remaining damage into last defender if assigning all damage // flush the remaining damage into last defender if assigning all damage
@@ -376,7 +376,7 @@ public class VAssignCombatDamage {
/** Reset Assign Damage back to how it was at the beginning. */ /** Reset Assign Damage back to how it was at the beginning. */
private void resetAssignedDamage() { private void resetAssignedDamage() {
for(DamageTarget dt : defenders) for (DamageTarget dt : defenders)
dt.damage = 0; dt.damage = 0;
} }
@@ -398,7 +398,7 @@ public class VAssignCombatDamage {
*/ */
private int getRemainingDamage() { private int getRemainingDamage() {
int spent = 0; int spent = 0;
for(DamageTarget dt : defenders) { for (DamageTarget dt : defenders) {
spent += dt.damage; spent += dt.damage;
} }
return totalDamageToAssign - spent; return totalDamageToAssign - spent;
@@ -407,11 +407,10 @@ public class VAssignCombatDamage {
/** Updates labels and other UI elements. /** Updates labels and other UI elements.
* @param index index of the last assigned damage*/ * @param index index of the last assigned damage*/
private void updateLabels() { private void updateLabels() {
int damageLeft = totalDamageToAssign; int damageLeft = totalDamageToAssign;
boolean allHaveLethal = true; boolean allHaveLethal = true;
for ( DamageTarget dt : defenders ) for (DamageTarget dt : defenders)
{ {
int dmg = dt.damage; int dmg = dt.damage;
damageLeft -= dmg; damageLeft -= dmg;
@@ -438,7 +437,7 @@ public class VAssignCombatDamage {
// assigned dynamically, the cards die off and further damage to them can't // assigned dynamically, the cards die off and further damage to them can't
// be modified. // be modified.
private void finish() { private void finish() {
if ( getRemainingDamage() > 0 ) if (getRemainingDamage() > 0)
return; return;
dlg.dispose(); dlg.dispose();
@@ -456,8 +455,7 @@ public class VAssignCombatDamage {
final CardView pw = (CardView)defender; final CardView pw = (CardView)defender;
lethalDamage = Integer.valueOf(pw.getCurrentState().getLoyalty()); lethalDamage = Integer.valueOf(pw.getCurrentState().getLoyalty());
} }
} } else {
else {
lethalDamage = Math.max(0, card.getLethalDamage()); lethalDamage = Math.max(0, card.getLethalDamage());
if (card.getCurrentState().getType().isPlaneswalker()) { if (card.getCurrentState().getType().isPlaneswalker()) {
lethalDamage = Integer.valueOf(card.getCurrentState().getLoyalty()); lethalDamage = Integer.valueOf(card.getCurrentState().getLoyalty());

View File

@@ -78,7 +78,6 @@ public class CDetail implements ICDoc {
@Override @Override
public void initialize() { public void initialize() {
} }
@Override @Override

View File

@@ -311,7 +311,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
final CardStateView state = getCard().getCurrentState(); final CardStateView state = getCard().getCurrentState();
final CardEdition ed = FModel.getMagicDb().getEditions().get(state.getSetCode()); final CardEdition ed = FModel.getMagicDb().getEditions().get(state.getSetCode());
boolean colorIsSet = false; boolean colorIsSet = false;
if (state.getType().isEmblem() || state.getType().hasStringType("Effect")) { if (getCard().isImmutable()) {
// Effects are drawn with orange border // Effects are drawn with orange border
g2d.setColor(Color.ORANGE); g2d.setColor(Color.ORANGE);
colorIsSet = true; colorIsSet = true;
@@ -368,7 +368,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
boolean nonselectable = matchUI.isSelecting() && !matchUI.isSelectable(getCard()); boolean nonselectable = matchUI.isSelecting() && !matchUI.isSelectable(getCard());
// if selecting, darken non-selectable cards // if selecting, darken non-selectable cards
if ( nonselectable ) { if (nonselectable) {
boolean noBorderPref = !isPreferenceEnabled(FPref.UI_RENDER_BLACK_BORDERS); boolean noBorderPref = !isPreferenceEnabled(FPref.UI_RENDER_BLACK_BORDERS);
boolean cardImgHasAlpha = imagePanel != null && imagePanel.getSrcImage() != null && imagePanel.getSrcImage().getColorModel().hasAlpha(); boolean cardImgHasAlpha = imagePanel != null && imagePanel.getSrcImage() != null && imagePanel.getSrcImage().getColorModel().hasAlpha();
final int cornerSize = noBorderPref && !cardImgHasAlpha ? 0 : Math.max(4, Math.round(cardWidth * CardPanel.ROUNDED_CORNER_SIZE)); final int cornerSize = noBorderPref && !cardImgHasAlpha ? 0 : Math.max(4, Math.round(cardWidth * CardPanel.ROUNDED_CORNER_SIZE));
@@ -390,7 +390,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
@Override @Override
public final void doLayout() { public final void doLayout() {
int borderSize = calculateBorderSize(); int borderSize = calculateBorderSize();
final Point imgPos = new Point(cardXOffset + borderSize, cardYOffset + borderSize); final Point imgPos = new Point(cardXOffset + borderSize, cardYOffset + borderSize);
@@ -408,7 +407,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
} }
private int calculateBorderSize() { private int calculateBorderSize() {
// Determine whether to render border from properties // Determine whether to render border from properties
boolean noBorderPref = !isPreferenceEnabled(FPref.UI_RENDER_BLACK_BORDERS); boolean noBorderPref = !isPreferenceEnabled(FPref.UI_RENDER_BLACK_BORDERS);
@@ -426,7 +424,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
} }
return 0; return 0;
} }
private Dimension calculateImageSize() { private Dimension calculateImageSize() {
@@ -491,7 +488,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
} }
} }
if (card.getCounters() != null && !card.getCounters().isEmpty()) { if (card.getCounters() != null && !card.getCounters().isEmpty()) {
switch (CounterDisplayType.from(FModel.getPreferences().getPref(FPref.UI_CARD_COUNTER_DISPLAY_TYPE))) { switch (CounterDisplayType.from(FModel.getPreferences().getPref(FPref.UI_CARD_COUNTER_DISPLAY_TYPE))) {
@@ -734,8 +730,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
abiY += abiSpace; abiY += abiSpace;
} }
} }
} } else {
else {
String keywordKey = card.getCurrentState().getKeywordKey(); String keywordKey = card.getCurrentState().getKeywordKey();
String abilityText = card.getCurrentState().getAbilityText(); String abilityText = card.getCurrentState().getAbilityText();
if (((keywordKey.indexOf("Flashback") == -1) if (((keywordKey.indexOf("Flashback") == -1)
@@ -752,7 +747,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
} }
private void drawCounterTabs(final Graphics g) { private void drawCounterTabs(final Graphics g) {
final Dimension imgSize = calculateImageSize(); final Dimension imgSize = calculateImageSize();
final int titleY = Math.round(imgSize.height * (54f / 640)) - 15; final int titleY = Math.round(imgSize.height * (54f / 640)) - 15;
@@ -767,7 +761,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
FontMetrics largeFontMetrics = g.getFontMetrics(largeCounterFont); FontMetrics largeFontMetrics = g.getFontMetrics(largeCounterFont);
if (CounterDisplayType.from(FModel.getPreferences().getPref(FPref.UI_CARD_COUNTER_DISPLAY_TYPE)) == CounterDisplayType.OLD_WHEN_SMALL) { if (CounterDisplayType.from(FModel.getPreferences().getPref(FPref.UI_CARD_COUNTER_DISPLAY_TYPE)) == CounterDisplayType.OLD_WHEN_SMALL) {
int maxCounters = 0; int maxCounters = 0;
for (Integer numberOfCounters : card.getCounters().values()) { for (Integer numberOfCounters : card.getCounters().values()) {
maxCounters = Math.max(maxCounters, numberOfCounters); maxCounters = Math.max(maxCounters, numberOfCounters);
@@ -780,9 +773,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
} }
for (Map.Entry<CounterType, Integer> counterEntry : card.getCounters().entrySet()) { for (Map.Entry<CounterType, Integer> counterEntry : card.getCounters().entrySet()) {
final CounterType counter = counterEntry.getKey(); final CounterType counter = counterEntry.getKey();
final int numberOfCounters = counterEntry.getValue(); final int numberOfCounters = counterEntry.getValue();
final int counterBoxRealWidth = counterBoxBaseWidth + largeFontMetrics.stringWidth(String.valueOf(numberOfCounters)); final int counterBoxRealWidth = counterBoxBaseWidth + largeFontMetrics.stringWidth(String.valueOf(numberOfCounters));
@@ -827,7 +818,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
} }
private void drawCounterImage(final Graphics g) { private void drawCounterImage(final Graphics g) {
int counters = 0; int counters = 0;
for (final Integer i : card.getCounters().values()) { for (final Integer i : card.getCounters().values()) {
counters += i; counters += i;
@@ -848,7 +838,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
} }
private void drawMarkersTabs(final Graphics g, List<String> markers) { private void drawMarkersTabs(final Graphics g, List<String> markers) {
final Dimension imgSize = calculateImageSize(); final Dimension imgSize = calculateImageSize();
final int titleY = Math.round(imgSize.height * (54f / 640)) - 15; final int titleY = Math.round(imgSize.height * (54f / 640)) - 15;
@@ -862,7 +851,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
FontMetrics smallFontMetrics = g.getFontMetrics(smallCounterFont); FontMetrics smallFontMetrics = g.getFontMetrics(smallCounterFont);
for (String marker : markers) { for (String marker : markers) {
final int markerBoxRealWidth = markerBoxBaseWidth + smallFontMetrics.stringWidth(marker); final int markerBoxRealWidth = markerBoxBaseWidth + smallFontMetrics.stringWidth(marker);
final int markerYOffset; final int markerYOffset;
@@ -907,7 +895,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
* @param font The font to use to draw the text. * @param font The font to use to draw the text.
*/ */
private void drawVerticallyCenteredString(Graphics g, String text, Rectangle area, Font font, final FontMetrics fontMetrics) { private void drawVerticallyCenteredString(Graphics g, String text, Rectangle area, Font font, final FontMetrics fontMetrics) {
Font oldFont = g.getFont(); Font oldFont = g.getFont();
int x = area.x; int x = area.x;
@@ -1114,8 +1101,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
} }
private boolean showCardManaCostOverlay() { private boolean showCardManaCostOverlay() {
return isShowingOverlays() && return isShowingOverlays() && isPreferenceEnabled(FPref.UI_OVERLAY_CARD_MANA_COST);
isPreferenceEnabled(FPref.UI_OVERLAY_CARD_MANA_COST);
} }
private boolean showCardIdOverlay() { private boolean showCardIdOverlay() {

View File

@@ -307,7 +307,7 @@ public abstract class CardPanelContainer extends SkinnedPanel {
fromPanel.dispose(); fromPanel.dispose();
getCardPanels().remove(fromPanel); getCardPanels().remove(fromPanel);
remove(fromPanel); remove(fromPanel);
if ( repaint ) { if (repaint) {
invalidate(); invalidate();
repaint(); repaint();
doingLayout(); doingLayout();
@@ -347,7 +347,7 @@ public abstract class CardPanelContainer extends SkinnedPanel {
} }
getCardPanels().clear(); getCardPanels().clear();
removeAll(); removeAll();
if ( repaint ) { if (repaint) {
setPreferredSize(new Dimension(0, 0)); setPreferredSize(new Dimension(0, 0));
invalidate(); invalidate();
getParent().validate(); getParent().validate();

View File

@@ -624,8 +624,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
if (toDelete.size() == getCardPanels().size()) { if (toDelete.size() == getCardPanels().size()) {
clear(false); clear(false);
} } else {
else {
for (final CardView card : toDelete) { for (final CardView card : toDelete) {
removeCardPanel(getCardPanel(card.getId()),false); removeCardPanel(getCardPanel(card.getId()),false);
} }
@@ -681,8 +680,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
if (card.isTapped()) { if (card.isTapped()) {
toPanel.setTapped(true); toPanel.setTapped(true);
toPanel.setTappedAngle(forge.view.arcane.CardPanel.TAPPED_ANGLE); toPanel.setTappedAngle(forge.view.arcane.CardPanel.TAPPED_ANGLE);
} } else {
else {
toPanel.setTapped(false); toPanel.setTapped(false);
toPanel.setTappedAngle(0); toPanel.setTappedAngle(0);
} }
@@ -705,8 +703,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
CardPanel attachedToPanel; CardPanel attachedToPanel;
if (card.getAttachedTo() != null) { if (card.getAttachedTo() != null) {
attachedToPanel = getCardPanel(card.getAttachedTo().getId()); attachedToPanel = getCardPanel(card.getAttachedTo().getId());
} } else {
else {
attachedToPanel = null; attachedToPanel = null;
} }
if (toPanel.getAttachedToPanel() != attachedToPanel) { if (toPanel.getAttachedToPanel() != attachedToPanel) {

View File

@@ -104,18 +104,13 @@ public class ScaledImagePanel extends JPanel {
return; return;
} }
//System.out.println(sz + " -- " + src);
//ResampleOp resizer = new ResampleOp(DimensionConstrain.createMaxDimension(this.getWidth(), this.getHeight(), !scaleLarger)); //ResampleOp resizer = new ResampleOp(DimensionConstrain.createMaxDimension(this.getWidth(), this.getHeight(), !scaleLarger));
//resizer.setUnsharpenMask(UnsharpenMask.Soft); //resizer.setUnsharpenMask(UnsharpenMask.Soft);
BufferedImage img = getSrcImage(); //resizer.filter(getSrcImage(), null); BufferedImage img = getSrcImage(); //resizer.filter(getSrcImage(), null);
boolean needsScale = img.getWidth() < sz.width; boolean needsScale = img.getWidth() < sz.width;
float scaleFactor = ((float)img.getWidth()) / sz.width; float scaleFactor = ((float)img.getWidth()) / sz.width;
if ( needsScale && ( scaleFactor < 0.95 || scaleFactor > 1.05 ) ) { // This should very low-quality scaling to draw during animation if (needsScale && ( scaleFactor < 0.95 || scaleFactor > 1.05 )) { // This should very low-quality scaling to draw during animation
//System.out.println("Painting: " + img.getWidth() + " -> " + sz.width );
float maxZoomX = ((float)sz.width) / img.getWidth(); float maxZoomX = ((float)sz.width) / img.getWidth();
float maxZoomY = ((float)sz.height) / img.getHeight(); float maxZoomY = ((float)sz.height) / img.getHeight();
float zoom = Math.min(maxZoomX, maxZoomY); float zoom = Math.min(maxZoomX, maxZoomY);

View File

@@ -6,13 +6,13 @@
<packaging.type>jar</packaging.type> <packaging.type>jar</packaging.type>
<build.min.memory>-Xms128m</build.min.memory> <build.min.memory>-Xms128m</build.min.memory>
<build.max.memory>-Xmx2048m</build.max.memory> <build.max.memory>-Xmx2048m</build.max.memory>
<alpha-version>1.6.42.001</alpha-version> <alpha-version>1.6.43.001</alpha-version>
</properties> </properties>
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.43-SNAPSHOT</version> <version>1.6.44-SNAPSHOT</version>
</parent> </parent>
<artifactId>forge-gui-ios</artifactId> <artifactId>forge-gui-ios</artifactId>

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.43-SNAPSHOT</version> <version>1.6.44-SNAPSHOT</version>
</parent> </parent>
<artifactId>forge-gui-mobile-dev</artifactId> <artifactId>forge-gui-mobile-dev</artifactId>

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.43-SNAPSHOT</version> <version>1.6.44-SNAPSHOT</version>
</parent> </parent>
<artifactId>forge-gui-mobile</artifactId> <artifactId>forge-gui-mobile</artifactId>

View File

@@ -46,7 +46,7 @@ import forge.util.Localizer;
import forge.util.Utils; import forge.util.Utils;
public class Forge implements ApplicationListener { public class Forge implements ApplicationListener {
public static final String CURRENT_VERSION = "1.6.42.001"; public static final String CURRENT_VERSION = "1.6.43.001";
private static final ApplicationListener app = new Forge(); private static final ApplicationListener app = new Forge();
private static Clipboard clipboard; private static Clipboard clipboard;

View File

@@ -291,6 +291,19 @@ public class TextRenderer {
case "clr": case "clr":
colorOverride = value != null ? new Color(Integer.parseInt(value)) : null; colorOverride = value != null ? new Color(Integer.parseInt(value)) : null;
break; break;
case "span":
// <span style="color:gray;">
if (value != null && value.contains("color:")) {
int startIdx = value.indexOf(':') + 1;
int endIdx = value.indexOf(';');
String colorName = value.substring(startIdx, endIdx);
if (colorName.equals("gray")) {
colorOverride = Color.GRAY;
}
} else {
colorOverride = null;
}
break;
default: default:
validKeyword = false; validKeyword = false;
break; break;

View File

@@ -362,8 +362,7 @@ public class CardImageRenderer {
drawDetails(g, card, gameView, altState, x, y, w, h); drawDetails(g, card, gameView, altState, x, y, w, h);
return; return;
} }
if(card.isToken() && card.getCurrentState().getType().hasSubtype("Effect") if(card.isImmutable() && FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_DISABLE_IMAGES_EFFECT_CARDS)){
&& FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_DISABLE_IMAGES_EFFECT_CARDS)){
drawDetails(g, card, gameView, altState, x, y, w, h); drawDetails(g, card, gameView, altState, x, y, w, h);
return; return;
} }

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.43-SNAPSHOT</version> <version>1.6.44-SNAPSHOT</version>
</parent> </parent>
<artifactId>forge-gui</artifactId> <artifactId>forge-gui</artifactId>

View File

@@ -1,10 +1,7 @@
#Add one announcement per line #Add one announcement per line
Get in the discord if you aren't yet. Get in the discord if you aren't yet. https://discord.gg/3v9JCVr
Card support has been extended up to "Modern Horizons 2", including the available previews from the upcoming "Dungeons and Dragons: Adventures in the Forgotten Realms". Pre-release build for "Dungeons and Dragons: Adventures in the Forgotten Realms", please report any bugs!
Collector Number has been added to all cards in the Catalog. Commander cards for the D&D set are still being worked on.
[Desktop] Card Catalog now adds the Cards' Collector Number as a sortable column. Custom cards can now be loaded from your user profile. Enable it your preferences.
[Desktop] Improved support for Custom Editions: now custom editions appear with their own names within other official editions, and can be selected in Set filters. Add support for Collector Boosters (not implemented everywhere yet)
Net decks have been expanded and there are now tons of new decks in constructed, commander, brawl, oathbreaker and tiny leaders. *** Android 7 & 8 support is now deprecated. Support will be dropped in an upcoming release. ***
Complete support to game mechanics for all cards in Modern format.
Various other improvements to game mechanics and AI: please check the Git log for details if interested.
Improvements to Italian translation.

View File

@@ -97,3 +97,4 @@ Kaldheim, 3/6/KHM, KHM
Time Spiral Remastered, 3/6/KHM, TSR Time Spiral Remastered, 3/6/KHM, TSR
Strixhaven: School of Mages, 3/6/STX, STX Strixhaven: School of Mages, 3/6/STX, STX
Modern Horizons 2, 3/6/MH2, MH2 Modern Horizons 2, 3/6/MH2, MH2
Adventures in the Forgotten Realms, 3/6/AFR, AFR

View File

@@ -2,11 +2,8 @@ Name:Academy Manufactor
ManaCost:3 ManaCost:3
Types:Artifact Creature Assembly-Worker Types:Artifact Creature Assembly-Worker
PT:1/3 PT:1/3
R:Event$ CreateToken | ActiveZones$ Battlefield | ValidPlayer$ You | ValidToken$ Clue,Food,Treasure | ReplaceWith$ DBToken | Description$ If you would create a Clue, Food, or Treasure token, instead create one of each. R:Event$ CreateToken | ActiveZones$ Battlefield | ValidPlayer$ You | ValidToken$ Clue,Food,Treasure | ReplaceWith$ TokenReplace | Description$ If you would create a Clue, Food, or Treasure token, instead create one of each.
SVar:DBToken:DB$ Token | TokenScript$ c_a_clue_draw | TokenAmount$ X | SubAbility$ DBToken2 SVar:TokenReplace:DB$ ReplaceToken | Type$ ReplaceToken | ValidCard$ Clue,Food,Treasure | TokenScript$ c_a_clue_draw,c_a_food_sac,c_a_treasure_sac
SVar:DBToken2:DB$ Token | TokenScript$ c_a_food_sac | TokenAmount$ X | SubAbility$ DBToken3
SVar:DBToken3:DB$ Token | TokenScript$ c_a_treasure_sac | TokenAmount$ X
SVar:X:ReplaceCount$TokenNum
DeckHas:Ability$Sacrifice & Ability$Token & Ability$LifeGain DeckHas:Ability$Sacrifice & Ability$Token & Ability$LifeGain
DeckHints:Ability$Investigate DeckHints:Ability$Investigate
Oracle:If you would create a Clue, Food, or Treasure token, instead create one of each. Oracle:If you would create a Clue, Food, or Treasure token, instead create one of each.

Some files were not shown because too many files have changed in this diff Show More