Merge branch 'Card-Forge:master' into charming

This commit is contained in:
tool4ever
2022-07-08 17:19:46 +02:00
committed by GitHub
3427 changed files with 29490 additions and 22325 deletions

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.50-SNAPSHOT</version> <version>1.6.54-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -119,10 +119,26 @@
<opts> <opts>
<opt>-Dfile.encoding=UTF-8</opt> <opt>-Dfile.encoding=UTF-8</opt>
<opt>--add-opens java.base/java.lang=ALL-UNNAMED</opt> <opt>--add-opens java.base/java.lang=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.math=ALL-UNNAMED</opt>
<opt>--add-opens java.base/jdk.internal.misc=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.nio=ALL-UNNAMED</opt>
<opt>--add-opens=java.base/sun.nio.ch=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.util=ALL-UNNAMED</opt> <opt>--add-opens java.base/java.util=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.lang.reflect=ALL-UNNAMED</opt> <opt>--add-opens java.base/java.lang.reflect=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.text=ALL-UNNAMED</opt> <opt>--add-opens java.base/java.text=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/java.awt=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/java.awt.font=ALL-UNNAMED</opt> <opt>--add-opens java.desktop/java.awt.font=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/java.awt.image=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/java.awt.color=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/sun.awt.image=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/javax.swing=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/javax.swing.border=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/javax.swing.event=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/sun.swing=ALL-UNNAMED</opt>
<opt>--add-opens java.desktop/java.beans=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.util.concurrent=ALL-UNNAMED</opt>
<opt>--add-opens java.base/java.net=ALL-UNNAMED</opt>
<opt>-Dio.netty.tryReflectionSetAccessible=true</opt>
</opts> </opts>
</jre> </jre>
<versionInfo> <versionInfo>
@@ -274,7 +290,7 @@
<dependency> <dependency>
<groupId>forge</groupId> <groupId>forge</groupId>
<artifactId>forge-gui-mobile</artifactId> <artifactId>forge-gui-mobile</artifactId>
<version>1.6.50-SNAPSHOT</version> <version>1.6.54-SNAPSHOT</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -10,7 +10,7 @@ java -version 1>nul 2>nul || (
for /f tokens^=2^ delims^=.-_^+^" %%j in ('java -fullversion 2^>^&1') do set "jver=%%j" for /f tokens^=2^ delims^=.-_^+^" %%j in ('java -fullversion 2^>^&1') do set "jver=%%j"
if %jver% GEQ 17 ( if %jver% GEQ 17 (
java --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$ java --add-opens java.desktop/java.beans=ALL-UNNAMED --add-opens java.desktop/javax.swing.border=ALL-UNNAMED --add-opens java.desktop/javax.swing.event=ALL-UNNAMED --add-opens java.desktop/sun.swing=ALL-UNNAMED --add-opens java.desktop/java.awt.image=ALL-UNNAMED --add-opens java.desktop/java.awt.color=ALL-UNNAMED --add-opens java.desktop/sun.awt.image=ALL-UNNAMED --add-opens java.desktop/javax.swing=ALL-UNNAMED --add-opens java.desktop/java.awt=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED -Dio.netty.tryReflectionSetAccessible=true -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$
popd popd
exit /b 0 exit /b 0
) )

View File

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

View File

@@ -20,6 +20,7 @@ package forge.ai;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
@@ -540,7 +541,7 @@ public class AiAttackController {
for (Card attacker : categorizedAttackers) { for (Card attacker : categorizedAttackers) {
if (!CombatUtil.canBeBlocked(attacker, accountedBlockers, null) if (!CombatUtil.canBeBlocked(attacker, accountedBlockers, null)
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { || StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) {
unblockedAttackers.add(attacker); unblockedAttackers.add(attacker);
} else { } else {
if (predictEvasion) { if (predictEvasion) {

View File

@@ -42,6 +42,7 @@ import forge.game.cost.Cost;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
import forge.game.staticability.StaticAbilityCantAttackBlock; import forge.game.staticability.StaticAbilityCantAttackBlock;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
@@ -204,7 +205,7 @@ public class AiBlockController {
} }
blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers); blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
// 2.Blockers that won't get destroyed // 2.Blockers that won't get destroyed
} else if (!attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") } else if (!StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)
&& !ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) { && !ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers); blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers);
// check whether it's better to block a creature without trample to absorb more damage // check whether it's better to block a creature without trample to absorb more damage
@@ -215,7 +216,7 @@ public class AiBlockController {
|| other.hasKeyword(Keyword.TRAMPLE) || other.hasKeyword(Keyword.TRAMPLE)
|| ComputerUtilCombat.attackerHasThreateningAfflict(other, ai) || ComputerUtilCombat.attackerHasThreateningAfflict(other, ai)
|| ComputerUtilCombat.canDestroyBlocker(ai, blocker, other, combat, false) || ComputerUtilCombat.canDestroyBlocker(ai, blocker, other, combat, false)
|| other.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { || StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(other)) {
continue; continue;
} }
@@ -668,7 +669,7 @@ public class AiBlockController {
Card attacker = attackers.get(0); Card attacker = attackers.get(0);
if (CombatUtil.getMinNumBlockersForAttacker(attacker, combat.getDefenderPlayerByAttacker(attacker)) > 1 if (CombatUtil.getMinNumBlockersForAttacker(attacker, combat.getDefenderPlayerByAttacker(attacker)) > 1
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") || StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)
|| ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) { || ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
attackers.remove(0); attackers.remove(0);
makeChumpBlocks(combat, attackers); makeChumpBlocks(combat, attackers);
@@ -689,7 +690,7 @@ public class AiBlockController {
} }
if (other.getNetCombatDamage() >= damageAbsorbed if (other.getNetCombatDamage() >= damageAbsorbed
&& !other.hasKeyword(Keyword.TRAMPLE) && !other.hasKeyword(Keyword.TRAMPLE)
&& !other.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") && !StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(other)
&& !ComputerUtilCombat.attackerHasThreateningAfflict(other, ai) && !ComputerUtilCombat.attackerHasThreateningAfflict(other, ai)
&& CombatUtil.canBlock(other, blocker, combat)) { && CombatUtil.canBlock(other, blocker, combat)) {
combat.addBlocker(other, blocker); combat.addBlocker(other, blocker);
@@ -756,7 +757,7 @@ public class AiBlockController {
for (final Card attacker : tramplingAttackers) { for (final Card attacker : tramplingAttackers) {
if (CombatUtil.getMinNumBlockersForAttacker(attacker, combat.getDefenderPlayerByAttacker(attacker)) > combat.getBlockers(attacker).size() if (CombatUtil.getMinNumBlockersForAttacker(attacker, combat.getDefenderPlayerByAttacker(attacker)) > combat.getBlockers(attacker).size()
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") || StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue; continue;
} }
@@ -945,6 +946,39 @@ public class AiBlockController {
} }
} }
private void makeRequiredBlocks(Combat combat) {
// assign blockers that have to block
final CardCollection chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able.");
// if an attacker with lure attacks - all that can block
for (final Card blocker : blockersLeft) {
if (CombatUtil.mustBlockAnAttacker(blocker, combat, null)) {
chumpBlockers.add(blocker);
}
}
if (!chumpBlockers.isEmpty()) {
for (final Card attacker : attackers) {
List<Card> blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
for (final Card blocker : blockers) {
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
&& (CombatUtil.mustBlockAnAttacker(blocker, combat, null)
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
combat.addBlocker(attacker, blocker);
if (!blocker.getMustBlockCards().isEmpty()) {
int mustBlockAmt = blocker.getMustBlockCards().size();
final CardCollectionView blockedSoFar = combat.getAttackersBlockedBy(blocker);
boolean canBlockAnother = CombatUtil.canBlockMoreCreatures(blocker, blockedSoFar);
if (!canBlockAnother || mustBlockAmt == blockedSoFar.size()) {
blockersLeft.remove(blocker);
}
} else {
blockersLeft.remove(blocker);
}
}
}
}
}
}
private void clearBlockers(final Combat combat, final List<Card> possibleBlockers) { private void clearBlockers(final Combat combat, final List<Card> possibleBlockers) {
for (final Card blocker : CardLists.filterControlledBy(combat.getAllBlockers(), ai)) { for (final Card blocker : CardLists.filterControlledBy(combat.getAllBlockers(), ai)) {
// don't touch other player's blockers // don't touch other player's blockers
@@ -1008,9 +1042,6 @@ public class AiBlockController {
clearBlockers(combat, possibleBlockers); clearBlockers(combat, possibleBlockers);
List<Card> blockers;
List<Card> chumpBlockers;
diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade
if (ai.getController().isAI() && diff > 0 && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.PLAY_AGGRO)) { if (ai.getController().isAI() && diff > 0 && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.PLAY_AGGRO)) {
diff = 0; diff = 0;
@@ -1106,37 +1137,9 @@ public class AiBlockController {
} }
} }
// assign blockers that have to block // block requirements
chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."); // TODO because this isn't done earlier, sometimes a good block will enforce a restriction that prevents another for the requirement
// if an attacker with lure attacks - all that can block makeRequiredBlocks(combat);
for (final Card blocker : blockersLeft) {
if (CombatUtil.mustBlockAnAttacker(blocker, combat, null)) {
chumpBlockers.add(blocker);
}
}
if (!chumpBlockers.isEmpty()) {
CardLists.shuffle(attackers);
for (final Card attacker : attackers) {
blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
for (final Card blocker : blockers) {
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
&& (CombatUtil.mustBlockAnAttacker(blocker, combat, null)
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
combat.addBlocker(attacker, blocker);
if (!blocker.getMustBlockCards().isEmpty()) {
int mustBlockAmt = blocker.getMustBlockCards().size();
final CardCollectionView blockedSoFar = combat.getAttackersBlockedBy(blocker);
boolean canBlockAnother = CombatUtil.canBlockMoreCreatures(blocker, blockedSoFar);
if (!canBlockAnother || mustBlockAmt == blockedSoFar.size()) {
blockersLeft.remove(blocker);
}
} else {
blockersLeft.remove(blocker);
}
}
}
}
}
// check to see if it's possible to defend a Planeswalker under attack with a chump block, // check to see if it's possible to defend a Planeswalker under attack with a chump block,
// unless life is low enough to be more worried about saving preserving the life total // unless life is low enough to be more worried about saving preserving the life total
@@ -1186,8 +1189,7 @@ public class AiBlockController {
* Orders a blocker that put onto the battlefield blocking. Depends heavily * Orders a blocker that put onto the battlefield blocking. Depends heavily
* on the implementation of orderBlockers(). * on the implementation of orderBlockers().
*/ */
public static CardCollection orderBlocker(final Card attacker, final Card blocker, public static CardCollection orderBlocker(final Card attacker, final Card blocker, final CardCollection oldBlockers) {
final CardCollection oldBlockers) {
// add blocker to existing ordering // add blocker to existing ordering
// sort by evaluate, then insert it appropriately // sort by evaluate, then insert it appropriately
// relies on current implementation of orderBlockers() // relies on current implementation of orderBlockers()

View File

@@ -889,6 +889,10 @@ public class AiController {
spellHost.setLastKnownZone(game.getStackZone()); // need to add to stack to make check Restrictions respect stack cmc spellHost.setLastKnownZone(game.getStackZone()); // need to add to stack to make check Restrictions respect stack cmc
spellHost.setCastFrom(card.getZone()); spellHost.setCastFrom(card.getZone());
} }
// TODO maybe other location for this?
if (!sa.isLegalAfterStack()) {
return AiPlayDecision.AnotherTime;
}
if (!sa.checkRestrictions(spellHost, player)) { if (!sa.checkRestrictions(spellHost, player)) {
return AiPlayDecision.AnotherTime; return AiPlayDecision.AnotherTime;
} }
@@ -1322,7 +1326,7 @@ public class AiController {
return discardList; return discardList;
} }
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
if (mode == PlayerActionConfirmMode.AlternativeDamageAssignment) { if (mode == PlayerActionConfirmMode.AlternativeDamageAssignment) {
return true; return true;
} }
@@ -1335,7 +1339,7 @@ public class AiController {
mode); mode);
throw new IllegalArgumentException(exMsg); throw new IllegalArgumentException(exMsg);
} }
return SpellApiToAi.Converter.get(api).confirmAction(player, sa, mode, message); return SpellApiToAi.Converter.get(api).confirmAction(player, sa, mode, message, params);
} }
public boolean confirmBidAction(SpellAbility sa, PlayerActionConfirmMode mode, String message, int bid, Player winner) { public boolean confirmBidAction(SpellAbility sa, PlayerActionConfirmMode mode, String message, int bid, Player winner) {
@@ -1925,8 +1929,8 @@ public class AiController {
return Math.min(player.getLife() -1,MyRandom.getRandom().nextInt(Math.max(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1); return Math.min(player.getLife() -1,MyRandom.getRandom().nextInt(Math.max(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1);
} else if ("HighestGetCounter".equals(logic)) { } else if ("HighestGetCounter".equals(logic)) {
return MyRandom.getRandom().nextInt(3); return MyRandom.getRandom().nextInt(3);
} else if (source.hasSVar("EnergyToPay")) { } else if (sa.hasSVar("EnergyToPay")) {
return AbilityUtils.calculateAmount(source, source.getSVar("EnergyToPay"), sa); return AbilityUtils.calculateAmount(source, sa.getSVar("EnergyToPay"), sa);
} else if ("Vermin".equals(logic)) { } else if ("Vermin".equals(logic)) {
return MyRandom.getRandom().nextInt(Math.max(player.getLife() - 5, 0)); return MyRandom.getRandom().nextInt(Math.max(player.getLife() - 5, 0));
} else if ("SweepCreatures".equals(logic)) { } else if ("SweepCreatures".equals(logic)) {

View File

@@ -48,6 +48,7 @@ import forge.game.replacement.ReplacementLayer;
import forge.game.replacement.ReplacementType; import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
import forge.game.staticability.StaticAbilityMustAttack; import forge.game.staticability.StaticAbilityMustAttack;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
@@ -325,8 +326,7 @@ public class ComputerUtilCombat {
final List<Card> blockers = combat.getBlockers(attacker); final List<Card> blockers = combat.getBlockers(attacker);
if (blockers.size() == 0 if (blockers.size() == 0
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage " || StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) {
+ "as though it weren't blocked.")) {
unblocked.add(attacker); unblocked.add(attacker);
} else if (attacker.hasKeyword(Keyword.TRAMPLE) } else if (attacker.hasKeyword(Keyword.TRAMPLE)
&& getAttack(attacker) > totalShieldDamage(attacker, blockers)) { && getAttack(attacker) > totalShieldDamage(attacker, blockers)) {
@@ -367,8 +367,7 @@ public class ComputerUtilCombat {
final List<Card> blockers = combat.getBlockers(attacker); final List<Card> blockers = combat.getBlockers(attacker);
if (blockers.size() == 0 if (blockers.size() == 0
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage" || StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) {
+ " as though it weren't blocked.")) {
unblocked.add(attacker); unblocked.add(attacker);
} else if (attacker.hasKeyword(Keyword.TRAMPLE) } else if (attacker.hasKeyword(Keyword.TRAMPLE)
&& getAttack(attacker) > totalShieldDamage(attacker, blockers)) { && getAttack(attacker) > totalShieldDamage(attacker, blockers)) {

View File

@@ -10,6 +10,7 @@ import forge.game.card.CounterEnumType;
import forge.game.cost.CostPayEnergy; import forge.game.cost.CostPayEnergy;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
import forge.game.staticability.StaticAbilityMustAttack; import forge.game.staticability.StaticAbilityMustAttack;
import java.util.List; import java.util.List;
@@ -62,7 +63,8 @@ public class CreatureEvaluator implements Function<Card, Integer> {
if (c.hasKeyword("Unblockable")) { if (c.hasKeyword("Unblockable")) {
value += addValue(power * 10, "unblockable"); value += addValue(power * 10, "unblockable");
} else { } else {
if (c.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { if (StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(c)
|| StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(c, false)) {
value += addValue(power * 6, "thorns"); value += addValue(power * 6, "thorns");
} }
if (c.hasKeyword(Keyword.FEAR)) { if (c.hasKeyword(Keyword.FEAR)) {

View File

@@ -15,7 +15,6 @@ import forge.card.MagicColor;
import forge.card.mana.ManaAtom; import forge.card.mana.ManaAtom;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.effects.DetachedCardEffect; import forge.game.ability.effects.DetachedCardEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -207,7 +206,7 @@ public abstract class GameState {
cardsReferencedByID.add(card.getExiledWith()); cardsReferencedByID.add(card.getExiledWith());
} }
if (zone == ZoneType.Battlefield) { if (zone == ZoneType.Battlefield) {
if (!card.getAttachedCards().isEmpty()) { if (card.hasCardAttachments()) {
// Remember the ID of cards that have attachments // Remember the ID of cards that have attachments
cardsReferencedByID.add(card); cardsReferencedByID.add(card);
} }
@@ -376,7 +375,7 @@ public abstract class GameState {
newText.append("|Imprinting:").append(TextUtil.join(imprintedCardIds, ",")); newText.append("|Imprinting:").append(TextUtil.join(imprintedCardIds, ","));
} }
if (!c.getMergedCards().isEmpty()) { if (c.hasMergedCard()) {
List<String> mergedCardNames = new ArrayList<>(); List<String> mergedCardNames = new ArrayList<>();
for (Card merged : c.getMergedCards()) { for (Card merged : c.getMergedCards()) {
if (c.getTopMergedCard() == merged) { if (c.getTopMergedCard() == merged) {
@@ -862,9 +861,7 @@ public abstract class GameState {
} }
if (sa.hasParam("RememberTargets")) { if (sa.hasParam("RememberTargets")) {
for (final GameObject o : sa.getTargets()) { sa.getHostCard().addRemembered(sa.getTargets());
sa.getHostCard().addRemembered(o);
}
} }
} }

View File

@@ -270,8 +270,8 @@ public class PlayerControllerAi extends PlayerController {
} }
@Override @Override
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return getAi().confirmAction(sa, mode, message); return getAi().confirmAction(sa, mode, message, params);
} }
@Override @Override

View File

@@ -305,7 +305,7 @@ public abstract class SpellAbilityAi {
return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb)); return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
} }
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method"); System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return true; return true;
} }

View File

@@ -71,6 +71,7 @@ public enum SpellApiToAi {
.put(ApiType.DigMultiple, DigMultipleAi.class) .put(ApiType.DigMultiple, DigMultipleAi.class)
.put(ApiType.DigUntil, DigUntilAi.class) .put(ApiType.DigUntil, DigUntilAi.class)
.put(ApiType.Discard, DiscardAi.class) .put(ApiType.Discard, DiscardAi.class)
.put(ApiType.Draft, ChooseCardNameAi.class)
.put(ApiType.DrainMana, DrainManaAi.class) .put(ApiType.DrainMana, DrainManaAi.class)
.put(ApiType.Draw, DrawAi.class) .put(ApiType.Draw, DrawAi.class)
.put(ApiType.EachDamage, DamageEachAi.class) .put(ApiType.EachDamage, DamageEachAi.class)
@@ -169,6 +170,7 @@ public enum SpellApiToAi {
.put(ApiType.StoreSVar, StoreSVarAi.class) .put(ApiType.StoreSVar, StoreSVarAi.class)
.put(ApiType.Subgame, AlwaysPlayAi.class) .put(ApiType.Subgame, AlwaysPlayAi.class)
.put(ApiType.Surveil, SurveilAi.class) .put(ApiType.Surveil, SurveilAi.class)
.put(ApiType.TakeInitiative, AlwaysPlayAi.class)
.put(ApiType.Tap, TapAi.class) .put(ApiType.Tap, TapAi.class)
.put(ApiType.TapAll, TapAllAi.class) .put(ApiType.TapAll, TapAllAi.class)
.put(ApiType.TapOrUntap, TapOrUntapAi.class) .put(ApiType.TapOrUntap, TapOrUntapAi.class)
@@ -184,7 +186,6 @@ public enum SpellApiToAi {
.put(ApiType.WinsGame, GameWinAi.class) .put(ApiType.WinsGame, GameWinAi.class)
.put(ApiType.DamageResolve, AlwaysPlayAi.class) .put(ApiType.DamageResolve, AlwaysPlayAi.class)
.put(ApiType.InternalEtbReplacement, CanPlayAsDrawbackAi.class)
.put(ApiType.InternalLegendaryRule, LegendaryRuleAi.class) .put(ApiType.InternalLegendaryRule, LegendaryRuleAi.class)
.put(ApiType.InternalIgnoreEffect, CannotPlayAi.class) .put(ApiType.InternalIgnoreEffect, CannotPlayAi.class)
.build()); .build());

View File

@@ -1,6 +1,8 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Map;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerActionConfirmMode;
@@ -16,7 +18,7 @@ public class AlwaysPlayAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }
} }

View File

@@ -85,7 +85,7 @@ public class AmassAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }

View File

@@ -240,7 +240,7 @@ public class AnimateAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return player.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2); return player.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2);
} }
@@ -500,17 +500,19 @@ public class AnimateAi extends SpellAbilityAi {
} }
// give sVars // give sVars
if (sVars.size() > 0) { if (sa.hasParam("sVars")) {
for (final String s : sVars) { Map<String, String> sVarsMap = Maps.newHashMap();
String actualsVar = source.getSVar(s); for (final String s : sa.getParam("sVars").split(",")) {
String actualsVar = AbilityUtils.getSVar(sa, s);
String name = s; String name = s;
if (actualsVar.startsWith("SVar:")) { if (actualsVar.startsWith("SVar:")) {
actualsVar = actualsVar.split("SVar:")[1]; actualsVar = actualsVar.split("SVar:")[1];
name = actualsVar.split(":")[0]; name = actualsVar.split(":")[0];
actualsVar = actualsVar.split(":")[1]; actualsVar = actualsVar.split(":")[1];
} }
card.setSVar(name, actualsVar); sVarsMap.put(name, actualsVar);
} }
card.addChangedSVars(sVarsMap, timestamp, 0);
} }
ComputerUtilCard.applyStaticContPT(game, card, null); ComputerUtilCard.applyStaticContPT(game, card, null);
} }

View File

@@ -1729,7 +1729,7 @@ public class AttachAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }

View File

@@ -1,35 +0,0 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class CanPlayAsDrawbackAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
/**
* <p>
* copySpellTriggerAI.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return false;
}
}

View File

@@ -1739,7 +1739,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
// AI was never asked // AI was never asked
return true; return true;
} }

View File

@@ -1,6 +1,7 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Collections; import java.util.Collections;
import java.util.Map;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
@@ -334,7 +335,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */
@Override @Override
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final String hostName = source.getName(); final String hostName = source.getName();
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0); final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);

View File

@@ -154,7 +154,7 @@ public class CloneAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
if (sa.hasParam("AILogic") && (!sa.usesTargeting() || sa.isTargetNumberValid())) { if (sa.hasParam("AILogic") && (!sa.usesTargeting() || sa.isTargetNumberValid())) {
// Had a special logic for it and managed to target, so confirm if viable // Had a special logic for it and managed to target, so confirm if viable
if ("CloneBestCreature".equals(sa.getParam("AILogic"))) { if ("CloneBestCreature".equals(sa.getParam("AILogic"))) {

View File

@@ -224,7 +224,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
//TODO: add logic here //TODO: add logic here
return true; return true;
} }

View File

@@ -134,7 +134,7 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
// Chain of Acid requires special attention here since otherwise the AI will confirm the copy and then // Chain of Acid requires special attention here since otherwise the AI will confirm the copy and then
// run into the necessity of confirming a mandatory Destroy, thus destroying all of its own permanents. // run into the necessity of confirming a mandatory Destroy, thus destroying all of its own permanents.
if ("ChainOfAcid".equals(sa.getParam("AILogic"))) { if ("ChainOfAcid".equals(sa.getParam("AILogic"))) {

View File

@@ -752,14 +752,16 @@ public class CountersPutAi extends CountersAi {
final int amount = AbilityUtils.calculateAmount(source, amountStr, sa); final int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
int left = amount; int left = amount;
final String[] types; final String[] types;
String type = "";
if (sa.hasParam("CounterType")) { if (sa.hasParam("CounterType")) {
// TODO some cards let you choose types, should check each // TODO some cards let you choose types, should check each
types = sa.getParam("CounterType").split(","); types = sa.getParam("CounterType").split(",");
} else { type = types[0];
} else if (sa.hasParam("CounterTypes")) {
// all types will be added // all types will be added
types = sa.getParam("CounterTypes").split(","); types = sa.getParam("CounterTypes").split(",");
type = types[0];
} }
final String type = types[0];
if (!sa.usesTargeting()) { if (!sa.usesTargeting()) {
// No target. So must be defined // No target. So must be defined
@@ -911,7 +913,7 @@ public class CountersPutAi extends CountersAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
if (mode == PlayerActionConfirmMode.Tribute) { if (mode == PlayerActionConfirmMode.Tribute) {
// add counter if that opponent has a giant creature // add counter if that opponent has a giant creature

View File

@@ -1,6 +1,7 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@@ -151,7 +152,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return player.getCreaturesInPlay().size() >= player.getWeakestOpponent().getCreaturesInPlay().size(); return player.getCreaturesInPlay().size() >= player.getWeakestOpponent().getCreaturesInPlay().size();
} }

View File

@@ -53,6 +53,7 @@ import forge.util.MyRandom;
public class DamageDealAi extends DamageAiBase { public class DamageDealAi extends DamageAiBase {
@Override @Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) { public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final SpellAbility root = sa.getRootAbility();
final String damage = sa.getParam("NumDmg"); final String damage = sa.getParam("NumDmg");
Card source = sa.getHostCard(); Card source = sa.getHostCard();
int dmg = AbilityUtils.calculateAmount(source, damage, sa); int dmg = AbilityUtils.calculateAmount(source, damage, sa);
@@ -76,7 +77,7 @@ public class DamageDealAi extends DamageAiBase {
if (dmg > energy || dmg < 1) { if (dmg > energy || dmg < 1) {
continue; // in case the calculation gets messed up somewhere continue; // in case the calculation gets messed up somewhere
} }
source.setSVar("EnergyToPay", "Number$" + dmg); root.setSVar("EnergyToPay", "Number$" + dmg);
return true; return true;
} }
} }
@@ -1075,6 +1076,11 @@ public class DamageDealAi extends DamageAiBase {
return null; return null;
} }
// chaining to this could miscalculate
if (sa.isDividedAsYouChoose()) {
return null;
}
// Try to chain damage/debuff effects // Try to chain damage/debuff effects
if (StringUtils.isNumeric(damage) || (damage.startsWith("-") && StringUtils.isNumeric(damage.substring(1)))) { if (StringUtils.isNumeric(damage) || (damage.startsWith("-") && StringUtils.isNumeric(damage.substring(1)))) {
// currently only works for predictable numeric damage // currently only works for predictable numeric damage

View File

@@ -1,5 +1,7 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Map;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
@@ -30,7 +32,7 @@ public class DayTimeAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }
} }

View File

@@ -353,12 +353,10 @@ public class DestroyAi extends SpellAbilityAi {
// Filter AI-specific targets if provided // Filter AI-specific targets if provided
preferred = ComputerUtil.filterAITgts(sa, ai, preferred, true); preferred = ComputerUtil.filterAITgts(sa, ai, preferred, true);
for (final Card c : preferred) { list.removeAll(preferred);
list.remove(c);
}
if (preferred.isEmpty() && !mandatory) { if (preferred.isEmpty() && !mandatory) {
return false; return false;
} }
while (sa.canAddMoreTarget()) { while (sa.canAddMoreTarget()) {

View File

@@ -209,7 +209,7 @@ public class DigAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
Card topc = player.getZone(ZoneType.Library).get(0); Card topc = player.getZone(ZoneType.Library).get(0);
// AI actions for individual cards (until this AI can be generalized) // AI actions for individual cards (until this AI can be generalized)

View File

@@ -1,5 +1,7 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Map;
import forge.ai.AiAttackController; import forge.ai.AiAttackController;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
@@ -94,7 +96,7 @@ public class DigMultipleAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }
} }

View File

@@ -1,6 +1,7 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import java.util.List;
import java.util.Map;
import forge.ai.AiAttackController; import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
@@ -122,7 +123,7 @@ public class DigUntilAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
if (sa.hasParam("AILogic")) { if (sa.hasParam("AILogic")) {
final String logic = sa.getParam("AILogic"); final String logic = sa.getParam("AILogic");
if ("OathOfDruids".equals(logic)) { if ("OathOfDruids".equals(logic)) {

View File

@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
@@ -211,11 +212,11 @@ public class DiscardAi extends SpellAbilityAi {
return true; return true;
} }
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
if (mode == PlayerActionConfirmMode.Random) { if (mode == PlayerActionConfirmMode.Random) {
// TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards // TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards
return true; return true;
} }
return super.confirmAction(player, sa, mode, message); return super.confirmAction(player, sa, mode, message, params);
} }
} }

View File

@@ -18,6 +18,8 @@
*/ */
package forge.ai.ability; package forge.ai.ability;
import java.util.Map;
import forge.ai.AiCostDecision; import forge.ai.AiCostDecision;
import forge.ai.AiProps; import forge.ai.AiProps;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
@@ -102,7 +104,7 @@ public class DrawAi extends SpellAbilityAi {
return false; return false;
} }
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source,sa)) { if (!ComputerUtilCost.checkDiscardCost(ai, cost, source, sa)) {
AiCostDecision aiDecisions = new AiCostDecision(ai, sa, false); AiCostDecision aiDecisions = new AiCostDecision(ai, sa, false);
for (final CostPart part : cost.getCostParts()) { for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostDiscard) { if (part instanceof CostDiscard) {
@@ -541,7 +543,7 @@ public class DrawAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) : 1; int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) : 1;
// AI shouldn't mill itself // AI shouldn't mill itself
if (numCards < player.getZone(ZoneType.Library).size()) if (numCards < player.getZone(ZoneType.Library).size())

View File

@@ -70,7 +70,7 @@ public final class EncodeAi extends SpellAbilityAi {
* forge.game.player.PlayerActionConfirmMode, java.lang.String) * forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
// only try to encode if there is a creature it can be used on // only try to encode if there is a creature it can be used on
return chooseCard(player, player.getCreaturesInPlay(), true) != null; return chooseCard(player, player.getCreaturesInPlay(), true) != null;
} }

View File

@@ -1,5 +1,7 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Map;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.card.Card;
@@ -42,7 +44,7 @@ public class FlipOntoBattlefieldAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }
} }

View File

@@ -31,6 +31,11 @@ public class ImmediateTriggerAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
// always add to stack, targeting happens after payment
if (mandatory) {
return true;
}
String logic = sa.getParamOrDefault("AILogic", ""); String logic = sa.getParamOrDefault("AILogic", "");
SpellAbility trigsa = sa.getAdditionalAbility("Execute"); SpellAbility trigsa = sa.getAdditionalAbility("Execute");
@@ -45,11 +50,7 @@ public class ImmediateTriggerAi extends SpellAbilityAi {
AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
trigsa.setActivatingPlayer(ai); trigsa.setActivatingPlayer(ai);
if (!sa.hasParam("OptionalDecider")) { return aic.doTrigger(trigsa, !"You".equals(sa.getParamOrDefault("OptionalDecider", "You")));
return aic.doTrigger(trigsa, true);
} else {
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
}
} }
@Override @Override

View File

@@ -1,6 +1,8 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Map;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
@@ -20,7 +22,7 @@ public class InvestigateAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }
} }

View File

@@ -1,6 +1,8 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Map;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.PlayerControllerAi; import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
@@ -32,7 +34,7 @@ public class LearnAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }

View File

@@ -43,7 +43,7 @@ public class ManifestAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }

View File

@@ -196,7 +196,7 @@ public class MillAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
if ("TimmerianFiends".equals(sa.getParam("AILogic"))) { if ("TimmerianFiends".equals(sa.getParam("AILogic"))) {
return SpecialCardAi.TimmerianFiends.consider(player, sa); return SpecialCardAi.TimmerianFiends.consider(player, sa);
} }

View File

@@ -65,7 +65,7 @@ public class MutateAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }
} }

View File

@@ -1,5 +1,7 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Map;
import forge.ai.AiAttackController; import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi; import forge.ai.SpellApiToAi;
@@ -70,7 +72,7 @@ public class PeekAndRevealAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
AbilitySub subAb = sa.getSubAbility(); AbilitySub subAb = sa.getSubAbility();
return subAb != null && SpellApiToAi.Converter.get(subAb.getApi()).chkDrawbackWithSubs(player, subAb); return subAb != null && SpellApiToAi.Converter.get(subAb.getApi()).chkDrawbackWithSubs(player, subAb);
} }

View File

@@ -131,7 +131,7 @@ public class PlayAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }

View File

@@ -24,6 +24,7 @@ import org.apache.commons.lang3.StringUtils;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
public class PumpAi extends PumpAiBase { public class PumpAi extends PumpAiBase {
@@ -731,7 +732,7 @@ public class PumpAi extends PumpAiBase {
if (minus > energy || minus < 1) { if (minus > energy || minus < 1) {
continue; // in case the calculation gets messed up somewhere continue; // in case the calculation gets messed up somewhere
} }
source.setSVar("EnergyToPay", "Number$" + minus); root.setSVar("EnergyToPay", "Number$" + minus);
return true; return true;
} }
} }
@@ -781,7 +782,7 @@ public class PumpAi extends PumpAiBase {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
//TODO Add logic here if necessary but I think the AI won't cast //TODO Add logic here if necessary but I think the AI won't cast
//the spell in the first place if it would curse its own creature //the spell in the first place if it would curse its own creature
//and the pump isn't mandatory //and the pump isn't mandatory

View File

@@ -1,6 +1,8 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Map;
import forge.ai.AiCardMemory; import forge.ai.AiCardMemory;
import forge.ai.AiController; import forge.ai.AiController;
import forge.ai.AiProps; import forge.ai.AiProps;
@@ -92,7 +94,7 @@ public class RearrangeTopOfLibraryAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
// Confirming this action means shuffling the library if asked. // Confirming this action means shuffling the library if asked.
// First, let's check if we can play the top card of the library // First, let's check if we can play the top card of the library

View File

@@ -1,6 +1,8 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Map;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.ai.*; import forge.ai.*;
@@ -40,7 +42,7 @@ public class RepeatAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
//TODO add logic to have computer make better choice (ArsenalNut) //TODO add logic to have computer make better choice (ArsenalNut)
return false; return false;
} }

View File

@@ -1,6 +1,8 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Map;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.Game; import forge.game.Game;
import forge.game.card.Card; import forge.game.card.Card;
@@ -43,7 +45,7 @@ public class RollDiceAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }
} }

View File

@@ -1,6 +1,7 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import java.util.List;
import java.util.Map;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
@@ -175,7 +176,7 @@ public class SacrificeAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }

View File

@@ -1,5 +1,7 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Map;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import forge.ai.ComputerUtilMana; import forge.ai.ComputerUtilMana;
@@ -132,7 +134,7 @@ public class ScryAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }
} }

View File

@@ -1,6 +1,7 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
@@ -264,7 +265,7 @@ public class SetStateAi extends SpellAbilityAi {
return true; return true;
} }
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
// TODO: improve the AI for when it may want to transform something that's optional to transform // TODO: improve the AI for when it may want to transform something that's optional to transform
return isSafeToTransformIntoLegendary(player, sa.getHostCard()); return isSafeToTransformIntoLegendary(player, sa.getHostCard());
} }

View File

@@ -1,5 +1,7 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Map;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
@@ -54,7 +56,7 @@ public class ShuffleAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
// ai could analyze parameter denoting the player to shuffle // ai could analyze parameter denoting the player to shuffle
return true; return true;
} }

View File

@@ -1,5 +1,7 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Map;
import forge.ai.AiAttackController; import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
@@ -18,7 +20,7 @@ public class SkipPhaseAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }

View File

@@ -1,5 +1,7 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Map;
import forge.ai.AiCardMemory; import forge.ai.AiCardMemory;
import forge.ai.AiProps; import forge.ai.AiProps;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
@@ -119,7 +121,7 @@ public class SurveilAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }
} }

View File

@@ -106,10 +106,8 @@ public abstract class TapAiBase extends SpellAbilityAi {
* @return a boolean. * @return a boolean.
*/ */
protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) { protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) {
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final Game game = ai.getGame(); final Game game = ai.getGame();
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents()); CardCollection tapList = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
tapList = CardLists.getTargetableCards(tapList, sa);
tapList = CardLists.filter(tapList, Presets.UNTAPPED); tapList = CardLists.filter(tapList, Presets.UNTAPPED);
tapList = CardLists.filter(tapList, new Predicate<Card>() { tapList = CardLists.filter(tapList, new Predicate<Card>() {
@Override @Override
@@ -129,8 +127,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
//use broader approach when the cost is a positive thing //use broader approach when the cost is a positive thing
if (tapList.isEmpty() && ComputerUtil.activateForCost(sa, ai)) { if (tapList.isEmpty() && ComputerUtil.activateForCost(sa, ai)) {
tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents()); tapList = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
tapList = CardLists.getTargetableCards(tapList, sa);
tapList = CardLists.filter(tapList, new Predicate<Card>() { tapList = CardLists.filter(tapList, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
@@ -150,8 +147,10 @@ public abstract class TapAiBase extends SpellAbilityAi {
//try to exclude things that will already be tapped due to something on stack or because something is //try to exclude things that will already be tapped due to something on stack or because something is
//already targeted in a parent or sub SA //already targeted in a parent or sub SA
CardCollection toExclude = ComputerUtilAbility.getCardsTargetedWithApi(ai, tapList, sa, ApiType.Tap); if (!sa.isTrigger() || mandatory) { // but if just confirming trigger no need to look for other targets and might still help anyway
tapList.removeAll(toExclude); CardCollection toExclude = ComputerUtilAbility.getCardsTargetedWithApi(ai, tapList, sa, ApiType.Tap);
tapList.removeAll(toExclude);
}
if (tapList.isEmpty()) { if (tapList.isEmpty()) {
return false; return false;
@@ -176,6 +175,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
} }
PhaseHandler phase = game.getPhaseHandler(); PhaseHandler phase = game.getPhaseHandler();
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
Card primeTarget = ComputerUtil.getKilledByTargeting(sa, tapList); Card primeTarget = ComputerUtil.getKilledByTargeting(sa, tapList);
if (primeTarget != null) { if (primeTarget != null) {
choice = primeTarget; choice = primeTarget;
@@ -193,7 +193,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
return CombatUtil.canAttack(c, opp); return CombatUtil.canAttack(c, opp);
} }
}); });
attackers.remove(sa.getHostCard()); attackers.remove(source);
} }
Predicate<Card> findBlockers = CardPredicates.possibleBlockerForAtLeastOne(attackers); Predicate<Card> findBlockers = CardPredicates.possibleBlockerForAtLeastOne(attackers);
List<Card> creatureList = CardLists.filter(tapList, findBlockers); List<Card> creatureList = CardLists.filter(tapList, findBlockers);
@@ -202,7 +202,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
if (!attackers.isEmpty() && !creatureList.isEmpty()) { if (!attackers.isEmpty() && !creatureList.isEmpty()) {
choice = ComputerUtilCard.getBestCreatureAI(creatureList); choice = ComputerUtilCard.getBestCreatureAI(creatureList);
} else if (sa.getRootAbility().isTrigger() || ComputerUtil.castSpellInMain1(ai, sa)) { } else if (sa.isTrigger() || ComputerUtil.castSpellInMain1(ai, sa)) {
choice = ComputerUtilCard.getMostExpensivePermanentAI(tapList); choice = ComputerUtilCard.getMostExpensivePermanentAI(tapList);
} }
} else if (phase.isPlayerTurn(opp) } else if (phase.isPlayerTurn(opp)
@@ -272,7 +272,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
return true; return true;
} }
// filter by enchantments and planeswalkers, their tapped state doesn't matter. // filter by enchantments and planeswalkers, their tapped state (usually) doesn't matter.
final String[] tappablePermanents = { "Enchantment", "Planeswalker" }; final String[] tappablePermanents = { "Enchantment", "Planeswalker" };
tapList = CardLists.getValidCards(list, tappablePermanents, source.getController(), source, sa); tapList = CardLists.getValidCards(list, tappablePermanents, source.getController(), source, sa);

View File

@@ -311,7 +311,7 @@ public class TokenAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
// TODO: AILogic // TODO: AILogic
return true; return true;
} }

View File

@@ -53,7 +53,7 @@ public class UntapAi extends SpellAbilityAi {
return false; return false;
} }
return ComputerUtilCost.checkDiscardCost(ai, cost, sa.getHostCard(), sa); return ComputerUtilCost.checkDiscardCost(ai, cost, source, sa);
} }
@Override @Override
@@ -174,8 +174,10 @@ public class UntapAi extends SpellAbilityAi {
//try to exclude things that will already be untapped due to something on stack or because something is //try to exclude things that will already be untapped due to something on stack or because something is
//already targeted in a parent or sub SA //already targeted in a parent or sub SA
CardCollection toExclude = ComputerUtilAbility.getCardsTargetedWithApi(ai, untapList, sa, ApiType.Untap); if (!sa.isTrigger() || mandatory) { // but if just confirming trigger no need to look for other targets and might still help anyway
untapList.removeAll(toExclude); CardCollection toExclude = ComputerUtilAbility.getCardsTargetedWithApi(ai, untapList, sa, ApiType.Untap);
untapList.removeAll(toExclude);
}
sa.resetTargets(); sa.resetTargets();
while (sa.canAddMoreTarget()) { while (sa.canAddMoreTarget()) {
@@ -199,7 +201,7 @@ public class UntapAi extends SpellAbilityAi {
if (choice == null) { if (choice == null) {
if (CardLists.getNotType(untapList, "Creature").isEmpty()) { if (CardLists.getNotType(untapList, "Creature").isEmpty()) {
choice = ComputerUtilCard.getBestCreatureAI(untapList); // if only creatures take the best choice = ComputerUtilCard.getBestCreatureAI(untapList); // if only creatures take the best
} else if (!sa.getPayCosts().hasManaCost() || sa.getRootAbility().isTrigger() } else if (!sa.getPayCosts().hasManaCost() || sa.isTrigger()
|| "Always".equals(sa.getParam("AILogic"))) { || "Always".equals(sa.getParam("AILogic"))) {
choice = ComputerUtilCard.getMostExpensivePermanentAI(untapList); choice = ComputerUtilCard.getMostExpensivePermanentAI(untapList);
} }
@@ -290,7 +292,7 @@ public class UntapAi extends SpellAbilityAi {
choice = ComputerUtilCard.getBestAI(tapList); choice = ComputerUtilCard.getBestAI(tapList);
if (choice == null) { // can't find anything left if (choice == null) { // can't find anything left
if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().size() == 0) { if (sa.getTargets().size() < tgt.getMinTargets(source, sa) || sa.getTargets().size() == 0) {
if (!mandatory) { if (!mandatory) {
sa.resetTargets(); sa.resetTargets();
} }
@@ -310,9 +312,7 @@ public class UntapAi extends SpellAbilityAi {
@Override @Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> list, boolean isOptional, Player targetedPlayer, Map<String, Object> params) { public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> list, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
PlayerCollection pl = new PlayerCollection(); PlayerCollection pl = ai.getYourTeam();
pl.add(ai);
pl.addAll(ai.getAllies());
return ComputerUtilCard.getBestAI(CardLists.filterControlledBy(list, pl)); return ComputerUtilCard.getBestAI(CardLists.filterControlledBy(list, pl));
} }

View File

@@ -36,7 +36,7 @@ public class VentureAi extends SpellAbilityAi {
} }
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
return true; return true;
} }

View File

@@ -91,6 +91,7 @@ public class GameCopier {
newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn()); newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn());
newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn()); newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
newPlayer.setLifeGainedThisTurn(origPlayer.getLifeGainedThisTurn()); newPlayer.setLifeGainedThisTurn(origPlayer.getLifeGainedThisTurn());
// TODO creatureAttackedThisTurn
for (Mana m : origPlayer.getManaPool()) { for (Mana m : origPlayer.getManaPool()) {
newPlayer.getManaPool().addMana(m, false); newPlayer.getManaPool().addMana(m, false);
} }
@@ -207,6 +208,13 @@ public class GameCopier {
private void copyGameState(Game newGame) { private void copyGameState(Game newGame) {
newGame.setAge(origGame.getAge()); newGame.setAge(origGame.getAge());
// TODO countersAddedThisTurn
if (origGame.getMonarch() != null) {
newGame.setMonarch(playerMap.get(origGame.getMonarch()));
}
for (ZoneType zone : ZONES) { for (ZoneType zone : ZONES) {
for (Card card : origGame.getCardsIn(zone)) { for (Card card : origGame.getCardsIn(zone)) {
addCard(newGame, zone, card); addCard(newGame, zone, card);
@@ -300,6 +308,7 @@ public class GameCopier {
newCard.setPTCharacterDefiningTable(c.getSetPTCharacterDefiningTable()); newCard.setPTCharacterDefiningTable(c.getSetPTCharacterDefiningTable());
newCard.setPTBoost(c.getPTBoostTable()); newCard.setPTBoost(c.getPTBoostTable());
// TODO copy by map
newCard.setDamage(c.getDamage()); newCard.setDamage(c.getDamage());
newCard.setChangedCardColors(c.getChangedCardColorsTable()); newCard.setChangedCardColors(c.getChangedCardColorsTable());

View File

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

View File

@@ -3,6 +3,7 @@ package forge;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.util.FileUtil; import forge.util.FileUtil;
import forge.util.TextUtil; import forge.util.TextUtil;
import forge.util.ThreadUtil;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.io.File; import java.io.File;
@@ -171,10 +172,18 @@ public final class ImageKeys {
return file; return file;
} }
//setlookup //setlookup
file = setLookUpFile(filename, fullborderFile); if (hasSetLookup(filename)) {
if (file != null) { //delay processing so gui is responsive
cachedCards.put(filename, file); ThreadUtil.delay(60, new Runnable() {
return file; @Override
public void run() {
File f = setLookUpFile(filename, fullborderFile);
if (f != null)
cachedCards.put(filename, f);
else //is null
missingCards.add(filename);
}
});
} }
} }
//if an image, like phenomenon or planes is missing .full in their filenames but you have an existing images that have .full/.fullborder //if an image, like phenomenon or planes is missing .full in their filenames but you have an existing images that have .full/.fullborder
@@ -250,7 +259,7 @@ public final class ImageKeys {
// System.out.println("File not found, no image created: " + key); // System.out.println("File not found, no image created: " + key);
//add missing cards - disable for desktop version for compatibility reasons with autodownloader //add missing cards - disable for desktop version for compatibility reasons with autodownloader
if (isLibGDXPort) if (isLibGDXPort && !hasSetLookup(filename)) //missing cards with setlookup is handled differently
missingCards.add(filename); missingCards.add(filename);
return null; return null;
} }
@@ -260,6 +269,13 @@ public final class ImageKeys {
? StaticData.instance().getEditions().getCode2ByCode(edition) // by default 2-letter codes from MWS are used ? StaticData.instance().getEditions().getCode2ByCode(edition) // by default 2-letter codes from MWS are used
: CACHE_CARD_PICS_SUBDIR.get(edition); // may use custom paths though : CACHE_CARD_PICS_SUBDIR.get(edition); // may use custom paths though
} }
static boolean hasSetLookup(String filename) {
if (!StaticData.instance().getSetLookup().isEmpty()) {
return StaticData.instance().getSetLookup().keySet().stream().anyMatch(setKey -> filename.startsWith(setKey));
}
return false;
}
private static File setLookUpFile(String filename, String fullborderFile) { private static File setLookUpFile(String filename, String fullborderFile) {
if (!StaticData.instance().getSetLookup().isEmpty()) { if (!StaticData.instance().getSetLookup().isEmpty()) {
for (String setKey : StaticData.instance().getSetLookup().keySet()) { for (String setKey : StaticData.instance().getSetLookup().keySet()) {

View File

@@ -214,8 +214,15 @@ public final class CardRules implements ICardCharacteristics {
} }
} }
public boolean isEnterableDungeon() {
if (mainPart.getOracleText().contains("You can't enter this dungeon unless")) {
return false;
}
return getType().isDungeon();
}
public boolean canBeCommander() { public boolean canBeCommander() {
if (mainPart.getOracleText().contains("can be your commander")) { if (mainPart.getOracleText().contains("can be your commander") || canBeBackground()) {
return true; return true;
} }
CardType type = mainPart.getType(); CardType type = mainPart.getType();
@@ -232,8 +239,15 @@ public final class CardRules implements ICardCharacteristics {
} }
public boolean canBePartnerCommander() { public boolean canBePartnerCommander() {
if (canBeBackground()) {
return true;
}
return canBeCommander() && (hasKeyword("Partner") || !this.partnerWith.isEmpty() || return canBeCommander() && (hasKeyword("Partner") || !this.partnerWith.isEmpty() ||
hasKeyword("Friends forever")); hasKeyword("Friends forever") || hasKeyword("Choose a Background"));
}
public boolean canBeBackground() {
return mainPart.getType().hasSubtype("Background");
} }
public boolean canBeOathbreaker() { public boolean canBeOathbreaker() {

View File

@@ -209,6 +209,16 @@ public final class CardRulesPredicates {
}; };
} }
public static Predicate<CardRules> deckHasExactly(final DeckHints.Type type, final String has[]) {
return new Predicate<CardRules>() {
@Override
public boolean apply(final CardRules card) {
DeckHints deckHas = card.getAiHints().getDeckHas();
return deckHas != null && deckHas.isValid() && deckHas.is(type, has);
}
};
}
/** /**
* Core type. * Core type.
* *

View File

@@ -33,6 +33,7 @@ import com.google.common.base.Predicate;
import com.google.common.collect.BiMap; import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap; import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@@ -50,6 +51,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
private static final long serialVersionUID = 4629853583167022151L; private static final long serialVersionUID = 4629853583167022151L;
public static final CardTypeView EMPTY = new CardType(false); public static final CardTypeView EMPTY = new CardType(false);
private static final Set<String> multiWordTypes = ImmutableSet.of("Serra's Realm", "Bolas's Meditation Realm", "Dungeon Master");
public enum CoreType { public enum CoreType {
Artifact(true, "artifacts"), Artifact(true, "artifacts"),
@@ -71,6 +73,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
public final String pluralName; public final String pluralName;
private static Map<String, CoreType> stringToCoreType = EnumUtils.getEnumMap(CoreType.class); private static Map<String, CoreType> stringToCoreType = EnumUtils.getEnumMap(CoreType.class);
private static final Set<String> allCoreTypeNames = stringToCoreType.keySet(); private static final Set<String> allCoreTypeNames = stringToCoreType.keySet();
public static final Set<CoreType> spellTypes = ImmutableSet.of(Instant, Sorcery);
public static CoreType getEnum(String name) { public static CoreType getEnum(String name) {
return stringToCoreType.get(name); return stringToCoreType.get(name);
@@ -535,7 +538,8 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
newType = new CardType(CardType.this); newType = new CardType(CardType.this);
if (ct.isRemoveCardTypes()) { if (ct.isRemoveCardTypes()) {
newType.coreTypes.clear(); // 205.1a However, an object with either the instant or sorcery card type retains that type.
newType.coreTypes.retainAll(CoreType.spellTypes);
} }
if (ct.isRemoveSuperTypes()) { if (ct.isRemoveSuperTypes()) {
newType.supertypes.clear(); newType.supertypes.clear();
@@ -612,6 +616,9 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
if (!isPlaneswalker()) { if (!isPlaneswalker()) {
Iterables.removeIf(subtypes, Predicates.IS_WALKER_TYPE); Iterables.removeIf(subtypes, Predicates.IS_WALKER_TYPE);
} }
if (!isDungeon()) {
Iterables.removeIf(subtypes, Predicates.IS_DUNGEON_TYPE);
}
} }
@Override @Override
@@ -746,12 +753,14 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
while (hasMoreTypes) { while (hasMoreTypes) {
final String type = typeText.substring(iTypeStart, iSpace == -1 ? typeText.length() : iSpace); final String type = typeText.substring(iTypeStart, iSpace == -1 ? typeText.length() : iSpace);
hasMoreTypes = iSpace != -1; hasMoreTypes = iSpace != -1;
if (!isMultiwordType(type) || !hasMoreTypes) { final String rest = typeText.substring(iTypeStart);
iTypeStart = iSpace + 1; if (isMultiwordType(rest)) {
if (!"-".equals(type)) { result.add(rest);
result.add(type); break;
}
} }
iTypeStart = iSpace + 1;
result.add(type);
iSpace = typeText.indexOf(space, iSpace + 1); iSpace = typeText.indexOf(space, iSpace + 1);
} }
return result; return result;
@@ -769,13 +778,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
} }
private static boolean isMultiwordType(final String type) { private static boolean isMultiwordType(final String type) {
final String[] multiWordTypes = { "Serra's Realm", "Bolas's Meditation Realm", "Dungeon Master" }; return multiWordTypes.contains(type);
for (int i = 0; i < multiWordTypes.length; ++i) {
if (multiWordTypes[i].startsWith(type) && !multiWordTypes[i].equals(type)) {
return true;
}
}
return false;
} }
public static class Constant { public static class Constant {
@@ -787,6 +790,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
public static final Set<String> ENCHANTMENT_TYPES = Sets.newHashSet(); public static final Set<String> ENCHANTMENT_TYPES = Sets.newHashSet();
public static final Set<String> ARTIFACT_TYPES = Sets.newHashSet(); public static final Set<String> ARTIFACT_TYPES = Sets.newHashSet();
public static final Set<String> WALKER_TYPES = Sets.newHashSet(); public static final Set<String> WALKER_TYPES = Sets.newHashSet();
public static final Set<String> DUNGEON_TYPES = Sets.newHashSet();
// singular -> plural // singular -> plural
public static final BiMap<String,String> pluralTypes = HashBiMap.create(); public static final BiMap<String,String> pluralTypes = HashBiMap.create();
@@ -846,6 +850,12 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
return CardType.isAPlaneswalkerType(input); return CardType.isAPlaneswalkerType(input);
} }
}; };
public static Predicate<String> IS_DUNGEON_TYPE = new Predicate<String>() {
@Override
public boolean apply(String input) {
return CardType.isADungeonType(input);
}
};
} }
///////// Utility methods ///////// Utility methods
@@ -878,6 +888,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
sortedSubTypes.addAll(Constant.ENCHANTMENT_TYPES); sortedSubTypes.addAll(Constant.ENCHANTMENT_TYPES);
sortedSubTypes.addAll(Constant.ARTIFACT_TYPES); sortedSubTypes.addAll(Constant.ARTIFACT_TYPES);
sortedSubTypes.addAll(Constant.WALKER_TYPES); sortedSubTypes.addAll(Constant.WALKER_TYPES);
sortedSubTypes.addAll(Constant.DUNGEON_TYPES);
Collections.sort(sortedSubTypes); Collections.sort(sortedSubTypes);
} }
return sortedSubTypes; return sortedSubTypes;
@@ -933,6 +944,9 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
return (Constant.SPELL_TYPES.contains(cardType)); return (Constant.SPELL_TYPES.contains(cardType));
} }
public static boolean isADungeonType(final String cardType) {
return (Constant.DUNGEON_TYPES.contains(cardType));
}
/** /**
* If the input is a plural type, return the corresponding singular form. * If the input is a plural type, return the corresponding singular form.
* Otherwise, simply return the input. * Otherwise, simply return the input.

View File

@@ -14,6 +14,7 @@ import com.google.common.collect.Iterables;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.util.PredicateString.StringOp; import forge.util.PredicateString.StringOp;
import forge.util.collect.FCollection;
/** /**
* DeckHints provides the ability for a Card to "want" another Card or type of * DeckHints provides the ability for a Card to "want" another Card or type of
@@ -73,12 +74,26 @@ public class DeckHints {
return false; return false;
} }
for (Pair<Type, String> filter : filters) { for (Pair<Type, String> filter : filters) {
if (filter.getLeft() == type && filter.getRight().equals(hint)) { if (filter.getLeft() == type && filter.getRight().contains(hint)) {
return true; return true;
} }
} }
return false; return false;
} }
public boolean is(Type type, String hints[]) {
if (filters == null) {
return false;
}
for (String hint : hints) {
for (Pair<Type, String> filter : filters) {
if (filter.getLeft() == type && filter.getRight().equals(hint)) {
continue;
}
}
return false;
}
return true;
}
/** /**
* Returns a Map of Cards by Type from the given Iterable<PaperCard> that match this * Returns a Map of Cards by Type from the given Iterable<PaperCard> that match this
@@ -95,6 +110,10 @@ public class DeckHints {
String param = pair.getRight(); String param = pair.getRight();
Iterable<PaperCard> cards = getCardsForFilter(cardList, type, param); Iterable<PaperCard> cards = getCardsForFilter(cardList, type, param);
if (cards != null) { if (cards != null) {
// if a type is used more than once intersect respective matches
if (ret.get(type) != null) {
Iterables.retainAll(cards, new FCollection<>(ret.get(type)));
}
ret.put(type, cards); ret.put(type, cards);
} }
} }
@@ -143,13 +162,16 @@ public class DeckHints {
private Iterable<PaperCard> getCardsForFilter(Iterable<PaperCard> cardList, Type type, String param) { private Iterable<PaperCard> getCardsForFilter(Iterable<PaperCard> cardList, Type type, String param) {
List<PaperCard> cards = new ArrayList<>(); List<PaperCard> cards = new ArrayList<>();
// this is case ABILITY, but other types can also use this when the implicit parsing would miss
String[] abilities = param.split("\\|");
for (String ability : abilities) {
Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.deckHas(type, ability), PaperCard.FN_GET_RULES));
}
// bonus if a DeckHas can satisfy the type with multiple ones
Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.deckHasExactly(type, abilities), PaperCard.FN_GET_RULES));
switch (type) { switch (type) {
case ABILITY:
String[] abilities = param.split("\\|");
for (String ability : abilities) {
Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.deckHas(Type.ABILITY, ability), PaperCard.FN_GET_RULES));
}
break;
case COLOR: case COLOR:
String[] colors = param.split("\\|"); String[] colors = param.split("\\|");
for (String color : colors) { for (String color : colors) {
@@ -187,6 +209,7 @@ public class DeckHints {
} }
break; break;
case NONE: case NONE:
case ABILITY: // already done above
break; break;
} }
return cards; return cards;

View File

@@ -262,6 +262,9 @@ public enum DeckFormat {
} else if (a.getRules().hasKeyword("Friends forever") && } else if (a.getRules().hasKeyword("Friends forever") &&
b.getRules().hasKeyword("Friends forever")) { b.getRules().hasKeyword("Friends forever")) {
// Stranger Things Secret Lair gimmick partner commander // Stranger Things Secret Lair gimmick partner commander
} else if (a.getRules().hasKeyword("Choose a Background") && b.getRules().canBeBackground()
|| b.getRules().hasKeyword("Choose a Background") && a.getRules().canBeBackground()) {
// commander with background
} else { } else {
return "has an illegal commander partnership"; return "has an illegal commander partnership";
} }

View File

@@ -76,13 +76,19 @@ public class Localizer {
return defaultValue; return defaultValue;
} }
} }
public String getEnglishMessage(final String key, final Object... messageArguments) {
return getMessage(true, key, messageArguments);
}
//FIXME: localizer should return default value from english locale or it will crash some GUI element like the NewGameMenu->NewGameScreen Popup when returned null... //FIXME: localizer should return default value from english locale or it will crash some GUI element like the NewGameMenu->NewGameScreen Popup when returned null...
public String getMessage(final String key, final Object... messageArguments) { public String getMessage(final String key, final Object... messageArguments) {
return getMessage(false, key, messageArguments);
}
public String getMessage(final boolean forcedEnglish, final String key, final Object... messageArguments) {
MessageFormat formatter = null; MessageFormat formatter = null;
try { try {
//formatter = new MessageFormat(resourceBundle.getString(key.toLowerCase()), locale); //formatter = new MessageFormat(resourceBundle.getString(key.toLowerCase()), locale);
formatter = new MessageFormat(english ? englishBundle.getString(key) : resourceBundle.getString(key), english ? Locale.ENGLISH : locale); formatter = new MessageFormat(english || forcedEnglish ? englishBundle.getString(key) : resourceBundle.getString(key), english || forcedEnglish ? Locale.ENGLISH : locale);
} catch (final IllegalArgumentException | MissingResourceException e) { } catch (final IllegalArgumentException | MissingResourceException e) {
if (!silent) if (!silent)
e.printStackTrace(); e.printStackTrace();
@@ -95,12 +101,12 @@ public class Localizer {
silent = false; silent = false;
formatter.setLocale(english ? Locale.ENGLISH : locale); formatter.setLocale(english || forcedEnglish ? Locale.ENGLISH : locale);
String formattedMessage = "CHAR ENCODING ERROR"; String formattedMessage = "CHAR ENCODING ERROR";
final String[] charsets = { "ISO-8859-1", "UTF-8" }; final String[] charsets = { "ISO-8859-1", "UTF-8" };
//Support non-English-standard characters //Support non-English-standard characters
String detectedCharset = charset(english ? englishBundle.getString(key) : resourceBundle.getString(key), charsets); String detectedCharset = charset(english || forcedEnglish ? englishBundle.getString(key) : resourceBundle.getString(key), charsets);
final int argLength = messageArguments.length; final int argLength = messageArguments.length;
Object[] syncEncodingMessageArguments = new Object[argLength]; Object[] syncEncodingMessageArguments = new Object[argLength];
@@ -149,7 +155,7 @@ public class Localizer {
englishBundle = ResourceBundle.getBundle("en-US", new Locale("en", "US"), loader); englishBundle = ResourceBundle.getBundle("en-US", new Locale("en", "US"), loader);
} catch (NullPointerException | MissingResourceException e) { } catch (NullPointerException | MissingResourceException e) {
//If the language can't be loaded, default to US English //If the language can't be loaded, default to US English
resourceBundle = ResourceBundle.getBundle("en-US", new Locale("en", "US"), loader); resourceBundle = ResourceBundle.getBundle("en-US", new Locale("en_US"), loader);
e.printStackTrace(); e.printStackTrace();
} }

View File

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

View File

@@ -20,6 +20,7 @@ import forge.game.card.CardPredicates;
import forge.game.card.CardState; import forge.game.card.CardState;
import forge.game.card.CardView; import forge.game.card.CardView;
import forge.game.card.IHasCardView; import forge.game.card.IHasCardView;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -34,6 +35,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
/** The host card. */ /** The host card. */
protected Card hostCard; protected Card hostCard;
protected CardState cardState = null; protected CardState cardState = null;
protected KeywordInterface keyword = null;
/** The map params. */ /** The map params. */
protected Map<String, String> originalMapParams = Maps.newHashMap(), protected Map<String, String> originalMapParams = Maps.newHashMap(),
@@ -55,15 +57,13 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
/** Keys of descriptive (text) parameters. */ /** Keys of descriptive (text) parameters. */
private static final ImmutableList<String> descriptiveKeys = ImmutableList.<String>builder() private static final ImmutableList<String> descriptiveKeys = ImmutableList.<String>builder()
.add("Description", "SpellDescription", "StackDescription", "TriggerDescription").build(); .add("Description", "SpellDescription", "StackDescription", "TriggerDescription").build();
/** Keys to be followed as SVar names when changing text. */
private static final ImmutableList<String> mutableKeys = ImmutableList.<String>builder()
.add("AddAbility").build();
/** /**
* Keys that should not changed * Keys that should not changed
*/ */
private static final ImmutableList<String> noChangeKeys = ImmutableList.<String>builder() private static final ImmutableList<String> noChangeKeys = ImmutableList.<String>builder()
.add("TokenScript", "LegacyImage", "TokenImage", "NewName", "ChooseFromList").build(); .add("TokenScript", "LegacyImage", "TokenImage", "NewName", "ChooseFromList")
.add("AddAbility").build();
/** /**
* <p> * <p>
@@ -140,6 +140,14 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
this.hostCard = c; this.hostCard = c;
} }
public KeywordInterface getKeyword() {
return this.keyword;
}
public void setKeyword(final KeywordInterface kw) {
this.keyword = kw;
}
/** /**
* <p> * <p>
* isSecondary. * isSecondary.
@@ -259,6 +267,18 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
} }
if (params.containsKey("Revolt")) { if (params.containsKey("Revolt")) {
if ("True".equalsIgnoreCase(params.get("Revolt")) != hostController.hasRevolt()) return false; if ("True".equalsIgnoreCase(params.get("Revolt")) != hostController.hasRevolt()) return false;
else if ("None".equalsIgnoreCase(params.get("Revolt"))) {
boolean none = true;
for (Player p : game.getRegisteredPlayers()) {
if (p.hasRevolt()) {
none = false;
break;
}
}
if (!none) {
return false;
}
}
} }
if (params.containsKey("Desert")) { if (params.containsKey("Desert")) {
if ("True".equalsIgnoreCase(params.get("Desert")) != hostController.hasDesert()) return false; if ("True".equalsIgnoreCase(params.get("Desert")) != hostController.hasDesert()) return false;
@@ -422,6 +442,16 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
if (!Expressions.compare(sVar, svarOperator, operandValue)) { if (!Expressions.compare(sVar, svarOperator, operandValue)) {
return false; return false;
} }
if (hasParam("CheckSecondSVar")) {
final int sVar2 = AbilityUtils.calculateAmount(this.hostCard, getParam("CheckSecondSVar"), this);
final String comparator2 = getParamOrDefault("SecondSVarCompare", "GE1");
final String svarOperator2 = comparator2.substring(0, 2);
final String svarOperand2 = comparator2.substring(2);
final int operandValue2 = AbilityUtils.calculateAmount(this.hostCard, svarOperand2, this);
if (!Expressions.compare(sVar2, svarOperator2, operandValue2)) {
return false;
}
}
} }
if (params.containsKey("ManaSpent")) { if (params.containsKey("ManaSpent")) {
@@ -489,11 +519,6 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
} else if (descriptiveKeys.contains(key)) { } else if (descriptiveKeys.contains(key)) {
// change descriptions differently // change descriptions differently
newValue = AbilityUtils.applyDescriptionTextChangeEffects(value, this); newValue = AbilityUtils.applyDescriptionTextChangeEffects(value, this);
} else if (mutableKeys.contains(key)) {
// follow SVar and change it
final String originalSVarValue = hostCard.getSVar(value);
hostCard.changeSVar(value, AbilityUtils.applyAbilityTextChangeEffects(originalSVarValue, this));
newValue = null;
} else if (this.getHostCard().hasSVar(value)) { } else if (this.getHostCard().hasSVar(value)) {
// don't change literal SVar names! // don't change literal SVar names!
newValue = null; newValue = null;
@@ -652,6 +677,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
copy.setCardState(cardState); copy.setCardState(cardState);
// dont use setHostCard to not trigger the not copied parts yet // dont use setHostCard to not trigger the not copied parts yet
copy.hostCard = host; copy.hostCard = host;
copy.keyword = this.keyword;
} }
abstract public List<Object> getTriggerRemembered(); abstract public List<Object> getTriggerRemembered();

View File

@@ -202,6 +202,11 @@ public class ForgeScript {
return sa.hasParam("Nightbound"); return sa.hasParam("Nightbound");
} else if (property.equals("paidPhyrexianMana")) { } else if (property.equals("paidPhyrexianMana")) {
return sa.getSpendPhyrexianMana(); return sa.getSpendPhyrexianMana();
} else if (property.startsWith("ManaSpent")) {
String[] k = property.split(" ", 2);
String comparator = k[1].substring(0, 2);
int y = AbilityUtils.calculateAmount(sa.getHostCard(), k[1].substring(2), sa);
return Expressions.compare(sa.getPayingMana().size(), comparator, y);
} else if (property.startsWith("ManaFrom")) { } else if (property.startsWith("ManaFrom")) {
final String fromWhat = property.substring(8); final String fromWhat = property.substring(8);
boolean found = false; boolean found = false;
@@ -247,7 +252,7 @@ public class ForgeScript {
} else if (property.startsWith("cmc")) { } else if (property.startsWith("cmc")) {
int y = 0; int y = 0;
// spell was on the stack // spell was on the stack
if (sa.getCardState().getCard().isInZone(ZoneType.Stack)) { if (sa.getHostCard().isInZone(ZoneType.Stack)) {
y = sa.getHostCard().getCMC(); y = sa.getHostCard().getCMC();
} else { } else {
y = sa.getPayCosts().getTotalMana().getCMC(); y = sa.getPayCosts().getTotalMana().getCMC();

View File

@@ -22,6 +22,7 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -47,6 +48,7 @@ import forge.game.ability.AbilityKey;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardDamageHistory;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
@@ -78,6 +80,7 @@ import forge.trackable.Tracker;
import forge.util.Aggregates; import forge.util.Aggregates;
import forge.util.MyRandom; import forge.util.MyRandom;
import forge.util.Visitor; import forge.util.Visitor;
import forge.util.collect.FCollection;
/** /**
* Represents the state of a <i>single game</i>, a new instance is created for each game. * Represents the state of a <i>single game</i>, a new instance is created for each game.
@@ -119,10 +122,14 @@ public class Game {
private Table<CounterType, Player, List<Pair<Card, Integer>>> countersAddedThisTurn = HashBasedTable.create(); private Table<CounterType, Player, List<Pair<Card, Integer>>> countersAddedThisTurn = HashBasedTable.create();
private FCollection<CardDamageHistory> globalDamageHistory = new FCollection<>();
private IdentityHashMap<Pair<Integer, Boolean>, Pair<Card, GameEntity>> damageThisTurnLKI = new IdentityHashMap<>();
private Map<Player, Card> topLibsCast = Maps.newHashMap(); private Map<Player, Card> topLibsCast = Maps.newHashMap();
private Map<Card, Integer> facedownWhileCasting = Maps.newHashMap(); private Map<Card, Integer> facedownWhileCasting = Maps.newHashMap();
private Player monarch = null; private Player monarch = null;
private Player initiative = null;
private Player monarchBeginTurn = null; private Player monarchBeginTurn = null;
private Player startingPlayer; private Player startingPlayer;
@@ -173,6 +180,13 @@ public class Game {
this.monarchBeginTurn = monarchBeginTurn; this.monarchBeginTurn = monarchBeginTurn;
} }
public Player getHasInitiative() {
return initiative;
}
public void setHasInitiative(final Player p ) {
initiative = p;
}
public CardCollectionView getLastStateBattlefield() { public CardCollectionView getLastStateBattlefield() {
return lastStateBattlefield; return lastStateBattlefield;
} }
@@ -243,7 +257,7 @@ public class Game {
if (c == null) { if (c == null) {
return null; return null;
} }
return changeZoneLKIInfo.containsKey(c.getId()) ? changeZoneLKIInfo.get(c.getId()) : c; return changeZoneLKIInfo.getOrDefault(c.getId(), c);
} }
public final void clearChangeZoneLKIInfo() { public final void clearChangeZoneLKIInfo() {
changeZoneLKIInfo.clear(); changeZoneLKIInfo.clear();
@@ -846,6 +860,18 @@ public class Game {
} }
} }
if (p.hasInitiative()) {
// The third way to take the initiative is if the player who currently has the initiative leaves the game.
// When that happens, the player whose turn it is takes the initiative.
// If the player who has the initiative leaves the game on their own turn,
// or the active player left the game at the same time, the next player in turn order takes the initiative.
if (p.equals(getPhaseHandler().getPlayerTurn())) {
getAction().takeInitiative(getNextPlayerAfter(p), null);
} else {
getAction().takeInitiative(getPhaseHandler().getPlayerTurn(), null);
}
}
// Remove leftover items from // Remove leftover items from
getStack().removeInstancesControlledBy(p); getStack().removeInstancesControlledBy(p);
@@ -1068,6 +1094,7 @@ public class Game {
public void onCleanupPhase() { public void onCleanupPhase() {
clearCounterAddedThisTurn(); clearCounterAddedThisTurn();
clearGlobalDamageHistory();
// some cards need this info updated even after a player lost, so don't skip them // some cards need this info updated even after a player lost, so don't skip them
for (Player player : getRegisteredPlayers()) { for (Player player : getRegisteredPlayers()) {
player.onCleanupPhase(); player.onCleanupPhase();
@@ -1109,6 +1136,48 @@ public class Game {
countersAddedThisTurn.clear(); countersAddedThisTurn.clear();
} }
/**
* Gets the damage instances done this turn.
* @param isCombat if true only combat damage matters, pass null for both
* @param anyIsEnough if true returns early once result has an entry
* @param validSourceCard
* @param validTargetEntity
* @param source
* @param sourceController
* @param ctb
* @return List<Integer> for each source
*/
public List<Integer> getDamageDoneThisTurn(Boolean isCombat, boolean anyIsEnough, String validSourceCard, String validTargetEntity, Card source, Player sourceController, CardTraitBase ctb) {
final List<Integer> dmgList = Lists.newArrayList();
for (CardDamageHistory cdh : globalDamageHistory) {
int dmg = cdh.getDamageDoneThisTurn(isCombat, anyIsEnough, validSourceCard, validTargetEntity, source, sourceController, ctb);
if (dmg == 0) {
continue;
}
dmgList.add(dmg);
if (anyIsEnough) {
break;
}
}
return dmgList;
}
public void addGlobalDamageHistory(CardDamageHistory cdh, Pair<Integer, Boolean> dmg, Card source, GameEntity target) {
globalDamageHistory.add(cdh);
damageThisTurnLKI.put(dmg, Pair.of(source, target));
}
public void clearGlobalDamageHistory() {
globalDamageHistory.clear();
damageThisTurnLKI.clear();
}
public Pair<Card, GameEntity> getDamageLKI(Pair<Integer, Boolean> dmg) {
return damageThisTurnLKI.get(dmg);
}
public Card getTopLibForPlayer(Player P) { public Card getTopLibForPlayer(Player P) {
return topLibsCast.get(P); return topLibsCast.get(P);
} }

View File

@@ -27,7 +27,6 @@ import java.util.Set;
import forge.util.*; import forge.util.*;
import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.ComparisonChain; import com.google.common.collect.ComparisonChain;
@@ -37,6 +36,7 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps; import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import forge.GameCommand; import forge.GameCommand;
import forge.StaticData; import forge.StaticData;
@@ -66,6 +66,7 @@ import forge.game.event.GameEventGameStarted;
import forge.game.event.GameEventScry; import forge.game.event.GameEventScry;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
import forge.game.keyword.KeywordsChange;
import forge.game.mulligan.MulliganService; import forge.game.mulligan.MulliganService;
import forge.game.player.GameLossReason; import forge.game.player.GameLossReason;
import forge.game.player.Player; import forge.game.player.Player;
@@ -161,14 +162,32 @@ public class GameAction {
// need to check before it enters // need to check before it enters
if (c.isAura() && !c.isAttachedToEntity() && toBattlefield && (zoneFrom == null || !zoneFrom.is(ZoneType.Stack))) { if (c.isAura() && !c.isAttachedToEntity() && toBattlefield && (zoneFrom == null || !zoneFrom.is(ZoneType.Stack))) {
boolean found = false; boolean found = false;
if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(c, null))) { try {
found = true; if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(c, null))) {
found = true;
}
} catch (Exception e1) {
found = false;
} }
else if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(c, null))) {
found = true; if (!found) {
try {
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(c, null))) {
found = true;
}
} catch (Exception e2) {
found = false;
}
} }
else if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(c, null))) {
found = true; if (!found) {
try {
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(c, null))) {
found = true;
}
} catch (Exception e3) {
found = false;
}
} }
if (!found) { if (!found) {
c.clearControllers(); c.clearControllers();
@@ -233,13 +252,6 @@ public class GameAction {
} }
} }
// Clean up the temporary Dash/Blitz SVar when the card leaves the battlefield
// Clean up the temporary AtEOT SVar
String endofTurn = c.getSVar("EndOfTurnLeavePlay");
if (fromBattlefield && (endofTurn.equals("Dash") || endofTurn.equals("Blitz") || endofTurn.equals("AtEOT"))) {
c.removeSVar("EndOfTurnLeavePlay");
}
if (fromBattlefield && !toBattlefield) { if (fromBattlefield && !toBattlefield) {
c.getController().setRevolt(true); c.getController().setRevolt(true);
} }
@@ -253,10 +265,6 @@ public class GameAction {
lastKnownInfo = CardUtil.getLKICopy(c); lastKnownInfo = CardUtil.getLKICopy(c);
} }
if (!suppress) {
copied.setTimestamp(game.getNextTimestamp());
}
if (!lastKnownInfo.hasKeyword("Counters remain on CARDNAME as it moves to any zone other than a player's hand or library.")) { if (!lastKnownInfo.hasKeyword("Counters remain on CARDNAME as it moves to any zone other than a player's hand or library.")) {
copied.clearCounters(); copied.clearCounters();
} }
@@ -281,6 +289,8 @@ public class GameAction {
copied = CardFactory.copyCard(c, false); copied = CardFactory.copyCard(c, false);
} }
copied.setTimestamp(c.getTimestamp());
if (zoneTo.is(ZoneType.Stack)) { if (zoneTo.is(ZoneType.Stack)) {
// when moving to stack, copy changed card information // when moving to stack, copy changed card information
copied.setChangedCardColors(c.getChangedCardColorsTable()); copied.setChangedCardColors(c.getChangedCardColorsTable());
@@ -293,7 +303,6 @@ public class GameAction {
copied.setDrawnThisTurn(c.getDrawnThisTurn()); copied.setDrawnThisTurn(c.getDrawnThisTurn());
copied.copyChangedTextFrom(c); copied.copyChangedTextFrom(c);
copied.setTimestamp(c.getTimestamp());
// clean up changes that come from its own static abilities // clean up changes that come from its own static abilities
copied.cleanupCopiedChangesFrom(c); copied.cleanupCopiedChangesFrom(c);
@@ -305,15 +314,20 @@ public class GameAction {
// copy bestow timestamp // copy bestow timestamp
copied.setBestowTimestamp(c.getBestowTimestamp()); copied.setBestowTimestamp(c.getBestowTimestamp());
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
copied.setCastSA(cause);
KeywordInterface kw = cause.getKeyword();
if (kw != null) {
copied.addKeywordForStaticAbility(kw);
}
}
} else { } else {
// when a card leaves the battlefield, ensure it's in its original state // when a card leaves the battlefield, ensure it's in its original state
// (we need to do this on the object before copying it, or it won't work correctly e.g. // (we need to do this on the object before copying it, or it won't work correctly e.g.
// on Transformed objects) // on Transformed objects)
copied.setState(CardStateName.Original, false); copied.setState(CardStateName.Original, false);
copied.setBackSide(false); copied.setBackSide(false);
// reset timestamp in changezone effects so they have same timestamp if ETB simultaneously
copied.setTimestamp(game.getNextTimestamp());
} }
copied.setUnearthed(c.isUnearthed()); copied.setUnearthed(c.isUnearthed());
@@ -337,7 +351,7 @@ public class GameAction {
CardCollectionView comCards = c.getOwner().getCardsIn(ZoneType.Command); CardCollectionView comCards = c.getOwner().getCardsIn(ZoneType.Command);
for (final Card effCard : comCards) { for (final Card effCard : comCards) {
for (final ReplacementEffect re : effCard.getReplacementEffects()) { for (final ReplacementEffect re : effCard.getReplacementEffects()) {
if (re.hasSVar("CommanderMoveReplacement") && effCard.getEffectSource().getName().equals(c.getRealCommander().getName())) { if (re.hasParam("CommanderMoveReplacement") && c.getMergedCards().contains(effCard.getEffectSource())) {
commanderEffect = effCard; commanderEffect = effCard;
break; break;
} }
@@ -345,8 +359,10 @@ public class GameAction {
if (commanderEffect != null) break; if (commanderEffect != null) break;
} }
// Disable the commander replacement effect // Disable the commander replacement effect
for (final ReplacementEffect re : commanderEffect.getReplacementEffects()) { if (commanderEffect != null) {
re.setSuppressed(true); for (final ReplacementEffect re : commanderEffect.getReplacementEffects()) {
re.setSuppressed(true);
}
} }
} }
@@ -365,7 +381,7 @@ public class GameAction {
} }
ReplacementResult repres = game.getReplacementHandler().run(ReplacementType.Moved, repParams); ReplacementResult repres = game.getReplacementHandler().run(ReplacementType.Moved, repParams);
if (repres != ReplacementResult.NotReplaced) { if (repres != ReplacementResult.NotReplaced && repres != ReplacementResult.Updated) {
// reset failed manifested Cards back to original // reset failed manifested Cards back to original
if (c.isManifested() && !c.isInPlay()) { if (c.isManifested() && !c.isInPlay()) {
c.forceTurnFaceUp(); c.forceTurnFaceUp();
@@ -395,6 +411,11 @@ public class GameAction {
} }
} }
if (!zoneTo.is(ZoneType.Stack) && !suppress) {
// reset timestamp in changezone effects so they have same timestamp if ETB simultaneously
copied.setTimestamp(game.getNextTimestamp());
}
copied.getOwner().removeInboundToken(copied); copied.getOwner().removeInboundToken(copied);
// Aura entering as Copy from stack // Aura entering as Copy from stack
@@ -489,6 +510,37 @@ public class GameAction {
if (!zoneTo.is(ZoneType.Exile) && !zoneTo.is(ZoneType.Stack)) { if (!zoneTo.is(ZoneType.Exile) && !zoneTo.is(ZoneType.Stack)) {
c.cleanupExiledWith(); c.cleanupExiledWith();
} }
// 400.7a Effects from static abilities that give a permanent spell on the stack an ability
// that allows it to be cast for an alternative cost continue to apply to the permanent that spell becomes.
if (zoneFrom.is(ZoneType.Stack) && toBattlefield) {
List<KeywordInterface> newKw = Lists.newArrayList();
for (Table.Cell<Long, Long, KeywordsChange> cell : c.getChangedCardKeywords().cellSet()) {
// comes from a static ability
if (cell.getColumnKey() == 0) {
continue;
}
for (KeywordInterface ki : cell.getValue().getKeywords()) {
boolean keepKeyword = false;
for (SpellAbility sa : ki.getAbilities()) {
if (!sa.isSpell()) {
continue;
}
if (sa.getAlternativeCost() != null) {
keepKeyword = true;
break;
}
}
if (keepKeyword) {
ki.setHostCard(copied);
newKw.add(ki);
}
}
}
if (!newKw.isEmpty()) {
copied.addChangedCardKeywordsInternal(newKw, null, false, copied.getTimestamp(), 0, true);
}
}
} }
// if an adventureCard is put from Stack somewhere else, need to reset to Original State // if an adventureCard is put from Stack somewhere else, need to reset to Original State
@@ -498,15 +550,6 @@ public class GameAction {
GameEntityCounterTable table = new GameEntityCounterTable(); GameEntityCounterTable table = new GameEntityCounterTable();
// need to suspend cards own replacement effects
if (!suppress) {
if (toBattlefield && !copied.getEtbCounters().isEmpty()) {
for (final ReplacementEffect re : copied.getReplacementEffects()) {
re.setSuppressed(true);
}
}
}
if (mergedCards != null) { if (mergedCards != null) {
// Move components of merged permanent here // Move components of merged permanent here
// Also handle 723.3e and 903.9a // Also handle 723.3e and 903.9a
@@ -553,14 +596,9 @@ public class GameAction {
} }
// do ETB counters after zone add // do ETB counters after zone add
if (!suppress) { if (!suppress && toBattlefield && !copied.getEtbCounters().isEmpty()) {
if (toBattlefield) { game.getTriggerHandler().registerActiveTrigger(copied, false);
copied.putEtbCounters(table); copied.putEtbCounters(table);
// enable replacement effects again
for (final ReplacementEffect re : copied.getReplacementEffects()) {
re.setSuppressed(false);
}
}
copied.clearEtbCounters(); copied.clearEtbCounters();
} }
@@ -581,7 +619,7 @@ public class GameAction {
} }
} }
table.replaceCounterEffect(game, null, true); table.replaceCounterEffect(game, null, true, true, params);
// Need to apply any static effects to produce correct triggers // Need to apply any static effects to produce correct triggers
checkStaticAbilities(); checkStaticAbilities();
@@ -1059,6 +1097,24 @@ public class GameAction {
return holdCheckingStaticAbilities; return holdCheckingStaticAbilities;
} }
// This doesn't check layers or if the ability gets removed by other effects
public boolean hasStaticAbilityAffectingZone(ZoneType zone, StaticAbilityLayer layer) {
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (!stAb.getParam("Mode").equals("Continuous") || stAb.isSuppressed() || !stAb.checkConditions()) {
continue;
}
if (layer != null && !stAb.getLayers().contains(layer)) {
continue;
}
if (ZoneType.listValueOf(stAb.getParamOrDefault("AffectedZone", ZoneType.Battlefield.toString())).contains(zone)) {
return true;
}
}
}
return false;
}
public final void checkStaticAbilities() { public final void checkStaticAbilities() {
checkStaticAbilities(true); checkStaticAbilities(true);
} }
@@ -1183,26 +1239,26 @@ public class GameAction {
} }
} }
for (Player p : game.getPlayers()) {
for (Card c : p.getCardsIn(ZoneType.Battlefield).threadSafeIterable()) {
if (!c.getController().equals(p)) {
controllerChangeZoneCorrection(c);
affectedCards.add(c);
}
if (c.isCreature() && c.isPaired()) {
Card partner = c.getPairedWith();
if (!partner.isCreature() || c.getController() != partner.getController() || !c.isInPlay()) {
c.setPairedWith(null);
partner.setPairedWith(null);
affectedCards.add(c);
}
}
}
}
// preList means that this is run by a pre Check with LKI objects // preList means that this is run by a pre Check with LKI objects
// in that case Always trigger should not Run // in that case Always trigger should not Run
if (preList.isEmpty()) { if (preList.isEmpty()) {
for (Player p : game.getPlayers()) {
for (Card c : p.getCardsIn(ZoneType.Battlefield).threadSafeIterable()) {
if (!c.getController().equals(p)) {
controllerChangeZoneCorrection(c);
affectedCards.add(c);
}
if (c.isCreature() && c.isPaired()) {
Card partner = c.getPairedWith();
if (!partner.isCreature() || c.getController() != partner.getController() || !c.isInPlay()) {
c.setPairedWith(null);
partner.setPairedWith(null);
affectedCards.add(c);
}
}
}
}
final Map<AbilityKey, Object> runParams = AbilityKey.newMap(); final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
game.getTriggerHandler().runTrigger(TriggerType.Always, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.Always, runParams, false);
@@ -1217,6 +1273,8 @@ public class GameAction {
c.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering c.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering
} }
// TODO filter out old copies from zone change
if (runEvents && !affectedCards.isEmpty()) { if (runEvents && !affectedCards.isEmpty()) {
game.fireEvent(new GameEventCardStatsChanged(affectedCards)); game.fireEvent(new GameEventCardStatsChanged(affectedCards));
} }
@@ -1293,33 +1351,13 @@ public class GameAction {
noRegCreats.add(c); noRegCreats.add(c);
checkAgain = true; checkAgain = true;
} else if (c.hasKeyword("CARDNAME can't be destroyed by lethal damage unless lethal damage dealt by a single source is marked on it.")) { } else if (c.hasKeyword("CARDNAME can't be destroyed by lethal damage unless lethal damage dealt by a single source is marked on it.")) {
// merge entries with same source if (c.getLethal() <= c.getMaxDamageFromSource() || c.hasBeenDealtDeathtouchDamage()) {
List<Integer> dmgList = Lists.newArrayList(); if (desCreats == null) {
List<Pair<Card, Integer>> remainingDamaged = Lists.newArrayList(c.getReceivedDamageFromThisTurn()); desCreats = new CardCollection();
while (!remainingDamaged.isEmpty()) {
Pair <Card, Integer> damaged = remainingDamaged.get(0);
int sum = damaged.getRight();
remainingDamaged.remove(damaged);
for (Pair<Card, Integer> other : Lists.newArrayList(remainingDamaged)) {
if (other.getLeft().equalsWithTimestamp(damaged.getLeft())) {
sum += other.getRight();
// once it got counted keep it out
remainingDamaged.remove(other);
}
}
dmgList.add(sum);
}
for (final Integer dmg : dmgList) {
if (c.getLethal() <= dmg.intValue() || c.hasBeenDealtDeathtouchDamage()) {
if (desCreats == null) {
desCreats = new CardCollection();
}
desCreats.add(c);
c.setHasBeenDealtDeathtouchDamage(false);
checkAgain = true;
break;
} }
desCreats.add(c);
c.setHasBeenDealtDeathtouchDamage(false);
checkAgain = true;
} }
} }
// Rule 704.5g - Destroy due to lethal damage // Rule 704.5g - Destroy due to lethal damage
@@ -1546,7 +1584,7 @@ public class GameAction {
c.getGame().getTracker().flush(); c.getGame().getTracker().flush();
c.setMoveToCommandZone(false); c.setMoveToCommandZone(false);
if (c.getOwner().getController().confirmAction(c.getFirstSpellAbility(), PlayerActionConfirmMode.ChangeZoneToAltDestination, c.getName() + ": If a commander is in a graveyard or in exile and that card was put into that zone since the last time state-based actions were checked, its owner may put it into the command zone.")) { if (c.getOwner().getController().confirmAction(c.getFirstSpellAbility(), PlayerActionConfirmMode.ChangeZoneToAltDestination, c.getName() + ": If a commander is in a graveyard or in exile and that card was put into that zone since the last time state-based actions were checked, its owner may put it into the command zone.", null)) {
moveTo(c.getOwner().getZone(ZoneType.Command), c, null); moveTo(c.getOwner().getZone(ZoneType.Command), c, null);
return true; return true;
} }
@@ -2203,6 +2241,32 @@ public class GameAction {
game.getTriggerHandler().runTrigger(TriggerType.BecomeMonarch, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.BecomeMonarch, runParams, false);
} }
public void takeInitiative(final Player p, final String set) {
final Player previous = game.getHasInitiative();
if (p == null) {
return;
}
if (!p.equals(previous)) {
if (previous != null) {
previous.removeInitiativeEffect();
}
if (p.hasLost()) { // the person who should take initiative is gone, it goes to next player
takeInitiative(game.getNextPlayerAfter(p), set);
}
game.setHasInitiative(p);
p.createInitiativeEffect(set);
}
// You can take the initiative even if you already have it
// Run triggers
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Player, p);
game.getTriggerHandler().runTrigger(TriggerType.TakesInitiative, runParams, false);
}
// Make scry an action function so that it can be used for mulligans (with a null cause) // Make scry an action function so that it can be used for mulligans (with a null cause)
// Assumes that the list of players is in APNAP order, which should be the case // Assumes that the list of players is in APNAP order, which should be the case
// Optional here as well to handle the way that mulligans do the choice // Optional here as well to handle the way that mulligans do the choice
@@ -2271,13 +2335,16 @@ public class GameAction {
final Player p = e.getKey(); final Player p = e.getKey();
final CardCollection toTop = e.getValue().getLeft(); final CardCollection toTop = e.getValue().getLeft();
final CardCollection toBottom = e.getValue().getRight(); final CardCollection toBottom = e.getValue().getRight();
int numLookedAt = 0;
if (toTop != null) { if (toTop != null) {
numLookedAt += toTop.size();
Collections.reverse(toTop); // reverse to get the correct order Collections.reverse(toTop); // reverse to get the correct order
for (Card c : toTop) { for (Card c : toTop) {
moveToLibrary(c, cause, null); moveToLibrary(c, cause, null);
} }
} }
if (toBottom != null) { if (toBottom != null) {
numLookedAt += toBottom.size();
for (Card c : toBottom) { for (Card c : toBottom) {
moveToBottomOfLibrary(c, cause, null); moveToBottomOfLibrary(c, cause, null);
} }
@@ -2287,6 +2354,7 @@ public class GameAction {
// set up triggers (but not actually do them until later) // set up triggers (but not actually do them until later)
final Map<AbilityKey, Object> runParams = AbilityKey.newMap(); final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Player, p); runParams.put(AbilityKey.Player, p);
runParams.put(AbilityKey.ScryNum, numLookedAt);
game.getTriggerHandler().runTrigger(TriggerType.Scry, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.Scry, runParams, false);
} }
} }
@@ -2308,6 +2376,7 @@ public class GameAction {
game.getReplacementHandler().runReplaceDamage(isCombat, damageMap, preventMap, counterTable, cause); game.getReplacementHandler().runReplaceDamage(isCombat, damageMap, preventMap, counterTable, cause);
Map<Card, Integer> lethalDamage = Maps.newHashMap(); Map<Card, Integer> lethalDamage = Maps.newHashMap();
Map<Integer, Card> lkiCache = Maps.newHashMap();
// Actually deal damage according to replaced damage map // Actually deal damage according to replaced damage map
for (Map.Entry<Card, Map<GameEntity, Integer>> et : damageMap.rowMap().entrySet()) { for (Map.Entry<Card, Map<GameEntity, Integer>> et : damageMap.rowMap().entrySet()) {
@@ -2334,6 +2403,8 @@ public class GameAction {
e.setValue(Integer.valueOf(e.getKey().addDamageAfterPrevention(e.getValue(), sourceLKI, isCombat, counterTable))); e.setValue(Integer.valueOf(e.getKey().addDamageAfterPrevention(e.getValue(), sourceLKI, isCombat, counterTable)));
sum += e.getValue(); sum += e.getValue();
sourceLKI.getDamageHistory().registerDamage(e.getValue(), isCombat, sourceLKI, e.getKey(), lkiCache);
} }
if (sum > 0 && sourceLKI.hasKeyword(Keyword.LIFELINK)) { if (sum > 0 && sourceLKI.hasKeyword(Keyword.LIFELINK)) {

View File

@@ -42,17 +42,12 @@ import forge.game.player.PlayerController;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementLayer; import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.AbilityManaPart; import forge.game.spellability.*;
import forge.game.spellability.AbilitySub; import forge.game.staticability.StaticAbilityLayer;
import forge.game.spellability.AlternativeCost;
import forge.game.spellability.OptionalCost;
import forge.game.spellability.OptionalCostValue;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityRestriction;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler; import forge.game.trigger.TriggerHandler;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Lang; import forge.util.Lang;
import forge.util.TextUtil; import forge.util.TextUtil;
@@ -91,7 +86,11 @@ public final class GameActionUtil {
Card source = sa.getHostCard(); Card source = sa.getHostCard();
final Game game = source.getGame(); final Game game = source.getGame();
if (sa.isSpell() && !source.isInPlay()) { if (sa.isSpell() && source.isInPlay()) {
return alternatives;
}
if (sa.isSpell()) {
boolean lkicheck = false; boolean lkicheck = false;
Card newHost = ((Spell)sa).getAlternateHost(source); Card newHost = ((Spell)sa).getAlternateHost(source);
@@ -179,12 +178,13 @@ public final class GameActionUtil {
SpellAbility newSA; SpellAbility newSA;
if (source.getAlternateState().getType().hasSubtype("Aura")) { if (source.getAlternateState().getType().hasSubtype("Aura")) {
newSA = source.getAlternateState().getFirstAbility().copyWithManaCostReplaced(activator, newSA = source.getAlternateState().getFirstAbility().copyWithManaCostReplaced(activator, disturbCost);
disturbCost);
} else { } else {
newSA = sa.copyWithManaCostReplaced(activator, disturbCost); newSA = new SpellPermanent(source);
newSA.setCardState(source.getAlternateState());
newSA.setPayCosts(disturbCost);
newSA.setActivatingPlayer(activator);
} }
newSA.setActivatingPlayer(activator);
newSA.putParam("PrecostDesc", "Disturb —"); newSA.putParam("PrecostDesc", "Disturb —");
newSA.putParam("CostDesc", disturbCost.toString()); newSA.putParam("CostDesc", disturbCost.toString());
@@ -210,7 +210,6 @@ public final class GameActionUtil {
final Cost escapeCost = new Cost(k[1], true); final Cost escapeCost = new Cost(k[1], true);
final SpellAbility newSA = sa.copyWithManaCostReplaced(activator, escapeCost); final SpellAbility newSA = sa.copyWithManaCostReplaced(activator, escapeCost);
newSA.setActivatingPlayer(activator);
newSA.putParam("PrecostDesc", "Escape—"); newSA.putParam("PrecostDesc", "Escape—");
newSA.putParam("CostDesc", escapeCost.toString()); newSA.putParam("CostDesc", escapeCost.toString());
@@ -290,6 +289,35 @@ public final class GameActionUtil {
foretold.putParam("AfterDescription", "(Foretold)"); foretold.putParam("AfterDescription", "(Foretold)");
alternatives.add(foretold); alternatives.add(foretold);
} }
// some needs to check after ability was put on the stack
// Currently this is only checked for Toolbox and that only cares about creature spells
if (source.isCreature() && game.getAction().hasStaticAbilityAffectingZone(ZoneType.Stack, StaticAbilityLayer.ABILITIES)) {
Zone oldZone = source.getLastKnownZone();
Card blitzCopy = source;
if (!source.isLKI()) {
blitzCopy = CardUtil.getLKICopy(source);
}
blitzCopy.setLastKnownZone(game.getStackZone());
lkicheck = true;
blitzCopy.clearStaticChangedCardKeywords(false);
CardCollection preList = new CardCollection(blitzCopy);
game.getAction().checkStaticAbilities(false, Sets.newHashSet(blitzCopy), preList);
// currently only for Keyword BLitz, but should affect Dash probably too
for (final KeywordInterface inst : blitzCopy.getKeywords(Keyword.BLITZ)) {
// TODO with mana value 4 or greater has blitz.
for (SpellAbility iSa : inst.getAbilities()) {
// do only non intrinsic
if (!iSa.isIntrinsic()) {
alternatives.add(iSa);
}
}
}
// need to reset to Old Zone, or canPlay would fail
blitzCopy.setLastKnownZone(oldZone);
}
} }
// reset static abilities // reset static abilities
@@ -300,74 +328,73 @@ public final class GameActionUtil {
// need to unfreeze tracker // need to unfreeze tracker
game.getTracker().unfreeze(); game.getTracker().unfreeze();
} }
} } else {
if (sa.isManaAbility() && sa.isActivatedAbility() && activator.hasKeyword("Piracy") && source.isLand() && source.isInPlay() && !activator.equals(source.getController()) && sa.getPayCosts().hasTapCost()) {
SpellAbility newSA = sa.copy(activator);
// to bypass Activator restriction, set Activator to Player
newSA.getRestrictions().setActivator("Player");
if (sa.isManaAbility() && sa.isActivatedAbility() && activator.hasKeyword("Piracy") && source.isLand() && source.isInPlay() && !activator.equals(source.getController()) && sa.getPayCosts().hasTapCost()) { // extra Mana restriction to only Spells
SpellAbility newSA = sa.copy(activator); for (AbilityManaPart mp : newSA.getAllManaParts()) {
// to bypass Activator restriction, set Activator to Player mp.setExtraManaRestriction("Spell");
newSA.getRestrictions().setActivator("Player");
// extra Mana restriction to only Spells
for (AbilityManaPart mp : newSA.getAllManaParts()) {
mp.setExtraManaRestriction("Spell");
}
alternatives.add(newSA);
}
// below are for some special cases of activated abilities
if (sa.isCycling() && activator.hasKeyword("CyclingForZero")) {
for (final KeywordInterface inst : source.getKeywords()) {
// need to find the correct Keyword from which this Ability is from
if (!inst.getAbilities().contains(sa)) {
continue;
} }
// set the cost to this directly to bypass non mana cost
final SpellAbility newSA = sa.copyWithDefinedCost("Discard<1/CARDNAME>");
newSA.setActivatingPlayer(activator);
newSA.putParam("CostDesc", ManaCostParser.parse("0"));
// need to build a new Keyword to get better Reminder Text
String data[] = inst.getOriginal().split(":");
data[1] = "0";
KeywordInterface newKi = Keyword.getInstance(StringUtils.join(data, ":"));
// makes new SpellDescription
final StringBuilder sb = new StringBuilder();
sb.append(newSA.getCostDescription());
sb.append("(").append(newKi.getReminderText()).append(")");
newSA.setDescription(sb.toString());
alternatives.add(newSA); alternatives.add(newSA);
} }
}
if (sa.isEquip() && activator.hasKeyword("You may pay 0 rather than pay equip costs.")) { // below are for some special cases of activated abilities
for (final KeywordInterface inst : source.getKeywords()) { if (sa.isCycling() && activator.hasKeyword("CyclingForZero")) {
// need to find the correct Keyword from which this Ability is from for (final KeywordInterface inst : source.getKeywords()) {
if (!inst.getAbilities().contains(sa)) { // need to find the correct Keyword from which this Ability is from
continue; if (!inst.getAbilities().contains(sa)) {
continue;
}
// set the cost to this directly to bypass non mana cost
final SpellAbility newSA = sa.copyWithDefinedCost("Discard<1/CARDNAME>");
newSA.setActivatingPlayer(activator);
newSA.putParam("CostDesc", ManaCostParser.parse("0"));
// need to build a new Keyword to get better Reminder Text
String data[] = inst.getOriginal().split(":");
data[1] = "0";
KeywordInterface newKi = Keyword.getInstance(StringUtils.join(data, ":"));
// makes new SpellDescription
final StringBuilder sb = new StringBuilder();
sb.append(newSA.getCostDescription());
sb.append("(").append(newKi.getReminderText()).append(")");
newSA.setDescription(sb.toString());
alternatives.add(newSA);
} }
}
if (sa.isEquip() && activator.hasKeyword("You may pay 0 rather than pay equip costs.")) {
for (final KeywordInterface inst : source.getKeywords()) {
// need to find the correct Keyword from which this Ability is from
if (!inst.getAbilities().contains(sa)) {
continue;
}
// set the cost to this directly to bypass non mana cost // set the cost to this directly to bypass non mana cost
SpellAbility newSA = sa.copyWithDefinedCost("0"); SpellAbility newSA = sa.copyWithDefinedCost("0");
newSA.setActivatingPlayer(activator); newSA.setActivatingPlayer(activator);
newSA.putParam("CostDesc", ManaCostParser.parse("0")); newSA.putParam("CostDesc", ManaCostParser.parse("0"));
// need to build a new Keyword to get better Reminder Text // need to build a new Keyword to get better Reminder Text
String data[] = inst.getOriginal().split(":"); String data[] = inst.getOriginal().split(":");
data[1] = "0"; data[1] = "0";
KeywordInterface newKi = Keyword.getInstance(StringUtils.join(data, ":")); KeywordInterface newKi = Keyword.getInstance(StringUtils.join(data, ":"));
// makes new SpellDescription // makes new SpellDescription
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
sb.append(newSA.getCostDescription()); sb.append(newSA.getCostDescription());
sb.append("(").append(newKi.getReminderText()).append(")"); sb.append("(").append(newKi.getReminderText()).append(")");
newSA.setDescription(sb.toString()); newSA.setDescription(sb.toString());
alternatives.add(newSA); alternatives.add(newSA);
}
} }
} }
return alternatives; return alternatives;
} }
@@ -677,13 +704,12 @@ public final class GameActionUtil {
if (!StringUtils.isNumeric(amount)) { if (!StringUtils.isNumeric(amount)) {
sa.setSVar(amount, sourceCard.getSVar(amount)); sa.setSVar(amount, sourceCard.getSVar(amount));
} }
CardFactoryUtil.setupETBReplacementAbility(sa);
String desc = "It enters the battlefield with "; String desc = "It enters the battlefield with ";
desc += Lang.nounWithNumeral(amount, CounterType.getType(counter).getName() + " counter"); desc += Lang.nounWithNumeral(amount, CounterType.getType(counter).getName() + " counter");
desc += " on it."; desc += " on it.";
String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | Description$ " + desc; String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | ReplacementResult$ Updated | Description$ " + desc;
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
re.setLayer(ReplacementLayer.Other); re.setLayer(ReplacementLayer.Other);

View File

@@ -17,9 +17,13 @@
*/ */
package forge.game; package forge.game;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
@@ -46,6 +50,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
private String name = ""; private String name = "";
protected CardCollection attachedCards = new CardCollection(); protected CardCollection attachedCards = new CardCollection();
protected Map<CounterType, Integer> counters = Maps.newHashMap(); protected Map<CounterType, Integer> counters = Maps.newHashMap();
protected List<Pair<Integer, Boolean>> damageReceivedThisTurn = Lists.newArrayList();
protected GameEntity(int id0) { protected GameEntity(int id0) {
id = id0; id = id0;
@@ -330,6 +335,30 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
addCounterInternal(CounterType.get(counterType), n, source, fireEvents, table); addCounterInternal(CounterType.get(counterType), n, source, fireEvents, table);
} }
public void receiveDamage(Pair<Integer, Boolean> dmg) {
damageReceivedThisTurn.add(dmg);
}
public final int getAssignedDamage() {
return getAssignedDamage(null, null);
}
public final int getAssignedCombatDamage() {
return getAssignedDamage(true, null);
}
public final int getAssignedDamage(Boolean isCombat, final Card source) {
int num = 0;
for (Pair<Integer, Boolean> dmg : damageReceivedThisTurn) {
if (isCombat != null && dmg.getRight() != isCombat) {
continue;
}
if (source != null && !getGame().getDamageLKI(dmg).getLeft().equalsWithTimestamp(source)) {
continue;
}
num += dmg.getLeft();
}
return num;
}
@Override @Override
public final boolean equals(Object o) { public final boolean equals(Object o) {
if (o == null) { return false; } if (o == null) { return false; }

View File

@@ -94,7 +94,9 @@ public class GameEntityCounterTable extends ForwardingTable<Optional<Player>, Ga
for (Map<CounterType, Integer> cm : gm.getValue().values()) { for (Map<CounterType, Integer> cm : gm.getValue().values()) {
Integer old = ObjectUtils.firstNonNull(result.get(gm.getKey()), 0); Integer old = ObjectUtils.firstNonNull(result.get(gm.getKey()), 0);
Integer v = ObjectUtils.firstNonNull(cm.get(type), 0); Integer v = ObjectUtils.firstNonNull(cm.get(type), 0);
result.put(gm.getKey(), old + v); if (old + v > 0) {
result.put(gm.getKey(), old + v);
}
} }
} }
} }
@@ -120,8 +122,12 @@ public class GameEntityCounterTable extends ForwardingTable<Optional<Player>, Ga
game.getTriggerHandler().runTrigger(TriggerType.CounterAddedAll, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.CounterAddedAll, runParams, false);
} }
@SuppressWarnings("unchecked")
public void replaceCounterEffect(final Game game, final SpellAbility cause, final boolean effect) { public void replaceCounterEffect(final Game game, final SpellAbility cause, final boolean effect) {
replaceCounterEffect(game, cause, effect, false, null);
}
@SuppressWarnings("unchecked")
public void replaceCounterEffect(final Game game, final SpellAbility cause, final boolean effect, final boolean etb, Map<AbilityKey, Object> params) {
if (isEmpty()) { if (isEmpty()) {
return; return;
} }
@@ -133,6 +139,10 @@ public class GameEntityCounterTable extends ForwardingTable<Optional<Player>, Ga
repParams.put(AbilityKey.Cause, cause); repParams.put(AbilityKey.Cause, cause);
repParams.put(AbilityKey.EffectOnly, effect); repParams.put(AbilityKey.EffectOnly, effect);
repParams.put(AbilityKey.CounterMap, values); repParams.put(AbilityKey.CounterMap, values);
repParams.put(AbilityKey.ETB, etb);
if (params != null) {
repParams.putAll(params);
}
switch (game.getReplacementHandler().run(ReplacementType.AddCounter, repParams)) { switch (game.getReplacementHandler().run(ReplacementType.AddCounter, repParams)) {
case NotReplaced: case NotReplaced:

View File

@@ -48,7 +48,7 @@ public class GameFormat implements Comparable<GameFormat> {
public enum FormatType { public enum FormatType {
SANCTIONED, SANCTIONED,
CASUAL, CASUAL,
HISTORIC, ARCHIVED,
DIGITAL, DIGITAL,
CUSTOM CUSTOM
} }
@@ -290,7 +290,7 @@ public class GameFormat implements Comparable<GameFormat> {
if (other.formatSubType != formatSubType){ if (other.formatSubType != formatSubType){
return formatSubType.compareTo(other.formatSubType); return formatSubType.compareTo(other.formatSubType);
} }
if (formatType.equals(FormatType.HISTORIC)){ if (formatType.equals(FormatType.ARCHIVED)){
int compareDates = this.effectiveDate.compareTo(other.effectiveDate); int compareDates = this.effectiveDate.compareTo(other.effectiveDate);
if (compareDates != 0) if (compareDates != 0)
return compareDates; return compareDates;
@@ -306,7 +306,7 @@ public class GameFormat implements Comparable<GameFormat> {
public static class Reader extends StorageReaderRecursiveFolderWithUserFolder<GameFormat> { public static class Reader extends StorageReaderRecursiveFolderWithUserFolder<GameFormat> {
List<GameFormat> naturallyOrdered = new ArrayList<>(); List<GameFormat> naturallyOrdered = new ArrayList<>();
boolean includeHistoric; boolean includeArchived;
private List<String> coreFormats = new ArrayList<>(); private List<String> coreFormats = new ArrayList<>();
{ {
coreFormats.add("Standard.txt"); coreFormats.add("Standard.txt");
@@ -321,14 +321,14 @@ public class GameFormat implements Comparable<GameFormat> {
coreFormats.add("Oathbreaker.txt"); coreFormats.add("Oathbreaker.txt");
} }
public Reader(File forgeFormats, File customFormats, boolean includeHistoric) { public Reader(File forgeFormats, File customFormats, boolean includeArchived) {
super(forgeFormats, customFormats, GameFormat.FN_GET_NAME); super(forgeFormats, customFormats, GameFormat.FN_GET_NAME);
this.includeHistoric=includeHistoric; this.includeArchived=includeArchived;
} }
@Override @Override
protected GameFormat read(File file) { protected GameFormat read(File file) {
if (!includeHistoric && !coreFormats.contains(file.getName())) { if (!includeArchived && !coreFormats.contains(file.getName())) {
return null; return null;
} }
final Map<String, List<String>> contents = FileSection.parseSections(FileUtil.readFile(file)); final Map<String, List<String>> contents = FileSection.parseSections(FileUtil.readFile(file));
@@ -348,7 +348,12 @@ public class GameFormat implements Comparable<GameFormat> {
try { try {
formatType = FormatType.valueOf(section.get("type").toUpperCase()); formatType = FormatType.valueOf(section.get("type").toUpperCase());
} catch (Exception e) { } catch (Exception e) {
formatType = FormatType.CUSTOM; if ("HISTORIC".equals(section.get("type").toUpperCase())) {
System.out.println("Historic is no longer used as a format Type. Please update " + file.getAbsolutePath() + " to use 'Archived' instead");
formatType = FormatType.ARCHIVED;
} else {
formatType = FormatType.CUSTOM;
}
} }
FormatSubType formatsubType; FormatSubType formatsubType;
try { try {
@@ -450,7 +455,7 @@ public class GameFormat implements Comparable<GameFormat> {
public Iterable<GameFormat> getFilterList() { public Iterable<GameFormat> getFilterList() {
List<GameFormat> coreList = new ArrayList<>(); List<GameFormat> coreList = new ArrayList<>();
for (GameFormat format: naturallyOrdered) { for (GameFormat format: naturallyOrdered) {
if (!format.getFormatType().equals(FormatType.HISTORIC) if (!format.getFormatType().equals(FormatType.ARCHIVED)
&&!format.getFormatType().equals(FormatType.DIGITAL)){ &&!format.getFormatType().equals(FormatType.DIGITAL)){
coreList.add(format); coreList.add(format);
} }
@@ -458,10 +463,10 @@ public class GameFormat implements Comparable<GameFormat> {
return coreList; return coreList;
} }
public Iterable<GameFormat> getHistoricList() { public Iterable<GameFormat> getArchivedList() {
List<GameFormat> coreList = new ArrayList<>(); List<GameFormat> coreList = new ArrayList<>();
for (GameFormat format: naturallyOrdered) { for (GameFormat format: naturallyOrdered) {
if (format.getFormatType().equals(FormatType.HISTORIC)){ if (format.getFormatType().equals(FormatType.ARCHIVED)){
coreList.add(format); coreList.add(format);
} }
} }
@@ -470,7 +475,7 @@ public class GameFormat implements Comparable<GameFormat> {
public Iterable<GameFormat> getBlockList() { public Iterable<GameFormat> getBlockList() {
List<GameFormat> blockFormats = new ArrayList<>(); List<GameFormat> blockFormats = new ArrayList<>();
for (GameFormat format : this.getHistoricList()){ for (GameFormat format : this.getArchivedList()){
if (format.getFormatSubType() != GameFormat.FormatSubType.BLOCK) if (format.getFormatSubType() != GameFormat.FormatSubType.BLOCK)
continue; continue;
if (!format.getName().endsWith("Block")) if (!format.getName().endsWith("Block"))
@@ -481,10 +486,10 @@ public class GameFormat implements Comparable<GameFormat> {
return blockFormats; return blockFormats;
} }
public Map<String, List<GameFormat>> getHistoricMap() { public Map<String, List<GameFormat>> getArchivedMap() {
Map<String, List<GameFormat>> coreList = new HashMap<>(); Map<String, List<GameFormat>> coreList = new HashMap<>();
for (GameFormat format: naturallyOrdered){ for (GameFormat format: naturallyOrdered){
if (format.getFormatType().equals(FormatType.HISTORIC)){ if (format.getFormatType().equals(FormatType.ARCHIVED)){
String alpha = format.getName().substring(0,1); String alpha = format.getName().substring(0,1);
if (!coreList.containsKey(alpha)) { if (!coreList.containsKey(alpha)) {
coreList.put(alpha,new ArrayList<>()); coreList.put(alpha,new ArrayList<>());
@@ -557,9 +562,9 @@ public class GameFormat implements Comparable<GameFormat> {
//exclude Commander format as other deck checks are not performed here //exclude Commander format as other deck checks are not performed here
continue; continue;
} }
if (gf.getFormatType().equals(FormatType.HISTORIC) && coveredTypes.contains(gf.getFormatSubType()) if (gf.getFormatType().equals(FormatType.ARCHIVED) && coveredTypes.contains(gf.getFormatSubType())
&& !exhaustive){ && !exhaustive){
//exclude duplicate formats - only keep first of e.g. Standard historical //exclude duplicate formats - only keep first of e.g. Standard archived
continue; continue;
} }
if (gf.isPoolLegal(allCards)) { if (gf.isPoolLegal(allCards)) {
@@ -590,7 +595,7 @@ public class GameFormat implements Comparable<GameFormat> {
if (gf2.formatSubType != gf1.formatSubType){ if (gf2.formatSubType != gf1.formatSubType){
return gf1.formatSubType.compareTo(gf2.formatSubType); return gf1.formatSubType.compareTo(gf2.formatSubType);
} }
if (gf1.formatType.equals(FormatType.HISTORIC)){ if (gf1.formatType.equals(FormatType.ARCHIVED)){
if (gf1.effectiveDate!=gf2.effectiveDate) {//for matching dates or default dates default to name sorting if (gf1.effectiveDate!=gf2.effectiveDate) {//for matching dates or default dates default to name sorting
return gf1.effectiveDate.compareTo(gf2.effectiveDate); return gf1.effectiveDate.compareTo(gf2.effectiveDate);
} }

View File

@@ -81,7 +81,8 @@ public class GameRules {
} }
public void setAppliedVariants(final Set<GameType> appliedVariants) { public void setAppliedVariants(final Set<GameType> appliedVariants) {
this.appliedVariants.addAll(appliedVariants); if (appliedVariants != null && !appliedVariants.isEmpty())
this.appliedVariants.addAll(appliedVariants);
} }
public boolean hasAppliedVariant(final GameType variant) { public boolean hasAppliedVariant(final GameType variant) {

View File

@@ -76,7 +76,7 @@ public enum GameType {
private final DeckFormat deckFormat; private final DeckFormat deckFormat;
private final boolean isCardPoolLimited, canSideboard, addWonCardsMidGame; private final boolean isCardPoolLimited, canSideboard, addWonCardsMidGame;
private final String name, description; private final String name, englishName, description;
private final Function<RegisteredPlayer, Deck> deckAutoGenerator; private final Function<RegisteredPlayer, Deck> deckAutoGenerator;
GameType(DeckFormat deckFormat0, boolean isCardPoolLimited0, boolean canSideboard0, boolean addWonCardsMidgame0, String name0, String description0) { GameType(DeckFormat deckFormat0, boolean isCardPoolLimited0, boolean canSideboard0, boolean addWonCardsMidgame0, String name0, String description0) {
@@ -90,6 +90,7 @@ public enum GameType {
canSideboard = canSideboard0; canSideboard = canSideboard0;
addWonCardsMidGame = addWonCardsMidgame0; addWonCardsMidGame = addWonCardsMidgame0;
name = localizer.getMessage(name0); name = localizer.getMessage(name0);
englishName = localizer.getEnglishMessage(name0);
if (description0.length()>0) { if (description0.length()>0) {
description0 = localizer.getMessage(description0); description0 = localizer.getMessage(description0);
} }
@@ -148,6 +149,9 @@ public enum GameType {
public String toString() { public String toString() {
return name; return name;
} }
public String getEnglishName() {
return englishName;
}
public String getDescription() { public String getDescription() {
return description; return description;

View File

@@ -33,7 +33,6 @@ public enum GlobalRuleChange {
onlyOneBlocker ("No more than one creature can block each combat."), onlyOneBlocker ("No more than one creature can block each combat."),
onlyOneBlockerPerOpponent ("Each opponent can't block with more than one creature."), onlyOneBlockerPerOpponent ("Each opponent can't block with more than one creature."),
onlyTwoBlockers ("No more than two creatures can block each combat."), onlyTwoBlockers ("No more than two creatures can block each combat."),
toughnessAssignsDamage ("Each creature assigns combat damage equal to its toughness rather than its power."),
blankIsChaos("Each blank roll of the planar dice is a {CHAOS} roll."); blankIsChaos("Each blank roll of the planar dice is a {CHAOS} roll.");
private final String ruleText; private final String ruleText;

View File

@@ -288,6 +288,8 @@ public class StaticEffect {
affectedCard.removeCanBlockAdditional(getTimestamp()); affectedCard.removeCanBlockAdditional(getTimestamp());
} }
affectedCard.removeChangedSVars(getTimestamp(), ability.getId());
affectedCard.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering affectedCard.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering
} }
return affectedCards; return affectedCards;

View File

@@ -327,8 +327,8 @@ public final class AbilityFactory {
} }
private static final TargetRestrictions readTarget(Map<String, String> mapParams) { private static final TargetRestrictions readTarget(Map<String, String> mapParams) {
final String min = mapParams.containsKey("TargetMin") ? mapParams.get("TargetMin") : "1"; final String min = mapParams.getOrDefault("TargetMin", "1");
final String max = mapParams.containsKey("TargetMax") ? mapParams.get("TargetMax") : "1"; final String max = mapParams.getOrDefault("TargetMax", "1");
// TgtPrompt should only be needed for more complicated ValidTgts // TgtPrompt should only be needed for more complicated ValidTgts
String tgtWhat = mapParams.get("ValidTgts"); String tgtWhat = mapParams.get("ValidTgts");

View File

@@ -67,6 +67,7 @@ public enum AbilityKey {
Explorer("Explorer"), Explorer("Explorer"),
ExtraTurn("ExtraTurn"), ExtraTurn("ExtraTurn"),
Event("Event"), Event("Event"),
ETB("ETB"),
Fighter("Fighter"), Fighter("Fighter"),
Fighters("Fighters"), Fighters("Fighters"),
FirstTime("FirstTime"), FirstTime("FirstTime"),
@@ -112,12 +113,14 @@ public enum AbilityKey {
Result("Result"), Result("Result"),
RoomName("RoomName"), RoomName("RoomName"),
Scheme("Scheme"), Scheme("Scheme"),
ScryNum("ScryNum"),
Sides("Sides"), Sides("Sides"),
Source("Source"), Source("Source"),
Sources("Sources"), Sources("Sources"),
SourceSA("SourceSA"), SourceSA("SourceSA"),
SpellAbility("SpellAbility"), SpellAbility("SpellAbility"),
SpellAbilityStackInstance("SpellAbilityStackInstance"), SpellAbilityStackInstance("SpellAbilityStackInstance"),
SpellAbilityTarget("SpellAbilityTarget"),
SpellAbilityTargetingCards("SpellAbilityTargetingCards"), SpellAbilityTargetingCards("SpellAbilityTargetingCards"),
StackInstance("StackInstance"), StackInstance("StackInstance"),
StackSa("StackSa"), StackSa("StackSa"),

View File

@@ -14,6 +14,7 @@ import java.util.regex.Pattern;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@@ -146,7 +147,7 @@ public class AbilityUtils {
} else if (defined.equals("TopOfGraveyard")) { } else if (defined.equals("TopOfGraveyard")) {
final CardCollectionView grave = hostCard.getController().getCardsIn(ZoneType.Graveyard); final CardCollectionView grave = hostCard.getController().getCardsIn(ZoneType.Graveyard);
if (grave.size() > 0) { // TopOfLibrary or BottomOfLibrary if (grave.size() > 0) {
c = grave.getLast(); c = grave.getLast();
} else { } else {
// we don't want this to fall through and return the "Self" // we don't want this to fall through and return the "Self"
@@ -220,7 +221,7 @@ public class AbilityUtils {
final Object crd = root.getReplacingObject(type); final Object crd = root.getReplacingObject(type);
if (crd instanceof Card) { if (crd instanceof Card) {
c = game.getCardState((Card) crd); c = (Card) crd;
} else if (crd instanceof Iterable<?>) { } else if (crd instanceof Iterable<?>) {
cards.addAll(Iterables.filter((Iterable<?>) crd, Card.class)); cards.addAll(Iterables.filter((Iterable<?>) crd, Card.class));
} }
@@ -501,7 +502,7 @@ public class AbilityUtils {
players.addAll(player.getOpponents()); players.addAll(player.getOpponents());
val = playerXCount(players, calcX[1], card, ability); val = playerXCount(players, calcX[1], card, ability);
} else if (hType.equals("RegisteredOpponents")) { } else if (hType.equals("RegisteredOpponents")) {
players.addAll(Iterables.filter(game.getRegisteredPlayers(),PlayerPredicates.isOpponentOf(player))); players.addAll(Iterables.filter(game.getRegisteredPlayers(), PlayerPredicates.isOpponentOf(player)));
val = playerXCount(players, calcX[1], card, ability); val = playerXCount(players, calcX[1], card, ability);
} else if (hType.equals("Other")) { } else if (hType.equals("Other")) {
players.addAll(player.getAllOtherPlayers()); players.addAll(player.getAllOtherPlayers());
@@ -535,6 +536,9 @@ public class AbilityUtils {
} }
} }
val = playerXCount(players, calcX[1], card, ability); val = playerXCount(players, calcX[1], card, ability);
} else if (hType.startsWith("Defined")) {
String defined = hType.split("Defined")[1];
val = playerXCount(getDefinedPlayers(card, defined, ability), calcX[1], card, ability);
} else { } else {
val = 0; val = 0;
} }
@@ -1060,17 +1064,16 @@ public class AbilityUtils {
final SpellAbility root = ((SpellAbility)sa).getRootAbility(); final SpellAbility root = ((SpellAbility)sa).getRootAbility();
Object o = null; Object o = null;
if (defParsed.endsWith("Controller")) { if (defParsed.endsWith("Controller")) {
String triggeringType = defParsed.substring(9); final boolean orCont = defParsed.endsWith("OrController");
triggeringType = triggeringType.substring(0, triggeringType.length() - 10); String triggeringType = defParsed.substring(9, defParsed.length() - (orCont ? 12 : 10));
final Object c = root.getTriggeringObject(AbilityKey.fromString(triggeringType)); final Object c = root.getTriggeringObject(AbilityKey.fromString(triggeringType));
if (c instanceof Card) { if (orCont && c instanceof Player) {
o = c;
} else if (c instanceof Card) {
o = ((Card) c).getController(); o = ((Card) c).getController();
} } else if (c instanceof SpellAbility) {
if (c instanceof SpellAbility) {
o = ((SpellAbility) c).getActivatingPlayer(); o = ((SpellAbility) c).getActivatingPlayer();
} } else if (c instanceof CardCollection) { // For merged permanent
// For merged permanent
if (c instanceof CardCollection) {
o = ((CardCollection) c).get(0).getController(); o = ((CardCollection) c).get(0).getController();
} }
} }
@@ -1413,11 +1416,7 @@ public class AbilityUtils {
// Needed - Equip an untapped creature with Sword of the Paruns then cast Deadshot on it. Should deal 2 more damage. // Needed - Equip an untapped creature with Sword of the Paruns then cast Deadshot on it. Should deal 2 more damage.
game.getAction().checkStaticAbilities(); // this will refresh continuous abilities for players and permanents. game.getAction().checkStaticAbilities(); // this will refresh continuous abilities for players and permanents.
if (sa.isReplacementAbility() && abSub.getApi() == ApiType.InternalEtbReplacement) { game.getTriggerHandler().resetActiveTriggers(!sa.isReplacementAbility());
game.getTriggerHandler().resetActiveTriggers(false);
} else {
game.getTriggerHandler().resetActiveTriggers();
}
AbilityUtils.resolveApiAbility(abSub, game); AbilityUtils.resolveApiAbility(abSub, game);
} }
@@ -1448,7 +1447,7 @@ public class AbilityUtils {
// The player who has the chance to cancel the ability // The player who has the chance to cancel the ability
final String pays = sa.getParamOrDefault("UnlessPayer", "TargetedController"); final String pays = sa.getParamOrDefault("UnlessPayer", "TargetedController");
final FCollectionView<Player> allPayers = getDefinedPlayers(sa.getHostCard(), pays, sa); final FCollectionView<Player> allPayers = getDefinedPlayers(source, pays, sa);
final String resolveSubs = sa.getParam("UnlessResolveSubs"); // no value means 'Always' final String resolveSubs = sa.getParam("UnlessResolveSubs"); // no value means 'Always'
final boolean execSubsWhenPaid = "WhenPaid".equals(resolveSubs) || StringUtils.isBlank(resolveSubs); final boolean execSubsWhenPaid = "WhenPaid".equals(resolveSubs) || StringUtils.isBlank(resolveSubs);
final boolean execSubsWhenNotPaid = "WhenNotPaid".equals(resolveSubs) || StringUtils.isBlank(resolveSubs); final boolean execSubsWhenNotPaid = "WhenNotPaid".equals(resolveSubs) || StringUtils.isBlank(resolveSubs);
@@ -1485,7 +1484,7 @@ public class AbilityUtils {
cost = new Cost(new ManaCost(new ManaCostParser(String.valueOf(source.getChosenNumber()))), true); cost = new Cost(new ManaCost(new ManaCostParser(String.valueOf(source.getChosenNumber()))), true);
} }
else if (unlessCost.startsWith("DefinedCost")) { else if (unlessCost.startsWith("DefinedCost")) {
CardCollection definedCards = getDefinedCards(sa.getHostCard(), unlessCost.split("_")[1], sa); CardCollection definedCards = getDefinedCards(source, unlessCost.split("_")[1], sa);
if (definedCards.isEmpty()) { if (definedCards.isEmpty()) {
sa.resolve(); sa.resolve();
resolveSubAbilities(sa, game); resolveSubAbilities(sa, game);
@@ -1505,7 +1504,7 @@ public class AbilityUtils {
cost = new Cost(newCost.toManaCost(), true); cost = new Cost(newCost.toManaCost(), true);
} }
else if (unlessCost.startsWith("DefinedSACost")) { else if (unlessCost.startsWith("DefinedSACost")) {
FCollection<SpellAbility> definedSAs = getDefinedSpellAbilities(sa.getHostCard(), unlessCost.split("_")[1], sa); FCollection<SpellAbility> definedSAs = getDefinedSpellAbilities(source, unlessCost.split("_")[1], sa);
if (definedSAs.isEmpty()) { if (definedSAs.isEmpty()) {
sa.resolve(); sa.resolve();
resolveSubAbilities(sa, game); resolveSubAbilities(sa, game);
@@ -2032,7 +2031,7 @@ public class AbilityUtils {
return doXMath(c.getTotalDamageDoneBy(), expr, c, ctb); return doXMath(c.getTotalDamageDoneBy(), expr, c, ctb);
} }
if (sq[0].equals("TotalDamageReceivedThisTurn")) { if (sq[0].equals("TotalDamageReceivedThisTurn")) {
return doXMath(c.getTotalDamageReceivedThisTurn(), expr, c, ctb); return doXMath(c.getAssignedDamage(), expr, c, ctb);
} }
if (sq[0].contains("CardPower")) { if (sq[0].contains("CardPower")) {
@@ -2093,13 +2092,6 @@ public class AbilityUtils {
return doXMath(c.getTimesMutated(), expr, c, ctb); return doXMath(c.getTimesMutated(), expr, c, ctb);
} }
if (sq[0].startsWith("DamageDoneByPlayerThisTurn")) {
int sum = 0;
for (Player p : getDefinedPlayers(c, sq[1], ctb)) {
sum += c.getReceivedDamageByPlayerThisTurn(p);
}
return doXMath(sum, expr, c, ctb);
}
if (sq[0].equals("RegeneratedThisTurn")) { if (sq[0].equals("RegeneratedThisTurn")) {
return doXMath(c.getRegeneratedThisTurn(), expr, c, ctb); return doXMath(c.getRegeneratedThisTurn(), expr, c, ctb);
} }
@@ -2260,6 +2252,9 @@ public class AbilityUtils {
if (sq[0].equals("Monarch")) { if (sq[0].equals("Monarch")) {
return doXMath(calculateAmount(c, sq[player.isMonarch() ? 1 : 2], ctb), expr, c, ctb); return doXMath(calculateAmount(c, sq[player.isMonarch() ? 1 : 2], ctb), expr, c, ctb);
} }
if (sq[0].equals("Initiative")) {
return doXMath(calculateAmount(c, sq[player.hasInitiative() ? 1: 2], ctb), expr, c, ctb);
}
if (sq[0].equals("StartingPlayer")) { if (sq[0].equals("StartingPlayer")) {
return doXMath(calculateAmount(c, sq[player.isStartingPlayer() ? 1: 2], ctb), expr, c, ctb); return doXMath(calculateAmount(c, sq[player.isStartingPlayer() ? 1: 2], ctb), expr, c, ctb);
} }
@@ -2358,20 +2353,28 @@ public class AbilityUtils {
return doXMath(player.getOpponentsTotalPoisonCounters(), expr, c, ctb); return doXMath(player.getOpponentsTotalPoisonCounters(), expr, c, ctb);
} }
if (sq[0].equals("YourDamageThisTurn")) {
return doXMath(player.getAssignedDamage(), expr, c, ctb);
}
if (sq[0].equals("TotalOppDamageThisTurn")) {
return doXMath(player.getOpponentsAssignedDamage(), expr, c, ctb);
}
if (sq[0].equals("MaxOppDamageThisTurn")) { if (sq[0].equals("MaxOppDamageThisTurn")) {
return doXMath(player.getMaxOpponentAssignedDamage(), expr, c, ctb); return doXMath(player.getMaxOpponentAssignedDamage(), expr, c, ctb);
} }
if (sq[0].startsWith("YourDamageSourcesThisTurn")) { if (sq[0].contains("TotalDamageThisTurn")) {
Iterable<Card> allSrc = player.getAssignedDamageSources(); String[] props = l[0].split(" ");
String restriction = sq[0].split(" ")[1]; int sum = 0;
return doXMath(CardLists.getValidCardCount(allSrc, restriction, player, c, ctb), expr, c, ctb); for (Pair<Integer, Boolean> p : c.getDamageReceivedThisTurn()) {
if (game.getDamageLKI(p).getLeft().isValid(props[1], player, c, ctb)) {
sum += p.getLeft();
}
}
return doXMath(sum, expr, c, ctb);
}
if (sq[0].contains("DamageThisTurn")) {
String[] props = l[0].split(" ");
Boolean isCombat = null;
if (sq[0].contains("CombatDamage")) {
isCombat = true;
}
return doXMath(game.getDamageDoneThisTurn(isCombat, false, props[1], props[2], c, player, ctb).size(), expr, c, ctb);
} }
if (sq[0].equals("YourTurns")) { if (sq[0].equals("YourTurns")) {
@@ -3324,12 +3327,6 @@ public class AbilityUtils {
totDmg += p.getAssignedDamage(); totDmg += p.getAssignedDamage();
} }
return doXMath(totDmg, m, source, ctb); return doXMath(totDmg, m, source, ctb);
} else if (sq[0].contains("LifeLostThisTurn")) {
int totDmg = 0;
for (Player p : players) {
totDmg += p.getLifeLostThisTurn();
}
return doXMath(totDmg, m, source, ctb);
} }
if (players.size() > 0) { if (players.size() > 0) {
@@ -3386,7 +3383,7 @@ public class AbilityUtils {
if (value.contains("DomainPlayer")) { if (value.contains("DomainPlayer")) {
int n = 0; int n = 0;
final CardCollectionView someCards = player.getCardsIn(ZoneType.Battlefield); final CardCollectionView someCards = player.getLandsInPlay();
final List<String> basic = MagicColor.Constant.BASIC_LANDS; final List<String> basic = MagicColor.Constant.BASIC_LANDS;
for (int i = 0; i < basic.size(); i++) { for (int i = 0; i < basic.size(); i++) {
@@ -3483,6 +3480,10 @@ public class AbilityUtils {
return doXMath(opps == null ? 0 : opps.size(), m, source, ctb); return doXMath(opps == null ? 0 : opps.size(), m, source, ctb);
} }
if (value.equals("OpponentsAttackedThisCombat")) {
return doXMath(game.getCombat().getAttackedOpponents(player).size(), m, source, ctb);
}
if (value.equals("DungeonsCompleted")) { if (value.equals("DungeonsCompleted")) {
return doXMath(player.getCompletedDungeons().size(), m, source, ctb); return doXMath(player.getCompletedDungeons().size(), m, source, ctb);
} }

View File

@@ -71,6 +71,7 @@ public enum ApiType {
DigUntil (DigUntilEffect.class), DigUntil (DigUntilEffect.class),
Discard (DiscardEffect.class), Discard (DiscardEffect.class),
DrainMana (DrainManaEffect.class), DrainMana (DrainManaEffect.class),
Draft (DraftEffect.class),
Draw (DrawEffect.class), Draw (DrawEffect.class),
EachDamage (DamageEachEffect.class), EachDamage (DamageEachEffect.class),
Effect (EffectEffect.class), Effect (EffectEffect.class),
@@ -171,6 +172,7 @@ public enum ApiType {
Subgame (SubgameEffect.class), Subgame (SubgameEffect.class),
Surveil (SurveilEffect.class), Surveil (SurveilEffect.class),
SwitchBlock (SwitchBlockEffect.class), SwitchBlock (SwitchBlockEffect.class),
TakeInitiative (TakeInitiativeEffect.class),
Tap (TapEffect.class), Tap (TapEffect.class),
TapAll (TapAllEffect.class), TapAll (TapAllEffect.class),
TapOrUntap (TapOrUntapEffect.class), TapOrUntap (TapOrUntapEffect.class),
@@ -188,7 +190,6 @@ public enum ApiType {
DamageResolve (DamageResolveEffect.class), DamageResolve (DamageResolveEffect.class),
ChangeZoneResolve (ChangeZoneResolveEffect.class), ChangeZoneResolve (ChangeZoneResolveEffect.class),
InternalEtbReplacement (ETBReplacementEffect.class),
InternalLegendaryRule (CharmEffect.class), InternalLegendaryRule (CharmEffect.class),
InternalIgnoreEffect (CharmEffect.class), InternalIgnoreEffect (CharmEffect.class),
UpdateRemember (UpdateRememberEffect.class); UpdateRemember (UpdateRememberEffect.class);

View File

@@ -300,13 +300,12 @@ public abstract class SpellAbilityEffect {
delTrig.append("| TriggerDescription$ ").append(desc); delTrig.append("| TriggerDescription$ ").append(desc);
final Trigger trig = TriggerHandler.parseTrigger(delTrig.toString(), CardUtil.getLKICopy(sa.getHostCard()), intrinsic); final Trigger trig = TriggerHandler.parseTrigger(delTrig.toString(), CardUtil.getLKICopy(sa.getHostCard()), intrinsic);
long ts = sa.getHostCard().getGame().getNextTimestamp();
for (final Card c : crds) { for (final Card c : crds) {
trig.addRemembered(c); trig.addRemembered(c);
// Svar for AI // Svar for AI
if (!c.hasSVar("EndOfTurnLeavePlay")) { c.addChangedSVars(Collections.singletonMap("EndOfTurnLeavePlay", "AtEOT"), ts, 0);
c.setSVar("EndOfTurnLeavePlay", "AtEOT");
}
} }
String trigSA = ""; String trigSA = "";
if (location.equals("Hand")) { if (location.equals("Hand")) {
@@ -346,9 +345,7 @@ public abstract class SpellAbilityEffect {
card.addTrigger(trig); card.addTrigger(trig);
// Svar for AI // Svar for AI
if (!card.hasSVar("EndOfTurnLeavePlay")) { card.addChangedSVars(Collections.singletonMap("EndOfTurnLeavePlay", "AtEOT"), card.getGame().getNextTimestamp(), 0);
card.setSVar("EndOfTurnLeavePlay", "AtEOT");
}
} }
protected static SpellAbility getForgetSpellAbility(final Card card) { protected static SpellAbility getForgetSpellAbility(final Card card) {

View File

@@ -25,7 +25,7 @@ public class AbandonEffect extends SpellAbilityEffect {
Player controller = source.getController(); Player controller = source.getController();
boolean isOptional = sa.hasParam("Optional"); boolean isOptional = sa.hasParam("Optional");
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeAbandonSource", CardTranslation.getTranslatedName(source.getName())))) { if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeAbandonSource", CardTranslation.getTranslatedName(source.getName())), null)) {
return; return;
} }

View File

@@ -1,10 +1,11 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.GameCommand; import forge.GameCommand;
import forge.card.CardType; import forge.card.CardType;
@@ -64,17 +65,17 @@ public class AnimateAllEffect extends AnimateEffectBase {
types.add(host.getChosenType2()); types.add(host.getChosenType2());
} }
final List<String> keywords = new ArrayList<>(); final List<String> keywords = Lists.newArrayList();
if (sa.hasParam("Keywords")) { if (sa.hasParam("Keywords")) {
keywords.addAll(Arrays.asList(sa.getParam("Keywords").split(" & "))); keywords.addAll(Arrays.asList(sa.getParam("Keywords").split(" & ")));
} }
final List<String> removeKeywords = new ArrayList<>(); final List<String> removeKeywords = Lists.newArrayList();
if (sa.hasParam("RemoveKeywords")) { if (sa.hasParam("RemoveKeywords")) {
removeKeywords.addAll(Arrays.asList(sa.getParam("RemoveKeywords").split(" & "))); removeKeywords.addAll(Arrays.asList(sa.getParam("RemoveKeywords").split(" & ")));
} }
final List<String> hiddenKeywords = new ArrayList<>(); final List<String> hiddenKeywords = Lists.newArrayList();
if (sa.hasParam("HiddenKeywords")) { if (sa.hasParam("HiddenKeywords")) {
hiddenKeywords.addAll(Arrays.asList(sa.getParam("HiddenKeywords").split(" & "))); hiddenKeywords.addAll(Arrays.asList(sa.getParam("HiddenKeywords").split(" & ")));
} }
@@ -99,27 +100,38 @@ public class AnimateAllEffect extends AnimateEffectBase {
} }
// abilities to add to the animated being // abilities to add to the animated being
final List<String> abilities = new ArrayList<>(); final List<String> abilities = Lists.newArrayList();
if (sa.hasParam("Abilities")) { if (sa.hasParam("Abilities")) {
abilities.addAll(Arrays.asList(sa.getParam("Abilities").split(","))); abilities.addAll(Arrays.asList(sa.getParam("Abilities").split(",")));
} }
// replacement effects to add to the animated being // replacement effects to add to the animated being
final List<String> replacements = new ArrayList<>(); final List<String> replacements = Lists.newArrayList();
if (sa.hasParam("Replacements")) { if (sa.hasParam("Replacements")) {
replacements.addAll(Arrays.asList(sa.getParam("Replacements").split(","))); replacements.addAll(Arrays.asList(sa.getParam("Replacements").split(",")));
} }
// triggers to add to the animated being // triggers to add to the animated being
final List<String> triggers = new ArrayList<>(); final List<String> triggers = Lists.newArrayList();
if (sa.hasParam("Triggers")) { if (sa.hasParam("Triggers")) {
triggers.addAll(Arrays.asList(sa.getParam("Triggers").split(","))); triggers.addAll(Arrays.asList(sa.getParam("Triggers").split(",")));
} }
// sVars to add to the animated being // sVars to add to the animated being
final List<String> sVars = new ArrayList<>(); final List<String> sVars = Lists.newArrayList();
if (sa.hasParam("sVars")) { if (sa.hasParam("sVars")) {
sVars.addAll(Arrays.asList(sa.getParam("sVars").split(","))); sVars.addAll(Arrays.asList(sa.getParam("sVars").split(",")));
} }
// static abilities to add to the animated being
final List<String> stAbs = Lists.newArrayList();
if (sa.hasParam("staticAbilities")) {
stAbs.addAll(Arrays.asList(sa.getParam("staticAbilities").split(",")));
}
Map<String, String> sVarsMap = Maps.newHashMap();
for (final String s : sVars) {
sVarsMap.put(s, AbilityUtils.getSVar(sa, s));
}
final String valid = sa.getParamOrDefault("ValidCards", ""); final String valid = sa.getParamOrDefault("ValidCards", "");
CardCollectionView list; CardCollectionView list;
@@ -133,15 +145,14 @@ public class AnimateAllEffect extends AnimateEffectBase {
list = CardLists.getValidCards(list, valid, host.getController(), host, sa); list = CardLists.getValidCards(list, valid, host.getController(), host, sa);
for (final Card c : list) { for (final Card c : list) {
doAnimate(c, sa, power, toughness, types, removeTypes, finalColors, doAnimate(c, sa, power, toughness, types, removeTypes, finalColors, keywords, removeKeywords,
keywords, removeKeywords, hiddenKeywords, hiddenKeywords, abilities, triggers, replacements, stAbs, timestamp);
abilities, triggers, replacements, ImmutableList.of(),
timestamp);
// give sVars // give sVars
for (final String s : sVars) { if (!sVarsMap.isEmpty() ) {
c.setSVar(s, AbilityUtils.getSVar(sa, s)); c.addChangedSVars(sVarsMap, timestamp, 0);
} }
game.fireEvent(new GameEventCardStatsChanged(c)); game.fireEvent(new GameEventCardStatsChanged(c));
final GameCommand unanimate = new GameCommand() { final GameCommand unanimate = new GameCommand() {

View File

@@ -2,7 +2,9 @@ package forge.game.ability.effects;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
import com.google.common.collect.Maps;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.card.CardType; import forge.card.CardType;
@@ -138,11 +140,22 @@ public class AnimateEffect extends AnimateEffectBase {
} }
// sVars to add to the animated being // sVars to add to the animated being
final List<String> sVars = Lists.newArrayList(); Map<String, String> sVarsMap = Maps.newHashMap();
if (sa.hasParam("sVars")) { if (sa.hasParam("sVars")) {
sVars.addAll(Arrays.asList(sa.getParam("sVars").split(","))); for (final String s : sa.getParam("sVars").split(",")) {
String actualsVar = AbilityUtils.getSVar(sa, s);
String name = s;
if (actualsVar.startsWith("SVar:")) {
actualsVar = actualsVar.split("SVar:")[1];
name = actualsVar.split(":")[0];
actualsVar = actualsVar.split(":")[1];
}
sVarsMap.put(name, actualsVar);
}
} }
List<Card> tgts = getCardsfromTargets(sa); List<Card> tgts = getCardsfromTargets(sa);
if (sa.hasParam("Optional")) { if (sa.hasParam("Optional")) {
@@ -151,7 +164,7 @@ public class AnimateEffect extends AnimateEffectBase {
? TextUtil.fastReplace(sa.getParam("OptionQuestion"), "TARGETS", targets) ? TextUtil.fastReplace(sa.getParam("OptionQuestion"), "TARGETS", targets)
: getStackDescription(sa); : getStackDescription(sa);
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message)) { if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message, null)) {
return; return;
} }
} }
@@ -166,15 +179,8 @@ public class AnimateEffect extends AnimateEffectBase {
} }
// give sVars // give sVars
for (final String s : sVars) { if (!sVarsMap.isEmpty()) {
String actualsVar = AbilityUtils.getSVar(sa, s); c.addChangedSVars(sVarsMap, timestamp, 0);
String name = s;
if (actualsVar.startsWith("SVar:")) {
actualsVar = actualsVar.split("SVar:")[1];
name = actualsVar.split(":")[0];
actualsVar = actualsVar.split(":")[1];
}
c.setSVar(name, actualsVar);
} }
// give Remembered // give Remembered

View File

@@ -137,6 +137,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
public void run() { public void run() {
doUnanimate(c, timestamp); doUnanimate(c, timestamp);
c.removeChangedSVars(timestamp, 0);
c.removeChangedName(timestamp, 0); c.removeChangedName(timestamp, 0);
c.updateStateForView(); c.updateStateForView();

View File

@@ -134,7 +134,7 @@ public class AttachEffect extends SpellAbilityEffect {
// If Cast Targets will be checked on the Stack // If Cast Targets will be checked on the Stack
for (final Card attachment : attachments) { for (final Card attachment : attachments) {
String message = Localizer.getInstance().getMessage("lblDoYouWantAttachSourceToTarget", CardTranslation.getTranslatedName(attachment.getName()), attachToName); String message = Localizer.getInstance().getMessage("lblDoYouWantAttachSourceToTarget", CardTranslation.getTranslatedName(attachment.getName()), attachToName);
if (sa.hasParam("Optional") && !p.getController().confirmAction(sa, null, message)) if (sa.hasParam("Optional") && !p.getController().confirmAction(sa, null, message, null))
// TODO add params for message // TODO add params for message
continue; continue;

View File

@@ -2,11 +2,10 @@ package forge.game.ability.effects;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.StringUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.util.Lang;
public class BecomeMonarchEffect extends SpellAbilityEffect { public class BecomeMonarchEffect extends SpellAbilityEffect {
@@ -16,8 +15,8 @@ public class BecomeMonarchEffect extends SpellAbilityEffect {
final List<Player> tgtPlayers = getTargetPlayers(sa); final List<Player> tgtPlayers = getTargetPlayers(sa);
sb.append(StringUtils.join(tgtPlayers, ", ")); sb.append(Lang.joinHomogenous(tgtPlayers)).append(tgtPlayers.size() == 1 ? " becomes" : " become");
sb.append(" becomes the Monarch."); sb.append(" the monarch.");
return sb.toString(); return sb.toString();
} }

View File

@@ -21,7 +21,7 @@ import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.util.CardTranslation; import forge.util.CardTranslation;
import forge.util.Localizer; import forge.util.Localizer;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollection;
public class ChangeCombatantsEffect extends SpellAbilityEffect { public class ChangeCombatantsEffect extends SpellAbilityEffect {
@@ -47,7 +47,8 @@ public class ChangeCombatantsEffect extends SpellAbilityEffect {
if ((tgt == null) || c.canBeTargetedBy(sa)) { if ((tgt == null) || c.canBeTargetedBy(sa)) {
final Combat combat = game.getCombat(); final Combat combat = game.getCombat();
final GameEntity originalDefender = combat.getDefenderByAttacker(c); final GameEntity originalDefender = combat.getDefenderByAttacker(c);
final FCollectionView<GameEntity> defs = combat.getDefenders(); final FCollection<GameEntity> defs = new FCollection<>();
defs.addAll(sa.hasParam("PlayerOnly") ? combat.getDefendingPlayers() : combat.getDefenders());
String title = Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(c.getName())); String title = Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(c.getName()));
Map<String, Object> params = Maps.newHashMap(); Map<String, Object> params = Maps.newHashMap();

View File

@@ -50,7 +50,7 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
// Redirect rules read 'you MAY choose new targets' ... okay! // Redirect rules read 'you MAY choose new targets' ... okay!
// TODO: Don't even ask to change targets, if the SA and subs don't actually have targets // TODO: Don't even ask to change targets, if the SA and subs don't actually have targets
boolean isOptional = sa.hasParam("Optional"); boolean isOptional = sa.hasParam("Optional");
if (isOptional && !chooser.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantChangeAbilityTargets", tgtSA.getHostCard().toString()))) { if (isOptional && !chooser.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantChangeAbilityTargets", tgtSA.getHostCard().toString()), null)) {
continue; continue;
} }
if (sa.hasParam("ChangeSingleTarget")) { if (sa.hasParam("ChangeSingleTarget")) {

View File

@@ -120,7 +120,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
message = Localizer.getInstance().getMessage("lblMoveTargetFromOriginToDestination", targets, Lang.joinHomogenous(origin, ZoneType.Accessors.GET_TRANSLATED_NAME), destination.getTranslatedName()); message = Localizer.getInstance().getMessage("lblMoveTargetFromOriginToDestination", targets, Lang.joinHomogenous(origin, ZoneType.Accessors.GET_TRANSLATED_NAME), destination.getTranslatedName());
} }
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message)) { if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message, null)) {
return; return;
} }
} }

View File

@@ -196,8 +196,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (destination.equals("Battlefield")) { if (destination.equals("Battlefield")) {
sb.append("onto the battlefield"); sb.append("onto the battlefield");
if (tapped) { if (tapped) {
sb.append(" tapped"); sb.append(" tapped").append(attacking ? " and" : "");
} }
sb.append(attacking ? " attacking" : "");
if (sa.hasParam("GainControl")) { if (sa.hasParam("GainControl")) {
sb.append(" under ").append(chooserNames).append("'s control"); sb.append(" under ").append(chooserNames).append("'s control");
} }
@@ -259,14 +260,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (destination.equals("Battlefield")) { if (destination.equals("Battlefield")) {
sb.append(" onto the battlefield"); sb.append(" onto the battlefield");
if (tapped) { if (tapped) {
sb.append(" tapped"); sb.append(" tapped").append(attacking ? " and" : "");
if (attacking) {
sb.append(" and");
}
}
if (attacking) {
sb.append(" attacking");
} }
sb.append(attacking ? " attacking" : "");
if (sa.hasParam("GainControl")) { if (sa.hasParam("GainControl")) {
sb.append(" under ").append(chooserNames).append("'s control"); sb.append(" under ").append(chooserNames).append("'s control");
} }
@@ -366,14 +362,16 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
final String fromGraveyard = " from the graveyard"; final String fromGraveyard = " from the graveyard";
if (destination.equals(ZoneType.Battlefield)) { if (destination.equals(ZoneType.Battlefield)) {
final boolean attacking = (sa.hasParam("Attacking"));
if (ZoneType.Graveyard.equals(origin)) { if (ZoneType.Graveyard.equals(origin)) {
sb.append("Return").append(targetname).append(fromGraveyard).append(" to the battlefield"); sb.append("Return").append(targetname).append(fromGraveyard).append(" to the battlefield");
} else { } else {
sb.append("Put").append(targetname).append(" onto the battlefield"); sb.append("Put").append(targetname).append(" onto the battlefield");
} }
if (sa.hasParam("Tapped")) { if (sa.hasParam("Tapped")) {
sb.append(" tapped"); sb.append(" tapped").append(attacking ? " and" : "");
} }
sb.append(attacking ? " attacking" : "");
if (sa.hasParam("GainControl")) { if (sa.hasParam("GainControl")) {
sb.append(" under your control"); sb.append(" under your control");
} }
@@ -494,7 +492,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
PlayerCollection deciders = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("AlternativeDecider"), sa); PlayerCollection deciders = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("AlternativeDecider"), sa);
alterDecider = deciders.isEmpty() ? null : deciders.get(0); alterDecider = deciders.isEmpty() ? null : deciders.get(0);
} }
if (alterDecider != null && !alterDecider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneToAltDestination, sb.toString())) { if (alterDecider != null && !alterDecider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneToAltDestination, sb.toString(), null)) {
destination = ZoneType.smartValueOf(sa.getParam("DestinationAlternative")); destination = ZoneType.smartValueOf(sa.getParam("DestinationAlternative"));
altDest = true; altDest = true;
} }
@@ -513,7 +511,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
continue; continue;
} }
removeFromStack(tgtSA, sa, si, game, triggerList); removeFromStack(tgtSA, sa, si, game, triggerList, counterTable);
} // End of change from stack } // End of change from stack
final String remember = sa.getParam("RememberChanged"); final String remember = sa.getParam("RememberChanged");
@@ -566,7 +564,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
} }
final String prompt = TextUtil.concatWithSpace(Localizer.getInstance().getMessage("lblDoYouWantMoveTargetFromOriToDest", CardTranslation.getTranslatedName(gameCard.getName()), Lang.joinHomogenous(origin, ZoneType.Accessors.GET_TRANSLATED_NAME), destination.getTranslatedName())); final String prompt = TextUtil.concatWithSpace(Localizer.getInstance().getMessage("lblDoYouWantMoveTargetFromOriToDest", CardTranslation.getTranslatedName(gameCard.getName()), Lang.joinHomogenous(origin, ZoneType.Accessors.GET_TRANSLATED_NAME), destination.getTranslatedName()));
if (optional && !chooser.getController().confirmAction(sa, null, prompt) ) if (optional && !chooser.getController().confirmAction(sa, null, prompt, null) )
continue; continue;
final Zone originZone = game.getZoneOf(gameCard); final Zone originZone = game.getZoneOf(gameCard);
@@ -958,7 +956,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
sb.append(sa.getParam("AlternativeMessage")).append(" "); sb.append(sa.getParam("AlternativeMessage")).append(" ");
sb.append(altFetchList.size()).append(" " + Localizer.getInstance().getMessage("lblCardMatchSearchingTypeInAlternateZones")); sb.append(altFetchList.size()).append(" " + Localizer.getInstance().getMessage("lblCardMatchSearchingTypeInAlternateZones"));
if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneFromAltSource, sb.toString())) { if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneFromAltSource, sb.toString(), null)) {
origin = alt; origin = alt;
} }
} }
@@ -970,7 +968,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
sb.append(sa.getParam("AlternativeDestinationMessage")); sb.append(sa.getParam("AlternativeDestinationMessage"));
if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneToAltDestination, sb.toString())) { if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneToAltDestination, sb.toString(), null)) {
destination = ZoneType.smartValueOf(sa.getParam("DestinationAlternative")); destination = ZoneType.smartValueOf(sa.getParam("DestinationAlternative"));
libraryPos = sa.hasParam("LibraryPositionAlternative") ? Integer.parseInt(sa.getParam("LibraryPositionAlternative")) : 0; libraryPos = sa.hasParam("LibraryPositionAlternative") ? Integer.parseInt(sa.getParam("LibraryPositionAlternative")) : 0;
} }
@@ -987,7 +985,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
prompt = Localizer.getInstance().getMessage("lblSearchPlayerZoneConfirm", "{player's}", Lang.joinHomogenous(origin, ZoneType.Accessors.GET_TRANSLATED_NAME).toLowerCase()); prompt = Localizer.getInstance().getMessage("lblSearchPlayerZoneConfirm", "{player's}", Lang.joinHomogenous(origin, ZoneType.Accessors.GET_TRANSLATED_NAME).toLowerCase());
} }
String message = MessageUtil.formatMessage(prompt , decider, player); String message = MessageUtil.formatMessage(prompt , decider, player);
if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneGeneral, message)) { if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneGeneral, message, null)) {
return; return;
} }
} }
@@ -1073,7 +1071,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
continue; continue;
} }
SpellAbility tgtSA = decider.getController().getAbilityToPlay(tgtCard, sas); SpellAbility tgtSA = decider.getController().getAbilityToPlay(tgtCard, sas);
if (!decider.getController().confirmAction(tgtSA, null, Localizer.getInstance().getMessage("lblDoYouWantPlayCard", CardTranslation.getTranslatedName(tgtCard.getName())))) { if (!decider.getController().confirmAction(tgtSA, null, Localizer.getInstance().getMessage("lblDoYouWantPlayCard", CardTranslation.getTranslatedName(tgtCard.getName())), null)) {
continue; continue;
} }
tgtSA.setSVar("IsCastFromPlayEffect", "True"); tgtSA.setSVar("IsCastFromPlayEffect", "True");
@@ -1189,7 +1187,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
String message = Localizer.getInstance().getMessage("lblCancelSearchUpToSelectNumCards", String.valueOf(num)); String message = Localizer.getInstance().getMessage("lblCancelSearchUpToSelectNumCards", String.valueOf(num));
if (fetchList.isEmpty() || sa.hasParam("SkipCancelPrompt") || if (fetchList.isEmpty() || sa.hasParam("SkipCancelPrompt") ||
decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneGeneral, message)) { decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneGeneral, message, null)) {
break; break;
} }
i--; i--;
@@ -1545,7 +1543,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
* object. * object.
* @param game * @param game
*/ */
private static void removeFromStack(final SpellAbility tgtSA, final SpellAbility srcSA, final SpellAbilityStackInstance si, final Game game, CardZoneTable triggerList) { private static void removeFromStack(final SpellAbility tgtSA, final SpellAbility srcSA, final SpellAbilityStackInstance si, final Game game, CardZoneTable triggerList, GameEntityCounterTable counterTable) {
final Card tgtHost = tgtSA.getHostCard(); final Card tgtHost = tgtSA.getHostCard();
final Zone originZone = tgtHost.getZone(); final Zone originZone = tgtHost.getZone();
game.getStack().remove(si); game.getStack().remove(si);
@@ -1557,7 +1555,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
Card movedCard = null; Card movedCard = null;
if (srcSA.hasParam("Destination")) { if (srcSA.hasParam("Destination")) {
final boolean remember = srcSA.hasParam("RememberChanged"); final boolean remember = srcSA.hasParam("RememberChanged");
final boolean rememberSpell = srcSA.hasParam("RememberSpell");
final boolean imprint = srcSA.hasParam("Imprint"); final boolean imprint = srcSA.hasParam("Imprint");
if (tgtSA.isAbility()) { if (tgtSA.isAbility()) {
// Shouldn't be able to target Abilities but leaving this in for now // Shouldn't be able to target Abilities but leaving this in for now
@@ -1589,13 +1586,20 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
+ srcSA.getHostCard().getName()); + srcSA.getHostCard().getName());
} }
if (srcSA.hasParam("WithCountersType")) {
Player placer = srcSA.getActivatingPlayer();
if (srcSA.hasParam("WithCountersPlacer")) {
placer = AbilityUtils.getDefinedPlayers(srcSA.getHostCard(), srcSA.getParam("WithCountersPlacer"), srcSA).get(0);
}
CounterType cType = CounterType.getType(srcSA.getParam("WithCountersType"));
int cAmount = AbilityUtils.calculateAmount(srcSA.getHostCard(), srcSA.getParamOrDefault("WithCountersAmount", "1"), srcSA);
movedCard.addCounter(cType, cAmount, placer, counterTable);
}
if (remember) { if (remember) {
srcSA.getHostCard().addRemembered(tgtHost); srcSA.getHostCard().addRemembered(tgtHost);
// TODO or remember moved? // TODO or remember moved?
} }
if (rememberSpell) {
srcSA.getHostCard().addRemembered(tgtSA);
}
if (imprint) { if (imprint) {
srcSA.getHostCard().addImprintedCard(tgtHost); srcSA.getHostCard().addImprintedCard(tgtHost);
} }

View File

@@ -180,7 +180,7 @@ public class CharmEffect extends SpellAbilityEffect {
num = Math.min(num, choices.size()); num = Math.min(num, choices.size());
boolean isOptional = sa.hasParam("Optional"); boolean isOptional = sa.hasParam("Optional");
if (isOptional && !activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeCharm", CardTranslation.getTranslatedName(source.getName())))) { if (isOptional && !activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeCharm", CardTranslation.getTranslatedName(source.getName())), null)) {
return false; return false;
} }

View File

@@ -131,7 +131,7 @@ public class ChooseCardEffect extends SpellAbilityEffect {
Localizer.getInstance().getMessage("lblSelectCreatureWithTotalPowerLessOrEqualTo", (totP - chosenP - negativeNum)) Localizer.getInstance().getMessage("lblSelectCreatureWithTotalPowerLessOrEqualTo", (totP - chosenP - negativeNum))
+ "\r\n(" + Localizer.getInstance().getMessage("lblSelected") + ":" + chosenPool + ")\r\n(" + Localizer.getInstance().getMessage("lblTotalPowerNum", chosenP) + ")", chosenP <= totP, null); + "\r\n(" + Localizer.getInstance().getMessage("lblSelected") + ":" + chosenPool + ")\r\n(" + Localizer.getInstance().getMessage("lblTotalPowerNum", chosenP) + ")", chosenP <= totP, null);
if (c == null) { if (c == null) {
if (p.getController().confirmAction(sa, PlayerActionConfirmMode.OptionalChoose, Localizer.getInstance().getMessage("lblCancelChooseConfirm"))) { if (p.getController().confirmAction(sa, PlayerActionConfirmMode.OptionalChoose, Localizer.getInstance().getMessage("lblCancelChooseConfirm"), null)) {
break; break;
} }
} else { } else {

View File

@@ -54,7 +54,6 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
} }
boolean randomChoice = sa.hasParam("AtRandom"); boolean randomChoice = sa.hasParam("AtRandom");
boolean draft = sa.hasParam("Draft"); //for digital "draft from spellbook" mechanic
boolean chooseFromDefined = sa.hasParam("ChooseFromDefinedCards"); boolean chooseFromDefined = sa.hasParam("ChooseFromDefinedCards");
boolean chooseFromList = sa.hasParam("ChooseFromList"); boolean chooseFromList = sa.hasParam("ChooseFromList");
boolean chooseFromOneTimeList = sa.hasParam("ChooseFromOneTimeList"); boolean chooseFromOneTimeList = sa.hasParam("ChooseFromOneTimeList");
@@ -62,8 +61,6 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
if (!randomChoice) { if (!randomChoice) {
if (sa.hasParam("SelectPrompt")) { if (sa.hasParam("SelectPrompt")) {
message = sa.getParam("SelectPrompt"); message = sa.getParam("SelectPrompt");
} else if (draft) {
message = Localizer.getInstance().getMessage("lblChooseCardDraft");
} else if (null == validDesc) { } else if (null == validDesc) {
message = Localizer.getInstance().getMessage("lblChooseACardName"); message = Localizer.getInstance().getMessage("lblChooseACardName");
} else { } else {
@@ -108,12 +105,6 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
chosen = p.getController().chooseCardName(sa, faces, message); chosen = p.getController().chooseCardName(sa, faces, message);
} else if (chooseFromList) { } else if (chooseFromList) {
String [] names = sa.getParam("ChooseFromList").split(","); String [] names = sa.getParam("ChooseFromList").split(",");
if (sa.hasParam("Draft")) {
List<String> options = Arrays.asList(names);
Collections.shuffle(options);
List<String> draftChoices = options.subList(0,3);
names = draftChoices.toArray(new String[0]);
}
List<ICardFace> faces = new ArrayList<>(); List<ICardFace> faces = new ArrayList<>();
for (String name : names) { for (String name : names) {
// Cardnames that include "," must use ";" instead in ChooseFromList$ (i.e. Tovolar; Dire Overlord) // Cardnames that include "," must use ";" instead in ChooseFromList$ (i.e. Tovolar; Dire Overlord)
@@ -169,9 +160,6 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
host.setNamedCard(chosen); host.setNamedCard(chosen);
if (!randomChoice) { if (!randomChoice) {
p.setNamedCard(chosen); p.setNamedCard(chosen);
if (!draft) { //drafting is secret
p.getGame().getAction().notifyOfValue(sa, host, Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), chosen), p);
}
} }
if (sa.hasParam("NoteFor")) { if (sa.hasParam("NoteFor")) {
p.addNoteForName(sa.getParam("NoteFor"), "Name:" + chosen); p.addNoteForName(sa.getParam("NoteFor"), "Name:" + chosen);

View File

@@ -108,7 +108,7 @@ public class CloneEffect extends SpellAbilityEffect {
} }
final boolean optional = sa.hasParam("Optional"); final boolean optional = sa.hasParam("Optional");
if (optional && !host.getController().getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantCopy", CardTranslation.getTranslatedName(cardToCopy.getName())))) { if (optional && !host.getController().getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantCopy", CardTranslation.getTranslatedName(cardToCopy.getName())), null)) {
return; return;
} }

View File

@@ -12,7 +12,10 @@ import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Lang; import forge.util.Lang;
import forge.util.Localizer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -24,13 +27,14 @@ public class ConniveEffect extends SpellAbilityEffect {
*/ */
@Override @Override
protected String getStackDescription(SpellAbility sa) { protected String getStackDescription(SpellAbility sa) {
final StringBuilder sb = new StringBuilder();
List<Card> tgt = getTargetCards(sa); List<Card> tgt = getTargetCards(sa);
if (tgt.size() <= 0) {
return "";
} else {
final StringBuilder sb = new StringBuilder();
sb.append(Lang.joinHomogenous(tgt)).append(tgt.size() > 1 ? " connive." : " connives."); sb.append(Lang.joinHomogenous(tgt)).append(tgt.size() > 1 ? " connive." : " connives.");
return sb.toString(); return sb.toString();
}
} }
/* (non-Javadoc) /* (non-Javadoc)
@@ -39,55 +43,70 @@ public class ConniveEffect extends SpellAbilityEffect {
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
final Player hostCon = host.getController();
final Game game = host.getGame(); final Game game = host.getGame();
final int num = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("ConniveNum", "1"), sa); final int num = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("ConniveNum", "1"), sa);
GameEntityCounterTable table = new GameEntityCounterTable(); CardCollection toConnive = getTargetCards(sa);
final CardZoneTable triggerList = new CardZoneTable(); if (toConnive.isEmpty()) { // if nothing is conniving, we're done
Map<Player, CardCollectionView> discardedMap = Maps.newHashMap(); return;
Map<AbilityKey, Object> moveParams = AbilityKey.newMap(); }
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
for (final Card c : getTargetCards(sa)) { List<Player> controllers = new ArrayList<>();
final Player p = c.getController(); for (Card c : toConnive) {
final Player controller = c.getController();
p.drawCards(num, sa, moveParams); if (!controllers.contains(controller)) {
controllers.add(controller);
CardCollectionView dPHand = p.getCardsIn(ZoneType.Hand); }
dPHand = CardLists.filter(dPHand, CardPredicates.Presets.NON_TOKEN); }
if (dPHand.isEmpty()) { // seems unlikely, but just to be safe //order controllers by APNAP
continue; // for loop over players int indexAP = controllers.indexOf(game.getPhaseHandler().getPlayerTurn());
} if (indexAP != -1) {
Collections.rotate(controllers, - indexAP);
CardCollection validCards = CardLists.getValidCards(dPHand, "Card", hostCon, host, sa); }
if (!p.canDiscardBy(sa, true)) { for (final Player p : controllers) {
continue; CardCollection connivers = CardLists.filterControlledBy(toConnive, p);
} while (connivers.size() > 0) {
GameEntityCounterTable table = new GameEntityCounterTable();
int amt = Math.min(validCards.size(), num); final CardZoneTable triggerList = new CardZoneTable();
CardCollectionView toBeDiscarded = amt == 0 ? CardCollection.EMPTY : Map<Player, CardCollectionView> discardedMap = Maps.newHashMap();
p.getController().chooseCardsToDiscardFrom(p, sa, validCards, amt, amt); Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
if (toBeDiscarded.size() > 1) { moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
} Card conniver = connivers.size() > 1 ? p.getController().chooseSingleEntityForEffect(connivers, sa,
Localizer.getInstance().getMessage("lblChooseConniver"), null) : connivers.get(0);
discardedMap.put(p, toBeDiscarded);
p.drawCards(num, sa, moveParams);
int numCntrs = CardLists.getValidCardCount(toBeDiscarded, "Card.nonLand", hostCon, host, sa);
CardCollection validDisards =
// need to get newest game state to check if it is still on the battlefield and the timestamp didn't change CardLists.filter(p.getCardsIn(ZoneType.Hand), CardPredicates.Presets.NON_TOKEN);
Card gamec = game.getCardState(c); if (validDisards.isEmpty() || !p.canDiscardBy(sa, true)) { // hand being empty unlikely, just to be safe
// if the card is not in the game anymore, this might still return true, but it's no problem continue;
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.equalsWithTimestamp(c)) { }
c.addCounter(CounterEnumType.P1P1, numCntrs, p, table);
int amt = Math.min(validDisards.size(), num);
CardCollectionView toBeDiscarded = amt == 0 ? CardCollection.EMPTY :
p.getController().chooseCardsToDiscardFrom(p, sa, validDisards, amt, amt);
if (toBeDiscarded.size() > 1) {
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
}
int numCntrs = CardLists.getValidCardCount(toBeDiscarded, "Card.nonLand", p, host, sa);
// need to get newest game state to check if it is still on the battlefield and the timestamp didn't change
Card gamec = game.getCardState(conniver);
// if the card is not in the game anymore, this might still return true, but it's no problem
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.equalsWithTimestamp(conniver)) {
conniver.addCounter(CounterEnumType.P1P1, numCntrs, p, table);
}
connivers.remove(conniver);
discardedMap.put(p, CardCollection.getView(toBeDiscarded));
discard(sa, triggerList, true, discardedMap, moveParams);
table.replaceCounterEffect(game, sa, true);
triggerList.triggerChangesZoneAll(game, sa);
} }
} }
discard(sa, triggerList, true, discardedMap, moveParams);
table.replaceCounterEffect(game, sa, true);
triggerList.triggerChangesZoneAll(game, sa);
} }
} }

View File

@@ -89,7 +89,7 @@ public class ControlExchangeEffect extends SpellAbilityEffect {
if (sa.hasParam("Optional") && !sa.getActivatingPlayer().getController().confirmAction(sa, null, if (sa.hasParam("Optional") && !sa.getActivatingPlayer().getController().confirmAction(sa, null,
Localizer.getInstance().getMessage("lblExchangeControl", Localizer.getInstance().getMessage("lblExchangeControl",
CardTranslation.getTranslatedName(object1.getName()), CardTranslation.getTranslatedName(object1.getName()),
CardTranslation.getTranslatedName(object2.getName())))) { CardTranslation.getTranslatedName(object2.getName())), null)) {
return; return;
} }

View File

@@ -1,5 +1,9 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.GameCommand; import forge.GameCommand;
@@ -16,9 +20,6 @@ import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Localizer; import forge.util.Localizer;
import java.util.Arrays;
import java.util.List;
public class ControlGainEffect extends SpellAbilityEffect { public class ControlGainEffect extends SpellAbilityEffect {
@Override @Override
@@ -118,22 +119,24 @@ public class ControlGainEffect extends SpellAbilityEffect {
final Game game = newController.getGame(); final Game game = newController.getGame();
CardCollectionView tgtCards = null; CardCollectionView tgtCards = null;
if (sa.hasParam("ControlledByTarget")) { if (sa.hasParam("Choices")) {
tgtCards = CardLists.filterControlledBy(tgtCards, getTargetPlayers(sa));
} else if (sa.hasParam("Choices")) {
Player chooser = sa.hasParam("Chooser") ? AbilityUtils.getDefinedPlayers(source, Player chooser = sa.hasParam("Chooser") ? AbilityUtils.getDefinedPlayers(source,
sa.getParam("Chooser"), sa).get(0) : activator; sa.getParam("Chooser"), sa).get(0) : activator;
CardCollectionView choices = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), CardCollectionView choices = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield),
sa.getParam("Choices"), activator, source, sa); sa.getParam("Choices"), activator, source, sa);
if (!choices.isEmpty()) { if (!choices.isEmpty()) {
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") :
Localizer.getInstance().getMessage("lblChooseaCard") +" "; Localizer.getInstance().getMessage("lblChooseaCard") +" ";
tgtCards = activator.getController().chooseCardsForEffect(choices, sa, title, 1, 1, false, null); tgtCards = chooser.getController().chooseCardsForEffect(choices, sa, title, 1, 1, false, null);
} }
} else { } else {
tgtCards = getDefinedCards(sa); tgtCards = getDefinedCards(sa);
} }
if (tgtCards != null & sa.hasParam("ControlledByTarget")) {
tgtCards = CardLists.filterControlledBy(tgtCards, getTargetPlayers(sa));
}
// in case source was LKI or still resolving // in case source was LKI or still resolving
if (source.isLKI() || source.getZone().is(ZoneType.Stack)) { if (source.isLKI() || source.getZone().is(ZoneType.Stack)) {
source = game.getCardState(source); source = game.getCardState(source);
@@ -208,11 +211,11 @@ public class ControlGainEffect extends SpellAbilityEffect {
} }
if (lose.contains("EOT")) { if (lose.contains("EOT")) {
game.getEndOfTurn().addUntil(loseControl); game.getEndOfTurn().addUntil(loseControl);
tgtC.setSVar("SacMe", "6"); tgtC.addChangedSVars(Collections.singletonMap("SacMe", "6"), tStamp, 0);
} }
if (lose.contains("EndOfCombat")) { if (lose.contains("EndOfCombat")) {
game.getEndOfCombat().addUntil(loseControl); game.getEndOfCombat().addUntil(loseControl);
tgtC.setSVar("SacMe", "6"); tgtC.addChangedSVars(Collections.singletonMap("SacMe", "6"), tStamp, 0);
} }
if (lose.contains("StaticCommandCheck")) { if (lose.contains("StaticCommandCheck")) {
String leftVar = sa.getSVar(sa.getParam("StaticCommandCheckSVar")); String leftVar = sa.getSVar(sa.getParam("StaticCommandCheckSVar"));
@@ -274,7 +277,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
@Override @Override
public void run() { public void run() {
doLoseControl(c, hostCard, bTapOnLose, tStamp); doLoseControl(c, hostCard, bTapOnLose, tStamp);
c.removeSVar("SacMe"); c.removeChangedSVars(tStamp, 0);
} }
}; };

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