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

This commit is contained in:
leriomaggio
2021-08-02 07:49:18 +01:00
283 changed files with 611 additions and 527 deletions

View File

@@ -253,7 +253,10 @@ public class AiController {
} }
boolean rightapi = false; boolean rightapi = false;
Player activatingPlayer = sa.getActivatingPlayer(); Player activatingPlayer = sa.getActivatingPlayer();
// for xPaid stuff
card.setCastSA(sa);
// Trigger play improvements // Trigger play improvements
for (final Trigger tr : card.getTriggers()) { for (final Trigger tr : card.getTriggers()) {
// These triggers all care for ETB effects // These triggers all care for ETB effects
@@ -743,7 +746,7 @@ public class AiController {
return AiPlayDecision.CantPlaySa; return AiPlayDecision.CantPlaySa;
} }
boolean xCost = sa.getPayCosts().hasXInAnyCostPart(); boolean xCost = sa.getPayCosts().hasXInAnyCostPart() || sa.getHostCard().hasStartOfKeyword("Strive");
if (!xCost && !ComputerUtilCost.canPayCost(sa, player)) { if (!xCost && !ComputerUtilCost.canPayCost(sa, player)) {
// for most costs, it's OK to check if they can be paid early in order to avoid running a heavy API check // for most costs, it's OK to check if they can be paid early in order to avoid running a heavy API check
// when the AI won't even be able to play the spell in the first place (even if it could afford it) // when the AI won't even be able to play the spell in the first place (even if it could afford it)
@@ -751,11 +754,11 @@ public class AiController {
} }
// state needs to be switched here so API checks evaluate the right face // state needs to be switched here so API checks evaluate the right face
if (sa.getCardState().getStateName() == CardStateName.Modal) { if (sa.getCardState() != null && !sa.getHostCard().isInPlay() && sa.getCardState().getStateName() == CardStateName.Modal) {
sa.getHostCard().setState(CardStateName.Modal, false); sa.getHostCard().setState(CardStateName.Modal, false);
} }
AiPlayDecision canPlay = canPlaySa(sa); // this is the "heaviest" check, which also sets up targets, defines X, etc. AiPlayDecision canPlay = canPlaySa(sa); // this is the "heaviest" check, which also sets up targets, defines X, etc.
if (sa.getCardState().getStateName() == CardStateName.Modal) { if (sa.getCardState() != null && !sa.getHostCard().isInPlay() && sa.getCardState().getStateName() == CardStateName.Modal) {
sa.getHostCard().setState(CardStateName.Original, false); sa.getHostCard().setState(CardStateName.Original, false);
} }
@@ -1754,10 +1757,10 @@ public class AiController {
} }
public boolean doTrigger(SpellAbility spell, boolean mandatory) { public boolean doTrigger(SpellAbility spell, boolean mandatory) {
if (spell.getApi() != null)
return SpellApiToAi.Converter.get(spell.getApi()).doTriggerAI(player, spell, mandatory);
if (spell instanceof WrappedAbility) if (spell instanceof WrappedAbility)
return doTrigger(((WrappedAbility)spell).getWrappedAbility(), mandatory); return doTrigger(((WrappedAbility)spell).getWrappedAbility(), mandatory);
if (spell.getApi() != null)
return SpellApiToAi.Converter.get(spell.getApi()).doTriggerAI(player, spell, mandatory);
if (spell.getPayCosts() == Cost.Zero && spell.getTargetRestrictions() == null) { if (spell.getPayCosts() == Cost.Zero && spell.getTargetRestrictions() == null) {
// For non-converted triggers (such as Cumulative Upkeep) that don't have costs or targets to worry about // For non-converted triggers (such as Cumulative Upkeep) that don't have costs or targets to worry about
return true; return true;

View File

@@ -695,8 +695,9 @@ public class ComputerUtilCost {
public static int getMaxXValue(SpellAbility sa, Player ai) { public static int getMaxXValue(SpellAbility sa, Player ai) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final SpellAbility root = sa.getRootAbility(); SpellAbility root = sa.getRootAbility();
final Cost abCost = root.getPayCosts(); final Cost abCost = root.getPayCosts();
if (abCost == null || !abCost.hasXInAnyCostPart()) { if (abCost == null || !abCost.hasXInAnyCostPart()) {
return 0; return 0;
} }

View File

@@ -827,8 +827,7 @@ public class ComputerUtilMana {
// remove from available lists // remove from available lists
Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard())); Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard()));
} } else {
else {
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment); final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment))) { if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment))) {
saList.remove(saPayment); saList.remove(saPayment);
@@ -863,8 +862,7 @@ public class ComputerUtilMana {
if (test) { if (test) {
resetPayment(paymentList); resetPayment(paymentList);
return false; return false;
} } else {
else {
System.out.println("ComputerUtil : payManaCost() cost was not paid for " + sa.getHostCard().getName() + ". Didn't find what to pay for " + toPay); System.out.println("ComputerUtil : payManaCost() cost was not paid for " + sa.getHostCard().getName() + ". Didn't find what to pay for " + toPay);
return false; return false;
} }
@@ -1233,8 +1231,7 @@ public class ComputerUtilMana {
if (!(ai.getGame().getPhaseHandler().isPlayerTurn(ai))) { if (!(ai.getGame().getPhaseHandler().isPlayerTurn(ai))) {
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK); AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT); AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT);
} } else
else
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK); AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK);
} else { } else {
if ((AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK)) || if ((AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK)) ||
@@ -1255,8 +1252,7 @@ public class ComputerUtilMana {
if (curPhase == PhaseType.MAIN2 || curPhase == PhaseType.CLEANUP) { if (curPhase == PhaseType.MAIN2 || curPhase == PhaseType.CLEANUP) {
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2); AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
} } else {
else {
if (AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2)) { if (AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2)) {
// This mana source is held elsewhere for a Main Phase 2 spell. // This mana source is held elsewhere for a Main Phase 2 spell.
return true; return true;
@@ -1266,7 +1262,6 @@ public class ComputerUtilMana {
return false; return false;
} }
private static ManaCostShard getNextShardToPay(ManaCostBeingPaid cost) { private static ManaCostShard getNextShardToPay(ManaCostBeingPaid cost) {
// mind the priorities // mind the priorities
// * Pay mono-colored first,curPhase == PhaseType.CLEANUP // * Pay mono-colored first,curPhase == PhaseType.CLEANUP
@@ -1352,8 +1347,7 @@ public class ComputerUtilMana {
String commonColor = ComputerUtilCard.getMostProminentColor(ai.getCardsIn(ZoneType.Hand)); String commonColor = ComputerUtilCard.getMostProminentColor(ai.getCardsIn(ZoneType.Hand));
if (!commonColor.isEmpty() && satisfiesColorChoice(abMana, choiceString, MagicColor.toShortString(commonColor)) && abMana.getComboColors().contains(MagicColor.toShortString(commonColor))) { if (!commonColor.isEmpty() && satisfiesColorChoice(abMana, choiceString, MagicColor.toShortString(commonColor)) && abMana.getComboColors().contains(MagicColor.toShortString(commonColor))) {
choice = MagicColor.toShortString(commonColor); choice = MagicColor.toShortString(commonColor);
} } else {
else {
// default to first available color // default to first available color
for (String c : comboColors) { for (String c : comboColors) {
if (satisfiesColorChoice(abMana, choiceString, c)) { if (satisfiesColorChoice(abMana, choiceString, c)) {
@@ -1398,8 +1392,7 @@ public class ComputerUtilMana {
break; break;
} }
} }
} } else {
else {
String color = MagicColor.toShortString(manaPart); String color = MagicColor.toShortString(manaPart);
boolean wasNeeded = testCost.ai_payMana(color, p.getManaPool()); boolean wasNeeded = testCost.ai_payMana(color, p.getManaPool());
if (!wasNeeded) { if (!wasNeeded) {

View File

@@ -434,7 +434,7 @@ public abstract class GameState {
boolean first = true; boolean first = true;
StringBuilder counterString = new StringBuilder(); StringBuilder counterString = new StringBuilder();
for(Entry<CounterType, Integer> kv : counters.entrySet()) { for (Entry<CounterType, Integer> kv : counters.entrySet()) {
if (!first) { if (!first) {
counterString.append(","); counterString.append(",");
} }
@@ -470,7 +470,7 @@ public abstract class GameState {
} }
public void parse(List<String> lines) { public void parse(List<String> lines) {
for(String line : lines) { for (String line : lines) {
parseLine(line); parseLine(line);
} }
} }
@@ -1110,13 +1110,13 @@ public abstract class GameState {
private void handleCardAttachments() { private void handleCardAttachments() {
// Unattach all permanents first // Unattach all permanents first
for(Entry<Card, Integer> entry : cardToAttachId.entrySet()) { for (Entry<Card, Integer> entry : cardToAttachId.entrySet()) {
Card attachedTo = idToCard.get(entry.getValue()); Card attachedTo = idToCard.get(entry.getValue());
attachedTo.unAttachAllCards(); attachedTo.unAttachAllCards();
} }
// Attach permanents by ID // Attach permanents by ID
for(Entry<Card, Integer> entry : cardToAttachId.entrySet()) { for (Entry<Card, Integer> entry : cardToAttachId.entrySet()) {
Card attachedTo = idToCard.get(entry.getValue()); Card attachedTo = idToCard.get(entry.getValue());
Card attacher = entry.getKey(); Card attacher = entry.getKey();
if (attacher.isAttachment()) { if (attacher.isAttachment()) {
@@ -1125,7 +1125,7 @@ public abstract class GameState {
} }
// Enchant players by ID // Enchant players by ID
for(Entry<Card, Integer> entry : cardToEnchantPlayerId.entrySet()) { for (Entry<Card, Integer> entry : cardToEnchantPlayerId.entrySet()) {
// TODO: improve this for game states with more than two players // TODO: improve this for game states with more than two players
Card attacher = entry.getKey(); Card attacher = entry.getKey();
Game game = attacher.getGame(); Game game = attacher.getGame();
@@ -1136,9 +1136,9 @@ public abstract class GameState {
} }
private void handleMergedCards() { private void handleMergedCards() {
for(Entry<Card, List<String>> entry : cardToMergedCards.entrySet()) { for (Entry<Card, List<String>> entry : cardToMergedCards.entrySet()) {
Card mergedTo = entry.getKey(); Card mergedTo = entry.getKey();
for(String mergedCardName : entry.getValue()) { for (String mergedCardName : entry.getValue()) {
Card c; Card c;
PaperCard pc = StaticData.instance().getCommonCards().getCard(mergedCardName. replace("^", ",")); PaperCard pc = StaticData.instance().getCommonCards().getCard(mergedCardName. replace("^", ","));
if (pc == null) { if (pc == null) {

View File

@@ -1064,7 +1064,7 @@ public class PlayerControllerAi extends PlayerController {
} }
} }
private boolean prepareSingleSa(final Card host, final SpellAbility sa, boolean isMandatory){ private boolean prepareSingleSa(final Card host, final SpellAbility sa, boolean isMandatory) {
if (sa.hasParam("TargetingPlayer")) { if (sa.hasParam("TargetingPlayer")) {
Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0); Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0);
sa.setTargetingPlayer(targetingPlayer); sa.setTargetingPlayer(targetingPlayer);

View File

@@ -115,8 +115,7 @@ public class AttachAi extends SpellAbilityAi {
} }
if (abCost.getTotalMana().countX() > 0 && sa.getSVar("X").equals("Count$xPaid")) { if (abCost.getTotalMana().countX() > 0 && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value. (Endless Scream and Venarian // Set PayX here to maximum value. (Endless Scream and Venarian Gold)
// Gold)
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai); final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
if (xPay == 0) { if (xPay == 0) {

View File

@@ -407,6 +407,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (num.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { if (num.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value. // Set PayX here to maximum value.
int xPay = ComputerUtilCost.getMaxXValue(sa, ai); int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
if (xPay == 0) return false;
xPay = Math.min(xPay, list.size()); xPay = Math.min(xPay, list.size());
sa.setXManaCostPaid(xPay); sa.setXManaCostPaid(xPay);
} }

View File

@@ -45,7 +45,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
} }
if ("MomirAvatar".equals(aiLogic)) { if ("MomirAvatar".equals(aiLogic)) {
return SpecialCardAi.MomirVigAvatar.consider(aiPlayer, sa); return SpecialCardAi.MomirVigAvatar.consider(aiPlayer, sa);
} else if ("MimicVat".equals(aiLogic)) { } else if ("MimicVat".equals(aiLogic)) {
return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa); return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
} else if ("AtEOT".equals(aiLogic)) { } else if ("AtEOT".equals(aiLogic)) {
@@ -59,7 +59,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
} }
} }
if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) { if (sa.hasParam("AtEOT") && !ph.is(PhaseType.MAIN1)) {
return false; return false;
} }
@@ -118,7 +118,6 @@ public class CopyPermanentAi extends SpellAbilityAi {
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final boolean canCopyLegendary = sa.hasParam("NonLegendary"); final boolean canCopyLegendary = sa.hasParam("NonLegendary");
// //// // ////
// Targeting // Targeting
if (sa.usesTargeting()) { if (sa.usesTargeting()) {

View File

@@ -163,8 +163,6 @@ public class DiscardAi extends SpellAbilityAi {
return false; return false;
} // discardTargetAI() } // discardTargetAI()
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
@@ -211,9 +209,8 @@ public class DiscardAi extends SpellAbilityAi {
return true; return true;
} // discardCheckDrawbackAI() } // discardCheckDrawbackAI()
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
if ( mode == PlayerActionConfirmMode.Random ) { // if ( mode == PlayerActionConfirmMode.Random ) {
// TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards // TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards
return true; return true;
} }

View File

@@ -4,8 +4,10 @@ import java.util.Map;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CounterEnumType; import forge.game.card.CounterEnumType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -23,15 +25,17 @@ public class LegendaryRuleAi extends SpellAbilityAi {
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false; // should not get here return false; // should not get here
} }
@Override @Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) { public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
// Choose a single legendary/planeswalker card to keep // Choose a single legendary/planeswalker card to keep
Card firstOption = Iterables.getFirst(options, null); CardCollection legends = new CardCollection(options);
CardCollection badOptions = ComputerUtil.choosePermanentsToSacrifice(ai, legends, legends.size() -1, sa, false, false);
legends.removeAll(badOptions);
Card firstOption = Iterables.getFirst(legends, null);
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker(); boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
if ( choosingFromPlanewalkers ) { if (choosingFromPlanewalkers) {
// AI decision making - should AI compare counters? // AI decision making - should AI compare counters?
} else { } else {
// AI decision making - should AI compare damage and debuffs? // AI decision making - should AI compare damage and debuffs?

View File

@@ -250,9 +250,6 @@ public class TokenAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
String tokenAmount = sa.getParamOrDefault("TokenAmount", "1");
final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
@@ -262,16 +259,21 @@ public class TokenAi extends SpellAbilityAi {
sa.getTargets().add(ai); sa.getTargets().add(ai);
} }
} }
Card actualToken = spawnToken(ai, sa); Card actualToken = spawnToken(ai, sa);
String tokenPower = sa.getParamOrDefault("TokenPower", actualToken.getBasePowerString()); String tokenPower = sa.getParamOrDefault("TokenPower", actualToken.getBasePowerString());
String tokenToughness = sa.getParamOrDefault("TokenToughness", actualToken.getBaseToughnessString()); String tokenToughness = sa.getParamOrDefault("TokenToughness", actualToken.getBaseToughnessString());
String tokenAmount = sa.getParamOrDefault("TokenAmount", "1");
final Card source = sa.getHostCard();
if ("X".equals(tokenAmount) || "X".equals(tokenPower) || "X".equals(tokenToughness)) { if ("X".equals(tokenAmount) || "X".equals(tokenPower) || "X".equals(tokenToughness)) {
int x = AbilityUtils.calculateAmount(source, tokenAmount, sa); int x = AbilityUtils.calculateAmount(source, tokenAmount, sa);
if (sa.getSVar("X").equals("Count$xPaid")) { if (sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value. if (x == 0) { // already paid outside trigger
x = ComputerUtilCost.getMaxXValue(sa, ai); // Set PayX here to maximum value.
sa.setXManaCostPaid(x); x = ComputerUtilCost.getMaxXValue(sa, ai);
sa.setXManaCostPaid(x);
}
} }
if (x <= 0) { if (x <= 0) {
return false; return false;

View File

@@ -414,7 +414,7 @@ public final class CardRules implements ICardCharacteristics {
String key = colonPos > 0 ? line.substring(0, colonPos) : line; String key = colonPos > 0 ? line.substring(0, colonPos) : line;
String value = colonPos > 0 ? line.substring(1+colonPos).trim() : null; String value = colonPos > 0 ? line.substring(1+colonPos).trim() : null;
switch(key.charAt(0)) { switch (key.charAt(0)) {
case 'A': case 'A':
if ("A".equals(key)) { if ("A".equals(key)) {
this.faces[curFace].addAbility(value); this.faces[curFace].addAbility(value);

View File

@@ -759,7 +759,6 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
}; };
} }
///////// Utility methods ///////// Utility methods
public static boolean isACardType(final String cardType) { public static boolean isACardType(final String cardType) {
return CoreType.isValidEnum(cardType); return CoreType.isValidEnum(cardType);

View File

@@ -40,7 +40,6 @@ public class PrintSheet {
private final ItemPool<PaperCard> cardsWithWeights; private final ItemPool<PaperCard> cardsWithWeights;
private final String name; private final String name;
public PrintSheet(String name0) { public PrintSheet(String name0) {
this(name0, null); this(name0, null);

View File

@@ -43,9 +43,8 @@ public class DeckGenPool implements IDeckGenPool {
Iterable<PaperCard> editionCards=Iterables.filter(cards.values(), filter); Iterable<PaperCard> editionCards=Iterables.filter(cards.values(), filter);
if (editionCards.iterator().hasNext()){ if (editionCards.iterator().hasNext()){
return editionCards.iterator().next(); return editionCards.iterator().next();
}else {
return getCard(name);
} }
return getCard(name);
} }
@Override @Override

View File

@@ -3,9 +3,6 @@ package forge.item;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
/** /**
* Filtering conditions for miscellaneous InventoryItems. * Filtering conditions for miscellaneous InventoryItems.
*/ */

View File

@@ -351,4 +351,3 @@ public final class PaperCard implements Comparable<IPaperCard>, InventoryItemFro
|| (this.getName().equals("Mountain")); || (this.getName().equals("Mountain"));
} }
} }

View File

@@ -95,7 +95,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
build.add(subtypes); build.add(subtypes);
// Are these keywords sorted? // Are these keywords sorted?
for(String keyword : rules.getMainPart().getKeywords()) { for (String keyword : rules.getMainPart().getKeywords()) {
build.add(keyword); build.add(keyword);
} }

View File

@@ -15,7 +15,7 @@ public class ImageUtil {
} }
public static PaperCard getPaperCardFromImageKey(String key) { public static PaperCard getPaperCardFromImageKey(String key) {
if ( key == null ) { if (key == null) {
return null; return null;
} }
@@ -42,6 +42,10 @@ public class ImageUtil {
String edition = key.substring(index + 1); String edition = key.substring(index + 1);
if (script.startsWith("emblem")) if (script.startsWith("emblem"))
return null; return null;
if (null == StaticData.instance().getCardEdition(edition)) {
script = key;
edition = "???";
}
script = script.replaceAll("[0-9]*$", ""); script = script.replaceAll("[0-9]*$", "");
return StaticData.instance().getAllTokens().getToken(script, edition); return StaticData.instance().getAllTokens().getToken(script, edition);
} }

View File

@@ -366,7 +366,6 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
if (!Expressions.compare(left, presentCompare, right)) { if (!Expressions.compare(left, presentCompare, right)) {
return false; return false;
} }
} }
if (params.containsKey("IsPresent2")) { if (params.containsKey("IsPresent2")) {

View File

@@ -1633,7 +1633,7 @@ public class GameAction {
recheck = true; recheck = true;
Card toKeep = p.getController().chooseSingleEntityForEffect(new CardCollection(cc), new SpellAbility.EmptySa(ApiType.InternalLegendaryRule, null, p), Card toKeep = p.getController().chooseSingleEntityForEffect(new CardCollection(cc), new SpellAbility.EmptySa(ApiType.InternalLegendaryRule, new Card(-1, game), p),
"You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)", null); "You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)", null);
for (Card c: cc) { for (Card c: cc) {
if (c != toKeep) { if (c != toKeep) {
@@ -1905,7 +1905,6 @@ public class GameAction {
game.getTriggerHandler().runTrigger(TriggerType.NewGame, AbilityKey.newMap(), true); game.getTriggerHandler().runTrigger(TriggerType.NewGame, AbilityKey.newMap(), true);
//</THIS CODE WILL WORK WITH PHASE = NULL> //</THIS CODE WILL WORK WITH PHASE = NULL>
game.getPhaseHandler().startFirstTurn(first, startGameHook); game.getPhaseHandler().startFirstTurn(first, startGameHook);
//after game ends, ensure Auto-Pass canceled for all players so it doesn't apply to next game //after game ends, ensure Auto-Pass canceled for all players so it doesn't apply to next game
for (Player p : game.getRegisteredPlayers()) { for (Player p : game.getRegisteredPlayers()) {
@@ -1980,7 +1979,6 @@ public class GameAction {
private void runPreOpeningHandActions(final Player first) { private void runPreOpeningHandActions(final Player first) {
Player takesAction = first; Player takesAction = first;
do { do {
//
List<Card> ploys = CardLists.filter(takesAction.getCardsIn(ZoneType.Command), new Predicate<Card>() { List<Card> ploys = CardLists.filter(takesAction.getCardsIn(ZoneType.Command), new Predicate<Card>() {
@Override @Override
public boolean apply(Card input) { public boolean apply(Card input) {
@@ -2054,8 +2052,7 @@ public class GameAction {
public void invoke(final Runnable proc) { public void invoke(final Runnable proc) {
if (ThreadUtil.isGameThread()) { if (ThreadUtil.isGameThread()) {
proc.run(); proc.run();
} } else {
else {
ThreadUtil.invokeInGameThread(proc); ThreadUtil.invokeInGameThread(proc);
} }
} }

View File

@@ -274,8 +274,7 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
blockers = att.getValue(); blockers = att.getValue();
if (blockers.isEmpty()) { if (blockers.isEmpty()) {
sb.append(Localizer.getInstance().getMessage("lblLogPlayerDidntBlockAttacker", controllerName, att.getKey())); sb.append(Localizer.getInstance().getMessage("lblLogPlayerDidntBlockAttacker", controllerName, att.getKey()));
} } else {
else {
sb.append(Localizer.getInstance().getMessage("lblLogPlayerAssignedBlockerToBlockAttacker", controllerName, Lang.joinHomogenous(blockers), att.getKey())); sb.append(Localizer.getInstance().getMessage("lblLogPlayerAssignedBlockerToBlockAttacker", controllerName, Lang.joinHomogenous(blockers), att.getKey()));
} }
firstAttacker = false; firstAttacker = false;

View File

@@ -178,7 +178,6 @@ public final class AbilityFactory {
return getAbility(type, type.getApiTypeOf(mapParams), mapParams, parseAbilityCost(state, mapParams, type), state, sVarHolder); return getAbility(type, type.getApiTypeOf(mapParams), mapParams, parseAbilityCost(state, mapParams, type), state, sVarHolder);
} }
public static Cost parseAbilityCost(final CardState state, Map<String, String> mapParams, AbilityRecordType type) { public static Cost parseAbilityCost(final CardState state, Map<String, String> mapParams, AbilityRecordType type) {
Cost abCost = null; Cost abCost = null;
if (type != AbilityRecordType.SubAbility) { if (type != AbilityRecordType.SubAbility) {

View File

@@ -1282,7 +1282,8 @@ 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")) { }
else if (defined.startsWith("NextPlayerToYour")) {
Direction dir = defined.substring(16).equals("Left") ? Direction.Left : Direction.Right; Direction dir = defined.substring(16).equals("Left") ? Direction.Left : Direction.Right;
players.add(game.getNextPlayerAfter(player, dir)); players.add(game.getNextPlayerAfter(player, dir));
} }
@@ -1806,6 +1807,19 @@ public class AbilityUtils {
} }
return count; return count;
} }
// Count$TriggeredManaCostDevotion.<Color>
if (sq[0].startsWith("TriggeredManaCostDevotion")) {
final SpellAbility root = sa.getRootAbility();
Card triggeringObject = (Card) root.getTriggeringObject(AbilityKey.Card);
int count = 0;
byte colorCode = ManaAtom.fromName(sq[1]);
for (ManaCostShard sh : triggeringObject.getManaCost()) {
if (sh.isColor(colorCode)) {
count++;
}
}
return count;
}
// Count$TriggeredPayingMana.<Color1>.<Color2> // Count$TriggeredPayingMana.<Color1>.<Color2>
if (sq[0].startsWith("TriggeredPayingMana")) { if (sq[0].startsWith("TriggeredPayingMana")) {
final SpellAbility root = sa.getRootAbility(); final SpellAbility root = sa.getRootAbility();
@@ -1927,7 +1941,7 @@ public class AbilityUtils {
// Count$DevotionDual.<color name>.<color name> // Count$DevotionDual.<color name>.<color name>
// Count$Devotion.<color name> // Count$Devotion.<color name>
if (sq[0].contains("Devotion")) { if (sq[0].contains("Devotion")) {
int colorOcurrencices = 0; int colorOccurrences = 0;
String colorName = sq[1]; String colorName = sq[1];
if (colorName.contains("Chosen")) { if (colorName.contains("Chosen")) {
colorName = MagicColor.toShortString(c.getChosenColor()); colorName = MagicColor.toShortString(c.getChosenColor());
@@ -1939,12 +1953,12 @@ public class AbilityUtils {
for (Card c0 : player.getCardsIn(ZoneType.Battlefield)) { for (Card c0 : player.getCardsIn(ZoneType.Battlefield)) {
for (ManaCostShard sh : c0.getManaCost()) { for (ManaCostShard sh : c0.getManaCost()) {
if (sh.isColor(colorCode)) { if (sh.isColor(colorCode)) {
colorOcurrencices++; colorOccurrences++;
} }
} }
colorOcurrencices += c0.getAmountOfKeyword("Your devotion to each color and each combination of colors is increased by one."); colorOccurrences += c0.getAmountOfKeyword("Your devotion to each color and each combination of colors is increased by one.");
} }
return doXMath(colorOcurrencices, expr, c, ctb); return doXMath(colorOccurrences, expr, c, ctb);
} }
} // end ctb != null } // end ctb != null

View File

@@ -1,21 +1,15 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.StaticData; import forge.StaticData;
import forge.card.CardFacePredicates; import forge.card.CardFacePredicates;
import forge.card.CardRules; import forge.card.CardRules;
import forge.card.CardRulesPredicates;
import forge.card.CardSplitType; import forge.card.CardSplitType;
import forge.card.ICardFace; import forge.card.ICardFace;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
@@ -26,9 +20,7 @@ import forge.game.card.CardLists;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.item.PaperCard;
import forge.util.Aggregates; import forge.util.Aggregates;
import forge.util.ComparableOp;
import forge.util.Localizer; import forge.util.Localizer;
public class ChooseCardNameEffect extends SpellAbilityEffect { public class ChooseCardNameEffect extends SpellAbilityEffect {

View File

@@ -327,11 +327,10 @@ public class EffectEffect extends SpellAbilityEffect {
hostCard.addImprintedCard(eff); hostCard.addImprintedCard(eff);
} }
eff.updateStateForView();
// TODO: Add targeting to the effect so it knows who it's dealing with // TODO: Add targeting to the effect so it knows who it's dealing with
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa); game.getAction().moveTo(ZoneType.Command, eff, sa);
eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
//if (effectTriggers != null) { //if (effectTriggers != null) {
// game.getTriggerHandler().registerActiveTrigger(cmdEffect, false); // game.getTriggerHandler().registerActiveTrigger(cmdEffect, false);

View File

@@ -36,7 +36,7 @@ public class RegenerateAllEffect extends RegenerateBaseEffect {
list = CardLists.getValidCards(list, valid.split(","), hostCard.getController(), hostCard, sa); list = CardLists.getValidCards(list, valid.split(","), hostCard.getController(), hostCard, sa);
// create Effect for Regeneration // create Effect for Regeneration
createRengenerationEffect(sa, list); createRegenerationEffect(sa, list);
} // regenerateAllResolve } // regenerateAllResolve
} }

View File

@@ -17,7 +17,7 @@ import forge.game.zone.ZoneType;
public abstract class RegenerateBaseEffect extends SpellAbilityEffect { public abstract class RegenerateBaseEffect extends SpellAbilityEffect {
public void createRengenerationEffect(SpellAbility sa, final Iterable<Card> list) { public void createRegenerationEffect(SpellAbility sa, final Iterable<Card> list) {
final Card hostCard = sa.getHostCard(); final Card hostCard = sa.getHostCard();
final Game game = hostCard.getGame(); final Game game = hostCard.getGame();

View File

@@ -46,7 +46,7 @@ public class RegenerateEffect extends RegenerateBaseEffect {
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
// create Effect for Regeneration // create Effect for Regeneration
createRengenerationEffect(sa, getTargetCards(sa)); createRegenerationEffect(sa, getTargetCards(sa));
} // regenerateResolve }
} }

View File

@@ -31,6 +31,7 @@ public class RegenerationEffect extends SpellAbilityEffect {
c.addRegeneratedThisTurn(); c.addRegeneratedThisTurn();
if (game.getCombat() != null) { if (game.getCombat() != null) {
game.getCombat().saveLKI(c);
game.getCombat().removeFromCombat(c); game.getCombat().removeFromCombat(c);
} }

View File

@@ -59,6 +59,7 @@ public class RemoveFromCombatEffect extends SpellAbilityEffect {
} }
} }
game.getCombat().saveLKI(c);
combat.removeFromCombat(c); combat.removeFromCombat(c);
if (rem) { if (rem) {

View File

@@ -49,6 +49,7 @@ public class RestartGameEffect extends SpellAbilityEffect {
game.clearCounterAddedThisTurn(); game.clearCounterAddedThisTurn();
game.resetPlayersAttackedOnNextTurn(); game.resetPlayersAttackedOnNextTurn();
game.resetPlayersAttackedOnNextTurn(); game.resetPlayersAttackedOnNextTurn();
game.setMonarch(null);
GameAction action = game.getAction(); GameAction action = game.getAction();
for (Player p: players) { for (Player p: players) {
@@ -59,6 +60,7 @@ public class RestartGameEffect extends SpellAbilityEffect {
p.setLandsPlayedLastTurn(0); p.setLandsPlayedLastTurn(0);
p.resetCommanderStats(); p.resetCommanderStats();
p.resetCompletedDungeons(); p.resetCompletedDungeons();
p.setBlessing(false);
CardCollection newLibrary = new CardCollection(p.getCardsIn(restartZones, false)); CardCollection newLibrary = new CardCollection(p.getCardsIn(restartZones, false));
List<Card> filteredCards = null; List<Card> filteredCards = null;
@@ -111,4 +113,3 @@ public class RestartGameEffect extends SpellAbilityEffect {
return TextUtil.fastReplace(desc, "CARDNAME", sa.getHostCard().getName()); return TextUtil.fastReplace(desc, "CARDNAME", sa.getHostCard().getName());
} }
} }

View File

@@ -32,6 +32,7 @@ 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.combat.Combat; import forge.game.combat.Combat;
import forge.game.combat.CombatLki;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.cost.CostSacrifice; import forge.game.cost.CostSacrifice;
import forge.game.event.*; import forge.game.event.*;
@@ -295,6 +296,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
private final Table<SpellAbility, StaticAbility, List<String>> chosenModesTurnStatic = HashBasedTable.create(); private final Table<SpellAbility, StaticAbility, List<String>> chosenModesTurnStatic = HashBasedTable.create();
private final Table<SpellAbility, StaticAbility, List<String>> chosenModesGameStatic = HashBasedTable.create(); private final Table<SpellAbility, StaticAbility, List<String>> chosenModesGameStatic = HashBasedTable.create();
private CombatLki combatLKI = null;
// Enumeration for CMC request types // Enumeration for CMC request types
public enum SplitCMCMode { public enum SplitCMCMode {
CurrentSideCMC, CurrentSideCMC,
@@ -1354,7 +1357,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
@Override @Override
public int addCounter(final CounterType counterType, final int n, final Player source, final SpellAbility cause, final boolean applyMultiplier, final boolean fireEvents, GameEntityCounterTable table) { public int addCounter(final CounterType counterType, final int n, final Player source, final SpellAbility cause, final boolean applyMultiplier, final boolean fireEvents, GameEntityCounterTable table) {
int addAmount = n; int addAmount = n;
if(addAmount <= 0 || !canReceiveCounters(counterType)) { if (addAmount <= 0 || !canReceiveCounters(counterType)) {
// As per rule 107.1b // As per rule 107.1b
return 0; return 0;
} }
@@ -1974,6 +1977,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
sbx.append(" (").append(inst.getReminderText()).append(")"); sbx.append(" (").append(inst.getReminderText()).append(")");
sbLong.append(sbx).append("\r\n"); sbLong.append(sbx).append("\r\n");
} }
} else if (keyword.startsWith("Trample:")) {
sbLong.append("Trample over planeswalkers").append(" (").append(inst.getReminderText()).append(")").append("\r\n");
} else if (keyword.startsWith("Hexproof:")) { } else if (keyword.startsWith("Hexproof:")) {
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
sbLong.append("Hexproof from ").append(k[2]) sbLong.append("Hexproof from ").append(k[2])
@@ -2000,7 +2005,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|| keyword.equals("Suspend") // for the ones without amount || keyword.equals("Suspend") // for the ones without amount
|| keyword.equals("Foretell") // for the ones without cost || keyword.equals("Foretell") // for the ones without cost
|| keyword.equals("Hideaway") || keyword.equals("Ascend") || keyword.equals("Hideaway") || keyword.equals("Ascend")
|| keyword.equals("Trample over planeswalkers")
|| keyword.equals("Totem armor") || keyword.equals("Battle cry") || keyword.equals("Totem armor") || keyword.equals("Battle cry")
|| keyword.equals("Devoid") || keyword.equals("Riot")){ || keyword.equals("Devoid") || keyword.equals("Riot")){
sbLong.append(keyword).append(" (").append(inst.getReminderText()).append(")"); sbLong.append(keyword).append(" (").append(inst.getReminderText()).append(")");
@@ -4670,6 +4674,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
setPhasedOut(!phasedOut); setPhasedOut(!phasedOut);
final Combat combat = getGame().getPhaseHandler().getCombat(); final Combat combat = getGame().getPhaseHandler().getCombat();
if (combat != null && phasedOut) { if (combat != null && phasedOut) {
combat.saveLKI(this);
combat.removeFromCombat(this); combat.removeFromCombat(this);
} }
@@ -6167,7 +6172,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return false; return false;
} }
if (source == null){ if (source == null) {
return true; return true;
} }
@@ -6924,4 +6929,18 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final void clearUntilLeavesBattlefield() { public final void clearUntilLeavesBattlefield() {
untilLeavesBattlefield = view.clearCards(untilLeavesBattlefield, TrackableProperty.UntilLeavesBattlefield); untilLeavesBattlefield = view.clearCards(untilLeavesBattlefield, TrackableProperty.UntilLeavesBattlefield);
} }
public CombatLki getCombatLKI() {
return combatLKI;
}
public void setCombatLKI(CombatLki combatLKI) {
this.combatLKI = combatLKI;
}
public boolean isAttacking() {
if (getCombatLKI() != null) {
return getCombatLKI().isAttacker;
}
return getGame().getCombat().isAttacking(this);
}
} }

View File

@@ -518,6 +518,7 @@ public class CardFactoryUtil {
final Set<String> protectionkw = Sets.newHashSet(); final Set<String> protectionkw = Sets.newHashSet();
final Set<String> protectionColorkw = Sets.newHashSet(); final Set<String> protectionColorkw = Sets.newHashSet();
final Set<String> hexproofkw = Sets.newHashSet(); final Set<String> hexproofkw = Sets.newHashSet();
final Set<String> tramplekw = Sets.newHashSet();
final Set<String> allkw = Sets.newHashSet(); final Set<String> allkw = Sets.newHashSet();
for (Card c : CardLists.getValidCards(cardlist, restrictions, p, host, null)) { for (Card c : CardLists.getValidCards(cardlist, restrictions, p, host, null)) {
@@ -535,6 +536,8 @@ public class CardFactoryUtil {
} }
} else if (k.startsWith("Hexproof")) { } else if (k.startsWith("Hexproof")) {
hexproofkw.add(k); hexproofkw.add(k);
} else if (k.startsWith("Trample")) {
tramplekw.add(k);
} }
allkw.add(k); allkw.add(k);
} }
@@ -548,6 +551,8 @@ public class CardFactoryUtil {
filteredkw.addAll(landkw); filteredkw.addAll(landkw);
} else if (keyword.equals("Hexproof")) { } else if (keyword.equals("Hexproof")) {
filteredkw.addAll(hexproofkw); filteredkw.addAll(hexproofkw);
} else if (keyword.equals("Trample")) {
filteredkw.addAll(tramplekw);
} else if (allkw.contains(keyword)) { } else if (allkw.contains(keyword)) {
filteredkw.add(keyword); filteredkw.add(keyword);
} }
@@ -781,7 +786,7 @@ public class CardFactoryUtil {
+ "TriggerZones$ Battlefield | Secondary$ True | TriggerDescription$ " + "TriggerZones$ Battlefield | Secondary$ True | TriggerDescription$ "
+ "Annihilator " + n + " (" + inst.getReminderText() + ")"; + "Annihilator " + n + " (" + inst.getReminderText() + ")";
final String effect = "DB$ Sacrifice | Defined$ DefendingPlayer | SacValid$ Permanent | Amount$ " + k[1]; final String effect = "DB$ Sacrifice | Defined$ TriggeredDefendingPlayer | SacValid$ Permanent | Amount$ " + k[1];
final Trigger trigger = TriggerHandler.parseTrigger(trig, card, intrinsic); final Trigger trigger = TriggerHandler.parseTrigger(trig, card, intrinsic);
trigger.setOverridingAbility(AbilityFactory.getAbility(effect, card)); trigger.setOverridingAbility(AbilityFactory.getAbility(effect, card));
@@ -1516,7 +1521,7 @@ public class CardFactoryUtil {
} else if (keyword.equals("Provoke")) { } else if (keyword.equals("Provoke")) {
final String actualTrigger = "Mode$ Attacks | ValidCard$ Card.Self | OptionalDecider$ You | Secondary$ True" final String actualTrigger = "Mode$ Attacks | ValidCard$ Card.Self | OptionalDecider$ You | Secondary$ True"
+ " | TriggerDescription$ Provoke (" + inst.getReminderText() + ")"; + " | TriggerDescription$ Provoke (" + inst.getReminderText() + ")";
final String blockStr = "DB$ MustBlock | ValidTgts$ Creature.DefenderCtrl | TgtPrompt$ Select target creature defending player controls"; final String blockStr = "DB$ MustBlock | ValidTgts$ Creature.ControlledBy TriggeredDefendingPlayer | TgtPrompt$ Select target creature defending player controls";
final String untapStr = "DB$ Untap | Defined$ Targeted"; final String untapStr = "DB$ Untap | Defined$ Targeted";
SpellAbility blockSA = AbilityFactory.getAbility(blockStr, card); SpellAbility blockSA = AbilityFactory.getAbility(blockStr, card);

View File

@@ -1412,8 +1412,7 @@ public class CardProperty {
// These predicated refer to ongoing combat. If no combat happens, they'll return false (meaning not attacking/blocking ATM) // These predicated refer to ongoing combat. If no combat happens, they'll return false (meaning not attacking/blocking ATM)
else if (property.startsWith("attacking")) { else if (property.startsWith("attacking")) {
if (null == combat) return false; if (null == combat) return false;
if (property.equals("attacking")) return combat.isAttacking(card); if (property.equals("attacking")) return card.isAttacking();
if (property.equals("attackingLKI")) return combat.isLKIAttacking(card);
if (property.equals("attackingYou")) return combat.isAttacking(card, sourceController); if (property.equals("attackingYou")) return combat.isAttacking(card, sourceController);
if (property.equals("attackingSame")) { if (property.equals("attackingSame")) {
final GameEntity attacked = combat.getDefenderByAttacker(source); final GameEntity attacked = combat.getDefenderByAttacker(source);
@@ -1514,12 +1513,10 @@ public class CardProperty {
return false; return false;
} }
String valid = property.split(" ")[1]; String valid = property.split(" ")[1];
for(Card c : blocked) { if (Iterables.any(blocked, CardPredicates.restriction(valid, card.getController(), source, spellAbility))) {
if (c.isValid(valid, card.getController(), source, spellAbility)) { return true;
return true;
}
} }
for(Card c : AbilityUtils.getDefinedCards(source, valid, spellAbility)) { for (Card c : AbilityUtils.getDefinedCards(source, valid, spellAbility)) {
if (blocked.contains(c)) { if (blocked.contains(c)) {
return true; return true;
} }

View File

@@ -323,6 +323,10 @@ public final class CardUtil {
newCopy.setExiledWith(getLKICopy(in.getExiledWith(), cachedMap)); newCopy.setExiledWith(getLKICopy(in.getExiledWith(), cachedMap));
if (in.getGame().getCombat() != null) {
newCopy.setCombatLKI(in.getGame().getCombat().saveLKI(newCopy));
}
return newCopy; return newCopy;
} }

View File

@@ -97,6 +97,9 @@ public class CounterType implements Comparable<CounterType>, Serializable {
final String[] k = sVal.split(":"); final String[] k = sVal.split(":");
return "Hexproof from " + k[2]; return "Hexproof from " + k[2];
} }
if (sVal.startsWith("Trample:")) {
return "Trample over Planeswalkers";
}
return sVal; return sVal;
} }
@@ -119,6 +122,9 @@ public class CounterType implements Comparable<CounterType>, Serializable {
if (sVal.startsWith("Hexproof:")) { if (sVal.startsWith("Hexproof:")) {
return true; return true;
} }
if (sVal.startsWith("Trample:")) {
return true;
}
return keywordCounter.contains(sVal); return keywordCounter.contains(sVal);
} }

View File

@@ -45,6 +45,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardDamageMap; import forge.game.card.CardDamageMap;
import forge.game.card.CardUtil;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementType; import forge.game.replacement.ReplacementType;
@@ -75,7 +76,7 @@ public class Combat {
private Map<Card, CardCollection> attackersOrderedForDamageAssignment = Maps.newHashMap(); private Map<Card, CardCollection> attackersOrderedForDamageAssignment = Maps.newHashMap();
private Map<Card, CardCollection> blockersOrderedForDamageAssignment = Maps.newHashMap(); private Map<Card, CardCollection> blockersOrderedForDamageAssignment = Maps.newHashMap();
private Map<GameEntity, CombatLki> lkiCache = Maps.newHashMap(); private CardCollection lkiCache = new CardCollection();
private CardDamageMap damageMap = new CardDamageMap(); private CardDamageMap damageMap = new CardDamageMap();
// List holds creatures who have dealt 1st strike damage to disallow them deal damage on regular basis (unless they have double-strike KW) // List holds creatures who have dealt 1st strike damage to disallow them deal damage on regular basis (unless they have double-strike KW)
@@ -300,7 +301,7 @@ public class Combat {
return ab; return ab;
} }
} }
CombatLki lki = lkiCache.get(c); CombatLki lki = lkiCache.get(c).getCombatLKI();
return lki == null || !lki.isAttacker ? null : lki.getFirstBand(); return lki == null || !lki.isAttacker ? null : lki.getFirstBand();
} }
@@ -316,14 +317,6 @@ public class Combat {
return Lists.newArrayList(attackedByBands.values()); return Lists.newArrayList(attackedByBands.values());
} }
/**
* Checks if a card is attacking, returns true if the card was attacking when it left the battlefield
*/
public final boolean isLKIAttacking(final Card c) {
AttackingBand ab = getBandOfAttacker(c);
return ab != null;
}
public boolean isAttacking(Card card, GameEntity defender) { public boolean isAttacking(Card card, GameEntity defender) {
AttackingBand ab = getBandOfAttacker(card); AttackingBand ab = getBandOfAttacker(card);
for (Entry<GameEntity, AttackingBand> ee : attackedByBands.entries()) { for (Entry<GameEntity, AttackingBand> ee : attackedByBands.entries()) {
@@ -786,7 +779,7 @@ public class Combat {
assignedDamage = true; assignedDamage = true;
GameEntity defender = getDefenderByAttacker(band); GameEntity defender = getDefenderByAttacker(band);
// If the Attacker is unblocked, or it's a trampler and has 0 blockers, deal damage to defender // If the Attacker is unblocked, or it's a trampler and has 0 blockers, deal damage to defender
if (defender instanceof Card && attacker.hasKeyword("Trample over planeswalkers")) { if (defender instanceof Card && attacker.hasKeyword("Trample:Planeswalker")) {
if (orderedBlockers == null || orderedBlockers.isEmpty()) { if (orderedBlockers == null || orderedBlockers.isEmpty()) {
CardCollection cc = new CardCollection(); CardCollection cc = new CardCollection();
cc.add((Card)defender); cc.add((Card)defender);
@@ -918,7 +911,7 @@ public class Combat {
return false; return false;
} }
CombatLki lki = lkiCache.get(blocker); CombatLki lki = lkiCache.get(blocker).getCombatLKI();
return null != lki && !lki.isAttacker; // was blocking something anyway return null != lki && !lki.isAttacker; // was blocking something anyway
} }
@@ -933,21 +926,36 @@ public class Combat {
return false; return false;
} }
CombatLki lki = lkiCache.get(blocker); CombatLki lki = lkiCache.get(blocker).getCombatLKI();
return null != lki && !lki.isAttacker && lki.relatedBands.contains(ab); // was blocking that very band return null != lki && !lki.isAttacker && lki.relatedBands.contains(ab); // was blocking that very band
} }
public void saveLKI(Card lastKnownInfo) { public CombatLki saveLKI(Card lki) {
if (!lki.isLKI()) {
lki = CardUtil.getLKICopy(lki);
}
FCollectionView<AttackingBand> attackersBlocked = null; FCollectionView<AttackingBand> attackersBlocked = null;
final AttackingBand attackingBand = getBandOfAttacker(lastKnownInfo); final AttackingBand attackingBand = getBandOfAttacker(lki);
final boolean isAttacker = attackingBand != null; final boolean isAttacker = attackingBand != null;
if (!isAttacker) { if (isAttacker) {
attackersBlocked = getAttackingBandsBlockedBy(lastKnownInfo); boolean found = false;
for (AttackingBand ab : attackedByBands.values()) {
if (ab.contains(lki)) {
found = true;
break;
}
}
if (!found) {
return null;
}
} else {
attackersBlocked = getAttackingBandsBlockedBy(lki);
if (attackersBlocked.isEmpty()) { if (attackersBlocked.isEmpty()) {
return; // card was not even in combat return null; // card was not even in combat
} }
} }
lkiCache.add(lki);
final FCollectionView<AttackingBand> relatedBands = isAttacker ? new FCollection<>(attackingBand) : attackersBlocked; final FCollectionView<AttackingBand> relatedBands = isAttacker ? new FCollection<>(attackingBand) : attackersBlocked;
lkiCache.put(lastKnownInfo, new CombatLki(isAttacker, relatedBands)); return new CombatLki(isAttacker, relatedBands);
} }
} }

View File

@@ -240,8 +240,7 @@ public class Cost implements Serializable {
xCantBe0 = true; xCantBe0 = true;
} else if ("Mandatory".equals(part)) { } else if ("Mandatory".equals(part)) {
this.isMandatory = true; this.isMandatory = true;
} } else {
else {
CostPart cp = parseCostPart(part, tapCost, untapCost); CostPart cp = parseCostPart(part, tapCost, untapCost);
if (null != cp ) if (null != cp )
if (cp instanceof CostPartMana ) { if (cp instanceof CostPartMana ) {
@@ -268,7 +267,6 @@ public class Cost implements Serializable {
} }
private static CostPart parseCostPart(String parse, boolean tapCost, boolean untapCost) { private static CostPart parseCostPart(String parse, boolean tapCost, boolean untapCost) {
if (parse.startsWith("Mana<")) { if (parse.startsWith("Mana<")) {
final String[] splitStr = TextUtil.split(abCostParse(parse, 1)[0], '\\'); final String[] splitStr = TextUtil.split(abCostParse(parse, 1)[0], '\\');
final String restriction = splitStr.length > 1 ? splitStr[1] : null; final String restriction = splitStr.length > 1 ? splitStr[1] : null;

View File

@@ -10,8 +10,7 @@ public class GameEventAnteCardsSelected extends GameEvent {
public GameEventAnteCardsSelected(Multimap<Player, Card> list) { public GameEventAnteCardsSelected(Multimap<Player, Card> list) {
cards = list; cards = list;
} }
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -14,7 +14,7 @@ public class GameEventAttackersDeclared extends GameEvent {
public final Player player; public final Player player;
public final Multimap<GameEntity, Card> attackersMap; public final Multimap<GameEntity, Card> attackersMap;
public GameEventAttackersDeclared(Player playerTurn, Multimap<GameEntity, Card> attackersMap) { public GameEventAttackersDeclared(Player playerTurn, Multimap<GameEntity, Card> attackersMap) {
this.player = playerTurn; this.player = playerTurn;
this.attackersMap = attackersMap; this.attackersMap = attackersMap;

View File

@@ -20,7 +20,7 @@ public class GameEventBlockersDeclared extends GameEvent {
public final Map<GameEntity, MapOfLists<Card, Card>> blockers; public final Map<GameEntity, MapOfLists<Card, Card>> blockers;
public final Player defendingPlayer; public final Player defendingPlayer;
public GameEventBlockersDeclared(Player who, Map<GameEntity, MapOfLists<Card, Card>> blockers) { public GameEventBlockersDeclared(Player who, Map<GameEntity, MapOfLists<Card, Card>> blockers) {
this.blockers = blockers; this.blockers = blockers;
defendingPlayer = who; defendingPlayer = who;

View File

@@ -8,13 +8,13 @@ public class GameEventCardAttachment extends GameEvent {
public final Card equipment; public final Card equipment;
public final GameEntity newTarget; // can enchant player, I'm ssaving a class to enchants - it could be incorrect. public final GameEntity newTarget; // can enchant player, I'm ssaving a class to enchants - it could be incorrect.
public final GameEntity oldEntiy; public final GameEntity oldEntiy;
public GameEventCardAttachment(Card attachment, GameEntity formerEntity, GameEntity newEntity) { public GameEventCardAttachment(Card attachment, GameEntity formerEntity, GameEntity newEntity) {
this.equipment = attachment; this.equipment = attachment;
this.newTarget = newEntity; this.newTarget = newEntity;
this.oldEntiy = formerEntity; this.oldEntiy = formerEntity;
} }
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -6,11 +6,10 @@ import forge.util.TextUtil;
public class GameEventCardChangeZone extends GameEvent { public class GameEventCardChangeZone extends GameEvent {
public final Card card;
public final Zone from;
public final Zone to;
public final Card card;
public final Zone from;
public final Zone to;
public GameEventCardChangeZone(Card c, Zone zoneFrom, Zone zoneTo) { public GameEventCardChangeZone(Card c, Zone zoneFrom, Zone zoneTo) {
card = c; card = c;
from = zoneFrom; from = zoneFrom;
@@ -21,7 +20,7 @@ public class GameEventCardChangeZone extends GameEvent {
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see java.lang.Object#toString() * @see java.lang.Object#toString()
*/ */

View File

@@ -15,7 +15,7 @@ public class GameEventCardCounters extends GameEvent {
this.oldValue = old; this.oldValue = old;
this.newValue = newValue; this.newValue = newValue;
} }
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -3,7 +3,7 @@ package forge.game.event;
import forge.game.card.Card; import forge.game.card.Card;
public class GameEventCardDamaged extends GameEvent { public class GameEventCardDamaged extends GameEvent {
public enum DamageType { public enum DamageType {
Normal, Normal,
M1M1Counters, M1M1Counters,

View File

@@ -1,7 +1,7 @@
package forge.game.event; package forge.game.event;
public class GameEventCardDestroyed extends GameEvent { public class GameEventCardDestroyed extends GameEvent {
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -3,12 +3,12 @@ package forge.game.event;
import forge.game.player.Player; import forge.game.player.Player;
public class GameEventCardModeChosen extends GameEvent { public class GameEventCardModeChosen extends GameEvent {
public final Player player; public final Player player;
public final String cardName; public final String cardName;
public final String mode; public final String mode;
public final boolean log; public final boolean log;
public GameEventCardModeChosen(Player player, String cardName, String mode, boolean log) { public GameEventCardModeChosen(Player player, String cardName, String mode, boolean log) {
this.player = player; this.player = player;
this.cardName = cardName; this.cardName = cardName;
@@ -21,5 +21,3 @@ public class GameEventCardModeChosen extends GameEvent {
return visitor.visit(this); return visitor.visit(this);
} }
} }

View File

@@ -10,7 +10,7 @@ public class GameEventCardPhased extends GameEvent {
public final Card card; public final Card card;
public final boolean phaseState; public final boolean phaseState;
public GameEventCardPhased(Card card, boolean state) { public GameEventCardPhased(Card card, boolean state) {
this.card = card; this.card = card;
phaseState = state; phaseState = state;
@@ -20,7 +20,7 @@ public class GameEventCardPhased extends GameEvent {
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see java.lang.Object#toString() * @see java.lang.Object#toString()
*/ */

View File

@@ -1,7 +1,7 @@
package forge.game.event; package forge.game.event;
public class GameEventCardRegenerated extends GameEvent { public class GameEventCardRegenerated extends GameEvent {
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -1,7 +1,7 @@
package forge.game.event; package forge.game.event;
public class GameEventCardSacrificed extends GameEvent { public class GameEventCardSacrificed extends GameEvent {
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -18,7 +18,7 @@ public class GameEventCardStatsChanged extends GameEvent {
public GameEventCardStatsChanged(Card affected) { public GameEventCardStatsChanged(Card affected) {
cards = Arrays.asList(affected); cards = Arrays.asList(affected);
} }
public GameEventCardStatsChanged(Collection<Card> affected) { public GameEventCardStatsChanged(Collection<Card> affected) {
cards = affected; cards = affected;
} }
@@ -31,13 +31,13 @@ public class GameEventCardStatsChanged extends GameEvent {
// TODO Auto-generated method stub // TODO Auto-generated method stub
return visitor.visit(this); return visitor.visit(this);
} }
@Override @Override
public String toString() { public String toString() {
Card card = Iterables.getFirst(cards, null); Card card = Iterables.getFirst(cards, null);
if ( null == card ) if (null == card)
return "Card state changes: (empty list)"; return "Card state changes: (empty list)";
if( cards.size() == 1) if (cards.size() == 1)
return "Card state changes: " + card.getName() + return "Card state changes: " + card.getName() +
" (" + StringUtils.join(card.getType(), ' ') + ") " + " (" + StringUtils.join(card.getType(), ' ') + ") " +
card.getNetPower() + "/" + card.getNetToughness(); card.getNetPower() + "/" + card.getNetToughness();

View File

@@ -10,8 +10,7 @@ public class GameEventCardTapped extends GameEvent {
this.tapped = tapped; this.tapped = tapped;
this.card = card; this.card = card;
} }
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -8,7 +8,7 @@ public class GameEventCombatEnded extends GameEvent {
public final List<Card> attackers; public final List<Card> attackers;
public final List<Card> blockers; public final List<Card> blockers;
public GameEventCombatEnded(List<Card> attackers, List<Card> blockers) { public GameEventCombatEnded(List<Card> attackers, List<Card> blockers) {
this.attackers = attackers; this.attackers = attackers;
this.blockers = blockers; this.blockers = blockers;

View File

@@ -1,8 +1,7 @@
package forge.game.event; package forge.game.event;
public class GameEventFlipCoin extends GameEvent { public class GameEventFlipCoin extends GameEvent {
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -12,8 +12,7 @@ public class GameEventGameOutcome extends GameEvent {
this.result = lastOne; this.result = lastOne;
this.history = history; this.history = history;
} }
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -9,12 +9,11 @@ import forge.game.player.Player;
public class GameEventGameRestarted extends GameEvent { public class GameEventGameRestarted extends GameEvent {
public final Player whoRestarted; public final Player whoRestarted;
public GameEventGameRestarted(Player playerTurn) { public GameEventGameRestarted(Player playerTurn) {
whoRestarted = playerTurn; whoRestarted = playerTurn;
} }
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -22,12 +22,11 @@ public class GameEventGameStarted extends GameEvent {
this.players = players; this.players = players;
} }
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see java.lang.Object#toString() * @see java.lang.Object#toString()
*/ */

View File

@@ -14,7 +14,6 @@ public class GameEventLandPlayed extends GameEvent {
} }
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -4,11 +4,11 @@ import forge.game.player.Player;
// This special event denotes loss of mana due to phase end // This special event denotes loss of mana due to phase end
public class GameEventManaBurn extends GameEvent { public class GameEventManaBurn extends GameEvent {
public final Player player; public final Player player;
public final boolean causedLifeLoss; public final boolean causedLifeLoss;
public final int amount; public final int amount;
/** /**
* TODO: Write javadoc for Constructor. * TODO: Write javadoc for Constructor.
* @param dealDamage * @param dealDamage
@@ -19,7 +19,7 @@ public class GameEventManaBurn extends GameEvent {
amount = burn; amount = burn;
causedLifeLoss = dealDamage; causedLifeLoss = dealDamage;
} }
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -24,7 +24,7 @@ public class GameEventManaPool extends GameEvent {
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see java.lang.Object#toString() * @see java.lang.Object#toString()
*/ */

View File

@@ -12,8 +12,7 @@ public class GameEventMulligan extends GameEvent {
public GameEventMulligan(Player p) { public GameEventMulligan(Player p) {
player = p; player = p;
} }
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -8,18 +8,18 @@ public class GameEventPlayerLivesChanged extends GameEvent {
public final Player player; public final Player player;
public final int oldLives; public final int oldLives;
public final int newLives; public final int newLives;
public GameEventPlayerLivesChanged(Player who, int oldValue, int newValue) { public GameEventPlayerLivesChanged(Player who, int oldValue, int newValue) {
player = who; player = who;
oldLives = oldValue; oldLives = oldValue;
newLives = newValue; newLives = newValue;
} }
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);
} }
@Override @Override
public String toString() { public String toString() {
return TextUtil.concatWithSpace(Lang.getInstance().getPossesive(player.getName()),"lives changed:", String.valueOf(oldLives),"->", String.valueOf(newLives)); return TextUtil.concatWithSpace(Lang.getInstance().getPossesive(player.getName()),"lives changed:", String.valueOf(oldLives),"->", String.valueOf(newLives));

View File

@@ -19,8 +19,7 @@ public class GameEventPlayerPoisoned extends GameEvent {
oldValue = old; oldValue = old;
amount = num; amount = num;
} }
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -13,7 +13,7 @@ public class GameEventPlayerPriority extends GameEvent {
public final Player turn; public final Player turn;
public final PhaseType phase; public final PhaseType phase;
public final Player priority; public final Player priority;
public GameEventPlayerPriority(Player playerTurn, PhaseType phase, Player priorityPlayer) { public GameEventPlayerPriority(Player playerTurn, PhaseType phase, Player priorityPlayer) {
turn = playerTurn; turn = playerTurn;
this.phase = phase; this.phase = phase;
@@ -25,7 +25,6 @@ public class GameEventPlayerPriority extends GameEvent {
return visitor.visit(this); return visitor.visit(this);
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see java.lang.Object#toString() * @see java.lang.Object#toString()
*/ */

View File

@@ -3,10 +3,10 @@ package forge.game.event;
import forge.game.player.Player; import forge.game.player.Player;
public class GameEventScry extends GameEvent { public class GameEventScry extends GameEvent {
public final Player player; public final Player player;
public final int toTop, toBottom; public final int toTop, toBottom;
public GameEventScry(Player player, int toTop, int toBottom) { public GameEventScry(Player player, int toTop, int toBottom) {
this.player = player; this.player = player;
this.toTop = toTop; this.toTop = toTop;
@@ -18,4 +18,3 @@ public class GameEventScry extends GameEvent {
return visitor.visit(this); return visitor.visit(this);
} }
} }

View File

@@ -5,9 +5,9 @@ import forge.util.Lang;
import forge.util.TextUtil; import forge.util.TextUtil;
public class GameEventShuffle extends GameEvent { public class GameEventShuffle extends GameEvent {
public final Player player; public final Player player;
public GameEventShuffle(Player player) { public GameEventShuffle(Player player) {
this.player = player; this.player = player;
} }
@@ -16,7 +16,7 @@ public class GameEventShuffle extends GameEvent {
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see java.lang.Object#toString() * @see java.lang.Object#toString()
*/ */

View File

@@ -13,7 +13,7 @@ public class GameEventSpellAbilityCast extends GameEvent {
public final SpellAbilityStackInstance si; public final SpellAbilityStackInstance si;
public final boolean replicate; public final boolean replicate;
public final int stackIndex; public final int stackIndex;
public GameEventSpellAbilityCast(SpellAbility sp, SpellAbilityStackInstance si, int stackIndex, boolean replicate) { public GameEventSpellAbilityCast(SpellAbility sp, SpellAbilityStackInstance si, int stackIndex, boolean replicate) {
sa = sp; sa = sp;
this.si = si; this.si = si;

View File

@@ -23,8 +23,6 @@ public class GameEventSpellResolved extends GameEvent {
this.hasFizzled = hasFizzled; this.hasFizzled = hasFizzled;
} }
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -3,10 +3,10 @@ package forge.game.event;
import forge.game.player.Player; import forge.game.player.Player;
public class GameEventSurveil extends GameEvent { public class GameEventSurveil extends GameEvent {
public final Player player; public final Player player;
public final int toLibrary, toGraveyard; public final int toLibrary, toGraveyard;
public GameEventSurveil(Player player, int toLibrary, int toGraveyard) { public GameEventSurveil(Player player, int toLibrary, int toGraveyard) {
this.player = player; this.player = player;
this.toLibrary = toLibrary; this.toLibrary = toLibrary;
@@ -18,4 +18,3 @@ public class GameEventSurveil extends GameEvent {
return visitor.visit(this); return visitor.visit(this);
} }
} }

View File

@@ -1,8 +1,7 @@
package forge.game.event; package forge.game.event;
public class GameEventTokenCreated extends GameEvent { public class GameEventTokenCreated extends GameEvent {
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -4,10 +4,10 @@ import forge.game.player.Player;
import forge.util.TextUtil; import forge.util.TextUtil;
public class GameEventTurnBegan extends GameEvent { public class GameEventTurnBegan extends GameEvent {
public final Player turnOwner; public final Player turnOwner;
public final int turnNumber; public final int turnNumber;
public GameEventTurnBegan(Player turnOwner, int turnNumber) { public GameEventTurnBegan(Player turnOwner, int turnNumber) {
super(); super();
this.turnOwner = turnOwner; this.turnOwner = turnOwner;
@@ -18,7 +18,7 @@ public class GameEventTurnBegan extends GameEvent {
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see java.lang.Object#toString() * @see java.lang.Object#toString()
*/ */

View File

@@ -1,8 +1,7 @@
package forge.game.event; package forge.game.event;
public class GameEventTurnEnded extends GameEvent { public class GameEventTurnEnded extends GameEvent {
@Override @Override
public <T> T visit(IGameEventVisitor<T> visitor) { public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);

View File

@@ -105,4 +105,3 @@ public interface IGameEventVisitor<T> {
public T visit(GameEventZone event) { return null; } public T visit(GameEventZone event) { return null; }
} }
} }

View File

@@ -152,8 +152,7 @@ public enum Keyword {
SURGE("Surge", KeywordWithCost.class, false, "You may cast this spell for its surge cost if you or a teammate has cast another spell this turn."), SURGE("Surge", KeywordWithCost.class, false, "You may cast this spell for its surge cost if you or a teammate has cast another spell this turn."),
SUSPEND("Suspend", Suspend.class, false, "Rather than cast this card from your hand, you may pay %s and exile it with {%d:time counter} on it. At the beginning of your upkeep, remove a time counter. When the last is removed, cast it without paying its mana cost."), SUSPEND("Suspend", Suspend.class, false, "Rather than cast this card from your hand, you may pay %s and exile it with {%d:time counter} on it. At the beginning of your upkeep, remove a time counter. When the last is removed, cast it without paying its mana cost."),
TOTEM_ARMOR("Totem armor", SimpleKeyword.class, true, "If enchanted permanent would be destroyed, instead remove all damage marked on it and destroy this Aura."), TOTEM_ARMOR("Totem armor", SimpleKeyword.class, true, "If enchanted permanent would be destroyed, instead remove all damage marked on it and destroy this Aura."),
TRAMPLE("Trample", SimpleKeyword.class, true, "This creature can deal excess combat damage to the player or planeswalker it's attacking."), TRAMPLE("Trample", Trample.class, true, "This creature can deal excess combat damage to the player or planeswalker it's attacking."),
TRAMPLE_OVER_PLANESWALKERS("Trample over planeswalkers", SimpleKeyword.class, true, "This creature can deal excess combat damage to the controller of the planeswalker its attacking."),
TRANSFIGURE("Transfigure", KeywordWithCost.class, false, "%s, Sacrifice this creature: Search your library for a creature card with the same mana value as this creature and put that card onto the battlefield, then shuffle. Transfigure only as a sorcery."), TRANSFIGURE("Transfigure", KeywordWithCost.class, false, "%s, Sacrifice this creature: Search your library for a creature card with the same mana value as this creature and put that card onto the battlefield, then shuffle. Transfigure only as a sorcery."),
TRANSMUTE("Transmute", KeywordWithCost.class, false, "%s, Discard this card: Search your library for a card with the same mana value as this card, reveal it, and put it into your hand, then shuffle. Transmute only as a sorcery."), TRANSMUTE("Transmute", KeywordWithCost.class, false, "%s, Discard this card: Search your library for a card with the same mana value as this card, reveal it, and put it into your hand, then shuffle. Transmute only as a sorcery."),
TRIBUTE("Tribute", KeywordWithAmount.class, false, "As this creature enters the battlefield, an opponent of your choice may put {%d:+1/+1 counter} on it."), TRIBUTE("Tribute", KeywordWithAmount.class, false, "As this creature enters the battlefield, an opponent of your choice may put {%d:+1/+1 counter} on it."),

View File

@@ -0,0 +1,35 @@
package forge.game.keyword;
import java.util.Collection;
public class Trample extends KeywordInstance<Trample> {
private String type = "";
@Override
protected void parse(String details) {
if (!details.isEmpty()) {
type = details.split(":")[0];
}
}
@Override
protected String formatReminderText(String reminderText) {
if (!type.isEmpty()) {
return "This creature can deal excess combat damage to the controller of the planeswalker its attacking.";
}
return reminderText;
}
/* (non-Javadoc)
* @see forge.game.keyword.KeywordInstance#redundant(java.util.Collection)
*/
@Override
public boolean redundant(Collection<KeywordInterface> list) {
for (KeywordInterface i : list) {
if (i.getOriginal().equals(getOriginal())) {
return true;
}
}
return false;
}
}

View File

@@ -20,8 +20,10 @@ package forge.game.mana;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.card.mana.ManaAtom; import forge.card.mana.ManaAtom;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardUtil;
import forge.game.spellability.AbilityManaPart; import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
/** /**
* <p> * <p>
@@ -71,7 +73,7 @@ public class Mana {
public Mana(final byte color, final Card source, final AbilityManaPart manaAbility) { public Mana(final byte color, final Card source, final AbilityManaPart manaAbility) {
this.color = color; this.color = color;
this.manaAbility = manaAbility; this.manaAbility = manaAbility;
this.sourceCard = source; this.sourceCard = source.isInZone(ZoneType.Battlefield) ? CardUtil.getLKICopy(source) : source.getGame().getChangeZoneLKIInfo(source);
} }
@Override @Override

View File

@@ -3221,21 +3221,21 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
public void updateKeywordCardAbilityText() { public void updateKeywordCardAbilityText() {
if(getKeywordCard() == null) if (getKeywordCard() == null)
return; return;
final PlayerZone com = getZone(ZoneType.Command); final PlayerZone com = getZone(ZoneType.Command);
keywordEffect.setText(""); keywordEffect.setText("");
keywordEffect.updateAbilityTextForView(); keywordEffect.updateAbilityTextForView();
boolean headerAdded = false; boolean headerAdded = false;
StringBuilder kw = new StringBuilder(); StringBuilder kw = new StringBuilder();
for(KeywordInterface k : keywords) { for (KeywordInterface k : keywords) {
if(!headerAdded) { if (!headerAdded) {
headerAdded = true; headerAdded = true;
kw.append(this.getName()).append(" has: \n"); kw.append(this.getName()).append(" has: \n");
} }
kw.append(k).append("\n"); kw.append(k).append("\n");
} }
if(!kw.toString().isEmpty()) { if (!kw.toString().isEmpty()) {
keywordEffect.setText(trimKeywords(kw.toString())); keywordEffect.setText(trimKeywords(kw.toString()));
keywordEffect.updateAbilityTextForView(); keywordEffect.updateAbilityTextForView();
} }
@@ -3275,7 +3275,7 @@ public class Player extends GameEntity implements Comparable<Player> {
final PlayerZone com = getZone(ZoneType.Command); final PlayerZone com = getZone(ZoneType.Command);
if(bless) { if (bless) {
blessingEffect = new Card(game.nextCardId(), null, game); blessingEffect = new Card(game.nextCardId(), null, game);
blessingEffect.setOwner(this); blessingEffect.setOwner(this);
blessingEffect.setImageKey("t:blessing"); blessingEffect.setImageKey("t:blessing");
@@ -3337,7 +3337,6 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
return targetPlayer == null || !targetPlayer.equals(sa.getActivatingPlayer()) return targetPlayer == null || !targetPlayer.equals(sa.getActivatingPlayer())
|| !hasKeyword("Spells and abilities you control can't cause you to search your library."); || !hasKeyword("Spells and abilities you control can't cause you to search your library.");
} }
public Card getKeywordCard() { public Card getKeywordCard() {

View File

@@ -65,7 +65,7 @@ public abstract class AbilityActivated extends SpellAbility implements Cloneable
this.setTargetRestrictions(tgt); this.setTargetRestrictions(tgt);
} }
public boolean isActivatedAbility() { return true; } public boolean isActivatedAbility() { return !isTrigger(); }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override

View File

@@ -30,6 +30,7 @@ import com.google.common.collect.Sets;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameStage;
import forge.game.IHasSVars; import forge.game.IHasSVars;
import forge.game.TriggerReplacementBase; import forge.game.TriggerReplacementBase;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
@@ -347,8 +348,9 @@ public abstract class Trigger extends TriggerReplacementBase {
} }
// host controller will be null when adding card in a simulation game // host controller will be null when adding card in a simulation game
if (this.getHostCard().getController() == null || !meetsCommonRequirements(this.mapParams)) if (this.getHostCard().getController() == null || game.getAge() != GameStage.Play || !meetsCommonRequirements(this.mapParams)) {
return false; return false;
}
return true; return true;
} }

View File

@@ -52,7 +52,6 @@ public class TriggerAbandoned extends Trigger {
* @param runParams*/ * @param runParams*/
@Override @Override
public final boolean performTest(final Map<AbilityKey, Object> runParams) { public final boolean performTest(final Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Scheme))) { if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Scheme))) {
return false; return false;
} }
@@ -60,7 +59,6 @@ public class TriggerAbandoned extends Trigger {
return true; return true;
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) { public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) {
@@ -72,4 +70,3 @@ public class TriggerAbandoned extends Trigger {
return ""; return "";
} }
} }

View File

@@ -63,7 +63,6 @@ public class TriggerAttached extends Trigger {
return true; return true;
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override

View File

@@ -54,7 +54,6 @@ public class TriggerAttackerUnblocked extends Trigger {
* @param runParams*/ * @param runParams*/
@Override @Override
public final boolean performTest(final Map<AbilityKey, Object> runParams) { public final boolean performTest(final Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Attacker))) { if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Attacker))) {
return false; return false;
} }

View File

@@ -57,7 +57,6 @@ public class TriggerAttacks extends Trigger {
* @param runParams*/ * @param runParams*/
@Override @Override
public final boolean performTest(final Map<AbilityKey, Object> runParams) { public final boolean performTest(final Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Attacker))) { if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Attacker))) {
return false; return false;
} }

View File

@@ -55,7 +55,6 @@ public class TriggerChampioned extends Trigger {
* @param runParams*/ * @param runParams*/
@Override @Override
public final boolean performTest(final Map<AbilityKey, Object> runParams) { public final boolean performTest(final Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Championed))) { if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Championed))) {
return false; return false;
} }

View File

@@ -16,7 +16,6 @@ public class TriggerDamageAll extends Trigger {
@Override @Override
public boolean performTest(Map<AbilityKey, Object> runParams) { public boolean performTest(Map<AbilityKey, Object> runParams) {
if (hasParam("CombatDamage")) { if (hasParam("CombatDamage")) {
if (getParam("CombatDamage").equals("True")) { if (getParam("CombatDamage").equals("True")) {
if (!((Boolean) runParams.get(AbilityKey.IsCombatDamage))) { if (!((Boolean) runParams.get(AbilityKey.IsCombatDamage))) {

View File

@@ -59,7 +59,6 @@ public class TriggerDamageDealtOnce extends Trigger {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public final boolean performTest(final Map<AbilityKey, Object> runParams) { public final boolean performTest(final Map<AbilityKey, Object> runParams) {
if (hasParam("CombatDamage")) { if (hasParam("CombatDamage")) {
if (getParam("CombatDamage").equals("True")) { if (getParam("CombatDamage").equals("True")) {
if (!((Boolean) runParams.get(AbilityKey.IsCombatDamage))) { if (!((Boolean) runParams.get(AbilityKey.IsCombatDamage))) {

View File

@@ -21,7 +21,6 @@ public class TriggerDamageDoneOnce extends Trigger {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public boolean performTest(Map<AbilityKey, Object> runParams) { public boolean performTest(Map<AbilityKey, Object> runParams) {
if (hasParam("CombatDamage")) { if (hasParam("CombatDamage")) {
if (getParam("CombatDamage").equals("True")) { if (getParam("CombatDamage").equals("True")) {
if (!((Boolean) runParams.get(AbilityKey.IsCombatDamage))) { if (!((Boolean) runParams.get(AbilityKey.IsCombatDamage))) {
@@ -54,7 +53,11 @@ public class TriggerDamageDoneOnce extends Trigger {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Map<Card, Integer> damageMap = (Map<Card, Integer>) runParams.get(AbilityKey.DamageMap); final Map<Card, Integer> damageMap = (Map<Card, Integer>) runParams.get(AbilityKey.DamageMap);
sa.setTriggeringObject(AbilityKey.Target, CardUtil.getLKICopy((Card)runParams.get(AbilityKey.DamageTarget))); Object target = runParams.get(AbilityKey.DamageTarget);
if (target instanceof Card) {
target = CardUtil.getLKICopy((Card)runParams.get(AbilityKey.DamageTarget));
}
sa.setTriggeringObject(AbilityKey.Target, target);
sa.setTriggeringObject(AbilityKey.Sources, getDamageSources(damageMap)); sa.setTriggeringObject(AbilityKey.Sources, getDamageSources(damageMap));
sa.setTriggeringObject(AbilityKey.DamageAmount, getDamageAmount(damageMap)); sa.setTriggeringObject(AbilityKey.DamageAmount, getDamageAmount(damageMap));
} }

View File

@@ -56,7 +56,6 @@ public class TriggerDamagePrevented extends Trigger {
* @param runParams*/ * @param runParams*/
@Override @Override
public final boolean performTest(final Map<AbilityKey, Object> runParams) { public final boolean performTest(final Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidSource", runParams.get(AbilityKey.DamageSource))) { if (!matchesValidParam("ValidSource", runParams.get(AbilityKey.DamageSource))) {
return false; return false;
} }

View File

@@ -55,7 +55,6 @@ public class TriggerDamagePreventedOnce extends Trigger {
* @param runParams*/ * @param runParams*/
@Override @Override
public final boolean performTest(final Map<AbilityKey, Object> runParams) { public final boolean performTest(final Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidTarget", runParams.get(AbilityKey.DamageTarget))) { if (!matchesValidParam("ValidTarget", runParams.get(AbilityKey.DamageTarget))) {
return false; return false;
} }

View File

@@ -82,4 +82,3 @@ public class TriggerExcessDamage extends Trigger {
return sb.toString(); return sb.toString();
} }
} }

View File

@@ -55,7 +55,6 @@ public class TriggerExploited extends Trigger {
* @param runParams*/ * @param runParams*/
@Override @Override
public final boolean performTest(final Map<AbilityKey, Object> runParams) { public final boolean performTest(final Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Exploited))) { if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Exploited))) {
return false; return false;
} }

View File

@@ -73,4 +73,3 @@ public class TriggerExplores extends Trigger {
return sb.toString(); return sb.toString();
} }
} }

View File

@@ -70,4 +70,3 @@ public class TriggerFightOnce extends Trigger {
return sb.toString(); return sb.toString();
} }
} }

View File

@@ -579,7 +579,7 @@ public class TriggerHandler {
sa.setOptionalTrigger(true); sa.setOptionalTrigger(true);
decider = AbilityUtils.getDefinedPlayers(host, regtrig.getParam("OptionalDecider"), sa).get(0); decider = AbilityUtils.getDefinedPlayers(host, regtrig.getParam("OptionalDecider"), sa).get(0);
} }
else if (sa instanceof AbilitySub || !sa.hasParam("Cost") || sa.getParam("Cost").equals("0")) { else if (sa instanceof AbilitySub || !sa.hasParam("Cost") || (sa.getPayCosts() != null && sa.getPayCosts().isMandatory()) || 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);

View File

@@ -67,7 +67,6 @@ public class TriggerPayCumulativeUpkeep extends Trigger {
return true; return true;
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) { public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) {

View File

@@ -66,7 +66,6 @@ public class TriggerPayEcho extends Trigger {
return true; return true;
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) { public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) {

View File

@@ -17,7 +17,6 @@ public class TriggerPhaseOut extends Trigger {
* @param runParams*/ * @param runParams*/
@Override @Override
public final boolean performTest(final Map<AbilityKey, Object> runParams) { public final boolean performTest(final Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Card))) { if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Card))) {
return false; return false;
} }

View File

@@ -116,7 +116,6 @@ public class TriggerSacrificed extends Trigger {
return true; return true;
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) { public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) {

View File

@@ -61,7 +61,6 @@ public class TriggerScry extends Trigger {
return true; return true;
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) { public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) {

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