diff --git a/forge-ai/pom.xml b/forge-ai/pom.xml index 3f176653a65..b8d21660c54 100644 --- a/forge-ai/pom.xml +++ b/forge-ai/pom.xml @@ -6,7 +6,7 @@ forge forge - 1.6.33-SNAPSHOT + 1.6.34-SNAPSHOT forge-ai diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index ff73d875785..bec6e19c4d6 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -91,9 +91,6 @@ public class ComputerUtil { } } - source.setCastSA(sa); - sa.setLastStateBattlefield(game.getLastStateBattlefield()); - sa.setLastStateGraveyard(game.getLastStateGraveyard()); sa.setHostCard(game.getAction().moveToStack(source, sa)); } @@ -219,9 +216,6 @@ public class ComputerUtil { final Card source = sa.getHostCard(); if (sa.isSpell() && !source.isCopiedSpell()) { - source.setCastSA(sa); - sa.setLastStateBattlefield(game.getLastStateBattlefield()); - sa.setLastStateGraveyard(game.getLastStateGraveyard()); sa.setHostCard(game.getAction().moveToStack(source, sa)); } @@ -246,9 +240,6 @@ public class ComputerUtil { final Card source = sa.getHostCard(); if (sa.isSpell() && !source.isCopiedSpell()) { - source.setCastSA(sa); - sa.setLastStateBattlefield(game.getLastStateBattlefield()); - sa.setLastStateGraveyard(game.getLastStateGraveyard()); sa.setHostCard(game.getAction().moveToStack(source, sa)); } @@ -267,9 +258,6 @@ public class ComputerUtil { final Card source = newSA.getHostCard(); if (newSA.isSpell() && !source.isCopiedSpell()) { - source.setCastSA(newSA); - sa.setLastStateBattlefield(game.getLastStateBattlefield()); - sa.setLastStateGraveyard(game.getLastStateGraveyard()); newSA.setHostCard(game.getAction().moveToStack(source, sa)); if (newSA.getApi() == ApiType.Charm && !newSA.isWrapper()) { @@ -290,9 +278,6 @@ public class ComputerUtil { if (ComputerUtilCost.canPayCost(sa, ai)) { final Card source = sa.getHostCard(); if (sa.isSpell() && !source.isCopiedSpell()) { - source.setCastSA(sa); - sa.setLastStateBattlefield(game.getLastStateBattlefield()); - sa.setLastStateGraveyard(game.getLastStateGraveyard()); sa.setHostCard(game.getAction().moveToStack(source, sa)); } @@ -2338,7 +2323,7 @@ public class ComputerUtil { return chosen; } - public static Object vote(Player ai, List options, SpellAbility sa, Multimap votes) { + public static Object vote(Player ai, List options, SpellAbility sa, Multimap votes, Player forPlayer) { final Card source = sa.getHostCard(); final Player controller = source.getController(); final Game game = controller.getGame(); diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 01385c3ca4e..b27476cae09 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -1447,7 +1447,8 @@ public class ComputerUtilCard { } if (pumpedDmg > dmg) { if ((!c.hasKeyword(Keyword.INFECT) && pumpedDmg >= opp.getLife()) - || (c.hasKeyword(Keyword.INFECT) && opp.canReceiveCounters(CounterType.POISON) && pumpedDmg >= opp.getPoisonCounters())) { + || (c.hasKeyword(Keyword.INFECT) && opp.canReceiveCounters(CounterType.POISON) && pumpedDmg >= opp.getPoisonCounters()) + || ("PumpForTrample".equals(sa.getParam("AILogic")))) { return true; } } diff --git a/forge-ai/src/main/java/forge/ai/GameState.java b/forge-ai/src/main/java/forge/ai/GameState.java index 9d71da36c6a..e07b65d95c4 100644 --- a/forge-ai/src/main/java/forge/ai/GameState.java +++ b/forge-ai/src/main/java/forge/ai/GameState.java @@ -981,14 +981,27 @@ public abstract class GameState { spellDef = spellDef.substring(0, spellDef.indexOf("->")).trim(); } - PaperCard pc = StaticData.instance().getCommonCards().getCard(spellDef); + Card c = null; - if (pc == null) { - System.err.println("ERROR: Could not find a card with name " + spellDef + " to precast!"); - return; + if (StringUtils.isNumeric(spellDef)) { + // Precast from a specific host + c = idToCard.get(Integer.parseInt(spellDef)); + if (c == null) { + System.err.println("ERROR: Could not find a card with ID " + spellDef + " to precast!"); + return; + } + } else { + // Precast from a card by name + PaperCard pc = StaticData.instance().getCommonCards().getCard(spellDef); + + if (pc == null) { + System.err.println("ERROR: Could not find a card with name " + spellDef + " to precast!"); + return; + } + + c = Card.fromPaperCard(pc, activator); } - Card c = Card.fromPaperCard(pc, activator); SpellAbility sa = null; if (!scriptID.isEmpty()) { diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 04a9f303832..56d592d5b8e 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -513,8 +513,8 @@ public class PlayerControllerAi extends PlayerController { } @Override - public Object vote(SpellAbility sa, String prompt, List options, ListMultimap votes) { - return ComputerUtil.vote(player, options, sa, votes); + public Object vote(SpellAbility sa, String prompt, List options, ListMultimap votes, Player forPlayer) { + return ComputerUtil.vote(player, options, sa, votes, forPlayer); } @Override diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index 27700e2455b..f5f4435c491 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -1295,6 +1295,26 @@ public class SpecialCardAi { } } + // Timmerian Fiends + public static class TimmerianFiends { + public static boolean consider(final Player ai, final SpellAbility sa) { + final Card targeted = sa.getParentTargetingCard().getTargetCard(); + if (targeted == null) { + return false; + } + + if (targeted.isCreature()) { + if (ComputerUtil.aiLifeInDanger(ai, true, 0)) { + return true; // do it, hoping to save a valuable potential blocker etc. + } + return ComputerUtilCard.evaluateCreature(targeted) >= 200; // might need tweaking + } else { + // TODO: this currently compares purely by CMC. To be somehow improved, especially for stuff like the Power Nine etc. + return ComputerUtilCard.evaluatePermanentList(new CardCollection(targeted)) >= 3; + } + } + } + // Volrath's Shapeshifter public static class VolrathsShapeshifter { public static boolean consider(final Player ai, final SpellAbility sa) { diff --git a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java index 630548b00bd..7a29ee98068 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java @@ -34,12 +34,12 @@ import forge.game.card.CardCollectionView; import forge.game.card.CardLists; import forge.game.phase.PhaseType; import forge.game.player.Player; +import forge.game.player.PlayerCollection; import forge.game.player.PlayerPredicates; import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions; import forge.game.zone.ZoneType; import forge.util.Aggregates; -import forge.util.collect.FCollectionView; //AB:GainControl|ValidTgts$Creature|TgtPrompt$Select target legendary creature|LoseControl$Untap,LoseControl|SpellDescription$Gain control of target xxxxxxx @@ -54,8 +54,6 @@ import forge.util.collect.FCollectionView; // (as a "&"-separated list; like Haste, Sacrifice CARDNAME at EOT, any standard keyword) // OppChoice - set to True if opponent chooses creature (for Preacher) - not implemented yet // Untap - set to True if target card should untap when control is taken -// DestroyTgt - actions upon which the tgt should be destroyed. same list as LoseControl -// NoRegen - set if destroyed creature can't be regenerated. used only with DestroyTgt /** *

@@ -77,7 +75,7 @@ public class ControlGainAi extends SpellAbilityAi { final TargetRestrictions tgt = sa.getTargetRestrictions(); final Game game = ai.getGame(); - final FCollectionView opponents = ai.getOpponents(); + final PlayerCollection opponents = ai.getOpponents(); // if Defined, then don't worry about targeting if (tgt == null) { @@ -94,18 +92,19 @@ public class ControlGainAi extends SpellAbilityAi { sa.setTargetingPlayer(targetingPlayer); return targetingPlayer.getController().chooseTargetsFor(sa); } - if (tgt.isRandomTarget()) { - sa.getTargets().add(Aggregates.random(tgt.getAllCandidates(sa, false))); - } + if (tgt.canOnlyTgtOpponent()) { - List oppList = Lists - .newArrayList(Iterables.filter(opponents, PlayerPredicates.isTargetableBy(sa))); + List oppList = opponents.filter(PlayerPredicates.isTargetableBy(sa)); if (oppList.isEmpty()) { return false; } - sa.getTargets().add(oppList.get(0)); + if (tgt.isRandomTarget()) { + sa.getTargets().add(Aggregates.random(oppList)); + } else { + sa.getTargets().add(oppList.get(0)); + } } } diff --git a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java index 8697b42b146..609aec7c92d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java @@ -11,6 +11,7 @@ import forge.card.mana.ManaCost; import forge.game.Game; import forge.game.ability.ApiType; import forge.game.card.*; +import forge.game.combat.Combat; import forge.game.combat.CombatUtil; import forge.game.cost.Cost; import forge.game.keyword.Keyword; @@ -307,6 +308,16 @@ public class EffectAi extends SpellAbilityAi { if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) { return false; } + } else if (logic.equals("Bribe")) { + Card host = sa.getHostCard(); + Combat combat = game.getCombat(); + if (combat != null && combat.isAttacking(host, ai) && !combat.isBlocked(host) + && game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS) + && !AiCardMemory.isRememberedCard(ai, host, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN)) { + AiCardMemory.rememberCard(ai, host, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN); // ideally needs once per combat or something + return true; + } + return false; } } else { //no AILogic return false; diff --git a/forge-ai/src/main/java/forge/ai/ability/MillAi.java b/forge-ai/src/main/java/forge/ai/ability/MillAi.java index fba432c9d48..52f0e8cda25 100644 --- a/forge-ai/src/main/java/forge/ai/ability/MillAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/MillAi.java @@ -1,15 +1,10 @@ package forge.ai.ability; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; - import com.google.common.collect.Lists; import com.google.common.collect.Maps; - import forge.ai.ComputerUtil; import forge.ai.ComputerUtilMana; +import forge.ai.SpecialCardAi; import forge.ai.SpellAbilityAi; import forge.game.ability.AbilityUtils; import forge.game.card.Card; @@ -24,6 +19,11 @@ import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions; import forge.game.zone.ZoneType; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + public class MillAi extends SpellAbilityAi { @Override @@ -196,6 +196,10 @@ public class MillAi extends SpellAbilityAi { */ @Override public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { + if ("TimmerianFiends".equals(sa.getParam("AILogic"))) { + return SpecialCardAi.TimmerianFiends.consider(player, sa); + } + return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java b/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java index 238ba76c409..925f8ce02a1 100644 --- a/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java @@ -1,7 +1,7 @@ package forge.ai.ability; import com.google.common.base.Predicate; - +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import forge.ai.*; import forge.game.Game; @@ -12,11 +12,9 @@ import forge.game.card.CardPredicates; import forge.game.combat.Combat; import forge.game.combat.CombatUtil; import forge.game.keyword.Keyword; -import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; import forge.game.zone.ZoneType; import java.util.List; @@ -28,7 +26,6 @@ public class MustBlockAi extends SpellAbilityAi { final Card source = sa.getHostCard(); final Game game = aiPlayer.getGame(); final Combat combat = game.getCombat(); - final PhaseHandler ph = game.getPhaseHandler(); final boolean onlyLethal = !"AllowNonLethal".equals(sa.getParam("AILogic")); if (combat == null || !combat.isAttacking(source)) { @@ -39,7 +36,6 @@ public class MustBlockAi extends SpellAbilityAi { return false; } - final TargetRestrictions abTgt = sa.getTargetRestrictions(); final List list = determineGoodBlockers(source, aiPlayer, combat.getDefenderPlayerByAttacker(source), sa, onlyLethal,false); if (!list.isEmpty()) { @@ -69,7 +65,6 @@ public class MustBlockAi extends SpellAbilityAi { @Override protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) { final Card source = sa.getHostCard(); - final TargetRestrictions abTgt = sa.getTargetRestrictions(); // only use on creatures that can attack if (!ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) { @@ -94,7 +89,7 @@ public class MustBlockAi extends SpellAbilityAi { boolean chance = false; - if (abTgt != null) { + if (sa.usesTargeting()) { final List list = determineGoodBlockers(definedAttacker, ai, ai.getWeakestOpponent(), sa, true,true); if (list.isEmpty()) { return false; @@ -119,6 +114,9 @@ public class MustBlockAi extends SpellAbilityAi { sa.getTargets().add(blocker); chance = true; + } else if (sa.hasParam("Choices")) { + // currently choice is attacked player + return true; } else { return false; } @@ -126,16 +124,9 @@ public class MustBlockAi extends SpellAbilityAi { return chance; } - private List determineGoodBlockers(final Card attacker, final Player ai, Player defender, SpellAbility sa, + private List determineBlockerFromList(final Card attacker, final Player ai, Iterable options, SpellAbility sa, final boolean onlyLethal, final boolean testTapped) { - final Card source = sa.getHostCard(); - final TargetRestrictions abTgt = sa.getTargetRestrictions(); - - List list = Lists.newArrayList(); - list = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); - list = CardLists.getTargetableCards(list, sa); - list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source, sa); - list = CardLists.filter(list, new Predicate() { + List list = CardLists.filter(options, new Predicate() { @Override public boolean apply(final Card c) { boolean tapped = c.isTapped(); @@ -161,4 +152,40 @@ public class MustBlockAi extends SpellAbilityAi { return list; } + + private List determineGoodBlockers(final Card attacker, final Player ai, Player defender, SpellAbility sa, + final boolean onlyLethal, final boolean testTapped) { + + List list = Lists.newArrayList(); + list = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); + + if (sa.usesTargeting()) { + list = CardLists.getTargetableCards(list, sa); + } + return determineBlockerFromList(attacker, ai, list, sa, onlyLethal, testTapped); + } + + @Override + protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable options, boolean isOptional, + Player targetedPlayer) { + final Card host = sa.getHostCard(); + + Card attacker = host; + + if (sa.hasParam("DefinedAttacker")) { + List attackers = AbilityUtils.getDefinedCards(host, sa.getParam("DefinedAttacker"), sa); + attacker = Iterables.getFirst(attackers, null); + } + if (attacker == null) { + return Iterables.getFirst(options, null); + } + + List better = determineBlockerFromList(attacker, ai, options, sa, false, false); + + if (!better.isEmpty()) { + return Iterables.getFirst(options, null); + } + + return Iterables.getFirst(options, null); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/VoteAi.java b/forge-ai/src/main/java/forge/ai/ability/VoteAi.java index 58aab03ecbb..33d23dd3b22 100644 --- a/forge-ai/src/main/java/forge/ai/ability/VoteAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/VoteAi.java @@ -46,6 +46,12 @@ public class VoteAi extends SpellAbilityAi { @Override public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map params) { + if (params.containsKey("Voter")) { + Player p = (Player)params.get("Voter"); + if (p.isOpponentOf(player)) { + return min; + } + } if (sa.getActivatingPlayer().isOpponentOf(player)) { return min; } diff --git a/forge-core/pom.xml b/forge-core/pom.xml index 9c13b0d69aa..763d968dc05 100644 --- a/forge-core/pom.xml +++ b/forge-core/pom.xml @@ -6,7 +6,7 @@ forge forge - 1.6.33-SNAPSHOT + 1.6.34-SNAPSHOT forge-core diff --git a/forge-core/src/main/java/forge/ImageKeys.java b/forge-core/src/main/java/forge/ImageKeys.java index e07f836566d..f2b976f3008 100644 --- a/forge-core/src/main/java/forge/ImageKeys.java +++ b/forge-core/src/main/java/forge/ImageKeys.java @@ -113,7 +113,11 @@ public final class ImageKeys { } //try fullborder... if (filename.contains(".full")) { - file = findFile(dir, TextUtil.fastReplace(filename, ".full", ".fullborder")); + String fullborderFile = TextUtil.fastReplace(filename, ".full", ".fullborder"); + file = findFile(dir, fullborderFile); + if (file != null) { return file; } + // if there's an art variant try without it + file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder")); if (file != null) { return file; } } //if an image, like phenomenon or planes is missing .full in their filenames but you have an existing images that have .full/.fullborder diff --git a/forge-core/src/main/java/forge/card/CardDb.java b/forge-core/src/main/java/forge/card/CardDb.java index 2c4e54ad21a..7fcfbfd2a49 100644 --- a/forge-core/src/main/java/forge/card/CardDb.java +++ b/forge-core/src/main/java/forge/card/CardDb.java @@ -312,17 +312,21 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { return tryGetCard(request); } - public int getCardCollectorNumber(String cardName, String reqEdition) { + public String getCardCollectorNumber(String cardName, String reqEdition, int artIndex) { cardName = getName(cardName); CardEdition edition = editions.get(reqEdition); if (edition == null) - return -1; + return null; + int numMatches = 0; for (CardInSet card : edition.getCards()) { if (card.name.equalsIgnoreCase(cardName)) { - return card.collectorNumber; + numMatches += 1; + if (numMatches == artIndex) { + return card.collectorNumber; + } } } - return -1; + return null; } private PaperCard tryGetCard(CardRequest request) { diff --git a/forge-core/src/main/java/forge/card/CardEdition.java b/forge-core/src/main/java/forge/card/CardEdition.java index e2b786c0322..ce6289bb5f5 100644 --- a/forge-core/src/main/java/forge/card/CardEdition.java +++ b/forge-core/src/main/java/forge/card/CardEdition.java @@ -38,6 +38,8 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** @@ -75,10 +77,10 @@ public final class CardEdition implements Comparable { // immutable public static class CardInSet { public final CardRarity rarity; - public final int collectorNumber; + public final String collectorNumber; public final String name; - public CardInSet(final String name, final int collectorNumber, final CardRarity rarity) { + public CardInSet(final String name, final String collectorNumber, final CardRarity rarity) { this.name = name; this.collectorNumber = collectorNumber; this.rarity = rarity; @@ -86,7 +88,7 @@ public final class CardEdition implements Comparable { // immutable public String toString() { StringBuilder sb = new StringBuilder(); - if (collectorNumber != -1) { + if (collectorNumber != null) { sb.append(collectorNumber); sb.append(' '); } @@ -190,6 +192,7 @@ public final class CardEdition implements Comparable { // immutable public boolean getSmallSetOverride() { return smallSetOverride; } public String getBoosterMustContain() { return boosterMustContain; } public CardInSet[] getCards() { return cards; } + public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others public Map getTokens() { return tokenNormalized; } @@ -266,24 +269,33 @@ public final class CardEdition implements Comparable { // immutable Map tokenNormalized = new HashMap<>(); List processedCards = new ArrayList<>(); if (contents.containsKey("cards")) { + final Pattern pattern = Pattern.compile( + /* + The following pattern will match the WAR Japanese art entries, + it should also match the Un-set and older alternate art cards + like Merseine from FEM (should the editions files ever be updated) + */ + //"(^(?[0-9]+.?) )?((?[SCURML]) )?(?.*)$" + /* Ideally we'd use the named group above, but Android 6 and + earlier don't appear to support named groups. + So, untill support for those devices is officially dropped, + we'll have to suffice with numbered groups. + We are looking for: + * cnum - grouping #2 + * rarity - grouping #4 + * name - grouping #5 + */ + "(^([0-9]+.?) )?(([SCURML]) )?(.*)$" + ); for(String line : contents.get("cards")) { - if (StringUtils.isBlank(line)) - continue; - - // Optional collector number at the start. - String[] split = line.split(" ", 2); - int collectorNumber = -1; - if (split.length >= 2 && StringUtils.isNumeric(split[0])) { - collectorNumber = Integer.parseInt(split[0]); - line = split[1]; + Matcher matcher = pattern.matcher(line); + if (matcher.matches()) { + String collectorNumber = matcher.group(2); + CardRarity r = CardRarity.smartValueOf(matcher.group(4)); + String cardName = matcher.group(5); + CardInSet cis = new CardInSet(cardName, collectorNumber, r); + processedCards.add(cis); } - - // You may omit rarity for early development - CardRarity r = CardRarity.smartValueOf(line.substring(0, 1)); - boolean hadRarity = r != CardRarity.Unknown && line.charAt(1) == ' '; - String cardName = hadRarity ? line.substring(2) : line; - CardInSet cis = new CardInSet(cardName, collectorNumber, r); - processedCards.add(cis); } } diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java index 322c003b6f2..a27b60adf2c 100644 --- a/forge-core/src/main/java/forge/card/CardRules.java +++ b/forge-core/src/main/java/forge/card/CardRules.java @@ -222,7 +222,12 @@ public final class CardRules implements ICardCharacteristics { public boolean canBeBrawlCommander() { CardType type = mainPart.getType(); - return (type.isLegendary() && type.isCreature()) || type.isPlaneswalker(); + return type.isLegendary() && (type.isCreature() || type.isPlaneswalker()); + } + + public boolean canBeTinyLeadersCommander() { + CardType type = mainPart.getType(); + return type.isLegendary() && (type.isCreature() || type.isPlaneswalker()); } public String getMeldWith() { diff --git a/forge-core/src/main/java/forge/card/CardRulesPredicates.java b/forge-core/src/main/java/forge/card/CardRulesPredicates.java index 1a1c99bac4a..ee2a841a6ce 100644 --- a/forge-core/src/main/java/forge/card/CardRulesPredicates.java +++ b/forge-core/src/main/java/forge/card/CardRulesPredicates.java @@ -595,8 +595,10 @@ public final class CardRulesPredicates { public static final Predicate IS_VANGUARD = CardRulesPredicates.coreType(true, CardType.CoreType.Vanguard); public static final Predicate IS_CONSPIRACY = CardRulesPredicates.coreType(true, CardType.CoreType.Conspiracy); public static final Predicate IS_NON_LAND = CardRulesPredicates.coreType(false, CardType.CoreType.Land); - public static final Predicate CAN_BE_BRAWL_COMMANDER = Predicates.or(Presets.IS_PLANESWALKER, - Predicates.and(Presets.IS_CREATURE, Presets.IS_LEGENDARY)); + public static final Predicate CAN_BE_BRAWL_COMMANDER = Predicates.and(Presets.IS_LEGENDARY, + Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER)); + public static final Predicate CAN_BE_TINY_LEADERS_COMMANDER = Predicates.and(Presets.IS_LEGENDARY, + Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER)); /** The Constant IS_NON_CREATURE_SPELL. **/ public static final Predicate IS_NON_CREATURE_SPELL = com.google.common.base.Predicates diff --git a/forge-core/src/main/java/forge/deck/DeckFormat.java b/forge-core/src/main/java/forge/deck/DeckFormat.java index f9cd57dc0ef..e369fccc075 100644 --- a/forge-core/src/main/java/forge/deck/DeckFormat.java +++ b/forge-core/src/main/java/forge/deck/DeckFormat.java @@ -73,7 +73,7 @@ public enum DeckFormat { private final Set bannedCards = ImmutableSet.of( "Ancestral Recall", "Balance", "Black Lotus", "Black Vise", "Channel", "Chaos Orb", "Contract From Below", "Counterbalance", "Darkpact", "Demonic Attorney", "Demonic Tutor", "Earthcraft", "Edric, Spymaster of Trest", "Falling Star", "Fastbond", "Flash", "Goblin Recruiter", "Grindstone", "Hermit Druid", "Imperial Seal", "Jeweled Bird", "Karakas", "Library of Alexandria", "Mana Crypt", "Mana Drain", "Mana Vault", "Metalworker", "Mind Twist", "Mishra's Workshop", - "Mox Emerald", "Mox Jet", "Mox Pearl", "Mox Ruby", "Mox Sapphire", "Necropotence", "Shahrazad", "Skullclamp", "Sol Ring", "Strip Mine", "Survival of the Fittest", "Sword of Body and Mind", "Time Vault", "Time Walk", "Timetwister", + "Mox Emerald", "Mox Jet", "Mox Pearl", "Mox Ruby", "Mox Sapphire", "Najeela, the Blade Blossom", "Necropotence", "Shahrazad", "Skullclamp", "Sol Ring", "Strip Mine", "Survival of the Fittest", "Sword of Body and Mind", "Time Vault", "Time Walk", "Timetwister", "Timmerian Fiends", "Tolarian Academy", "Umezawa's Jitte", "Vampiric Tutor", "Wheel of Fortune", "Yawgmoth's Will"); @Override @@ -463,6 +463,9 @@ public enum DeckFormat { if (this.equals(DeckFormat.Brawl)) { return rules.canBeBrawlCommander(); } + if (this.equals(DeckFormat.TinyLeaders)) { + return rules.canBeTinyLeadersCommander(); + } return rules.canBeCommander(); } diff --git a/forge-core/src/main/java/forge/item/IPaperCard.java b/forge-core/src/main/java/forge/item/IPaperCard.java index 0c14064e059..53ea1d066e4 100644 --- a/forge-core/src/main/java/forge/item/IPaperCard.java +++ b/forge-core/src/main/java/forge/item/IPaperCard.java @@ -6,6 +6,7 @@ import forge.card.CardRarity; import forge.card.CardRules; import forge.card.CardType.CoreType; import forge.card.MagicColor; +import forge.util.PredicateCard; import forge.util.PredicateString; import org.apache.commons.lang3.StringUtils; @@ -60,6 +61,8 @@ public interface IPaperCard extends InventoryItem { return new PredicateNames(what); } + public static PredicateCards cards(final List what) { return new PredicateCards(what); } + private static final class PredicateColor implements Predicate { private final byte operand; @@ -161,6 +164,25 @@ public interface IPaperCard extends InventoryItem { } } + private static final class PredicateCards extends PredicateCard { + private final List operand; + + @Override + public boolean apply(final PaperCard card) { + for (final PaperCard element : this.operand) { + if (this.op(card, element)) { + return true; + } + } + return false; + } + + private PredicateCards(final List operand) { + super(StringOp.EQUALS); + this.operand = operand; + } + } + /** * Pre-built predicates are stored here to allow their re-usage and * easier access from code. diff --git a/forge-core/src/main/java/forge/item/generation/BoosterGenerator.java b/forge-core/src/main/java/forge/item/generation/BoosterGenerator.java index 896e8d6de6b..833ee7e6bf7 100644 --- a/forge-core/src/main/java/forge/item/generation/BoosterGenerator.java +++ b/forge-core/src/main/java/forge/item/generation/BoosterGenerator.java @@ -566,12 +566,8 @@ public class BoosterGenerator { toAdd = IPaperCard.Predicates.printedInSets(sets); } else if (operator.startsWith("fromSheet(") && invert) { String sheetName = StringUtils.strip(operator.substring(9), "()\" "); - Iterable src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList(); - List cardNames = Lists.newArrayList(); - for (PaperCard card : src) { - cardNames.add(card.getName()); - } - toAdd = IPaperCard.Predicates.names(Lists.newArrayList(cardNames)); + Iterable cards = StaticData.instance().getPrintSheets().get(sheetName).toFlatList(); + toAdd = IPaperCard.Predicates.cards(Lists.newArrayList(cards)); } if (toAdd == null) { diff --git a/forge-core/src/main/java/forge/util/PredicateCard.java b/forge-core/src/main/java/forge/util/PredicateCard.java new file mode 100644 index 00000000000..308b34f68ca --- /dev/null +++ b/forge-core/src/main/java/forge/util/PredicateCard.java @@ -0,0 +1,83 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2020 Jamin W. Collins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.util; + +import com.google.common.base.Predicate; +import forge.item.PaperCard; + +/** + * Special predicate class to perform string operations. + * + * @param + * the generic type + */ +public abstract class PredicateCard implements Predicate { + /** Possible operators for string operands. */ + public enum StringOp { + /** The EQUALS. */ + EQUALS, + } + + /** The operator. */ + private final StringOp operator; + + /** + * Op. + * + * @param op1 + * the op1 + * @param op2 + * the op2 + * @return true, if successful + */ + protected final boolean op(final PaperCard op1, final PaperCard op2) { + switch (this.getOperator()) { + case EQUALS: + return op1.equals(op2); + default: + return false; + } + } + + /** + * Instantiates a new predicate string. + * + * @param operator + * the operator + */ + public PredicateCard(final StringOp operator) { + this.operator = operator; + } + + /** + * @return the operator + */ + public StringOp getOperator() { + return operator; + } + + public static PredicateCard equals(final PaperCard what) { + return new PredicateCard(StringOp.EQUALS) { + @Override + public boolean apply(PaperCard subject) { + return op(subject, what); + } + }; + } + +} diff --git a/forge-core/src/main/java/forge/util/TextUtil.java b/forge-core/src/main/java/forge/util/TextUtil.java index 4974a8e087f..06a706b20f0 100644 --- a/forge-core/src/main/java/forge/util/TextUtil.java +++ b/forge-core/src/main/java/forge/util/TextUtil.java @@ -5,6 +5,8 @@ import forge.item.PaperCard; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; +import com.google.common.collect.ImmutableSortedMap; + import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -17,6 +19,22 @@ import java.util.Map.Entry; */ public class TextUtil { + static ImmutableSortedMap romanMap = ImmutableSortedMap.naturalOrder() + .put(1000, "M").put(900, "CM") + .put(500, "D").put(400, "CD") + .put(100, "C").put(90, "XC") + .put(50, "L").put(40, "XL") + .put(10, "X").put(9, "IX") + .put(5, "V").put(4, "IV").put(1, "I").build(); + + public final static String toRoman(int number) { + if (number <= 0) { + return ""; + } + int l = romanMap.floorKey(number); + return romanMap.get(l) + toRoman(number-l); + } + /** * Safely converts an object to a String. * diff --git a/forge-game/pom.xml b/forge-game/pom.xml index 117c4abbebd..e9cf2c327e8 100644 --- a/forge-game/pom.xml +++ b/forge-game/pom.xml @@ -6,7 +6,7 @@ forge forge - 1.6.33-SNAPSHOT + 1.6.34-SNAPSHOT forge-game diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index 849e2de4a0d..2a49bae9d59 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -914,4 +914,17 @@ public class Game { } return false; } + + public Player getControlVote() { + Player result = null; + long maxValue = 0; + for (Player p : getPlayers()) { + Long v = p.getHighestControlVote(); + if (v != null && v > maxValue) { + maxValue = v; + result = p; + } + } + return result; + } } diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index b9670553746..16ec6662f0d 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -375,7 +375,7 @@ public class GameAction { // the LKI needs to be the Card itself, // or it might not updated correctly // TODO be reworked when ZoneTrigger Update is done - if (toBattlefield) { + if (toBattlefield || zoneTo.is(ZoneType.Stack)) { lastKnownInfo = c; } @@ -547,6 +547,13 @@ public class GameAction { c.setCastSA(null); } else if (zoneTo.is(ZoneType.Stack)) { c.setCastFrom(zoneFrom.getZoneType()); + if (cause != null && cause.isSpell() && c.equals(cause.getHostCard()) && !c.isCopiedSpell()) { + cause.setLastStateBattlefield(game.getLastStateBattlefield()); + cause.setLastStateGraveyard(game.getLastStateGraveyard()); + c.setCastSA(cause); + } else { + c.setCastSA(null); + } } else if (!(zoneTo.is(ZoneType.Battlefield) && zoneFrom.is(ZoneType.Stack))) { c.setCastFrom(null); c.setCastSA(null); @@ -565,16 +572,31 @@ public class GameAction { public final void controllerChangeZoneCorrection(final Card c) { System.out.println("Correcting zone for " + c.toString()); final Zone oldBattlefield = game.getZoneOf(c); - if (oldBattlefield == null || oldBattlefield.getZoneType() == ZoneType.Stack) { + + if (oldBattlefield == null || oldBattlefield.is(ZoneType.Stack)) { return; } + final Player original = oldBattlefield.getPlayer(); - final PlayerZone newBattlefield = c.getController().getZone(oldBattlefield.getZoneType()); + final Player controller = c.getController(); + if (original == null || controller == null || original.equals(controller)) { + return; + } + final PlayerZone newBattlefield = controller.getZone(oldBattlefield.getZoneType()); if (newBattlefield == null || oldBattlefield.equals(newBattlefield)) { return; } + // 702.94e A paired creature becomes unpaired if any of the following occur: + // another player gains control of it or the creature it’s paired with + if (c.isPaired()) { + Card partner = c.getPairedWith(); + c.setPairedWith(null); + partner.setPairedWith(null); + partner.updateStateForView(); + } + game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); for (Player p : game.getPlayers()) { ((PlayerZoneBattlefield) p.getZone(ZoneType.Battlefield)).setTriggers(false); diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index 353111565fb..6d9ed807964 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -22,9 +22,10 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; -import forge.card.CardStateName; +import forge.card.MagicColor; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostParser; +import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.*; @@ -33,9 +34,15 @@ import forge.game.cost.Cost; import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import forge.game.player.PlayerController; +import forge.game.replacement.ReplacementEffect; +import forge.game.replacement.ReplacementHandler; +import forge.game.replacement.ReplacementLayer; import forge.game.spellability.*; import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerHandler; +import forge.game.trigger.TriggerType; import forge.game.zone.ZoneType; +import forge.util.Lang; import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; @@ -78,68 +85,9 @@ public final class GameActionUtil { if (sa.isSpell() && !source.isInZone(ZoneType.Battlefield)) { boolean lkicheck = false; - // need to be done before so it works with Vivien and Zoetic Cavern - if (source.isFaceDown() && source.isInZone(ZoneType.Exile)) { - if (!source.isLKI()) { - source = CardUtil.getLKICopy(source); - } - - source.turnFaceUp(false, false); - lkicheck = true; - } - - if (sa.isBestow() && !source.isBestowed() && !source.isInZone(ZoneType.Battlefield)) { - if (!source.isLKI()) { - source = CardUtil.getLKICopy(source); - } - - source.animateBestow(false); - lkicheck = true; - } else if (sa.isCastFaceDown()) { - // need a copy of the card to turn facedown without trigger anything - if (!source.isLKI()) { - source = CardUtil.getLKICopy(source); - } - source.turnFaceDownNoUpdate(); - lkicheck = true; - } else if (sa.isAdventure()) { - if (!source.isLKI()) { - source = CardUtil.getLKICopy(source); - } - - source.setState(CardStateName.Adventure, false); - - // need to reset CMC - source.setLKICMC(-1); - source.setLKICMC(source.getCMC()); - lkicheck = true; - } else if (source.isSplitCard() && (sa.isLeftSplit() || sa.isRightSplit())) { - if (!source.isLKI()) { - source = CardUtil.getLKICopy(source); - } - if (sa.isLeftSplit()) { - if (!source.hasState(CardStateName.LeftSplit)) { - source.addAlternateState(CardStateName.LeftSplit, false); - source.getState(CardStateName.LeftSplit).copyFrom( - sa.getHostCard().getState(CardStateName.LeftSplit), true); - } - - source.setState(CardStateName.LeftSplit, false); - } - - if (sa.isRightSplit()) { - if (!source.hasState(CardStateName.RightSplit)) { - source.addAlternateState(CardStateName.RightSplit, false); - source.getState(CardStateName.RightSplit).copyFrom( - sa.getHostCard().getState(CardStateName.RightSplit), true); - } - - source.setState(CardStateName.RightSplit, false); - } - - // need to reset CMC - source.setLKICMC(-1); - source.setLKICMC(source.getCMC()); + Card newHost = ((Spell)sa).getAlternateHost(source); + if (newHost != null) { + source = newHost; lkicheck = true; } @@ -218,6 +166,7 @@ public final class GameActionUtil { final Cost escapeCost = new Cost(k[1], true); final SpellAbility newSA = sa.copyWithDefinedCost(escapeCost); + newSA.setActivatingPlayer(activator); newSA.getMapParams().put("PrecostDesc", "Escape—"); newSA.getMapParams().put("CostDesc", escapeCost.toString()); @@ -276,6 +225,7 @@ public final class GameActionUtil { if (sa.isCycling() && activator.hasKeyword("CyclingForZero")) { // set the cost to this directly to buypass non mana cost final SpellAbility newSA = sa.copyWithDefinedCost("Discard<1/CARDNAME>"); + newSA.setActivatingPlayer(activator); newSA.setBasicSpell(false); newSA.getMapParams().put("CostDesc", ManaCostParser.parse("0")); // makes new SpellDescription @@ -421,10 +371,11 @@ public final class GameActionUtil { } SpellAbility result = null; final Card host = sa.getHostCard(); + final Game game = host.getGame(); final Player activator = sa.getActivatingPlayer(); final PlayerController pc = activator.getController(); - host.getGame().getAction().checkStaticAbilities(false); + game.getAction().checkStaticAbilities(false); boolean reset = false; @@ -487,7 +438,60 @@ public final class GameActionUtil { int v = pc.chooseNumberForKeywordCost(sa, cost, ki, str, Integer.MAX_VALUE); if (v > 0) { - host.addReplacementEffect(CardFactoryUtil.makeEtbCounter("etbCounter:P1P1:" + v, host, false)); + + final Card eff = new Card(game.nextCardId(), game); + eff.setTimestamp(game.getNextTimestamp()); + eff.setName(c.getName() + "'s Effect"); + eff.addType("Effect"); + eff.setToken(true); // Set token to true, so when leaving play it gets nuked + eff.setOwner(activator); + + eff.setImageKey(c.getImageKey()); + eff.setColor(MagicColor.COLORLESS); + eff.setImmutable(true); + // try to get the SpellAbility from the mana ability + //eff.setEffectSource((SpellAbility)null); + + eff.addRemembered(host); + + String abStr = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ P1P1 | ETB$ True | CounterNum$ " + v; + + SpellAbility saAb = AbilityFactory.getAbility(abStr, c); + + CardFactoryUtil.setupETBReplacementAbility(saAb); + + String desc = "It enters the battlefield with "; + desc += Lang.nounWithNumeral(v, CounterType.P1P1.getName() + " counter"); + desc += " on it."; + + String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | Description$ " + desc; + + ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true); + re.setLayer(ReplacementLayer.Other); + re.setOverridingAbility(saAb); + + eff.addReplacementEffect(re); + + // Forgot Trigger + String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Any | TriggerZones$ Command | Static$ True"; + String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard"; + String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile" + + " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0"; + + SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, eff); + AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, eff); + saForget.setSubAbility(saExile); + + final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, eff, true); + parsedTrigger.setOverridingAbility(saForget); + eff.addTrigger(parsedTrigger); + 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, null); + game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + if (result == null) { result = sa.copy(); } diff --git a/forge-game/src/main/java/forge/game/StaticEffect.java b/forge-game/src/main/java/forge/game/StaticEffect.java index dc9d51094a0..d6ce1813d7e 100644 --- a/forge-game/src/main/java/forge/game/StaticEffect.java +++ b/forge-game/src/main/java/forge/game/StaticEffect.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -37,7 +37,7 @@ import com.google.common.collect.Maps; *

* StaticEffect class. *

- * + * * @author Forge * @version $Id$ */ @@ -72,7 +72,7 @@ public class StaticEffect { /** * setTimestamp TODO Write javadoc for this method. - * + * * @param t * a long */ @@ -82,7 +82,7 @@ public class StaticEffect { /** * getTimestamp. TODO Write javadoc for this method. - * + * * @return a long */ public final long getTimestamp() { @@ -93,7 +93,7 @@ public class StaticEffect { *

* Getter for the field source. *

- * + * * @return a {@link forge.game.card.Card} object. */ public final Card getSource() { @@ -104,7 +104,7 @@ public class StaticEffect { *

* Getter for the field affectedCards. *

- * + * * @return a {@link forge.CardList} object. */ public final CardCollectionView getAffectedCards() { @@ -115,7 +115,7 @@ public class StaticEffect { *

* Setter for the field affectedCards. *

- * + * * @param list * a {@link forge.CardList} object. */ @@ -125,7 +125,7 @@ public class StaticEffect { /** * Gets the affected players. - * + * * @return the affected players */ public final List getAffectedPlayers() { @@ -134,7 +134,7 @@ public class StaticEffect { /** * Sets the affected players. - * + * * @param list * the new affected players */ @@ -144,7 +144,7 @@ public class StaticEffect { /** * setParams. TODO Write javadoc for this method. - * + * * @param params * a HashMap */ @@ -154,7 +154,7 @@ public class StaticEffect { /** * Gets the params. - * + * * @return the params */ public final Map getParams() { @@ -171,13 +171,12 @@ public class StaticEffect { /** * Undo everything that was changed by this effect. - * + * * @return a {@link CardCollectionView} of all affected cards. */ final CardCollectionView remove() { final CardCollectionView affectedCards = getAffectedCards(); final List affectedPlayers = getAffectedPlayers(); - //final Map params = getParams(); String changeColorWordsTo = null; @@ -245,6 +244,10 @@ public class StaticEffect { p.removeMaxLandPlays(getTimestamp()); p.removeMaxLandPlaysInfinite(getTimestamp()); + + p.removeControlVote(getTimestamp()); + p.removeAdditionalVote(getTimestamp()); + p.removeAdditionalOptionalVote(getTimestamp()); } // modify the affected card diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index 0562aea1bf4..f5b40e4ca3a 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -1318,9 +1318,16 @@ public class AbilityUtils { return; } + Player pl = sa.getActivatingPlayer(); + final Game game = pl.getGame(); + + if (sa.isTrigger() && sa.getParent() == null && sa.getPayCosts() != null) { + // when trigger cost are paid before the effect does resolve, need to clean the trigger + game.getTriggerHandler().resetActiveTriggers(); + } + // do blessing there before condition checks if (sa.isSpell() && sa.isBlessing() && !sa.getHostCard().isPermanent()) { - Player pl = sa.getActivatingPlayer(); if (pl != null && pl.getZone(ZoneType.Battlefield).size() >= 10) { pl.setBlessing(true); } @@ -1335,7 +1342,7 @@ public class AbilityUtils { return; } - AbilityUtils.resolveApiAbility(sa, sa.getActivatingPlayer().getGame()); + AbilityUtils.resolveApiAbility(sa, game); } private static void resolveSubAbilities(final SpellAbility sa, final Game game) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java index 6402171ac78..ff0c74c1020 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java @@ -62,7 +62,9 @@ public class AttachEffect extends SpellAbilityEffect { if (sa.hasParam("ChooseAnObject")) { Card c = p.getController().chooseSingleEntityForEffect(attachments, sa, sa.getParam("ChooseAnObject")); attachments.clear(); - attachments.add(c); + if (c != null) { + attachments.add(c); + } } } else { attachments = new CardCollection(source); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java index 335329a3aef..a69a02623f3 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java @@ -6,7 +6,6 @@ import java.util.List; import com.google.common.collect.Lists; import forge.GameCommand; -import forge.card.mana.ManaCost; import forge.game.Game; import forge.game.GameEntity; import forge.game.ability.AbilityUtils; @@ -18,7 +17,6 @@ import forge.game.combat.Combat; import forge.game.event.GameEventCardStatsChanged; import forge.game.event.GameEventCombatChanged; import forge.game.player.Player; -import forge.game.spellability.Ability; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; import forge.util.collect.FCollectionView; @@ -26,9 +24,7 @@ import forge.util.Localizer; import forge.util.CardTranslation; public class ControlGainEffect extends SpellAbilityEffect { - /* (non-Javadoc) - * @see forge.card.abilityfactory.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility) - */ + @Override protected String getStackDescription(SpellAbility sa) { final StringBuilder sb = new StringBuilder(); @@ -67,15 +63,17 @@ public class ControlGainEffect extends SpellAbilityEffect { if (null == c || c.hasKeyword("Other players can't gain control of CARDNAME.")) { return; } + final Game game = host.getGame(); if (c.isInPlay()) { c.removeTempController(tStamp); + game.getAction().controllerChangeZoneCorrection(c); + if (tapOnLose) { c.tap(); } } // if host.removeGainControlTargets(c); - } @Override @@ -84,11 +82,9 @@ public class ControlGainEffect extends SpellAbilityEffect { final boolean bUntap = sa.hasParam("Untap"); final boolean bTapOnLose = sa.hasParam("TapOnLose"); - final boolean bNoRegen = sa.hasParam("NoRegen"); final boolean remember = sa.hasParam("RememberControlled"); final boolean forget = sa.hasParam("ForgetControlled"); final boolean attacking = sa.hasParam("Attacking"); - final List destroyOn = sa.hasParam("DestroyTgt") ? Arrays.asList(sa.getParam("DestroyTgt").split(",")) : null; final List keywords = sa.hasParam("AddKWs") ? Arrays.asList(sa.getParam("AddKWs").split(" & ")) : null; final List lose = sa.hasParam("LoseControl") ? Arrays.asList(sa.getParam("LoseControl").split(",")) : null; @@ -189,18 +185,6 @@ public class ControlGainEffect extends SpellAbilityEffect { } } - if (destroyOn != null) { - if (destroyOn.contains("LeavesPlay")) { - sa.getHostCard().addLeavesPlayCommand(getDestroyCommand(tgtC, source, bNoRegen)); - } - if (destroyOn.contains("Untap")) { - sa.getHostCard().addUntapCommand(getDestroyCommand(tgtC, source, bNoRegen)); - } - if (destroyOn.contains("LoseControl")) { - sa.getHostCard().addChangeControllerCommand(getDestroyCommand(tgtC, source, bNoRegen)); - } - } - if (keywords != null) { // Add keywords only until end of turn final GameCommand untilKeywordEOT = new GameCommand() { @@ -241,43 +225,6 @@ public class ControlGainEffect extends SpellAbilityEffect { } // end foreach target } - /** - *

- * getDestroyCommand. - *

- * - * @param i - * a int. - * @return a {@link forge.GameCommand} object. - */ - private static GameCommand getDestroyCommand(final Card c, final Card hostCard, final boolean bNoRegen) { - final GameCommand destroy = new GameCommand() { - private static final long serialVersionUID = 878543373519872418L; - - @Override - public void run() { - final Game game = hostCard.getGame(); - final Ability ability = new Ability(hostCard, ManaCost.ZERO) { - @Override - public void resolve() { - game.getAction().destroy(c, null, !bNoRegen, null); - } - }; - final StringBuilder sb = new StringBuilder(); - sb.append(hostCard).append(" - destroy ").append(c.getName()).append("."); - if (bNoRegen) { - sb.append(" It can't be regenerated."); - } - ability.setStackDescription(sb.toString()); - ability.setTrigger(true); - - game.getStack().addSimultaneousStackEntry(ability); - } - - }; - return destroy; - } - /** *

* getLoseControlCommand. diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java index 558650c28d6..2f48a5a20f6 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java @@ -35,61 +35,77 @@ import java.util.List; public class CountersPutEffect extends SpellAbilityEffect { @Override - protected String getStackDescription(SpellAbility sa) { - final StringBuilder sb = new StringBuilder(); - final Card card = sa.getHostCard(); - final boolean dividedAsYouChoose = sa.hasParam("DividedAsYouChoose"); + protected String getStackDescription(SpellAbility spellAbility) { + final StringBuilder stringBuilder = new StringBuilder(); + final Card card = spellAbility.getHostCard(); + final boolean dividedAsYouChoose = spellAbility.hasParam("DividedAsYouChoose"); - - final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("CounterNum", "1"), sa); - if (sa.hasParam("Bolster")) { - sb.append("Bolster ").append(amount); - return sb.toString(); + final int amount = AbilityUtils.calculateAmount(card, spellAbility.getParamOrDefault("CounterNum", "1"), spellAbility); + if (spellAbility.hasParam("Bolster")) { + stringBuilder.append("Bolster ").append(amount); + return stringBuilder.toString(); } if (dividedAsYouChoose) { - sb.append("Distribute "); + stringBuilder.append("Distribute "); } else { - sb.append("Put "); + stringBuilder.append("Put "); } - if (sa.hasParam("UpTo")) { - sb.append("up to "); + if (spellAbility.hasParam("UpTo")) { + stringBuilder.append("up to "); } - sb.append(amount).append(" "); + stringBuilder.append(amount).append(" "); - String type = sa.getParam("CounterType"); + String type = spellAbility.getParam("CounterType"); if (type.equals("ExistingCounter")) { - sb.append("of an existing counter"); + stringBuilder.append("of an existing counter"); } else { - - sb.append( CounterType.valueOf(type).getName()).append(" counter"); + stringBuilder.append(CounterType.valueOf(type).getName()).append(" counter"); } + if (amount != 1) { - sb.append("s"); + stringBuilder.append("s"); } + if (dividedAsYouChoose) { - sb.append(" among "); + stringBuilder.append(" among "); } else { - sb.append(" on "); + stringBuilder.append(" on "); } - final List tgtCards = getTargetCards(sa); - final Iterator it = tgtCards.iterator(); - while (it.hasNext()) { - final Card tgtC = it.next(); - if (tgtC.isFaceDown()) { - sb.append("Morph"); - } else { - sb.append(tgtC); + // if use targeting we show all targets and corresponding counters + if(spellAbility.usesTargeting()) { + final List targetCards = SpellAbilityEffect.getTargetCards(spellAbility); + for(int i = 0; i < targetCards.size(); i++) { + Card targetCard = targetCards.get(i); + stringBuilder.append(targetCard).append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetCard)).append(" counter)"); + + if(i == targetCards.size() - 2) { + stringBuilder.append(" and "); + } + else if(i + 1 < targetCards.size()) { + stringBuilder.append(", "); + } } + } else { + final List targetCards = SpellAbilityEffect.getTargetCards(spellAbility); + final Iterator it = targetCards.iterator(); + while (it.hasNext()) { + final Card targetCard = it.next(); + if (targetCard.isFaceDown()) { + stringBuilder.append("Morph"); + } else { + stringBuilder.append(targetCard); + } - if (it.hasNext()) { - sb.append(", "); + if (it.hasNext()) { + stringBuilder.append(", "); + } } } - sb.append("."); + stringBuilder.append("."); - return sb.toString(); + return stringBuilder.toString(); } @Override @@ -156,7 +172,7 @@ public class CountersPutEffect extends SpellAbilityEffect { if (existingCounter) { final List choices = Lists.newArrayList(); if (obj instanceof GameEntity) { - GameEntity entity = (GameEntity)obj; + GameEntity entity = (GameEntity) obj; // get types of counters for (CounterType ct : entity.getCounters().keySet()) { if (entity.canReceiveCounters(ct)) { @@ -166,7 +182,7 @@ public class CountersPutEffect extends SpellAbilityEffect { } if (eachExistingCounter) { - for(CounterType ct : choices) { + for (CounterType ct : choices) { if (obj instanceof Player) { ((Player) obj).addCounter(ct, counterAmount, placer, true, table); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java index 6092fde9af8..9a80817d6e3 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java @@ -6,6 +6,7 @@ import forge.game.Game; import forge.game.GameEntityCounterTable; import forge.game.GameObject; import forge.game.ability.AbilityUtils; +import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardDamageMap; @@ -25,45 +26,86 @@ public class DamageDealEffect extends DamageBaseEffect { * @see forge.game.ability.SpellAbilityEffect#getStackDescription(forge.game.spellability.SpellAbility) */ @Override - protected String getStackDescription(SpellAbility sa) { + protected String getStackDescription(SpellAbility spellAbility) { // when damageStackDescription is called, just build exactly what is happening - final StringBuilder sb = new StringBuilder(); - final String damage = sa.getParam("NumDmg"); - final int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa); + final StringBuilder stringBuilder = new StringBuilder(); + final String damage = spellAbility.getParam("NumDmg"); + final int dmg = AbilityUtils.calculateAmount(spellAbility.getHostCard(), damage, spellAbility); - List tgts = getTargets(sa); - if (tgts.isEmpty()) + List targets = SpellAbilityEffect.getTargets(spellAbility); + if (targets.isEmpty()) { return ""; + } - final List definedSources = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DamageSource"), sa); + final List definedSources = AbilityUtils.getDefinedCards(spellAbility.getHostCard(), spellAbility.getParam("DamageSource"), spellAbility); - if (!definedSources.isEmpty() && definedSources.get(0) != sa.getHostCard()) { - sb.append(definedSources.get(0).toString()).append(" deals"); + if (!definedSources.isEmpty() && definedSources.get(0) != spellAbility.getHostCard()) { + stringBuilder.append(definedSources.get(0).toString()).append(" deals"); } else { - sb.append("Deals"); + stringBuilder.append("Deals"); } - sb.append(" ").append(dmg).append(" damage "); + stringBuilder.append(" ").append(dmg).append(" damage "); - if (sa.hasParam("DivideEvenly")) { - sb.append("divided evenly (rounded down) "); - } else if (sa.hasParam("DividedAsYouChoose")) { - sb.append("divided as you choose "); + // if use targeting we show all targets and corresponding damage + if (spellAbility.usesTargeting()) { + if (spellAbility.hasParam("DivideEvenly")) { + stringBuilder.append("divided evenly (rounded down) to\n"); + } else if (spellAbility.hasParam("DividedAsYouChoose")) { + stringBuilder.append("divided to\n"); + } + + final List targetCards = SpellAbilityEffect.getTargetCards(spellAbility); + final List players = SpellAbilityEffect.getTargetPlayers(spellAbility); + + int targetCount = targetCards.size() + players.size(); + + // target cards + for (int i = 0; i < targetCards.size(); i++) { + Card targetCard = targetCards.get(i); + stringBuilder.append(targetCard).append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetCard)).append(" damage)"); + + if (i == targetCount - 2) { + stringBuilder.append(" and "); + } else if (i + 1 < targetCount) { + stringBuilder.append(", "); + } + } + + // target players + for (int i = 0; i < players.size(); i++) { + + Player targetPlayer = players.get(i); + stringBuilder.append(targetPlayer).append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetPlayer)).append(" damage)"); + + if (i == players.size() - 2) { + stringBuilder.append(" and "); + } else if (i + 1 < players.size()) { + stringBuilder.append(", "); + } + } + + } else { + if (spellAbility.hasParam("DivideEvenly")) { + stringBuilder.append("divided evenly (rounded down) "); + } else if (spellAbility.hasParam("DividedAsYouChoose")) { + stringBuilder.append("divided as you choose "); + } + stringBuilder.append("to ").append(Lang.joinHomogenous(targets)); } - sb.append("to ").append(Lang.joinHomogenous(tgts)); - if (sa.hasParam("Radiance")) { - sb.append(" and each other ").append(sa.getParam("ValidTgts")) + if (spellAbility.hasParam("Radiance")) { + stringBuilder.append(" and each other ").append(spellAbility.getParam("ValidTgts")) .append(" that shares a color with "); - if (tgts.size() > 1) { - sb.append("them"); + if (targets.size() > 1) { + stringBuilder.append("them"); } else { - sb.append("it"); + stringBuilder.append("it"); } } - sb.append(". "); - return sb.toString(); + stringBuilder.append("."); + return stringBuilder.toString(); } /* (non-Javadoc) @@ -153,7 +195,7 @@ public class DamageDealEffect extends DamageBaseEffect { // Do we have a way of doing this in a better fashion? for (GameObject obj : tgts) { if (obj instanceof Card) { - assigneeCards.add((Card)obj); + assigneeCards.add((Card) obj); } } @@ -195,8 +237,7 @@ public class DamageDealEffect extends DamageBaseEffect { c.setDamage(0); c.setHasBeenDealtDeathtouchDamage(false); c.clearAssignedDamage(); - } - else { + } else { c.addDamage(dmg, sourceLKI, false, noPrevention, damageMap, preventMap, counterTable, sa); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java index 442fe3c5340..2595c792dec 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java @@ -70,7 +70,7 @@ public class DebuffEffect extends SpellAbilityEffect { for (final Card tgtC : getTargetCards(sa)) { final List addedKW = Lists.newArrayList(); final List removedKW = Lists.newArrayList(); - if (tgtC.isInPlay() && tgtC.canBeTargetedBy(sa)) { + if (tgtC.isInPlay() && (!sa.usesTargeting() || tgtC.canBeTargetedBy(sa))) { if (sa.hasParam("AllSuffixKeywords")) { String suffix = sa.getParam("AllSuffixKeywords"); for (final KeywordInterface kw : tgtC.getKeywords()) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/MustBlockEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MustBlockEffect.java index 504d17ba0b1..f83f720d8ad 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/MustBlockEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/MustBlockEffect.java @@ -1,34 +1,62 @@ package forge.game.ability.effects; +import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; +import forge.game.card.CardCollectionView; +import forge.game.card.CardLists; +import forge.game.player.Player; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; +import forge.game.zone.ZoneType; +import forge.util.Localizer; -import java.util.ArrayList; import java.util.List; +import com.google.common.collect.Lists; + public class MustBlockEffect extends SpellAbilityEffect { @Override public void resolve(SpellAbility sa) { final Card host = sa.getHostCard(); + final Player activator = sa.getActivatingPlayer(); + final Game game = activator.getGame(); + + List tgtCards = Lists.newArrayList(); + if (sa.hasParam("Choices")) { + Player chooser = activator; + if (sa.hasParam("Chooser")) { + final String choose = sa.getParam("Chooser"); + chooser = AbilityUtils.getDefinedPlayers(sa.getHostCard(), choose, sa).get(0); + } + + CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield); + choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host); + if (!choices.isEmpty()) { + String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseaCard") +" "; + + Card choosen = chooser.getController().chooseSingleEntityForEffect(choices, sa, title, false); + + if (choosen != null) { + tgtCards.add(choosen); + } + } + } else { + tgtCards = getTargetCards(sa); + } - List tgtCards = getTargetCards(sa); - final TargetRestrictions tgt = sa.getTargetRestrictions(); final boolean mustBlockAll = sa.hasParam("BlockAllDefined"); List cards; if (sa.hasParam("DefinedAttacker")) { cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedAttacker"), sa); } else { - cards = new ArrayList<>(); - cards.add(host); + cards = Lists.newArrayList(host); } for (final Card c : tgtCards) { - if ((tgt == null) || c.canBeTargetedBy(sa)) { + if ((!sa.usesTargeting()) || c.canBeTargetedBy(sa)) { if (mustBlockAll) { c.addMustBlockCards(cards); } else { @@ -48,8 +76,6 @@ public class MustBlockEffect extends SpellAbilityEffect { // end standard pre- - final List tgtCards = getTargetCards(sa); - String attacker = null; if (sa.hasParam("DefinedAttacker")) { final List cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedAttacker"), sa); @@ -58,10 +84,13 @@ public class MustBlockEffect extends SpellAbilityEffect { attacker = host.toString(); } - for (final Card c : tgtCards) { - sb.append(c).append(" must block ").append(attacker).append(" if able."); + if (sa.hasParam("Choices")) { + sb.append("Choosen creature ").append(" must block ").append(attacker).append(" if able."); + } else { + for (final Card c : getTargetCards(sa)) { + sb.append(c).append(" must block ").append(attacker).append(" if able."); + } } - return sb.toString(); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/VoteEffect.java b/forge-game/src/main/java/forge/game/ability/effects/VoteEffect.java index 769e758388c..cc572a9c5fd 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/VoteEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/VoteEffect.java @@ -1,6 +1,7 @@ package forge.game.ability.effects; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -9,9 +10,9 @@ import forge.game.ability.AbilityKey; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import forge.game.Game; import forge.game.ability.AbilityFactory; @@ -19,10 +20,7 @@ import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.card.CardLists; -import forge.game.card.CardPredicates; import forge.game.player.Player; -import forge.game.player.PlayerCollection; -import forge.game.player.PlayerPredicates; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; import forge.game.trigger.TriggerType; @@ -67,33 +65,29 @@ public class VoteEffect extends SpellAbilityEffect { return; } - // starting with the activator - int pSize = tgtPlayers.size(); Player activator = sa.getActivatingPlayer(); - while (tgtPlayers.contains(activator) && !activator.equals(Iterables.getFirst(tgtPlayers, null))) { - tgtPlayers.add(pSize - 1, tgtPlayers.remove(0)); + + // starting with the activator + int aidx = tgtPlayers.indexOf(activator); + if (aidx != -1) { + Collections.rotate(tgtPlayers, -aidx); } + ListMultimap votes = ArrayListMultimap.create(); - Player voter = null; - - PlayerCollection voters = game.getPlayers().filter(PlayerPredicates.hasKeyword("You choose how each player votes this turn.")); - - if (voters.size() > 1) { - List illusions = CardLists.filter(voters.getCardsIn(ZoneType.Command), CardPredicates.nameEquals("Illusion of Choice Effect")); - voter = Collections.max(illusions, CardPredicates.compareByTimestamp()).getController(); - } else if (voters.size() == 1) { - voter = voters.get(0); - } + Player voter = game.getControlVote(); for (final Player p : tgtPlayers) { - int voteAmount = p.getKeywords().getAmount("You get an additional vote.") + 1; - int optionalVotes = p.getKeywords().getAmount("You may vote an additional time."); - voteAmount += p.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyAdditionalVotesDoYouWant"), 0, optionalVotes); + int voteAmount = p.getAdditionalVotesAmount() + 1; + int optionalVotes = p.getAdditionalOptionalVotesAmount(); Player realVoter = voter == null ? p : voter; + Map params = Maps.newHashMap(); + params.put("Voter", realVoter); + voteAmount += p.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyAdditionalVotesDoYouWant"), 0, optionalVotes, params); + for (int i = 0; i < voteAmount; i++) { - Object result = realVoter.getController().vote(sa, host + Localizer.getInstance().getMessage("lblVote") + ":", voteType, votes); + Object result = realVoter.getController().vote(sa, host + Localizer.getInstance().getMessage("lblVote") + ":", voteType, votes, p); votes.put(result, p); host.getGame().getAction().nofityOfValue(sa, p, result + "\r\n" + Localizer.getInstance().getMessage("lblCurrentVote") + ":" + votes, p); @@ -104,34 +98,49 @@ public class VoteEffect extends SpellAbilityEffect { runParams.put(AbilityKey.AllVotes, votes); game.getTriggerHandler().runTrigger(TriggerType.Vote, runParams, false); - List subAbs = Lists.newArrayList(); - final List mostVotes = getMostVotes(votes); - if (sa.hasParam("Tied") && mostVotes.size() > 1) { - subAbs.add(sa.getParam("Tied")); - } else if (sa.hasParam("VoteSubAbility")) { - for (final Object o : mostVotes) { - host.addRemembered(o); - } - subAbs.add(sa.getParam("VoteSubAbility")); - } else { - for (Object type : mostVotes) { - subAbs.add(sa.getParam("Vote" + type.toString())); - } - } - if (sa.hasParam("StoreVoteNum")) { - for (final Object type : voteType) { - host.setSVar("VoteNum" + type, "Number$" + votes.get(type).size()); - } - } else { - for (final String subAb : subAbs) { - final SpellAbility action = AbilityFactory.getAbility(host.getSVar(subAb), host); + if (sa.hasParam("EachVote")) { + for (Map.Entry> e : votes.asMap().entrySet()) { + final SpellAbility action = AbilityFactory.getAbility(host, sa.getParam("Vote" + e.getKey().toString())); + action.setActivatingPlayer(sa.getActivatingPlayer()); ((AbilitySub) action).setParent(sa); - AbilityUtils.resolve(action); + + for (Player p : e.getValue()) { + host.addRemembered(p); + AbilityUtils.resolve(action); + host.removeRemembered(p); + } + } + } else { + List subAbs = Lists.newArrayList(); + final List mostVotes = getMostVotes(votes); + if (sa.hasParam("Tied") && mostVotes.size() > 1) { + subAbs.add(sa.getParam("Tied")); + } else if (sa.hasParam("VoteSubAbility")) { + for (final Object o : mostVotes) { + host.addRemembered(o); + } + subAbs.add(sa.getParam("VoteSubAbility")); + } else { + for (Object type : mostVotes) { + subAbs.add(sa.getParam("Vote" + type.toString())); + } + } + if (sa.hasParam("StoreVoteNum")) { + for (final Object type : voteType) { + host.setSVar("VoteNum" + type, "Number$" + votes.get(type).size()); + } + } else { + for (final String subAb : subAbs) { + final SpellAbility action = AbilityFactory.getAbility(host, subAb); + action.setActivatingPlayer(sa.getActivatingPlayer()); + ((AbilitySub) action).setParent(sa); + AbilityUtils.resolve(action); + } + } + if (sa.hasParam("VoteSubAbility")) { + host.clearRemembered(); } - } - if (sa.hasParam("VoteSubAbility")) { - host.clearRemembered(); } } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 0be3279d3e4..f2ab81b38b9 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -3938,7 +3938,9 @@ public class Card extends GameEntity implements Comparable { keywordsGrantedByTextChanges.add(newKw); } } - addChangedCardKeywordsInternal(addKeywords, removeKeywords, false, false, timestamp, true); + if (!addKeywords.isEmpty() || !removeKeywords.isEmpty()) { + addChangedCardKeywordsInternal(addKeywords, removeKeywords, false, false, timestamp, true); + } } private void updateKeywordsOnRemoveChangedText(final KeywordsChange k) { @@ -6353,6 +6355,10 @@ public class Card extends GameEntity implements Comparable { removeSVar("PayX"); // Temporary AI X announcement variable removeSVar("IsCastFromPlayEffect"); // Temporary SVar indicating that the spell is cast indirectly via AF Play setSunburstValue(0); // Sunburst + setXManaCostPaid(0); + setXManaCostPaidByColor(null); + setKickerMagnitude(0); + setPseudoMultiKickerMagnitude(0); } public final int getFinalChapterNr() { diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index e60f3e16bd1..88307959cdb 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -20,7 +20,6 @@ package forge.game.card; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Predicates; -import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -3010,24 +3009,43 @@ public class CardFactoryUtil { inst.addTrigger(parsedTrigger); } else if (keyword.startsWith("Saga")) { - // Saga there doesn't need Max value anymore? final String[] k = keyword.split(":"); - final String[] abs = k[2].split(","); + final List abs = Arrays.asList(k[2].split(",")); + if (abs.size() != Integer.valueOf(k[1])) { + throw new RuntimeException("Saga max differ from Ability amount"); + } - int i = 1; - for (String ab : abs) { - SpellAbility sa = AbilityFactory.getAbility(card, ab); - sa.setChapter(i); + int idx = 0; + int skipId = 0; + for(String ab : abs) { + idx += 1; + if (idx <= skipId) { + continue; + } - // TODO better logic for Roman numbers - // In the Description try to merge Chapter trigger with the Same Effect - String trigStr = "Mode$ CounterAdded | ValidCard$ Card.Self | TriggerZones$ Battlefield" - + "| CounterType$ LORE | CounterAmount$ EQ" + i - + "| TriggerDescription$ " + Strings.repeat("I", i) + " - " + sa.getDescription(); - final Trigger t = TriggerHandler.parseTrigger(trigStr, card, intrinsic); - t.setOverridingAbility(sa); - inst.addTrigger(t); - ++i; + skipId = idx + abs.subList(idx - 1, abs.size()).lastIndexOf(ab); + StringBuilder desc = new StringBuilder(); + for (int i = idx; i <= skipId; i++) { + if (i != idx) { + desc.append(", "); + } + desc.append(TextUtil.toRoman(i)); + } + + for (int i = idx; i <= skipId; i++) { + SpellAbility sa = AbilityFactory.getAbility(card, ab); + sa.setChapter(i); + + StringBuilder trigStr = new StringBuilder("Mode$ CounterAdded | ValidCard$ Card.Self | TriggerZones$ Battlefield"); + trigStr.append("| CounterType$ LORE | CounterAmount$ EQ").append(i); + if (i != idx) { + trigStr.append(" | Secondary$ True"); + } + trigStr.append("| TriggerDescription$ ").append(desc).append(" — ").append(sa.getDescription()); + final Trigger t = TriggerHandler.parseTrigger(trigStr.toString(), card, intrinsic); + t.setOverridingAbility(sa); + inst.addTrigger(t); + } } } else if (keyword.equals("Soulbond")) { // Setup ETB trigger for card with Soulbond keyword diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index 53e52c0a69c..c14d8610cff 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -1518,27 +1518,38 @@ public class CardProperty { } else if (property.startsWith("blockedByThisTurn")) { return !card.getBlockedByThisTurn().isEmpty(); } else if (property.startsWith("blockedValidThisTurn ")) { - if (card.getBlockedThisTurn() == null) { + CardCollectionView blocked = card.getBlockedThisTurn(); + if (blocked == null) { return false; } - String valid = property.split(" ")[1]; - for(Card c : card.getBlockedThisTurn()) { + for(Card c : blocked) { if (c.isValid(valid, card.getController(), source, spellAbility)) { return true; } } + for(Card c : AbilityUtils.getDefinedCards(source, valid, spellAbility)) { + if (blocked.contains(c)) { + return true; + } + }; return false; } else if (property.startsWith("blockedByValidThisTurn ")) { - if (card.getBlockedByThisTurn() == null) { + CardCollectionView blocked = card.getBlockedByThisTurn(); + if (blocked == null) { return false; } String valid = property.split(" ")[1]; - for(Card c : card.getBlockedByThisTurn()) { + for(Card c : blocked) { if (c.isValid(valid, card.getController(), source, spellAbility)) { return true; } } + for(Card c : AbilityUtils.getDefinedCards(source, valid, spellAbility)) { + if (blocked.contains(c)) { + return true; + } + }; return false; } else if (property.startsWith("blockedBySourceThisTurn")) { return source.getBlockedByThisTurn().contains(card); @@ -1779,4 +1790,4 @@ public class CardProperty { return true; } -} \ No newline at end of file +} diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index db14603c195..012cb1bb76e 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -203,27 +203,22 @@ public class CardView extends GameEntityView { void updateCommander(Card c) { boolean isCommander = c.isCommander(); set(TrackableProperty.IsCommander, isCommander); - if (c.getGame().getRules().hasAppliedVariant(GameType.Oathbreaker)) { - //store alternate type for oathbreaker or signature spell for display in card text - if (isCommander) { + if (isCommander) { + if (c.getGame().getRules().hasAppliedVariant(GameType.Oathbreaker)) { + //store alternate type for oathbreaker or signature spell for display in card text if (c.getPaperCard().getRules().canBeSignatureSpell()) { set(TrackableProperty.CommanderAltType, "Signature Spell"); } else { set(TrackableProperty.CommanderAltType, "Oathbreaker"); } - } - else { - set(TrackableProperty.CommanderAltType, null); + } else { + set(TrackableProperty.CommanderAltType, "Commander"); } } } public String getCommanderType() { - String type = get(TrackableProperty.CommanderAltType); - if (type == null) { - type = "Commander"; - } - return type; + return get(TrackableProperty.CommanderAltType); } public Map getCounters() { diff --git a/forge-game/src/main/java/forge/game/combat/Combat.java b/forge-game/src/main/java/forge/game/combat/Combat.java index 4fc83734d74..4c85ccd5cac 100644 --- a/forge-game/src/main/java/forge/game/combat/Combat.java +++ b/forge-game/src/main/java/forge/game/combat/Combat.java @@ -882,6 +882,10 @@ public class Combat { return true; // is blocking something at the moment } + if (!blocker.isLKI()) { + return false; + } + CombatLki lki = lkiCache.get(blocker); return null != lki && !lki.isAttacker; // was blocking something anyway } @@ -892,7 +896,11 @@ public class Combat { if (blockers != null && blockers.contains(blocker)) { return true; // is blocking the attacker's band at the moment } - + + if (!blocker.isLKI()) { + return false; + } + CombatLki lki = lkiCache.get(blocker); return null != lki && !lki.isAttacker && lki.relatedBands.contains(ab); // was blocking that very band } diff --git a/forge-game/src/main/java/forge/game/mana/ManaPool.java b/forge-game/src/main/java/forge/game/mana/ManaPool.java index e90e6eb9b50..d11afbe776c 100644 --- a/forge-game/src/main/java/forge/game/mana/ManaPool.java +++ b/forge-game/src/main/java/forge/game/mana/ManaPool.java @@ -254,7 +254,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { } } if (mana.addsCounters(sa)) { - mana.getManaAbility().createETBCounters(host); + mana.getManaAbility().createETBCounters(host, this.owner); } if (mana.triggersWhenSpent()) { mana.getManaAbility().addTriggersWhenSpent(sa, host); diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index af850385eee..f21c28a6d60 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -162,6 +162,10 @@ public class Player extends GameEntity implements Comparable { private Card blessingEffect = null; private Card keywordEffect = null; + private Map additionalVotes = Maps.newHashMap(); + private Map additionalOptionalVotes = Maps.newHashMap(); + private SortedSet controlVotes = Sets.newTreeSet(); + private final AchievementTracker achievementTracker = new AchievementTracker(); private final PlayerView view; @@ -2706,6 +2710,8 @@ public class Player extends GameEntity implements Comparable { public void incCommanderCast(Card commander) { commanderCast.put(commander, getCommanderCast(commander) + 1); + getView().updateCommanderCast(this, commander); + getGame().fireEvent(new GameEventPlayerStatsChanged(this, false)); } public int getTotalCommanderCast() { @@ -3065,4 +3071,86 @@ public class Player extends GameEntity implements Comparable { this.updateZoneForView(com); return keywordEffect; } + + public void addAdditionalVote(long timestamp, int value) { + additionalVotes.put(timestamp, value); + getView().updateAdditionalVote(this); + getGame().fireEvent(new GameEventPlayerStatsChanged(this, false)); + } + + public void removeAdditionalVote(long timestamp) { + if (additionalVotes.remove(timestamp) != null) { + getView().updateAdditionalVote(this); + getGame().fireEvent(new GameEventPlayerStatsChanged(this, false)); + } + } + + public int getAdditionalVotesAmount() { + int value = 0; + for (Integer i : additionalVotes.values()) { + value += i; + } + return value; + } + + public void addAdditionalOptionalVote(long timestamp, int value) { + additionalOptionalVotes.put(timestamp, value); + getView().updateOptionalAdditionalVote(this); + getGame().fireEvent(new GameEventPlayerStatsChanged(this, false)); + } + public void removeAdditionalOptionalVote(long timestamp) { + if (additionalOptionalVotes.remove(timestamp) != null) { + getView().updateOptionalAdditionalVote(this); + getGame().fireEvent(new GameEventPlayerStatsChanged(this, false)); + } + } + + public int getAdditionalOptionalVotesAmount() { + int value = 0; + for (Integer i : additionalOptionalVotes.values()) { + value += i; + } + return value; + } + + public boolean addControlVote(long timestamp) { + if (controlVotes.add(timestamp)) { + updateControlVote(); + return true; + } + return false; + } + public boolean removeControlVote(long timestamp) { + if (controlVotes.remove(timestamp)) { + updateControlVote(); + return true; + } + return false; + } + + void updateControlVote() { + // need to update all players because it can't know + Player control = getGame().getControlVote(); + for (Player pl : getGame().getPlayers()) { + pl.getView().updateControlVote(pl.equals(control)); + getGame().fireEvent(new GameEventPlayerStatsChanged(pl, false)); + } + } + + public Set getControlVote() { + return controlVotes; + } + + public void setControlVote(Set value) { + controlVotes.clear(); + controlVotes.addAll(value); + updateControlVote(); + } + + public Long getHighestControlVote() { + if (controlVotes.isEmpty()) { + return null; + } + return controlVotes.last(); + } } diff --git a/forge-game/src/main/java/forge/game/player/PlayerController.java b/forge-game/src/main/java/forge/game/player/PlayerController.java index 62d57539212..ec79ece50b7 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerController.java +++ b/forge-game/src/main/java/forge/game/player/PlayerController.java @@ -166,7 +166,7 @@ public abstract class PlayerController { return chooseSomeType(kindOfType, sa, validTypes, invalidTypes, false); } - public abstract Object vote(SpellAbility sa, String prompt, List options, ListMultimap votes); + public abstract Object vote(SpellAbility sa, String prompt, List options, ListMultimap votes, Player forPlayer); public abstract boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question); public abstract CardCollectionView getCardsToMulligan(Player firstPlayer); diff --git a/forge-game/src/main/java/forge/game/player/PlayerView.java b/forge-game/src/main/java/forge/game/player/PlayerView.java index 5c240280b88..2da3dac4bf6 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerView.java +++ b/forge-game/src/main/java/forge/game/player/PlayerView.java @@ -7,7 +7,6 @@ import forge.card.CardType; import forge.card.mana.ManaAtom; import forge.game.card.CounterType; -import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; import com.google.common.base.MoreObjects; @@ -107,8 +106,7 @@ public class PlayerView extends GameEntityView { } public boolean isOpponentOf(final PlayerView other) { - FCollectionView opponents = getOpponents(); - return opponents != null && opponents.contains(other); + return getOpponents().contains(other); } public final String getCommanderInfo(CardView v) { @@ -117,14 +115,15 @@ public class PlayerView extends GameEntityView { } final StringBuilder sb = new StringBuilder(); - Iterable opponents = getOpponents(); - if (opponents == null) { - opponents = Collections.emptyList(); - } - for (final PlayerView p : Iterables.concat(Collections.singleton(this), opponents)) { + + sb.append(Localizer.getInstance().getMessage("lblCommanderCastCard", String.valueOf(getCommanderCast(v)))); + sb.append("\n"); + + for (final PlayerView p : Iterables.concat(Collections.singleton(this), getOpponents())) { final int damage = p.getCommanderDamage(v); if (damage > 0) { sb.append(Localizer.getInstance().getMessage("lblCommanderDealNDamageToPlayer", p.toString(), CardTranslation.getTranslatedName(v.getName()), String.valueOf(damage))); + sb.append("\n"); } } return sb.toString(); @@ -144,7 +143,11 @@ public class PlayerView extends GameEntityView { } final List info = Lists.newArrayListWithExpectedSize(opponents.size()); - info.add(TextUtil.concatWithSpace("Commanders:", Lang.joinHomogenous(commanders))); + + info.add("Commanders:"); + for (final CardView v : commanders) { + info.add(Localizer.getInstance().getMessage("lblCommanderCastPlayer", CardTranslation.getTranslatedName(v.getName()), String.valueOf(getCommanderCast(v)))); + } // own commanders for (final CardView v : commanders) { @@ -268,6 +271,27 @@ public class PlayerView extends GameEntityView { set(TrackableProperty.NumDrawnThisTurn, p.getNumDrawnThisTurn()); } + public int getAdditionalVote() { + return get(TrackableProperty.AdditionalVote); + } + public void updateAdditionalVote(Player p) { + set(TrackableProperty.AdditionalVote, p.getAdditionalVotesAmount()); + } + + public int getOptionalAdditionalVote() { + return get(TrackableProperty.OptionalAdditionalVote); + } + public void updateOptionalAdditionalVote(Player p) { + set(TrackableProperty.OptionalAdditionalVote, p.getAdditionalOptionalVotesAmount()); + } + + public boolean getControlVote() { + return get(TrackableProperty.ControlVotes); + } + public void updateControlVote(boolean val) { + set(TrackableProperty.ControlVotes, val); + } + public ImmutableMultiset getKeywords() { return get(TrackableProperty.Keywords); } @@ -300,13 +324,29 @@ public class PlayerView extends GameEntityView { return damage == null ? 0 : damage.intValue(); } void updateCommanderDamage(Player p) { - Map map = new HashMap<>(); + Map map = Maps.newHashMap(); for (Entry entry : p.getCommanderDamage()) { map.put(entry.getKey().getId(), entry.getValue()); } set(TrackableProperty.CommanderDamage, map); } + public int getCommanderCast(CardView commander) { + Map map = get(TrackableProperty.CommanderCast); + if (map == null) { return 0; } + Integer damage = map.get(commander.getId()); + return damage == null ? 0 : damage.intValue(); + } + + void updateCommanderCast(Player p, Card c) { + Map map = get(TrackableProperty.CommanderCast); + if (map == null) { + map = Maps.newHashMap(); + } + map.put(c.getId(), p.getCommanderCast(c)); + set(TrackableProperty.CommanderCast, map); + } + public PlayerView getMindSlaveMaster() { return get(TrackableProperty.MindSlaveMaster); } @@ -478,6 +518,19 @@ public class PlayerView extends GameEntityView { details.add(Localizer.getInstance().getMessage("lblCardDrawnThisTurnHas", String.valueOf(getNumDrawnThisTurn()))); details.add(Localizer.getInstance().getMessage("lblDamagepreventionHas", String.valueOf(getPreventNextDamage()))); + int v = getAdditionalVote(); + if (v > 0) { + details.add(Localizer.getInstance().getMessage("lblAdditionalVotes", String.valueOf(v))); + } + v = getOptionalAdditionalVote(); + if (v > 0) { + details.add(Localizer.getInstance().getMessage("lblOptionalAdditionalVotes", String.valueOf(v))); + } + + if (getControlVote()) { + details.add(Localizer.getInstance().getMessage("lblControlsVote")); + } + if (getIsExtraTurn()) { details.add(Localizer.getInstance().getMessage("lblIsExtraTurn")); } diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java index da064a4cbd6..510458a6189 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -19,9 +19,11 @@ package forge.game.spellability; import com.google.common.collect.Lists; import com.google.common.collect.Maps; + import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.ManaAtom; +import forge.game.Game; import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityKey; import forge.game.card.Card; @@ -34,6 +36,8 @@ import forge.game.replacement.*; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerHandler; import forge.game.trigger.TriggerType; +import forge.game.zone.ZoneType; +import forge.util.Lang; import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; @@ -46,7 +50,7 @@ import java.util.regex.Pattern; *

* Abstract AbilityMana class. *

- * + * * @author Forge * @version $Id$ */ @@ -78,7 +82,7 @@ public class AbilityManaPart implements java.io.Serializable { *

* Constructor for AbilityMana. *

- * + * * @param sourceCard * a {@link forge.game.card.Card} object. */ @@ -111,7 +115,7 @@ public class AbilityManaPart implements java.io.Serializable { *

* produceMana. *

- * + * * @param produced * a {@link java.lang.String} object. * @param player @@ -169,7 +173,7 @@ public class AbilityManaPart implements java.io.Serializable { * cannotCounterPaidWith. *

* @param saBeingPaid - * + * * @return a {@link java.lang.String} object. */ public boolean cannotCounterPaidWith(SpellAbility saBeingPaid) { @@ -186,7 +190,7 @@ public class AbilityManaPart implements java.io.Serializable { * addKeywords. *

* @param saBeingPaid - * + * * @return a {@link java.lang.String} object. */ public boolean addKeywords(SpellAbility saBeingPaid) { @@ -205,7 +209,7 @@ public class AbilityManaPart implements java.io.Serializable { *

* getKeywords. *

- * + * * @return a {@link java.lang.String} object. */ public String getKeywords() { @@ -217,7 +221,7 @@ public class AbilityManaPart implements java.io.Serializable { * addsCounters. *

* @param saBeingPaid - * + * * @return a {@link java.lang.String} object. */ public boolean addsCounters(SpellAbility saBeingPaid) { @@ -227,10 +231,26 @@ public class AbilityManaPart implements java.io.Serializable { /** * createETBCounters */ - public void createETBCounters(Card c) { + public void createETBCounters(Card c, Player controller) { String[] parse = this.addsCounters.split("_"); // Convert random SVars if there are other cards with this effect if (c.isValid(parse[0], c.getController(), c, null)) { + final Game game = this.sourceCard.getGame(); + final Card eff = new Card(game.nextCardId(), game); + eff.setTimestamp(game.getNextTimestamp()); + eff.setName(sourceCard.getName() + "'s Effect"); + eff.addType("Effect"); + eff.setToken(true); // Set token to true, so when leaving play it gets nuked + eff.setOwner(controller); + + eff.setImageKey(sourceCard.getImageKey()); + eff.setColor(MagicColor.COLORLESS); + eff.setImmutable(true); + // try to get the SpellAbility from the mana ability + //eff.setEffectSource((SpellAbility)null); + + eff.addRemembered(c); + String abStr = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ " + parse[1] + " | ETB$ True | CounterNum$ " + parse[2]; @@ -240,15 +260,37 @@ public class AbilityManaPart implements java.io.Serializable { } CardFactoryUtil.setupETBReplacementAbility(sa); - String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield " - + " | Secondary$ True | Description$ CARDNAME" - + " enters the battlefield with " + CounterType.valueOf(parse[1]).getName() + " counters."; + String desc = "It enters the battlefield with "; + desc += Lang.nounWithNumeral(parse[2], CounterType.valueOf(parse[1]).getName() + " counter"); + desc += " on it."; - ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, c, false); + String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | Description$ " + desc; + + ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true); re.setLayer(ReplacementLayer.Other); re.setOverridingAbility(sa); - c.addReplacementEffect(re); + eff.addReplacementEffect(re); + + // Forgot Trigger + String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Any | TriggerZones$ Command | Static$ True"; + String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard"; + String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile" + + " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0"; + + SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, eff); + AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, eff); + saForget.setSubAbility(saExile); + + final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, eff, true); + parsedTrigger.setOverridingAbility(saForget); + eff.addTrigger(parsedTrigger); + 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, null); + game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); } } @@ -269,7 +311,7 @@ public class AbilityManaPart implements java.io.Serializable { *

* getManaRestrictions. *

- * + * * @return a {@link java.lang.String} object. */ public String getManaRestrictions() { @@ -280,7 +322,7 @@ public class AbilityManaPart implements java.io.Serializable { *

* meetsManaRestrictions. *

- * + * * @param sa * a {@link forge.game.spellability.SpellAbility} object. * @return a boolean. @@ -296,7 +338,7 @@ public class AbilityManaPart implements java.io.Serializable { if (restriction.equals("nonSpell")) { return !sa.isSpell(); } - + if (restriction.equals("CumulativeUpkeep")) { if (sa.isCumulativeupkeep()) { return true; @@ -349,7 +391,7 @@ public class AbilityManaPart implements java.io.Serializable { *

* mana. *

- * + * * @return a {@link java.lang.String} object. */ public final String mana() { @@ -438,7 +480,7 @@ public class AbilityManaPart implements java.io.Serializable { *

* canProduce. *

- * + * * @param s * a {@link java.lang.String} object. * @return a boolean. @@ -468,7 +510,7 @@ public class AbilityManaPart implements java.io.Serializable { *

* isBasic. *

- * + * * @return a boolean. */ public final boolean isBasic() { @@ -541,7 +583,7 @@ public class AbilityManaPart implements java.io.Serializable { public Card getSourceCard() { return sourceCard; } - + public void setSourceCard(final Card host) { sourceCard = host; } diff --git a/forge-game/src/main/java/forge/game/spellability/Spell.java b/forge-game/src/main/java/forge/game/spellability/Spell.java index 87fbb0d4e84..2978d406f34 100644 --- a/forge-game/src/main/java/forge/game/spellability/Spell.java +++ b/forge-game/src/main/java/forge/game/spellability/Spell.java @@ -75,12 +75,12 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable @Override public boolean canPlay() { Card card = this.getHostCard(); + if (card.isInZone(ZoneType.Battlefield)) { + return false; + } // Save the original cost and the face down info for a later check since the LKI copy will overwrite them ManaCost origCost = card.getState(card.isFaceDown() ? CardStateName.Original : card.getCurrentStateName()).getManaCost(); - boolean wasFaceDownInstant = card.isFaceDown() - && !card.getLastKnownZone().is(ZoneType.Battlefield) - && card.getState(CardStateName.Original).getType().isInstant(); Player activator = this.getActivatingPlayer(); if (activator == null) { @@ -95,61 +95,29 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable return false; } - boolean isInstant = card.isInstant(); - // special case for split cards - if (card.isSplitCard()) { - CardStateName name = isLeftSplit() ? CardStateName.LeftSplit : CardStateName.RightSplit; - isInstant = card.getState(name).getType().isInstant(); - } else if (isAdventure()) { - if (card.hasState(CardStateName.Adventure)) { - isInstant = card.getState(CardStateName.Adventure).getType().isInstant(); - } - } - boolean lkicheck = false; - boolean flash = false; // do performanceMode only for cases where the activator is different than controller - if (!Spell.performanceMode && activator != null && !card.getController().equals(activator) - && !card.isInZone(ZoneType.Battlefield)) { + if (!Spell.performanceMode && activator != null && !card.getController().equals(activator)) { // always make a lki copy in this case? card = CardUtil.getLKICopy(card); card.setController(activator, 0); lkicheck = true; } - if (isBestow() && !card.isBestowed() && !card.isInZone(ZoneType.Battlefield)) { - // Rule 601.3: cast Bestow with Flash - // for the check the card does need to be animated - // otherwise the StaticAbility will not found them - if (!card.isLKI()) { - card = CardUtil.getLKICopy(card); - } - card.animateBestow(false); - lkicheck = true; - } else if (isCastFaceDown()) { - // need a copy of the card to turn facedown without trigger anything - if (!card.isLKI()) { - card = CardUtil.getLKICopy(card); - } - card.turnFaceDownNoUpdate(); - lkicheck = true; - } else if (isAdventure()) { - if (!card.isLKI()) { - card = CardUtil.getLKICopy(card); - } - - card.setState(CardStateName.Adventure, false); + Card lkiHost = getAlternateHost(card); + if (lkiHost != null) { + card = lkiHost; lkicheck = true; } - if (lkicheck) { game.getTracker().freeze(); //prevent views flickering during while updating for state-based effects game.getAction().checkStaticAbilities(false, Sets.newHashSet(card), new CardCollection(card)); } - flash = card.withFlash(activator); + boolean isInstant = card.isInstant(); + boolean flash = card.withFlash(activator); // reset static abilities if (lkicheck) { @@ -160,8 +128,7 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable } if (!(isInstant || activator.canCastSorcery() || flash || getRestrictions().isInstantSpeed() - || hasSVar("IsCastFromPlayEffect") - || wasFaceDownInstant)) { + || hasSVar("IsCastFromPlayEffect"))) { return false; } @@ -235,4 +202,74 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable this.castFaceDown = faceDown; } + public Card getAlternateHost(Card source) { + boolean lkicheck = false; + + // need to be done before so it works with Vivien and Zoetic Cavern + if (source.isFaceDown() && source.isInZone(ZoneType.Exile)) { + if (!source.isLKI()) { + source = CardUtil.getLKICopy(source); + } + + source.turnFaceUp(false, false); + lkicheck = true; + } + + if (isBestow() && !source.isBestowed()) { + if (!source.isLKI()) { + source = CardUtil.getLKICopy(source); + } + + source.animateBestow(false); + lkicheck = true; + } else if (isCastFaceDown()) { + // need a copy of the card to turn facedown without trigger anything + if (!source.isLKI()) { + source = CardUtil.getLKICopy(source); + } + source.turnFaceDownNoUpdate(); + lkicheck = true; + } else if (isAdventure()) { + if (!source.isLKI()) { + source = CardUtil.getLKICopy(source); + } + + source.setState(CardStateName.Adventure, false); + + // need to reset CMC + source.setLKICMC(-1); + source.setLKICMC(source.getCMC()); + lkicheck = true; + } else if (source.isSplitCard() && (isLeftSplit() || isRightSplit())) { + if (!source.isLKI()) { + source = CardUtil.getLKICopy(source); + } + if (isLeftSplit()) { + if (!source.hasState(CardStateName.LeftSplit)) { + source.addAlternateState(CardStateName.LeftSplit, false); + source.getState(CardStateName.LeftSplit).copyFrom( + getHostCard().getState(CardStateName.LeftSplit), true); + } + + source.setState(CardStateName.LeftSplit, false); + } + + if (isRightSplit()) { + if (!source.hasState(CardStateName.RightSplit)) { + source.addAlternateState(CardStateName.RightSplit, false); + source.getState(CardStateName.RightSplit).copyFrom( + getHostCard().getState(CardStateName.RightSplit), true); + } + + source.setState(CardStateName.RightSplit, false); + } + + // need to reset CMC + source.setLKICMC(-1); + source.setLKICMC(source.getCMC()); + lkicheck = true; + } + + return lkicheck ? source : null; + } } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 75479eaadfc..2bfe8b388a4 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -1299,6 +1299,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit String announce = getParam("Announce"); if (StringUtils.isBlank(announce)) { mapParams.put("Announce", variable); + originalMapParams.put("Announce", variable); return; } String[] announcedOnes = TextUtil.split(announce, ','); @@ -1308,6 +1309,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } } mapParams.put("Announce", announce + ";" + variable); + originalMapParams.put("Announce", announce + ";" + variable); } public boolean isXCost() { diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java index f8442310ec0..421edc80b60 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java @@ -181,15 +181,9 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone layers.add(StaticAbilityLayer.MODIFYPT); } - if (hasParam("AddHiddenKeyword")) { - layers.add(StaticAbilityLayer.RULES); - } - - if (hasParam("IgnoreEffectCost") || hasParam("Goad") || hasParam("CanBlockAny") || hasParam("CanBlockAmount")) { - layers.add(StaticAbilityLayer.RULES); - } - - if (hasParam("AdjustLandPlays")) { + if (hasParam("AddHiddenKeyword") + || hasParam("IgnoreEffectCost") || hasParam("Goad") || hasParam("CanBlockAny") || hasParam("CanBlockAmount") + || hasParam("AdjustLandPlays") || hasParam("ControlVote") || hasParam("AdditionalVote") || hasParam("AdditionalOptionalVote")) { layers.add(StaticAbilityLayer.RULES); } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java index f0b0813bfc3..f6d8a9b6984 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -513,6 +513,20 @@ public final class StaticAbilityContinuous { } } + if (params.containsKey("ControlVote")) { + p.addControlVote(se.getTimestamp()); + } + if (params.containsKey("AdditionalVote")) { + String mhs = params.get("AdditionalVote"); + int add = AbilityUtils.calculateAmount(hostCard, mhs, stAb); + p.addAdditionalVote(se.getTimestamp(), add); + } + if (params.containsKey("AdditionalOptionalVote")) { + String mhs = params.get("AdditionalOptionalVote"); + int add = AbilityUtils.calculateAmount(hostCard, mhs, stAb); + p.addAdditionalOptionalVote(se.getTimestamp(), add); + } + if (params.containsKey("RaiseMaxHandSize")) { String rmhs = params.get("RaiseMaxHandSize"); int rmax = AbilityUtils.calculateAmount(hostCard, rmhs, stAb); diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java index 8dfcc69f698..33a8e48663e 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java @@ -274,7 +274,7 @@ public class TriggerHandler { } private void runStateTrigger(final Map runParams) { - for (final Trigger t: activeTriggers) { + for (final Trigger t: Lists.newArrayList(activeTriggers)) { if (canRunTrigger(t, TriggerType.Always, runParams)) { runSingleTrigger(t, runParams); } @@ -542,13 +542,20 @@ public class TriggerHandler { } sa = AbilityFactory.getAbility(host, name); + // need to set as Overriding Abiltiy so it can be copied better + regtrig.setOverridingAbility(sa); + } + sa.setActivatingPlayer(host.getController()); + + if (regtrig.isIntrinsic()) { + sa.setIntrinsic(true); + sa.changeText(); } } else { // need to copy the SA because of TriggeringObjects - sa = sa.copy(); + sa = sa.copy(host, host.getController(), false); } - sa.setHostCard(host); sa.setLastStateBattlefield(game.getLastStateBattlefield()); sa.setLastStateGraveyard(game.getLastStateGraveyard()); @@ -560,9 +567,7 @@ public class TriggerHandler { sa.setTriggeringObjects(regtrig.getStoredTriggeredObjects()); } - if (sa.getActivatingPlayer() == null) { // overriding delayed trigger should have set activator - sa.setActivatingPlayer(host.getController()); - } else if (sa.getDeltrigActivatingPlayer() != null) { + if (sa.getDeltrigActivatingPlayer() != null) { // make sure that the original delayed trigger activator is restored // (may have been overwritten by the AI simulation routines, e.g. Rainbow Vale) sa.setActivatingPlayer(sa.getDeltrigActivatingPlayer()); @@ -577,11 +582,6 @@ public class TriggerHandler { host.addRemembered(sa.getActivatingPlayer()); } - if (regtrig.isIntrinsic() && regtrig.getOverridingAbility() == null) { - sa.setIntrinsic(true); - sa.changeText(); - } - sa.setStackDescription(sa.toString()); if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { if (!CharmEffect.makeChoices(sa)) { diff --git a/forge-game/src/main/java/forge/trackable/TrackableProperty.java b/forge-game/src/main/java/forge/trackable/TrackableProperty.java index 742ab344d9f..039085ee9d0 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableProperty.java +++ b/forge-game/src/main/java/forge/trackable/TrackableProperty.java @@ -134,8 +134,12 @@ public enum TrackableProperty { HasUnlimitedLandPlay(TrackableTypes.BooleanType), NumLandThisTurn(TrackableTypes.IntegerType), NumDrawnThisTurn(TrackableTypes.IntegerType), + AdditionalVote(TrackableTypes.IntegerType), + OptionalAdditionalVote(TrackableTypes.IntegerType), + ControlVotes(TrackableTypes.BooleanType), Keywords(TrackableTypes.KeywordCollectionViewType, FreezeMode.IgnoresFreeze), Commander(TrackableTypes.CardViewCollectionType, FreezeMode.IgnoresFreeze), + CommanderCast(TrackableTypes.IntegerMapType), CommanderDamage(TrackableTypes.IntegerMapType), MindSlaveMaster(TrackableTypes.PlayerViewType), Ante(TrackableTypes.CardViewCollectionType, FreezeMode.IgnoresFreeze), diff --git a/forge-game/src/main/java/forge/trackable/TrackableTypes.java b/forge-game/src/main/java/forge/trackable/TrackableTypes.java index 6214563f8f7..0dce88f71e5 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableTypes.java +++ b/forge-game/src/main/java/forge/trackable/TrackableTypes.java @@ -100,17 +100,21 @@ public class TrackableTypes { if (newCollection != null) { //swap in objects in old collection for objects in new collection for (int i = 0; i < newCollection.size(); i++) { - T newObj = newCollection.get(i); - if (newObj != null) { - T existingObj = from.getTracker().getObj(itemType, newObj.getId()); - if (existingObj != null) { //if object exists already, update its changed properties - existingObj.copyChangedProps(newObj); - newCollection.remove(i); - newCollection.add(i, existingObj); - } - else { //if object is new, cache in object lookup - from.getTracker().putObj(itemType, newObj.getId(), newObj); + try { + T newObj = newCollection.get(i); + if (newObj != null) { + T existingObj = from.getTracker().getObj(itemType, newObj.getId()); + if (existingObj != null) { //if object exists already, update its changed properties + existingObj.copyChangedProps(newObj); + newCollection.remove(i); + newCollection.add(i, existingObj); + } + else { //if object is new, cache in object lookup + from.getTracker().putObj(itemType, newObj.getId(), newObj); + } } + } catch (IndexOutOfBoundsException e) { + System.err.println("got an IndexOutOfBoundsException, trying to continue ..."); } } } diff --git a/forge-gui-android/AndroidManifest.xml b/forge-gui-android/AndroidManifest.xml index 5edbcfad66e..9a984611ed7 100644 --- a/forge-gui-android/AndroidManifest.xml +++ b/forge-gui-android/AndroidManifest.xml @@ -6,7 +6,7 @@ + android:targetSdkVersion="26" /> diff --git a/forge-gui-android/pom.xml b/forge-gui-android/pom.xml index 76158aee896..854f9159678 100644 --- a/forge-gui-android/pom.xml +++ b/forge-gui-android/pom.xml @@ -19,7 +19,7 @@ forge forge - 1.6.33-SNAPSHOT + 1.6.34-SNAPSHOT forge-gui-android @@ -142,7 +142,7 @@ true - 25 + 26 true ${project.basedir}/AndroidManifest.xml @@ -183,7 +183,7 @@ false - 25 + 26 false diff --git a/forge-gui-android/project.properties b/forge-gui-android/project.properties index 735ea3db305..94206b9059a 100644 --- a/forge-gui-android/project.properties +++ b/forge-gui-android/project.properties @@ -9,4 +9,4 @@ # Project target. project.type=0 -target=android-20 +target=android-26 diff --git a/forge-gui-android/res/mipmap-hdpi/ic_launcher.png b/forge-gui-android/res/mipmap-hdpi/ic_launcher.png index 13ac6220632..4271e4c73a0 100644 Binary files a/forge-gui-android/res/mipmap-hdpi/ic_launcher.png and b/forge-gui-android/res/mipmap-hdpi/ic_launcher.png differ diff --git a/forge-gui-android/res/mipmap-hdpi/ic_launcher_foreground.png b/forge-gui-android/res/mipmap-hdpi/ic_launcher_foreground.png index a29e46aa510..a749b470515 100644 Binary files a/forge-gui-android/res/mipmap-hdpi/ic_launcher_foreground.png and b/forge-gui-android/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/forge-gui-android/res/mipmap-hdpi/ic_launcher_round.png b/forge-gui-android/res/mipmap-hdpi/ic_launcher_round.png index 07a75d76bfa..23880358c4f 100644 Binary files a/forge-gui-android/res/mipmap-hdpi/ic_launcher_round.png and b/forge-gui-android/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/forge-gui-android/res/mipmap-ldpi/ic_launcher.png b/forge-gui-android/res/mipmap-ldpi/ic_launcher.png index ee25ac432d9..ae40d73a33c 100644 Binary files a/forge-gui-android/res/mipmap-ldpi/ic_launcher.png and b/forge-gui-android/res/mipmap-ldpi/ic_launcher.png differ diff --git a/forge-gui-android/res/mipmap-mdpi/ic_launcher.png b/forge-gui-android/res/mipmap-mdpi/ic_launcher.png index 5fdd9db84cf..75cac9e8a25 100644 Binary files a/forge-gui-android/res/mipmap-mdpi/ic_launcher.png and b/forge-gui-android/res/mipmap-mdpi/ic_launcher.png differ diff --git a/forge-gui-android/res/mipmap-mdpi/ic_launcher_foreground.png b/forge-gui-android/res/mipmap-mdpi/ic_launcher_foreground.png index 524a6232021..bcb5f32aa66 100644 Binary files a/forge-gui-android/res/mipmap-mdpi/ic_launcher_foreground.png and b/forge-gui-android/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/forge-gui-android/res/mipmap-mdpi/ic_launcher_round.png b/forge-gui-android/res/mipmap-mdpi/ic_launcher_round.png index bb574ba21ae..fa0400329c5 100644 Binary files a/forge-gui-android/res/mipmap-mdpi/ic_launcher_round.png and b/forge-gui-android/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/forge-gui-android/res/mipmap-xhdpi/ic_launcher.png b/forge-gui-android/res/mipmap-xhdpi/ic_launcher.png index 5d5de8a444f..ad7c6ed96bb 100644 Binary files a/forge-gui-android/res/mipmap-xhdpi/ic_launcher.png and b/forge-gui-android/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/forge-gui-android/res/mipmap-xhdpi/ic_launcher_foreground.png b/forge-gui-android/res/mipmap-xhdpi/ic_launcher_foreground.png index 0e93289f2d1..25275afddb0 100644 Binary files a/forge-gui-android/res/mipmap-xhdpi/ic_launcher_foreground.png and b/forge-gui-android/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/forge-gui-android/res/mipmap-xhdpi/ic_launcher_round.png b/forge-gui-android/res/mipmap-xhdpi/ic_launcher_round.png index 96328be12a9..42f78d72996 100644 Binary files a/forge-gui-android/res/mipmap-xhdpi/ic_launcher_round.png and b/forge-gui-android/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/forge-gui-android/res/mipmap-xxhdpi/ic_launcher.png b/forge-gui-android/res/mipmap-xxhdpi/ic_launcher.png index e6ea9ce1897..3f9d487ce92 100644 Binary files a/forge-gui-android/res/mipmap-xxhdpi/ic_launcher.png and b/forge-gui-android/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/forge-gui-android/res/mipmap-xxhdpi/ic_launcher_foreground.png b/forge-gui-android/res/mipmap-xxhdpi/ic_launcher_foreground.png index a01a5036f4f..a01aaa6e9df 100644 Binary files a/forge-gui-android/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/forge-gui-android/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/forge-gui-android/res/mipmap-xxhdpi/ic_launcher_round.png b/forge-gui-android/res/mipmap-xxhdpi/ic_launcher_round.png index e4d1b24e68e..c1f4153cef3 100644 Binary files a/forge-gui-android/res/mipmap-xxhdpi/ic_launcher_round.png and b/forge-gui-android/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/forge-gui-android/res/mipmap-xxxhdpi/ic_launcher.png b/forge-gui-android/res/mipmap-xxxhdpi/ic_launcher.png index 3380a1063bb..6d186851cd6 100644 Binary files a/forge-gui-android/res/mipmap-xxxhdpi/ic_launcher.png and b/forge-gui-android/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/forge-gui-android/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/forge-gui-android/res/mipmap-xxxhdpi/ic_launcher_foreground.png index e8ccb890b9e..aaa2395dcb9 100644 Binary files a/forge-gui-android/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/forge-gui-android/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/forge-gui-android/res/mipmap-xxxhdpi/ic_launcher_round.png b/forge-gui-android/res/mipmap-xxxhdpi/ic_launcher_round.png index 68aca8009d4..8ad3506407f 100644 Binary files a/forge-gui-android/res/mipmap-xxxhdpi/ic_launcher_round.png and b/forge-gui-android/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/forge-gui-android/res/values/ic_launcher_background.xml b/forge-gui-android/res/values/ic_launcher_background.xml index 7f8bb682c03..ab983282473 100644 --- a/forge-gui-android/res/values/ic_launcher_background.xml +++ b/forge-gui-android/res/values/ic_launcher_background.xml @@ -1,4 +1,4 @@ - #f0f0f0 + #ffffff \ No newline at end of file diff --git a/forge-gui-desktop/pom.xml b/forge-gui-desktop/pom.xml index c2bb8f2388f..e1fa8d95747 100644 --- a/forge-gui-desktop/pom.xml +++ b/forge-gui-desktop/pom.xml @@ -4,7 +4,7 @@ forge forge - 1.6.33-SNAPSHOT + 1.6.34-SNAPSHOT forge-gui-desktop @@ -213,7 +213,7 @@ com.akathist.maven.plugins.launch4j launch4j-maven-plugin - 1.5.2 + 1.7.25 l4j-gui @@ -235,7 +235,10 @@ 1.8.0 - 1024 + 4096 + + -Dfile.encoding=UTF-8 + @@ -367,7 +370,7 @@ com.akathist.maven.plugins.launch4j launch4j-maven-plugin - 1.5.2 + 1.7.25 l4j-gui @@ -389,7 +392,10 @@ 1.8.0 - 1024 + 4096 + + -Dfile.encoding=UTF-8 + @@ -564,7 +570,7 @@