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;
Player activatingPlayer = sa.getActivatingPlayer();
// for xPaid stuff
card.setCastSA(sa);
// Trigger play improvements
for (final Trigger tr : card.getTriggers()) {
// These triggers all care for ETB effects
@@ -743,7 +746,7 @@ public class AiController {
return AiPlayDecision.CantPlaySa;
}
boolean xCost = sa.getPayCosts().hasXInAnyCostPart();
boolean xCost = sa.getPayCosts().hasXInAnyCostPart() || sa.getHostCard().hasStartOfKeyword("Strive");
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
// 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
if (sa.getCardState().getStateName() == CardStateName.Modal) {
if (sa.getCardState() != null && !sa.getHostCard().isInPlay() && sa.getCardState().getStateName() == CardStateName.Modal) {
sa.getHostCard().setState(CardStateName.Modal, false);
}
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);
}
@@ -1754,10 +1757,10 @@ public class AiController {
}
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)
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) {
// For non-converted triggers (such as Cumulative Upkeep) that don't have costs or targets to worry about
return true;

View File

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

View File

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

View File

@@ -434,7 +434,7 @@ public abstract class GameState {
boolean first = true;
StringBuilder counterString = new StringBuilder();
for(Entry<CounterType, Integer> kv : counters.entrySet()) {
for (Entry<CounterType, Integer> kv : counters.entrySet()) {
if (!first) {
counterString.append(",");
}
@@ -470,7 +470,7 @@ public abstract class GameState {
}
public void parse(List<String> lines) {
for(String line : lines) {
for (String line : lines) {
parseLine(line);
}
}
@@ -1110,13 +1110,13 @@ public abstract class GameState {
private void handleCardAttachments() {
// Unattach all permanents first
for(Entry<Card, Integer> entry : cardToAttachId.entrySet()) {
for (Entry<Card, Integer> entry : cardToAttachId.entrySet()) {
Card attachedTo = idToCard.get(entry.getValue());
attachedTo.unAttachAllCards();
}
// 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 attacher = entry.getKey();
if (attacher.isAttachment()) {
@@ -1125,7 +1125,7 @@ public abstract class GameState {
}
// 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
Card attacher = entry.getKey();
Game game = attacher.getGame();
@@ -1136,9 +1136,9 @@ public abstract class GameState {
}
private void handleMergedCards() {
for(Entry<Card, List<String>> entry : cardToMergedCards.entrySet()) {
for (Entry<Card, List<String>> entry : cardToMergedCards.entrySet()) {
Card mergedTo = entry.getKey();
for(String mergedCardName : entry.getValue()) {
for (String mergedCardName : entry.getValue()) {
Card c;
PaperCard pc = StaticData.instance().getCommonCards().getCard(mergedCardName. replace("^", ","));
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")) {
Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0);
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")) {
// Set PayX here to maximum value. (Endless Scream and Venarian
// Gold)
// Set PayX here to maximum value. (Endless Scream and Venarian Gold)
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
if (xPay == 0) {

View File

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

View File

@@ -45,7 +45,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
}
if ("MomirAvatar".equals(aiLogic)) {
return SpecialCardAi.MomirVigAvatar.consider(aiPlayer, sa);
return SpecialCardAi.MomirVigAvatar.consider(aiPlayer, sa);
} else if ("MimicVat".equals(aiLogic)) {
return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
} 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;
}
@@ -118,7 +118,6 @@ public class CopyPermanentAi extends SpellAbilityAi {
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final boolean canCopyLegendary = sa.hasParam("NonLegendary");
// ////
// Targeting
if (sa.usesTargeting()) {

View File

@@ -163,8 +163,6 @@ public class DiscardAi extends SpellAbilityAi {
return false;
} // discardTargetAI()
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
@@ -211,9 +209,8 @@ public class DiscardAi extends SpellAbilityAi {
return true;
} // discardCheckDrawbackAI()
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
return true;
}

View File

@@ -4,8 +4,10 @@ import java.util.Map;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CounterEnumType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -23,15 +25,17 @@ public class LegendaryRuleAi extends SpellAbilityAi {
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false; // should not get here
}
@Override
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
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();
if ( choosingFromPlanewalkers ) {
if (choosingFromPlanewalkers) {
// AI decision making - should AI compare counters?
} else {
// AI decision making - should AI compare damage and debuffs?

View File

@@ -250,9 +250,6 @@ public class TokenAi extends SpellAbilityAi {
@Override
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();
if (tgt != null) {
sa.resetTargets();
@@ -262,16 +259,21 @@ public class TokenAi extends SpellAbilityAi {
sa.getTargets().add(ai);
}
}
Card actualToken = spawnToken(ai, sa);
String tokenPower = sa.getParamOrDefault("TokenPower", actualToken.getBasePowerString());
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)) {
int x = AbilityUtils.calculateAmount(source, tokenAmount, sa);
if (sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
x = ComputerUtilCost.getMaxXValue(sa, ai);
sa.setXManaCostPaid(x);
if (x == 0) { // already paid outside trigger
// Set PayX here to maximum value.
x = ComputerUtilCost.getMaxXValue(sa, ai);
sa.setXManaCostPaid(x);
}
}
if (x <= 0) {
return false;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ public class ImageUtil {
}
public static PaperCard getPaperCardFromImageKey(String key) {
if ( key == null ) {
if (key == null) {
return null;
}
@@ -42,6 +42,10 @@ public class ImageUtil {
String edition = key.substring(index + 1);
if (script.startsWith("emblem"))
return null;
if (null == StaticData.instance().getCardEdition(edition)) {
script = key;
edition = "???";
}
script = script.replaceAll("[0-9]*$", "");
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)) {
return false;
}
}
if (params.containsKey("IsPresent2")) {

View File

@@ -1633,7 +1633,7 @@ public class GameAction {
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);
for (Card c: cc) {
if (c != toKeep) {
@@ -1905,7 +1905,6 @@ public class GameAction {
game.getTriggerHandler().runTrigger(TriggerType.NewGame, AbilityKey.newMap(), true);
//</THIS CODE WILL WORK WITH PHASE = NULL>
game.getPhaseHandler().startFirstTurn(first, startGameHook);
//after game ends, ensure Auto-Pass canceled for all players so it doesn't apply to next game
for (Player p : game.getRegisteredPlayers()) {
@@ -1980,7 +1979,6 @@ public class GameAction {
private void runPreOpeningHandActions(final Player first) {
Player takesAction = first;
do {
//
List<Card> ploys = CardLists.filter(takesAction.getCardsIn(ZoneType.Command), new Predicate<Card>() {
@Override
public boolean apply(Card input) {
@@ -2054,8 +2052,7 @@ public class GameAction {
public void invoke(final Runnable proc) {
if (ThreadUtil.isGameThread()) {
proc.run();
}
else {
} else {
ThreadUtil.invokeInGameThread(proc);
}
}

View File

@@ -274,8 +274,7 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
blockers = att.getValue();
if (blockers.isEmpty()) {
sb.append(Localizer.getInstance().getMessage("lblLogPlayerDidntBlockAttacker", controllerName, att.getKey()));
}
else {
} else {
sb.append(Localizer.getInstance().getMessage("lblLogPlayerAssignedBlockerToBlockAttacker", controllerName, Lang.joinHomogenous(blockers), att.getKey()));
}
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);
}
public static Cost parseAbilityCost(final CardState state, Map<String, String> mapParams, AbilityRecordType type) {
Cost abCost = null;
if (type != AbilityRecordType.SubAbility) {

View File

@@ -1282,7 +1282,8 @@ public class AbilityUtils {
}
else if (defined.equals("Opponent")) {
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;
players.add(game.getNextPlayerAfter(player, dir));
}
@@ -1806,6 +1807,19 @@ public class AbilityUtils {
}
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>
if (sq[0].startsWith("TriggeredPayingMana")) {
final SpellAbility root = sa.getRootAbility();
@@ -1927,7 +1941,7 @@ public class AbilityUtils {
// Count$DevotionDual.<color name>.<color name>
// Count$Devotion.<color name>
if (sq[0].contains("Devotion")) {
int colorOcurrencices = 0;
int colorOccurrences = 0;
String colorName = sq[1];
if (colorName.contains("Chosen")) {
colorName = MagicColor.toShortString(c.getChosenColor());
@@ -1939,12 +1953,12 @@ public class AbilityUtils {
for (Card c0 : player.getCardsIn(ZoneType.Battlefield)) {
for (ManaCostShard sh : c0.getManaCost()) {
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

View File

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

View File

@@ -327,11 +327,10 @@ public class EffectEffect extends SpellAbilityEffect {
hostCard.addImprintedCard(eff);
}
eff.updateStateForView();
// TODO: Add targeting to the effect so it knows who it's dealing with
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa);
eff.updateStateForView();
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
//if (effectTriggers != null) {
// 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);
// create Effect for Regeneration
createRengenerationEffect(sa, list);
createRegenerationEffect(sa, list);
} // regenerateAllResolve
}

View File

@@ -17,7 +17,7 @@ import forge.game.zone.ZoneType;
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 Game game = hostCard.getGame();

View File

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

View File

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

View File

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

View File

@@ -49,6 +49,7 @@ public class RestartGameEffect extends SpellAbilityEffect {
game.clearCounterAddedThisTurn();
game.resetPlayersAttackedOnNextTurn();
game.resetPlayersAttackedOnNextTurn();
game.setMonarch(null);
GameAction action = game.getAction();
for (Player p: players) {
@@ -59,6 +60,7 @@ public class RestartGameEffect extends SpellAbilityEffect {
p.setLandsPlayedLastTurn(0);
p.resetCommanderStats();
p.resetCompletedDungeons();
p.setBlessing(false);
CardCollection newLibrary = new CardCollection(p.getCardsIn(restartZones, false));
List<Card> filteredCards = null;
@@ -111,4 +113,3 @@ public class RestartGameEffect extends SpellAbilityEffect {
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.ApiType;
import forge.game.combat.Combat;
import forge.game.combat.CombatLki;
import forge.game.cost.Cost;
import forge.game.cost.CostSacrifice;
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>> chosenModesGameStatic = HashBasedTable.create();
private CombatLki combatLKI = null;
// Enumeration for CMC request types
public enum SplitCMCMode {
CurrentSideCMC,
@@ -1354,7 +1357,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
@Override
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;
if(addAmount <= 0 || !canReceiveCounters(counterType)) {
if (addAmount <= 0 || !canReceiveCounters(counterType)) {
// As per rule 107.1b
return 0;
}
@@ -1974,6 +1977,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
sbx.append(" (").append(inst.getReminderText()).append(")");
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:")) {
final String[] k = keyword.split(":");
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("Foretell") // for the ones without cost
|| keyword.equals("Hideaway") || keyword.equals("Ascend")
|| keyword.equals("Trample over planeswalkers")
|| keyword.equals("Totem armor") || keyword.equals("Battle cry")
|| keyword.equals("Devoid") || keyword.equals("Riot")){
sbLong.append(keyword).append(" (").append(inst.getReminderText()).append(")");
@@ -4670,6 +4674,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
setPhasedOut(!phasedOut);
final Combat combat = getGame().getPhaseHandler().getCombat();
if (combat != null && phasedOut) {
combat.saveLKI(this);
combat.removeFromCombat(this);
}
@@ -6167,7 +6172,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return false;
}
if (source == null){
if (source == null) {
return true;
}
@@ -6924,4 +6929,18 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final void clearUntilLeavesBattlefield() {
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> protectionColorkw = Sets.newHashSet();
final Set<String> hexproofkw = Sets.newHashSet();
final Set<String> tramplekw = Sets.newHashSet();
final Set<String> allkw = Sets.newHashSet();
for (Card c : CardLists.getValidCards(cardlist, restrictions, p, host, null)) {
@@ -535,6 +536,8 @@ public class CardFactoryUtil {
}
} else if (k.startsWith("Hexproof")) {
hexproofkw.add(k);
} else if (k.startsWith("Trample")) {
tramplekw.add(k);
}
allkw.add(k);
}
@@ -548,6 +551,8 @@ public class CardFactoryUtil {
filteredkw.addAll(landkw);
} else if (keyword.equals("Hexproof")) {
filteredkw.addAll(hexproofkw);
} else if (keyword.equals("Trample")) {
filteredkw.addAll(tramplekw);
} else if (allkw.contains(keyword)) {
filteredkw.add(keyword);
}
@@ -781,7 +786,7 @@ public class CardFactoryUtil {
+ "TriggerZones$ Battlefield | Secondary$ True | TriggerDescription$ "
+ "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);
trigger.setOverridingAbility(AbilityFactory.getAbility(effect, card));
@@ -1516,7 +1521,7 @@ public class CardFactoryUtil {
} else if (keyword.equals("Provoke")) {
final String actualTrigger = "Mode$ Attacks | ValidCard$ Card.Self | OptionalDecider$ You | Secondary$ True"
+ " | 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";
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)
else if (property.startsWith("attacking")) {
if (null == combat) return false;
if (property.equals("attacking")) return combat.isAttacking(card);
if (property.equals("attackingLKI")) return combat.isLKIAttacking(card);
if (property.equals("attacking")) return card.isAttacking();
if (property.equals("attackingYou")) return combat.isAttacking(card, sourceController);
if (property.equals("attackingSame")) {
final GameEntity attacked = combat.getDefenderByAttacker(source);
@@ -1514,12 +1513,10 @@ public class CardProperty {
return false;
}
String valid = property.split(" ")[1];
for(Card c : blocked) {
if (c.isValid(valid, card.getController(), source, spellAbility)) {
return true;
}
if (Iterables.any(blocked, CardPredicates.restriction(valid, card.getController(), source, spellAbility))) {
return true;
}
for(Card c : AbilityUtils.getDefinedCards(source, valid, spellAbility)) {
for (Card c : AbilityUtils.getDefinedCards(source, valid, spellAbility)) {
if (blocked.contains(c)) {
return true;
}

View File

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

View File

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

View File

@@ -45,6 +45,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardDamageMap;
import forge.game.card.CardUtil;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.replacement.ReplacementType;
@@ -75,7 +76,7 @@ public class Combat {
private Map<Card, CardCollection> attackersOrderedForDamageAssignment = 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();
// 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;
}
}
CombatLki lki = lkiCache.get(c);
CombatLki lki = lkiCache.get(c).getCombatLKI();
return lki == null || !lki.isAttacker ? null : lki.getFirstBand();
}
@@ -316,14 +317,6 @@ public class Combat {
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) {
AttackingBand ab = getBandOfAttacker(card);
for (Entry<GameEntity, AttackingBand> ee : attackedByBands.entries()) {
@@ -786,7 +779,7 @@ public class Combat {
assignedDamage = true;
GameEntity defender = getDefenderByAttacker(band);
// 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()) {
CardCollection cc = new CardCollection();
cc.add((Card)defender);
@@ -918,7 +911,7 @@ public class Combat {
return false;
}
CombatLki lki = lkiCache.get(blocker);
CombatLki lki = lkiCache.get(blocker).getCombatLKI();
return null != lki && !lki.isAttacker; // was blocking something anyway
}
@@ -933,21 +926,36 @@ public class Combat {
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
}
public void saveLKI(Card lastKnownInfo) {
public CombatLki saveLKI(Card lki) {
if (!lki.isLKI()) {
lki = CardUtil.getLKICopy(lki);
}
FCollectionView<AttackingBand> attackersBlocked = null;
final AttackingBand attackingBand = getBandOfAttacker(lastKnownInfo);
final AttackingBand attackingBand = getBandOfAttacker(lki);
final boolean isAttacker = attackingBand != null;
if (!isAttacker) {
attackersBlocked = getAttackingBandsBlockedBy(lastKnownInfo);
if (isAttacker) {
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()) {
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;
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;
} else if ("Mandatory".equals(part)) {
this.isMandatory = true;
}
else {
} else {
CostPart cp = parseCostPart(part, tapCost, untapCost);
if (null != cp )
if (cp instanceof CostPartMana ) {
@@ -268,7 +267,6 @@ public class Cost implements Serializable {
}
private static CostPart parseCostPart(String parse, boolean tapCost, boolean untapCost) {
if (parse.startsWith("Mana<")) {
final String[] splitStr = TextUtil.split(abCostParse(parse, 1)[0], '\\');
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) {
cards = list;
}
@Override
public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this);

View File

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

View File

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

View File

@@ -8,13 +8,13 @@ public class GameEventCardAttachment extends GameEvent {
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 oldEntiy;
public GameEventCardAttachment(Card attachment, GameEntity formerEntity, GameEntity newEntity) {
this.equipment = attachment;
this.newTarget = newEntity;
this.oldEntiy = formerEntity;
}
@Override
public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,6 @@ public class GameEventLandPlayed extends GameEvent {
}
@Override
public <T> T visit(IGameEventVisitor<T> visitor) {
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
public class GameEventManaBurn extends GameEvent {
public final Player player;
public final boolean causedLifeLoss;
public final int amount;
/**
* TODO: Write javadoc for Constructor.
* @param dealDamage
@@ -19,7 +19,7 @@ public class GameEventManaBurn extends GameEvent {
amount = burn;
causedLifeLoss = dealDamage;
}
@Override
public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this);

View File

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

View File

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

View File

@@ -8,18 +8,18 @@ public class GameEventPlayerLivesChanged extends GameEvent {
public final Player player;
public final int oldLives;
public final int newLives;
public GameEventPlayerLivesChanged(Player who, int oldValue, int newValue) {
player = who;
oldLives = oldValue;
newLives = newValue;
}
@Override
public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
public String toString() {
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;
amount = num;
}
@Override
public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -105,4 +105,3 @@ public interface IGameEventVisitor<T> {
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."),
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."),
TRAMPLE("Trample", SimpleKeyword.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."),
TRAMPLE("Trample", Trample.class, true, "This creature can deal excess combat damage to the player or planeswalker it's 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."),
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."),

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

View File

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

View File

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

View File

@@ -30,6 +30,7 @@ import com.google.common.collect.Sets;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameStage;
import forge.game.IHasSVars;
import forge.game.TriggerReplacementBase;
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
if (this.getHostCard().getController() == null || !meetsCommonRequirements(this.mapParams))
if (this.getHostCard().getController() == null || game.getAge() != GameStage.Play || !meetsCommonRequirements(this.mapParams)) {
return false;
}
return true;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,6 @@ public class TriggerDamageDoneOnce extends Trigger {
@SuppressWarnings("unchecked")
@Override
public boolean performTest(Map<AbilityKey, Object> runParams) {
if (hasParam("CombatDamage")) {
if (getParam("CombatDamage").equals("True")) {
if (!((Boolean) runParams.get(AbilityKey.IsCombatDamage))) {
@@ -54,7 +53,11 @@ public class TriggerDamageDoneOnce extends Trigger {
@SuppressWarnings("unchecked")
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.DamageAmount, getDamageAmount(damageMap));
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -579,7 +579,7 @@ public class TriggerHandler {
sa.setOptionalTrigger(true);
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;
} else { // triggers with a cost can't be mandatory
sa.setOptionalTrigger(true);

View File

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

View File

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

View File

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

View File

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

View File

@@ -61,7 +61,6 @@ public class TriggerScry extends Trigger {
return true;
}
/** {@inheritDoc} */
@Override
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