mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-11 16:26:22 +00:00
Merge branch 'master' of https://github.com/Card-Forge/forge
This commit is contained in:
@@ -20,6 +20,7 @@ package forge.ai;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
@@ -540,7 +541,7 @@ public class AiAttackController {
|
||||
|
||||
for (Card attacker : categorizedAttackers) {
|
||||
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);
|
||||
} else {
|
||||
if (predictEvasion) {
|
||||
|
||||
@@ -42,6 +42,7 @@ import forge.game.cost.Cost;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
|
||||
import forge.game.staticability.StaticAbilityCantAttackBlock;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
@@ -204,7 +205,7 @@ public class AiBlockController {
|
||||
}
|
||||
blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
|
||||
// 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)) {
|
||||
blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers);
|
||||
// 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)
|
||||
|| ComputerUtilCombat.attackerHasThreateningAfflict(other, ai)
|
||||
|| 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;
|
||||
}
|
||||
|
||||
@@ -668,7 +669,7 @@ public class AiBlockController {
|
||||
Card attacker = attackers.get(0);
|
||||
|
||||
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)) {
|
||||
attackers.remove(0);
|
||||
makeChumpBlocks(combat, attackers);
|
||||
@@ -689,7 +690,7 @@ public class AiBlockController {
|
||||
}
|
||||
if (other.getNetCombatDamage() >= damageAbsorbed
|
||||
&& !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)
|
||||
&& CombatUtil.canBlock(other, blocker, combat)) {
|
||||
combat.addBlocker(other, blocker);
|
||||
@@ -756,7 +757,7 @@ public class AiBlockController {
|
||||
|
||||
for (final Card attacker : tramplingAttackers) {
|
||||
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.")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1326,7 +1326,7 @@ public class AiController {
|
||||
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) {
|
||||
return true;
|
||||
}
|
||||
@@ -1339,7 +1339,7 @@ public class AiController {
|
||||
mode);
|
||||
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) {
|
||||
|
||||
@@ -131,10 +131,9 @@ public class ComputerUtil {
|
||||
|
||||
sa = GameActionUtil.addExtraKeywordCost(sa);
|
||||
|
||||
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
||||
if (!CharmEffect.makeChoices(sa)) {
|
||||
return false;
|
||||
}
|
||||
if (sa.getApi() == ApiType.Charm && !CharmEffect.makeChoices(sa)) {
|
||||
// 603.3c If no mode is chosen, the ability is removed from the stack.
|
||||
return false;
|
||||
}
|
||||
if (chooseTargets != null) {
|
||||
chooseTargets.run();
|
||||
@@ -250,6 +249,13 @@ public class ComputerUtil {
|
||||
return false;
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
Zone fromZone = game.getZoneOf(source);
|
||||
int zonePosition = 0;
|
||||
if (fromZone != null) {
|
||||
zonePosition = fromZone.getCards().indexOf(source);
|
||||
}
|
||||
|
||||
if (sa.isSpell() && !source.isCopiedSpell()) {
|
||||
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
||||
}
|
||||
@@ -257,11 +263,18 @@ public class ComputerUtil {
|
||||
sa = GameActionUtil.addExtraKeywordCost(sa);
|
||||
|
||||
final Cost cost = sa.getPayCosts();
|
||||
final CostPayment pay = new CostPayment(cost, sa);
|
||||
|
||||
// do this after card got added to stack
|
||||
if (!sa.checkRestrictions(ai)) {
|
||||
GameActionUtil.rollbackAbility(sa, fromZone, zonePosition, pay, source);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cost == null) {
|
||||
ComputerUtilMana.payManaCost(ai, sa, false);
|
||||
game.getStack().add(sa);
|
||||
} else {
|
||||
final CostPayment pay = new CostPayment(cost, sa);
|
||||
if (pay.payComputerCosts(new AiCostDecision(ai, sa, false))) {
|
||||
game.getStack().add(sa);
|
||||
}
|
||||
@@ -292,17 +305,30 @@ public class ComputerUtil {
|
||||
newSA = GameActionUtil.addExtraKeywordCost(newSA);
|
||||
|
||||
final Card source = newSA.getHostCard();
|
||||
|
||||
Zone fromZone = game.getZoneOf(source);
|
||||
int zonePosition = 0;
|
||||
if (fromZone != null) {
|
||||
zonePosition = fromZone.getCards().indexOf(source);
|
||||
}
|
||||
|
||||
if (newSA.isSpell() && !source.isCopiedSpell()) {
|
||||
newSA.setHostCard(game.getAction().moveToStack(source, newSA));
|
||||
|
||||
if (newSA.getApi() == ApiType.Charm && !newSA.isWrapper()) {
|
||||
if (!CharmEffect.makeChoices(newSA)) {
|
||||
return false;
|
||||
}
|
||||
if (newSA.getApi() == ApiType.Charm && !CharmEffect.makeChoices(newSA)) {
|
||||
// 603.3c If no mode is chosen, the ability is removed from the stack.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final CostPayment pay = new CostPayment(newSA.getPayCosts(), newSA);
|
||||
|
||||
// do this after card got added to stack
|
||||
if (!sa.checkRestrictions(ai)) {
|
||||
GameActionUtil.rollbackAbility(sa, fromZone, zonePosition, pay, source);
|
||||
return false;
|
||||
}
|
||||
|
||||
pay.payComputerCosts(new AiCostDecision(ai, newSA, false));
|
||||
|
||||
game.getStack().add(newSA);
|
||||
@@ -2798,7 +2824,7 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
return type.is(CounterEnumType.AWAKENING) || type.is(CounterEnumType.MANIFESTATION) || type.is(CounterEnumType.PETRIFICATION)
|
||||
|| type.is(CounterEnumType.TRAINING);
|
||||
|| type.is(CounterEnumType.TRAINING) || type.is(CounterEnumType.GHOSTFORM);
|
||||
}
|
||||
|
||||
public static Player evaluateBoardPosition(final List<Player> listToEvaluate) {
|
||||
|
||||
@@ -48,6 +48,7 @@ import forge.game.replacement.ReplacementLayer;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
|
||||
import forge.game.staticability.StaticAbilityMustAttack;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
@@ -325,8 +326,7 @@ public class ComputerUtilCombat {
|
||||
final List<Card> blockers = combat.getBlockers(attacker);
|
||||
|
||||
if (blockers.size() == 0
|
||||
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage "
|
||||
+ "as though it weren't blocked.")) {
|
||||
|| StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) {
|
||||
unblocked.add(attacker);
|
||||
} else if (attacker.hasKeyword(Keyword.TRAMPLE)
|
||||
&& getAttack(attacker) > totalShieldDamage(attacker, blockers)) {
|
||||
@@ -367,8 +367,7 @@ public class ComputerUtilCombat {
|
||||
final List<Card> blockers = combat.getBlockers(attacker);
|
||||
|
||||
if (blockers.size() == 0
|
||||
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage"
|
||||
+ " as though it weren't blocked.")) {
|
||||
|| StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) {
|
||||
unblocked.add(attacker);
|
||||
} else if (attacker.hasKeyword(Keyword.TRAMPLE)
|
||||
&& getAttack(attacker) > totalShieldDamage(attacker, blockers)) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import forge.ai.AiCardMemory.MemorySet;
|
||||
import forge.ai.ability.AnimateAi;
|
||||
import forge.ai.ability.TokenAi;
|
||||
import forge.card.ColorSet;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -14,6 +15,7 @@ import forge.game.ability.ApiType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.*;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -692,6 +694,72 @@ public class ComputerUtilCost {
|
||||
return false;
|
||||
} else if ("LowPriority".equals(aiLogic) && MyRandom.getRandom().nextInt(100) < 67) {
|
||||
return false;
|
||||
} else if (aiLogic != null && aiLogic.startsWith("Fabricate")) {
|
||||
final int n = Integer.valueOf(aiLogic.substring("Fabricate".length()));
|
||||
|
||||
// if host would leave the play or if host is useless, create tokens
|
||||
if (source.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(payer, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// need a copy for one with extra +1/+1 counter boost,
|
||||
// without causing triggers to run
|
||||
final Card copy = CardUtil.getLKICopy(source);
|
||||
copy.setCounters(CounterEnumType.P1P1, copy.getCounters(CounterEnumType.P1P1) + n);
|
||||
copy.setZone(source.getZone());
|
||||
|
||||
// if host would put into the battlefield attacking
|
||||
Combat combat = source.getGame().getCombat();
|
||||
if (combat != null && combat.isAttacking(source)) {
|
||||
final Player defender = combat.getDefenderPlayerByAttacker(source);
|
||||
if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy, true)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the host has haste and can attack
|
||||
if (CombatUtil.canAttack(copy)) {
|
||||
for (final Player opp : payer.getOpponents()) {
|
||||
if (CombatUtil.canAttack(copy, opp) &&
|
||||
opp.canLoseLife() &&
|
||||
!ComputerUtilCard.canBeBlockedProfitably(opp, copy, true))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO check for trigger to turn token ETB into +1/+1 counter for host
|
||||
// TODO check for trigger to turn token ETB into damage or life loss for opponent
|
||||
// in this cases Token might be prefered even if they would not survive
|
||||
final Card tokenCard = TokenAi.spawnToken(payer, sa);
|
||||
|
||||
// Token would not survive
|
||||
if (!tokenCard.isCreature() || tokenCard.getNetToughness() < 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Special Card logic, this one try to median its power with the number of artifacts
|
||||
if ("Marionette Master".equals(source.getName())) {
|
||||
CardCollection list = CardLists.filter(payer.getCardsIn(ZoneType.Battlefield), Presets.ARTIFACTS);
|
||||
return list.size() >= copy.getNetPower();
|
||||
} else if ("Cultivator of Blades".equals(source.getName())) {
|
||||
// Cultivator does try to median with number of Creatures
|
||||
CardCollection list = payer.getCreaturesInPlay();
|
||||
return list.size() >= copy.getNetPower();
|
||||
}
|
||||
|
||||
// evaluate Creature with +1/+1
|
||||
int evalCounter = ComputerUtilCard.evaluateCreature(copy);
|
||||
|
||||
final CardCollection tokenList = new CardCollection(source);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
tokenList.add(TokenAi.spawnToken(payer, sa));
|
||||
}
|
||||
|
||||
// evaluate Host with Tokens
|
||||
int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList);
|
||||
|
||||
return evalToken < evalCounter;
|
||||
}
|
||||
|
||||
// Check for shocklands and similar ETB replacement effects
|
||||
|
||||
@@ -10,6 +10,7 @@ import forge.game.card.CounterEnumType;
|
||||
import forge.game.cost.CostPayEnergy;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
|
||||
import forge.game.staticability.StaticAbilityMustAttack;
|
||||
|
||||
import java.util.List;
|
||||
@@ -62,7 +63,8 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
if (c.hasKeyword("Unblockable")) {
|
||||
value += addValue(power * 10, "unblockable");
|
||||
} 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");
|
||||
}
|
||||
if (c.hasKeyword(Keyword.FEAR)) {
|
||||
|
||||
@@ -206,7 +206,7 @@ public abstract class GameState {
|
||||
cardsReferencedByID.add(card.getExiledWith());
|
||||
}
|
||||
if (zone == ZoneType.Battlefield) {
|
||||
if (!card.getAttachedCards().isEmpty()) {
|
||||
if (card.hasCardAttachments()) {
|
||||
// Remember the ID of cards that have attachments
|
||||
cardsReferencedByID.add(card);
|
||||
}
|
||||
@@ -375,7 +375,7 @@ public abstract class GameState {
|
||||
newText.append("|Imprinting:").append(TextUtil.join(imprintedCardIds, ","));
|
||||
}
|
||||
|
||||
if (!c.getMergedCards().isEmpty()) {
|
||||
if (c.hasMergedCard()) {
|
||||
List<String> mergedCardNames = new ArrayList<>();
|
||||
for (Card merged : c.getMergedCards()) {
|
||||
if (c.getTopMergedCard() == merged) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import forge.game.GameObject;
|
||||
import forge.game.GameType;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
@@ -269,8 +270,8 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return getAi().confirmAction(sa, mode, message);
|
||||
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
|
||||
return getAi().confirmAction(sa, mode, message, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1045,16 +1046,14 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
}
|
||||
|
||||
/* FIXME: the new implementation (below) requires implementing setupNewTargets in the AI controller, among other possible changes, otherwise breaks AI
|
||||
if (sa.isMayChooseNewTargets()) {
|
||||
sa.setupNewTargets(player);
|
||||
}
|
||||
*/
|
||||
if (sa.isMayChooseNewTargets() && !sa.setupTargets()) {
|
||||
if (sa.isSpell()) {
|
||||
getGame().getAction().ceaseToExist(sa.getHostCard(), false);
|
||||
TargetChoices tc = sa.getTargets();
|
||||
if (!sa.setupTargets()) {
|
||||
// if AI can't choose targets need to keep old one even if illegal
|
||||
sa.setTargets(tc);
|
||||
}
|
||||
continue;
|
||||
// FIXME: the new implementation (below) requires implementing setupNewTargets in the AI controller, among other possible changes, otherwise breaks AI
|
||||
// sa.setupNewTargets(player);
|
||||
}
|
||||
}
|
||||
// need finally add the new spell to the stack
|
||||
@@ -1064,6 +1063,9 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
private boolean prepareSingleSa(final Card host, final SpellAbility sa, boolean isMandatory) {
|
||||
if (sa.getApi() == ApiType.Charm) {
|
||||
return CharmEffect.makeChoices(sa);
|
||||
}
|
||||
if (sa.hasParam("TargetingPlayer")) {
|
||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||
sa.setTargetingPlayer(targetingPlayer);
|
||||
@@ -1087,7 +1089,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
||||
Spell spell = (Spell) tgtSA;
|
||||
// TODO if mandatory AI is only forced to use mana when it's already in the pool
|
||||
if (tgtSA.checkRestrictions(brains.getPlayer()) && (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional)) {
|
||||
if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) {
|
||||
if (noManaCost) {
|
||||
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame());
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ public abstract class SpellAbilityAi {
|
||||
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");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.DigMultiple, DigMultipleAi.class)
|
||||
.put(ApiType.DigUntil, DigUntilAi.class)
|
||||
.put(ApiType.Discard, DiscardAi.class)
|
||||
.put(ApiType.Draft, ChooseCardNameAi.class)
|
||||
.put(ApiType.DrainMana, DrainManaAi.class)
|
||||
.put(ApiType.Draw, DrawAi.class)
|
||||
.put(ApiType.EachDamage, DamageEachAi.class)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
@@ -16,7 +18,7 @@ public class AlwaysPlayAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public class AmassAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1729,7 +1729,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
*/
|
||||
@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
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
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)
|
||||
*/
|
||||
@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 String hostName = source.getName();
|
||||
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
||||
|
||||
@@ -89,7 +89,6 @@ public class CharmAi extends SpellAbilityAi {
|
||||
// First pass using standard canPlayAi() for good choices
|
||||
for (AbilitySub sub : choices) {
|
||||
sub.setActivatingPlayer(ai);
|
||||
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
||||
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
|
||||
chosenList.add(sub);
|
||||
if (chosenList.size() == num) {
|
||||
@@ -101,8 +100,6 @@ public class CharmAi extends SpellAbilityAi {
|
||||
// Second pass using doTrigger(false) to fulfill minimum choice
|
||||
choices.removeAll(chosenList);
|
||||
for (AbilitySub sub : choices) {
|
||||
sub.setActivatingPlayer(ai);
|
||||
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
||||
if (aic.doTrigger(sub, false)) {
|
||||
chosenList.add(sub);
|
||||
if (chosenList.size() == min) {
|
||||
@@ -114,8 +111,6 @@ public class CharmAi extends SpellAbilityAi {
|
||||
if (chosenList.size() < min) {
|
||||
choices.removeAll(chosenList);
|
||||
for (AbilitySub sub : choices) {
|
||||
sub.setActivatingPlayer(ai);
|
||||
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
||||
if (aic.doTrigger(sub, true)) {
|
||||
chosenList.add(sub);
|
||||
if (chosenList.size() == min) {
|
||||
@@ -231,7 +226,6 @@ public class CharmAi extends SpellAbilityAi {
|
||||
} else {
|
||||
// Standard canPlayAi()
|
||||
sub.setActivatingPlayer(ai);
|
||||
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
||||
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
|
||||
chosenList.add(sub);
|
||||
if (chosenList.size() == min) {
|
||||
@@ -255,15 +249,6 @@ public class CharmAi extends SpellAbilityAi {
|
||||
return Aggregates.random(opponents);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
// already done by chooseOrderOfSimultaneousStackEntry
|
||||
if (sa.getChosenList() != null) {
|
||||
return true;
|
||||
}
|
||||
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
|
||||
// choices were already targeted
|
||||
|
||||
@@ -9,7 +9,6 @@ import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -19,12 +18,9 @@ import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
@@ -43,7 +39,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
if ("Khans".equals(aiLogic) || "Dragons".equals(aiLogic)) {
|
||||
return true;
|
||||
} else if (aiLogic.startsWith("Fabricate") || "Riot".equals(aiLogic)) {
|
||||
} else if ("Riot".equals(aiLogic)) {
|
||||
return true;
|
||||
} else if ("Pump".equals(aiLogic) || "BestOption".equals(aiLogic)) {
|
||||
for (AbilitySub sb : sa.getAdditionalAbilityList("Choices")) {
|
||||
@@ -265,83 +261,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
}
|
||||
// if unsure, random?
|
||||
return Aggregates.random(spells);
|
||||
} else if (logic.startsWith("Fabricate")) {
|
||||
final int n = Integer.valueOf(logic.substring("Fabricate".length()));
|
||||
if(spells.size() < 2) {
|
||||
// If the creature is no longer on the battlefield, the option
|
||||
// to add counters is already removed at this point. Return the
|
||||
// only available option: create servo tokens.
|
||||
return spells.get(0);
|
||||
}
|
||||
SpellAbility counterSA = spells.get(0), tokenSA = spells.get(1);
|
||||
|
||||
// check for something which might prevent the counters to be placed on host
|
||||
if (!host.canReceiveCounters(CounterEnumType.P1P1)) {
|
||||
return tokenSA;
|
||||
}
|
||||
|
||||
// if host would leave the play or if host is useless, create tokens
|
||||
if (host.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(player, host)) {
|
||||
return tokenSA;
|
||||
}
|
||||
|
||||
// need a copy for one with extra +1/+1 counter boost,
|
||||
// without causing triggers to run
|
||||
final Card copy = CardUtil.getLKICopy(host);
|
||||
copy.setCounters(CounterEnumType.P1P1, copy.getCounters(CounterEnumType.P1P1) + n);
|
||||
copy.setZone(host.getZone());
|
||||
|
||||
// if host would put into the battlefield attacking
|
||||
if (combat != null && combat.isAttacking(host)) {
|
||||
final Player defender = combat.getDefenderPlayerByAttacker(host);
|
||||
if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy, true)) {
|
||||
return counterSA;
|
||||
}
|
||||
return tokenSA;
|
||||
}
|
||||
|
||||
// if the host has haste and can attack
|
||||
if (CombatUtil.canAttack(copy)) {
|
||||
for (final Player opp : player.getOpponents()) {
|
||||
if (CombatUtil.canAttack(copy, opp) &&
|
||||
opp.canLoseLife() &&
|
||||
!ComputerUtilCard.canBeBlockedProfitably(opp, copy, true))
|
||||
return counterSA;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO check for trigger to turn token ETB into +1/+1 counter for host
|
||||
// TODO check for trigger to turn token ETB into damage or life loss for opponent
|
||||
// in this cases Token might be prefered even if they would not survive
|
||||
final Card tokenCard = TokenAi.spawnToken(player, tokenSA);
|
||||
|
||||
// Token would not survive
|
||||
if (!tokenCard.isCreature() || tokenCard.getNetToughness() < 1) {
|
||||
return counterSA;
|
||||
}
|
||||
|
||||
// Special Card logic, this one try to median its power with the number of artifacts
|
||||
if ("Marionette Master".equals(sourceName)) {
|
||||
CardCollection list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), Presets.ARTIFACTS);
|
||||
return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
|
||||
} else if ("Cultivator of Blades".equals(sourceName)) {
|
||||
// Cultivator does try to median with number of Creatures
|
||||
CardCollection list = player.getCreaturesInPlay();
|
||||
return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
|
||||
}
|
||||
|
||||
// evaluate Creature with +1/+1
|
||||
int evalCounter = ComputerUtilCard.evaluateCreature(copy);
|
||||
|
||||
final CardCollection tokenList = new CardCollection(host);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
tokenList.add(TokenAi.spawnToken(player, tokenSA));
|
||||
}
|
||||
|
||||
// evaluate Host with Tokens
|
||||
int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList);
|
||||
|
||||
return evalToken >= evalCounter ? tokenSA : counterSA;
|
||||
} else if ("CombustibleGearhulk".equals(logic)) {
|
||||
Player controller = sa.getActivatingPlayer();
|
||||
List<ZoneType> zones = ZoneType.listValueOf("Graveyard, Battlefield, Exile");
|
||||
|
||||
@@ -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)
|
||||
*/
|
||||
@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())) {
|
||||
// Had a special logic for it and managed to target, so confirm if viable
|
||||
if ("CloneBestCreature".equals(sa.getParam("AILogic"))) {
|
||||
|
||||
@@ -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)
|
||||
*/
|
||||
@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
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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
|
||||
// run into the necessity of confirming a mandatory Destroy, thus destroying all of its own permanents.
|
||||
if ("ChainOfAcid".equals(sa.getParam("AILogic"))) {
|
||||
|
||||
@@ -913,7 +913,7 @@ public class CountersPutAi extends CountersAi {
|
||||
}
|
||||
|
||||
@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();
|
||||
if (mode == PlayerActionConfirmMode.Tribute) {
|
||||
// add counter if that opponent has a giant creature
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
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)
|
||||
*/
|
||||
@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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -30,7 +32,7 @@ public class DayTimeAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
*/
|
||||
@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);
|
||||
|
||||
// AI actions for individual cards (until this AI can be generalized)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
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)
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
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)
|
||||
*/
|
||||
@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")) {
|
||||
final String logic = sa.getParam("AILogic");
|
||||
if ("OathOfDruids".equals(logic)) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
@@ -211,11 +212,11 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
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) {
|
||||
// TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards
|
||||
return true;
|
||||
}
|
||||
return super.confirmAction(player, sa, mode, message);
|
||||
return super.confirmAction(player, sa, mode, message, params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.AiCostDecision;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
@@ -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)
|
||||
*/
|
||||
@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;
|
||||
// AI shouldn't mill itself
|
||||
if (numCards < player.getZone(ZoneType.Library).size())
|
||||
|
||||
@@ -70,7 +70,7 @@ public final class EncodeAi extends SpellAbilityAi {
|
||||
* forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@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
|
||||
return chooseCard(player, player.getCreaturesInPlay(), true) != null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
@@ -42,7 +44,7 @@ public class FlipOntoBattlefieldAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -20,7 +22,7 @@ public class InvestigateAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -32,7 +34,7 @@ public class LearnAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
*/
|
||||
@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"))) {
|
||||
return SpecialCardAi.TimmerianFiends.consider(player, sa);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class MutateAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
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)
|
||||
*/
|
||||
@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();
|
||||
return subAb != null && SpellApiToAi.Converter.get(subAb.getApi()).chkDrawbackWithSubs(player, subAb);
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ public class PlayAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class PumpAi extends PumpAiBase {
|
||||
|
||||
@@ -781,7 +782,7 @@ public class PumpAi extends PumpAiBase {
|
||||
}
|
||||
|
||||
@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
|
||||
//the spell in the first place if it would curse its own creature
|
||||
//and the pump isn't mandatory
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.AiCardMemory;
|
||||
import forge.ai.AiController;
|
||||
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)
|
||||
*/
|
||||
@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.
|
||||
|
||||
// First, let's check if we can play the top card of the library
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.*;
|
||||
@@ -40,7 +42,7 @@ public class RepeatAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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)
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
@@ -43,7 +45,7 @@ public class RollDiceAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
@@ -175,7 +176,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.ai.ComputerUtilMana;
|
||||
@@ -132,7 +134,7 @@ public class ScryAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
@@ -264,7 +265,7 @@ public class SetStateAi extends SpellAbilityAi {
|
||||
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
|
||||
return isSafeToTransformIntoLegendary(player, sa.getHostCard());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
@@ -54,7 +56,7 @@ public class ShuffleAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
@@ -18,7 +20,7 @@ public class SkipPhaseAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.AiCardMemory;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
@@ -119,7 +121,7 @@ public class SurveilAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
*/
|
||||
@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
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class VentureAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ public class GameCopier {
|
||||
newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn());
|
||||
newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
|
||||
newPlayer.setLifeGainedThisTurn(origPlayer.getLifeGainedThisTurn());
|
||||
// TODO creatureAttackedThisTurn
|
||||
for (Mana m : origPlayer.getManaPool()) {
|
||||
newPlayer.getManaPool().addMana(m, false);
|
||||
}
|
||||
@@ -207,6 +208,13 @@ public class GameCopier {
|
||||
|
||||
private void copyGameState(Game newGame) {
|
||||
newGame.setAge(origGame.getAge());
|
||||
|
||||
// TODO countersAddedThisTurn
|
||||
|
||||
if (origGame.getMonarch() != null) {
|
||||
newGame.setMonarch(playerMap.get(origGame.getMonarch()));
|
||||
}
|
||||
|
||||
for (ZoneType zone : ZONES) {
|
||||
for (Card card : origGame.getCardsIn(zone)) {
|
||||
addCard(newGame, zone, card);
|
||||
@@ -300,6 +308,7 @@ public class GameCopier {
|
||||
newCard.setPTCharacterDefiningTable(c.getSetPTCharacterDefiningTable());
|
||||
|
||||
newCard.setPTBoost(c.getPTBoostTable());
|
||||
// TODO copy by map
|
||||
newCard.setDamage(c.getDamage());
|
||||
|
||||
newCard.setChangedCardColors(c.getChangedCardColorsTable());
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge;
|
||||
import forge.item.PaperCard;
|
||||
import forge.util.FileUtil;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.ThreadUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -30,6 +31,7 @@ public final class ImageKeys {
|
||||
private static Map<String, String> CACHE_CARD_PICS_SUBDIR;
|
||||
|
||||
private static Map<String, Boolean> editionImageLookup = new HashMap<>();
|
||||
private static Set<String> toFind = new HashSet<>();
|
||||
|
||||
private static boolean isLibGDXPort = false;
|
||||
|
||||
@@ -109,7 +111,8 @@ public final class ImageKeys {
|
||||
filename = key;
|
||||
dir = CACHE_CARD_PICS_DIR;
|
||||
}
|
||||
|
||||
if (toFind.contains(filename))
|
||||
return null;
|
||||
if (missingCards.contains(filename))
|
||||
return null;
|
||||
|
||||
@@ -171,10 +174,20 @@ public final class ImageKeys {
|
||||
return file;
|
||||
}
|
||||
//setlookup
|
||||
file = setLookUpFile(filename, fullborderFile);
|
||||
if (file != null) {
|
||||
cachedCards.put(filename, file);
|
||||
return file;
|
||||
if (hasSetLookup(filename)) {
|
||||
toFind.add(filename);
|
||||
try {
|
||||
ThreadUtil.getServicePool().submit(() -> {
|
||||
File f = setLookUpFile(filename, fullborderFile);
|
||||
if (f != null)
|
||||
cachedCards.put(filename, f);
|
||||
else //is null
|
||||
missingCards.add(filename);
|
||||
toFind.remove(filename);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
toFind.remove(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
//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 +263,7 @@ public final class ImageKeys {
|
||||
|
||||
// System.out.println("File not found, no image created: " + key);
|
||||
//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);
|
||||
return null;
|
||||
}
|
||||
@@ -260,34 +273,44 @@ public final class ImageKeys {
|
||||
? 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
|
||||
}
|
||||
private static File setLookUpFile(String filename, String fullborderFile) {
|
||||
public static boolean hasSetLookup(String filename) {
|
||||
if (!StaticData.instance().getSetLookup().isEmpty()) {
|
||||
return StaticData.instance().getSetLookup().keySet().stream().anyMatch(setKey -> filename.startsWith(setKey));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
public static File setLookUpFile(String filename, String fullborderFile) {
|
||||
if (!StaticData.instance().getSetLookup().isEmpty()) {
|
||||
for (String setKey : StaticData.instance().getSetLookup().keySet()) {
|
||||
if (filename.startsWith(setKey)) {
|
||||
for (String setLookup : StaticData.instance().getSetLookup().get(setKey)) {
|
||||
String lookupDirectory = CACHE_CARD_PICS_DIR + setLookup;
|
||||
File f = new File(lookupDirectory);
|
||||
String[] cardNames = f.list();
|
||||
if (cardNames != null) {
|
||||
Set<String> cardList = new HashSet<>(Arrays.asList(cardNames));
|
||||
if (f.exists() && f.isDirectory()) {
|
||||
for (String ext : FILE_EXTENSIONS) {
|
||||
if (ext.equals(""))
|
||||
continue;
|
||||
File placeholder;
|
||||
String fb1 = fullborderFile.replace(setKey+"/","")+ext;
|
||||
if (cardList.contains(fb1)) {
|
||||
return new File(lookupDirectory+"/"+fb1);
|
||||
placeholder = new File(lookupDirectory+"/"+fb1);
|
||||
if (placeholder.exists()) {
|
||||
return placeholder;
|
||||
}
|
||||
String fb2 = fullborderFile.replace(setKey+"/","").replaceAll("[0-9]*.fullborder", "1.fullborder")+ext;
|
||||
if (cardList.contains(fb2)) {
|
||||
return new File(lookupDirectory+"/"+fb2);
|
||||
placeholder = new File(lookupDirectory+"/"+fb2);
|
||||
if (placeholder.exists()) {
|
||||
return placeholder;
|
||||
}
|
||||
String f1 = filename.replace(setKey+"/","")+ext;
|
||||
if (cardList.contains(f1)) {
|
||||
return new File(lookupDirectory+"/"+f1);
|
||||
placeholder = new File(lookupDirectory+"/"+f1);
|
||||
if (placeholder.exists()) {
|
||||
return placeholder;
|
||||
}
|
||||
String f2 = filename.replace(setKey+"/","").replaceAll("[0-9]*.full", "1.full")+ext;
|
||||
if (cardList.contains(f2)) {
|
||||
return new File(lookupDirectory+"/"+f2);
|
||||
placeholder = new File(lookupDirectory+"/"+f2);
|
||||
if (placeholder.exists()) {
|
||||
return placeholder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ import forge.card.PrintSheet;
|
||||
import forge.item.*;
|
||||
import forge.token.TokenDb;
|
||||
import forge.util.FileUtil;
|
||||
import forge.util.ImageUtil;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.storage.IStorage;
|
||||
import forge.util.storage.StorageBase;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
@@ -45,6 +47,7 @@ public class StaticData {
|
||||
|
||||
private boolean allowCustomCardsInDecksConformance;
|
||||
private boolean enableSmartCardArtSelection;
|
||||
private boolean loadNonLegalCards;
|
||||
|
||||
// Loaded lazily:
|
||||
private IStorage<SealedProduct.Template> boosters;
|
||||
@@ -74,6 +77,7 @@ public class StaticData {
|
||||
this.customCardReader = customCardReader;
|
||||
this.allowCustomCardsInDecksConformance = allowCustomCardsInDecksConformance;
|
||||
this.enableSmartCardArtSelection = enableSmartCardArtSelection;
|
||||
this.loadNonLegalCards = loadNonLegalCards;
|
||||
lastInstance = this;
|
||||
List<String> funnyCards = new ArrayList<>();
|
||||
List<String> filtered = new ArrayList<>();
|
||||
@@ -752,6 +756,129 @@ public class StaticData {
|
||||
preferences_avails[i] = prettifyCardArtPreferenceName(preferences[i]);
|
||||
return preferences_avails;
|
||||
}
|
||||
public Pair<Integer, Integer> audit(StringBuffer noImageFound, StringBuffer cardNotImplemented) {
|
||||
int missingCount = 0;
|
||||
int notImplementedCount = 0;
|
||||
for (CardEdition e : editions) {
|
||||
if (CardEdition.Type.FUNNY.equals(e.getType()))
|
||||
continue;
|
||||
boolean nifHeader = false;
|
||||
boolean cniHeader = false;
|
||||
boolean tokenHeader = false;
|
||||
|
||||
String imagePath;
|
||||
int artIndex = 1;
|
||||
|
||||
HashMap<String, Pair<Boolean, Integer>> cardCount = new HashMap<>();
|
||||
for (CardEdition.CardInSet c : e.getAllCardsInSet()) {
|
||||
if (cardCount.containsKey(c.name)) {
|
||||
cardCount.put(c.name, Pair.of(c.collectorNumber.startsWith("F"), cardCount.get(c.name).getRight() + 1));
|
||||
} else {
|
||||
cardCount.put(c.name, Pair.of(c.collectorNumber.startsWith("F"), 1));
|
||||
}
|
||||
}
|
||||
|
||||
// loop through the cards in this edition, considering art variations...
|
||||
for (Map.Entry<String, Pair<Boolean, Integer>> entry : cardCount.entrySet()) {
|
||||
String c = entry.getKey();
|
||||
artIndex = entry.getValue().getRight();
|
||||
|
||||
PaperCard cp = getCommonCards().getCard(c, e.getCode(), artIndex);
|
||||
if (cp == null) {
|
||||
cp = getVariantCards().getCard(c, e.getCode(), artIndex);
|
||||
}
|
||||
|
||||
if (cp == null) {
|
||||
if (entry.getValue().getLeft()) //skip funny cards
|
||||
continue;
|
||||
if (!loadNonLegalCards && CardEdition.Type.FUNNY.equals(e.getType()))
|
||||
continue;
|
||||
if (!cniHeader) {
|
||||
cardNotImplemented.append("Edition: ").append(e.getName()).append(" ").append("(").append(e.getCode()).append("/").append(e.getCode2()).append(")\n");
|
||||
cniHeader = true;
|
||||
}
|
||||
cardNotImplemented.append(" ").append(c).append("\n");
|
||||
notImplementedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// check the front image
|
||||
imagePath = ImageUtil.getImageRelativePath(cp, false, true, false);
|
||||
if (imagePath != null) {
|
||||
File file = ImageKeys.getImageFile(imagePath);
|
||||
if (file == null && ImageKeys.hasSetLookup(imagePath))
|
||||
file = ImageKeys.setLookUpFile(imagePath, imagePath+"border");
|
||||
if (file == null) {
|
||||
if (!nifHeader) {
|
||||
noImageFound.append("Edition: ").append(e.getName()).append(" ").append("(").append(e.getCode()).append("/").append(e.getCode2()).append(")\n");
|
||||
nifHeader = true;
|
||||
}
|
||||
noImageFound.append(" ").append(imagePath).append("\n");
|
||||
missingCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// check the back face
|
||||
if (cp.hasBackFace()) {
|
||||
imagePath = ImageUtil.getImageRelativePath(cp, true, true, false);
|
||||
if (imagePath != null) {
|
||||
File file = ImageKeys.getImageFile(imagePath);
|
||||
if (file == null && ImageKeys.hasSetLookup(imagePath))
|
||||
file = ImageKeys.setLookUpFile(imagePath, imagePath+"border");
|
||||
if (file == null) {
|
||||
if (!nifHeader) {
|
||||
noImageFound.append("Edition: ").append(e.getName()).append(" ").append("(").append(e.getCode()).append("/").append(e.getCode2()).append(")\n");
|
||||
nifHeader = true;
|
||||
}
|
||||
noImageFound.append(" ").append(imagePath).append("\n");
|
||||
missingCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Audit token images here...
|
||||
for(Map.Entry<String, Integer> tokenEntry : e.getTokens().entrySet()) {
|
||||
String name = tokenEntry.getKey();
|
||||
artIndex = tokenEntry.getValue();
|
||||
try {
|
||||
PaperToken token = getAllTokens().getToken(name, e.getCode());
|
||||
if (token == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for(int i = 0; i < artIndex; i++) {
|
||||
String imgKey = token.getImageKey(i);
|
||||
File file = ImageKeys.getImageFile(imgKey);
|
||||
if (file == null) {
|
||||
if (!nifHeader) {
|
||||
noImageFound.append("Edition: ").append(e.getName()).append(" ").append("(").append(e.getCode()).append("/").append(e.getCode2()).append(")\n");
|
||||
nifHeader = true;
|
||||
}
|
||||
if (!tokenHeader) {
|
||||
noImageFound.append("\nTOKENS\n");
|
||||
tokenHeader = true;
|
||||
}
|
||||
noImageFound.append(" ").append(token.getImageFilename(i + 1)).append("\n");
|
||||
missingCount++;
|
||||
}
|
||||
}
|
||||
} catch(Exception ex) {
|
||||
System.out.println("No Token found: " + name + " in " + e.getName());
|
||||
}
|
||||
}
|
||||
if (nifHeader)
|
||||
noImageFound.append("\n");
|
||||
}
|
||||
|
||||
String totalStats = "Missing images: " + missingCount + "\nUnimplemented cards: " + notImplementedCount + "\n";
|
||||
cardNotImplemented.append("\n-----------\n");
|
||||
cardNotImplemented.append(totalStats);
|
||||
cardNotImplemented.append("-----------\n\n");
|
||||
|
||||
noImageFound.append(cardNotImplemented); // combine things together...
|
||||
return Pair.of(missingCount, notImplementedCount);
|
||||
}
|
||||
|
||||
private String prettifyCardArtPreferenceName(CardDb.CardArtPreference preference) {
|
||||
StringBuilder label = new StringBuilder();
|
||||
|
||||
@@ -51,6 +51,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
private static final long serialVersionUID = 4629853583167022151L;
|
||||
|
||||
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 {
|
||||
Artifact(true, "artifacts"),
|
||||
@@ -752,12 +753,14 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
while (hasMoreTypes) {
|
||||
final String type = typeText.substring(iTypeStart, iSpace == -1 ? typeText.length() : iSpace);
|
||||
hasMoreTypes = iSpace != -1;
|
||||
if (!isMultiwordType(type) || !hasMoreTypes) {
|
||||
iTypeStart = iSpace + 1;
|
||||
if (!"-".equals(type)) {
|
||||
result.add(type);
|
||||
}
|
||||
final String rest = typeText.substring(iTypeStart);
|
||||
if (isMultiwordType(rest)) {
|
||||
result.add(rest);
|
||||
break;
|
||||
}
|
||||
|
||||
iTypeStart = iSpace + 1;
|
||||
result.add(type);
|
||||
iSpace = typeText.indexOf(space, iSpace + 1);
|
||||
}
|
||||
return result;
|
||||
@@ -775,13 +778,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
}
|
||||
|
||||
private static boolean isMultiwordType(final String type) {
|
||||
final String[] multiWordTypes = { "Serra's Realm", "Bolas's Meditation Realm", "Dungeon Master" };
|
||||
for (int i = 0; i < multiWordTypes.length; ++i) {
|
||||
if (multiWordTypes[i].startsWith(type) && !multiWordTypes[i].equals(type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return multiWordTypes.contains(type);
|
||||
}
|
||||
|
||||
public static class Constant {
|
||||
|
||||
@@ -76,13 +76,19 @@ public class Localizer {
|
||||
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...
|
||||
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;
|
||||
|
||||
try {
|
||||
//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) {
|
||||
if (!silent)
|
||||
e.printStackTrace();
|
||||
@@ -95,12 +101,12 @@ public class Localizer {
|
||||
|
||||
silent = false;
|
||||
|
||||
formatter.setLocale(english ? Locale.ENGLISH : locale);
|
||||
formatter.setLocale(english || forcedEnglish ? Locale.ENGLISH : locale);
|
||||
|
||||
String formattedMessage = "CHAR ENCODING ERROR";
|
||||
final String[] charsets = { "ISO-8859-1", "UTF-8" };
|
||||
//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;
|
||||
Object[] syncEncodingMessageArguments = new Object[argLength];
|
||||
@@ -149,7 +155,7 @@ public class Localizer {
|
||||
englishBundle = ResourceBundle.getBundle("en-US", new Locale("en", "US"), loader);
|
||||
} catch (NullPointerException | MissingResourceException e) {
|
||||
//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();
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,27 @@ public class ThreadUtil {
|
||||
return Thread.currentThread().getName().startsWith("Game");
|
||||
}
|
||||
|
||||
private static ExecutorService service = Executors.newWorkStealingPool();
|
||||
public static ExecutorService getServicePool() {
|
||||
return service;
|
||||
}
|
||||
public static void refreshServicePool() {
|
||||
service = Executors.newWorkStealingPool();
|
||||
}
|
||||
public static <T> T limit(Callable<T> task, long millis){
|
||||
Future<T> future = null;
|
||||
T result;
|
||||
try {
|
||||
future = service.submit(task);
|
||||
result = future.get(millis, TimeUnit.MILLISECONDS);
|
||||
} catch (Exception e) {
|
||||
result = null;
|
||||
} finally {
|
||||
if (future != null)
|
||||
future.cancel(true);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
public static <T> T executeWithTimeout(Callable<T> task, int milliseconds) {
|
||||
ExecutorService executor = Executors.newCachedThreadPool();
|
||||
Future<T> future = executor.submit(task);
|
||||
|
||||
@@ -20,6 +20,7 @@ import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardState;
|
||||
import forge.game.card.CardView;
|
||||
import forge.game.card.IHasCardView;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
@@ -34,6 +35,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
/** The host card. */
|
||||
protected Card hostCard;
|
||||
protected CardState cardState = null;
|
||||
protected KeywordInterface keyword = null;
|
||||
|
||||
/** The map params. */
|
||||
protected Map<String, String> originalMapParams = Maps.newHashMap(),
|
||||
@@ -138,6 +140,14 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
this.hostCard = c;
|
||||
}
|
||||
|
||||
public KeywordInterface getKeyword() {
|
||||
return this.keyword;
|
||||
}
|
||||
|
||||
public void setKeyword(final KeywordInterface kw) {
|
||||
this.keyword = kw;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isSecondary.
|
||||
@@ -667,6 +677,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
copy.setCardState(cardState);
|
||||
// dont use setHostCard to not trigger the not copied parts yet
|
||||
copy.hostCard = host;
|
||||
copy.keyword = this.keyword;
|
||||
}
|
||||
|
||||
abstract public List<Object> getTriggerRemembered();
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -47,6 +48,7 @@ import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardDamageHistory;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardUtil;
|
||||
@@ -78,6 +80,7 @@ import forge.trackable.Tracker;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
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.
|
||||
@@ -119,6 +122,9 @@ public class Game {
|
||||
|
||||
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<Card, Integer> facedownWhileCasting = Maps.newHashMap();
|
||||
|
||||
@@ -251,7 +257,7 @@ public class Game {
|
||||
if (c == null) {
|
||||
return null;
|
||||
}
|
||||
return changeZoneLKIInfo.containsKey(c.getId()) ? changeZoneLKIInfo.get(c.getId()) : c;
|
||||
return changeZoneLKIInfo.getOrDefault(c.getId(), c);
|
||||
}
|
||||
public final void clearChangeZoneLKIInfo() {
|
||||
changeZoneLKIInfo.clear();
|
||||
@@ -1088,6 +1094,7 @@ public class Game {
|
||||
|
||||
public void onCleanupPhase() {
|
||||
clearCounterAddedThisTurn();
|
||||
clearGlobalDamageHistory();
|
||||
// some cards need this info updated even after a player lost, so don't skip them
|
||||
for (Player player : getRegisteredPlayers()) {
|
||||
player.onCleanupPhase();
|
||||
@@ -1129,6 +1136,48 @@ public class Game {
|
||||
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) {
|
||||
return topLibsCast.get(P);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import java.util.Set;
|
||||
import forge.util.*;
|
||||
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
@@ -163,14 +162,32 @@ public class GameAction {
|
||||
// need to check before it enters
|
||||
if (c.isAura() && !c.isAttachedToEntity() && toBattlefield && (zoneFrom == null || !zoneFrom.is(ZoneType.Stack))) {
|
||||
boolean found = false;
|
||||
if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(c, null))) {
|
||||
found = true;
|
||||
try {
|
||||
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) {
|
||||
c.clearControllers();
|
||||
@@ -297,6 +314,14 @@ public class GameAction {
|
||||
|
||||
// copy bestow timestamp
|
||||
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 {
|
||||
// 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.
|
||||
@@ -334,8 +359,10 @@ public class GameAction {
|
||||
if (commanderEffect != null) break;
|
||||
}
|
||||
// Disable the commander replacement effect
|
||||
for (final ReplacementEffect re : commanderEffect.getReplacementEffects()) {
|
||||
re.setSuppressed(true);
|
||||
if (commanderEffect != null) {
|
||||
for (final ReplacementEffect re : commanderEffect.getReplacementEffects()) {
|
||||
re.setSuppressed(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,6 +532,7 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
if (keepKeyword) {
|
||||
ki.setHostCard(copied);
|
||||
newKw.add(ki);
|
||||
}
|
||||
}
|
||||
@@ -591,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
|
||||
checkStaticAbilities();
|
||||
@@ -1323,33 +1351,13 @@ public class GameAction {
|
||||
noRegCreats.add(c);
|
||||
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.")) {
|
||||
// merge entries with same source
|
||||
List<Integer> dmgList = Lists.newArrayList();
|
||||
List<Pair<Card, Integer>> remainingDamaged = Lists.newArrayList(c.getReceivedDamageFromThisTurn());
|
||||
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;
|
||||
if (c.getLethal() <= c.getMaxDamageFromSource() || c.hasBeenDealtDeathtouchDamage()) {
|
||||
if (desCreats == null) {
|
||||
desCreats = new CardCollection();
|
||||
}
|
||||
desCreats.add(c);
|
||||
c.setHasBeenDealtDeathtouchDamage(false);
|
||||
checkAgain = true;
|
||||
}
|
||||
}
|
||||
// Rule 704.5g - Destroy due to lethal damage
|
||||
@@ -1576,7 +1584,7 @@ public class GameAction {
|
||||
c.getGame().getTracker().flush();
|
||||
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
@@ -2327,13 +2335,16 @@ public class GameAction {
|
||||
final Player p = e.getKey();
|
||||
final CardCollection toTop = e.getValue().getLeft();
|
||||
final CardCollection toBottom = e.getValue().getRight();
|
||||
int numLookedAt = 0;
|
||||
if (toTop != null) {
|
||||
numLookedAt += toTop.size();
|
||||
Collections.reverse(toTop); // reverse to get the correct order
|
||||
for (Card c : toTop) {
|
||||
moveToLibrary(c, cause, null);
|
||||
}
|
||||
}
|
||||
if (toBottom != null) {
|
||||
numLookedAt += toBottom.size();
|
||||
for (Card c : toBottom) {
|
||||
moveToBottomOfLibrary(c, cause, null);
|
||||
}
|
||||
@@ -2343,6 +2354,7 @@ public class GameAction {
|
||||
// set up triggers (but not actually do them until later)
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, p);
|
||||
runParams.put(AbilityKey.ScryNum, numLookedAt);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Scry, runParams, false);
|
||||
}
|
||||
}
|
||||
@@ -2364,6 +2376,7 @@ public class GameAction {
|
||||
game.getReplacementHandler().runReplaceDamage(isCombat, damageMap, preventMap, counterTable, cause);
|
||||
|
||||
Map<Card, Integer> lethalDamage = Maps.newHashMap();
|
||||
Map<Integer, Card> lkiCache = Maps.newHashMap();
|
||||
|
||||
// Actually deal damage according to replaced damage map
|
||||
for (Map.Entry<Card, Map<GameEntity, Integer>> et : damageMap.rowMap().entrySet()) {
|
||||
@@ -2390,6 +2403,8 @@ public class GameAction {
|
||||
|
||||
e.setValue(Integer.valueOf(e.getKey().addDamageAfterPrevention(e.getValue(), sourceLKI, isCombat, counterTable)));
|
||||
sum += e.getValue();
|
||||
|
||||
sourceLKI.getDamageHistory().registerDamage(e.getValue(), isCombat, sourceLKI, e.getKey(), lkiCache);
|
||||
}
|
||||
|
||||
if (sum > 0 && sourceLKI.hasKeyword(Keyword.LIFELINK)) {
|
||||
@@ -2397,6 +2412,9 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
// for Zangief do this before runWaitingTriggers DamageDone
|
||||
damageMap.triggerExcessDamage(isCombat, lethalDamage, game);
|
||||
|
||||
// lose life simultaneously
|
||||
if (isCombat) {
|
||||
for (Player p : game.getPlayers()) {
|
||||
@@ -2425,7 +2443,6 @@ public class GameAction {
|
||||
preventMap.clear();
|
||||
|
||||
damageMap.triggerDamageDoneOnce(isCombat, game);
|
||||
damageMap.triggerExcessDamage(isCombat, lethalDamage, game);
|
||||
damageMap.clear();
|
||||
|
||||
counterTable.replaceCounterEffect(game, cause, !isCombat);
|
||||
|
||||
@@ -34,6 +34,7 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.CardPlayOption.PayManaCost;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPayment;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.player.Player;
|
||||
@@ -210,7 +211,6 @@ public final class GameActionUtil {
|
||||
final Cost escapeCost = new Cost(k[1], true);
|
||||
|
||||
final SpellAbility newSA = sa.copyWithManaCostReplaced(activator, escapeCost);
|
||||
newSA.setActivatingPlayer(activator);
|
||||
|
||||
newSA.putParam("PrecostDesc", "Escape—");
|
||||
newSA.putParam("CostDesc", escapeCost.toString());
|
||||
@@ -843,4 +843,46 @@ public final class GameActionUtil {
|
||||
c.getGame().getTriggerHandler().resetActiveTriggers();
|
||||
}
|
||||
|
||||
public static void rollbackAbility(SpellAbility ability, final Zone fromZone, final int zonePosition, CostPayment payment, Card oldCard) {
|
||||
// cancel ability during target choosing
|
||||
final Game game = ability.getActivatingPlayer().getGame();
|
||||
|
||||
if (fromZone != null) { // and not a copy
|
||||
oldCard.setCastSA(null);
|
||||
oldCard.setCastFrom(null);
|
||||
// add back to where it came from, hopefully old state
|
||||
// skip GameAction
|
||||
oldCard.getZone().remove(oldCard);
|
||||
fromZone.add(oldCard, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null);
|
||||
ability.setHostCard(oldCard);
|
||||
ability.setXManaCostPaid(null);
|
||||
ability.setSpendPhyrexianMana(false);
|
||||
if (ability.hasParam("Announce")) {
|
||||
for (final String aVar : ability.getParam("Announce").split(",")) {
|
||||
final String varName = aVar.trim();
|
||||
if (!varName.equals("X")) {
|
||||
ability.setSVar(varName, "0");
|
||||
}
|
||||
}
|
||||
}
|
||||
// better safe than sorry approach in case rolled back ability was copy (from addExtraKeywordCost)
|
||||
for (SpellAbility sa : oldCard.getSpells()) {
|
||||
sa.setHostCard(oldCard);
|
||||
}
|
||||
//for Chorus of the Conclave
|
||||
ability.rollback();
|
||||
|
||||
oldCard.setBackSide(false);
|
||||
oldCard.setState(oldCard.getFaceupCardStateName(), true);
|
||||
oldCard.unanimateBestow();
|
||||
}
|
||||
|
||||
ability.clearTargets();
|
||||
|
||||
ability.resetOnceResolved();
|
||||
payment.refundPayment();
|
||||
game.getStack().clearFrozen();
|
||||
game.getTriggerHandler().clearWaitingTriggers();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,9 +17,13 @@
|
||||
*/
|
||||
package forge.game;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -46,6 +50,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
private String name = "";
|
||||
protected CardCollection attachedCards = new CardCollection();
|
||||
protected Map<CounterType, Integer> counters = Maps.newHashMap();
|
||||
protected List<Pair<Integer, Boolean>> damageReceivedThisTurn = Lists.newArrayList();
|
||||
|
||||
protected GameEntity(int id0) {
|
||||
id = id0;
|
||||
@@ -330,6 +335,30 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
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
|
||||
public final boolean equals(Object o) {
|
||||
if (o == null) { return false; }
|
||||
|
||||
@@ -122,8 +122,12 @@ public class GameEntityCounterTable extends ForwardingTable<Optional<Player>, Ga
|
||||
game.getTriggerHandler().runTrigger(TriggerType.CounterAddedAll, runParams, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
@@ -135,6 +139,10 @@ public class GameEntityCounterTable extends ForwardingTable<Optional<Player>, Ga
|
||||
repParams.put(AbilityKey.Cause, cause);
|
||||
repParams.put(AbilityKey.EffectOnly, effect);
|
||||
repParams.put(AbilityKey.CounterMap, values);
|
||||
repParams.put(AbilityKey.ETB, etb);
|
||||
if (params != null) {
|
||||
repParams.putAll(params);
|
||||
}
|
||||
|
||||
switch (game.getReplacementHandler().run(ReplacementType.AddCounter, repParams)) {
|
||||
case NotReplaced:
|
||||
|
||||
@@ -48,7 +48,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
public enum FormatType {
|
||||
SANCTIONED,
|
||||
CASUAL,
|
||||
HISTORIC,
|
||||
ARCHIVED,
|
||||
DIGITAL,
|
||||
CUSTOM
|
||||
}
|
||||
@@ -290,7 +290,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
if (other.formatSubType != formatSubType){
|
||||
return formatSubType.compareTo(other.formatSubType);
|
||||
}
|
||||
if (formatType.equals(FormatType.HISTORIC)){
|
||||
if (formatType.equals(FormatType.ARCHIVED)){
|
||||
int compareDates = this.effectiveDate.compareTo(other.effectiveDate);
|
||||
if (compareDates != 0)
|
||||
return compareDates;
|
||||
@@ -306,7 +306,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
|
||||
public static class Reader extends StorageReaderRecursiveFolderWithUserFolder<GameFormat> {
|
||||
List<GameFormat> naturallyOrdered = new ArrayList<>();
|
||||
boolean includeHistoric;
|
||||
boolean includeArchived;
|
||||
private List<String> coreFormats = new ArrayList<>();
|
||||
{
|
||||
coreFormats.add("Standard.txt");
|
||||
@@ -321,14 +321,14 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
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);
|
||||
this.includeHistoric=includeHistoric;
|
||||
this.includeArchived=includeArchived;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GameFormat read(File file) {
|
||||
if (!includeHistoric && !coreFormats.contains(file.getName())) {
|
||||
if (!includeArchived && !coreFormats.contains(file.getName())) {
|
||||
return null;
|
||||
}
|
||||
final Map<String, List<String>> contents = FileSection.parseSections(FileUtil.readFile(file));
|
||||
@@ -348,7 +348,12 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
try {
|
||||
formatType = FormatType.valueOf(section.get("type").toUpperCase());
|
||||
} 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;
|
||||
try {
|
||||
@@ -450,7 +455,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
public Iterable<GameFormat> getFilterList() {
|
||||
List<GameFormat> coreList = new ArrayList<>();
|
||||
for (GameFormat format: naturallyOrdered) {
|
||||
if (!format.getFormatType().equals(FormatType.HISTORIC)
|
||||
if (!format.getFormatType().equals(FormatType.ARCHIVED)
|
||||
&&!format.getFormatType().equals(FormatType.DIGITAL)){
|
||||
coreList.add(format);
|
||||
}
|
||||
@@ -458,10 +463,10 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
return coreList;
|
||||
}
|
||||
|
||||
public Iterable<GameFormat> getHistoricList() {
|
||||
public Iterable<GameFormat> getArchivedList() {
|
||||
List<GameFormat> coreList = new ArrayList<>();
|
||||
for (GameFormat format: naturallyOrdered) {
|
||||
if (format.getFormatType().equals(FormatType.HISTORIC)){
|
||||
if (format.getFormatType().equals(FormatType.ARCHIVED)){
|
||||
coreList.add(format);
|
||||
}
|
||||
}
|
||||
@@ -470,7 +475,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
|
||||
public Iterable<GameFormat> getBlockList() {
|
||||
List<GameFormat> blockFormats = new ArrayList<>();
|
||||
for (GameFormat format : this.getHistoricList()){
|
||||
for (GameFormat format : this.getArchivedList()){
|
||||
if (format.getFormatSubType() != GameFormat.FormatSubType.BLOCK)
|
||||
continue;
|
||||
if (!format.getName().endsWith("Block"))
|
||||
@@ -481,10 +486,10 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
return blockFormats;
|
||||
}
|
||||
|
||||
public Map<String, List<GameFormat>> getHistoricMap() {
|
||||
public Map<String, List<GameFormat>> getArchivedMap() {
|
||||
Map<String, List<GameFormat>> coreList = new HashMap<>();
|
||||
for (GameFormat format: naturallyOrdered){
|
||||
if (format.getFormatType().equals(FormatType.HISTORIC)){
|
||||
if (format.getFormatType().equals(FormatType.ARCHIVED)){
|
||||
String alpha = format.getName().substring(0,1);
|
||||
if (!coreList.containsKey(alpha)) {
|
||||
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
|
||||
continue;
|
||||
}
|
||||
if (gf.getFormatType().equals(FormatType.HISTORIC) && coveredTypes.contains(gf.getFormatSubType())
|
||||
if (gf.getFormatType().equals(FormatType.ARCHIVED) && coveredTypes.contains(gf.getFormatSubType())
|
||||
&& !exhaustive){
|
||||
//exclude duplicate formats - only keep first of e.g. Standard historical
|
||||
//exclude duplicate formats - only keep first of e.g. Standard archived
|
||||
continue;
|
||||
}
|
||||
if (gf.isPoolLegal(allCards)) {
|
||||
@@ -590,7 +595,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
if (gf2.formatSubType != gf1.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
|
||||
return gf1.effectiveDate.compareTo(gf2.effectiveDate);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ public class GameRules {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -76,7 +76,7 @@ public enum GameType {
|
||||
|
||||
private final DeckFormat deckFormat;
|
||||
private final boolean isCardPoolLimited, canSideboard, addWonCardsMidGame;
|
||||
private final String name, description;
|
||||
private final String name, englishName, description;
|
||||
private final Function<RegisteredPlayer, Deck> deckAutoGenerator;
|
||||
|
||||
GameType(DeckFormat deckFormat0, boolean isCardPoolLimited0, boolean canSideboard0, boolean addWonCardsMidgame0, String name0, String description0) {
|
||||
@@ -90,6 +90,7 @@ public enum GameType {
|
||||
canSideboard = canSideboard0;
|
||||
addWonCardsMidGame = addWonCardsMidgame0;
|
||||
name = localizer.getMessage(name0);
|
||||
englishName = localizer.getEnglishMessage(name0);
|
||||
if (description0.length()>0) {
|
||||
description0 = localizer.getMessage(description0);
|
||||
}
|
||||
@@ -148,6 +149,9 @@ public enum GameType {
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
public String getEnglishName() {
|
||||
return englishName;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
|
||||
@@ -277,7 +277,7 @@ public final class AbilityFactory {
|
||||
}
|
||||
}
|
||||
|
||||
if (api == ApiType.Charm || api == ApiType.GenericChoice || api == ApiType.AssignGroup) {
|
||||
if (api == ApiType.Charm || api == ApiType.GenericChoice || api == ApiType.AssignGroup) {
|
||||
final String key = "Choices";
|
||||
if (mapParams.containsKey(key)) {
|
||||
List<String> names = Lists.newArrayList(mapParams.get(key).split(","));
|
||||
@@ -327,8 +327,8 @@ public final class AbilityFactory {
|
||||
}
|
||||
|
||||
private static final TargetRestrictions readTarget(Map<String, String> mapParams) {
|
||||
final String min = mapParams.containsKey("TargetMin") ? mapParams.get("TargetMin") : "1";
|
||||
final String max = mapParams.containsKey("TargetMax") ? mapParams.get("TargetMax") : "1";
|
||||
final String min = mapParams.getOrDefault("TargetMin", "1");
|
||||
final String max = mapParams.getOrDefault("TargetMax", "1");
|
||||
|
||||
// TgtPrompt should only be needed for more complicated ValidTgts
|
||||
String tgtWhat = mapParams.get("ValidTgts");
|
||||
@@ -426,7 +426,9 @@ public final class AbilityFactory {
|
||||
private static final void makeRestrictions(final SpellAbility sa) {
|
||||
// SpellAbilityRestrictions should be added in here
|
||||
final SpellAbilityRestriction restrict = sa.getRestrictions();
|
||||
restrict.setRestrictions(sa.getMapParams());
|
||||
if (restrict != null) {
|
||||
restrict.setRestrictions(sa.getMapParams());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -438,7 +440,7 @@ public final class AbilityFactory {
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
*/
|
||||
private static final void makeConditions(final SpellAbility sa) {
|
||||
// SpellAbilityRestrictions should be added in here
|
||||
// SpellAbilityConditions should be added in here
|
||||
final SpellAbilityCondition condition = sa.getConditions();
|
||||
condition.setConditions(sa.getMapParams());
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ public enum AbilityKey {
|
||||
Explorer("Explorer"),
|
||||
ExtraTurn("ExtraTurn"),
|
||||
Event("Event"),
|
||||
ETB("ETB"),
|
||||
Fighter("Fighter"),
|
||||
Fighters("Fighters"),
|
||||
FirstTime("FirstTime"),
|
||||
@@ -112,6 +113,7 @@ public enum AbilityKey {
|
||||
Result("Result"),
|
||||
RoomName("RoomName"),
|
||||
Scheme("Scheme"),
|
||||
ScryNum("ScryNum"),
|
||||
Sides("Sides"),
|
||||
Source("Source"),
|
||||
Sources("Sources"),
|
||||
|
||||
@@ -14,6 +14,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -146,7 +147,7 @@ public class AbilityUtils {
|
||||
} else if (defined.equals("TopOfGraveyard")) {
|
||||
final CardCollectionView grave = hostCard.getController().getCardsIn(ZoneType.Graveyard);
|
||||
|
||||
if (grave.size() > 0) { // TopOfLibrary or BottomOfLibrary
|
||||
if (grave.size() > 0) {
|
||||
c = grave.getLast();
|
||||
} else {
|
||||
// we don't want this to fall through and return the "Self"
|
||||
@@ -162,17 +163,15 @@ public class AbilityUtils {
|
||||
return cards;
|
||||
}
|
||||
}
|
||||
else if (defined.equals("Targeted") && sa instanceof SpellAbility) {
|
||||
else if ((defined.equals("Targeted") || defined.equals("TargetedCard")) && sa instanceof SpellAbility) {
|
||||
for (TargetChoices tc : ((SpellAbility)sa).getAllTargetChoices()) {
|
||||
Iterables.addAll(cards, tc.getTargetCards());
|
||||
}
|
||||
}
|
||||
else if (defined.equals("TargetedSource") && sa instanceof SpellAbility) {
|
||||
for (TargetChoices tc : ((SpellAbility)sa).getAllTargetChoices()) {
|
||||
for (SpellAbility s : tc.getTargetSpells()) {
|
||||
for (TargetChoices tc : ((SpellAbility)sa).getAllTargetChoices()) for (SpellAbility s : tc.getTargetSpells()) {
|
||||
cards.add(s.getHostCard());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (defined.equals("ThisTargetedCard") && sa instanceof SpellAbility) { // do not add parent targeted
|
||||
if (((SpellAbility)sa).getTargets() != null) {
|
||||
@@ -972,7 +971,7 @@ public class AbilityUtils {
|
||||
|
||||
final Player player = sa instanceof SpellAbility ? ((SpellAbility)sa).getActivatingPlayer() : card.getController();
|
||||
|
||||
if (defined.equals("Self") || defined.equals("ThisTargetedCard") || defined.startsWith("Valid") || getPaidCards(sa, defined) != null) {
|
||||
if (defined.equals("Self") || defined.equals("TargetedCard") || defined.equals("ThisTargetedCard") || defined.startsWith("Valid") || getPaidCards(sa, defined) != null) {
|
||||
// do nothing, Self is for Cards, not Players
|
||||
} else if (defined.equals("TargetedOrController")) {
|
||||
players.addAll(getDefinedPlayers(card, "Targeted", sa));
|
||||
@@ -2030,7 +2029,7 @@ public class AbilityUtils {
|
||||
return doXMath(c.getTotalDamageDoneBy(), expr, c, ctb);
|
||||
}
|
||||
if (sq[0].equals("TotalDamageReceivedThisTurn")) {
|
||||
return doXMath(c.getTotalDamageReceivedThisTurn(), expr, c, ctb);
|
||||
return doXMath(c.getAssignedDamage(), expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].contains("CardPower")) {
|
||||
@@ -2091,13 +2090,6 @@ public class AbilityUtils {
|
||||
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")) {
|
||||
return doXMath(c.getRegeneratedThisTurn(), expr, c, ctb);
|
||||
}
|
||||
@@ -2359,20 +2351,28 @@ public class AbilityUtils {
|
||||
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")) {
|
||||
return doXMath(player.getMaxOpponentAssignedDamage(), expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].startsWith("YourDamageSourcesThisTurn")) {
|
||||
Iterable<Card> allSrc = player.getAssignedDamageSources();
|
||||
String restriction = sq[0].split(" ")[1];
|
||||
return doXMath(CardLists.getValidCardCount(allSrc, restriction, player, c, ctb), expr, c, ctb);
|
||||
if (sq[0].contains("TotalDamageThisTurn")) {
|
||||
String[] props = l[0].split(" ");
|
||||
int sum = 0;
|
||||
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")) {
|
||||
@@ -3325,12 +3325,6 @@ public class AbilityUtils {
|
||||
totDmg += p.getAssignedDamage();
|
||||
}
|
||||
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) {
|
||||
@@ -3387,7 +3381,7 @@ public class AbilityUtils {
|
||||
|
||||
if (value.contains("DomainPlayer")) {
|
||||
int n = 0;
|
||||
final CardCollectionView someCards = player.getCardsIn(ZoneType.Battlefield);
|
||||
final CardCollectionView someCards = player.getLandsInPlay();
|
||||
final List<String> basic = MagicColor.Constant.BASIC_LANDS;
|
||||
|
||||
for (int i = 0; i < basic.size(); i++) {
|
||||
|
||||
@@ -71,6 +71,7 @@ public enum ApiType {
|
||||
DigUntil (DigUntilEffect.class),
|
||||
Discard (DiscardEffect.class),
|
||||
DrainMana (DrainManaEffect.class),
|
||||
Draft (DraftEffect.class),
|
||||
Draw (DrawEffect.class),
|
||||
EachDamage (DamageEachEffect.class),
|
||||
Effect (EffectEffect.class),
|
||||
|
||||
@@ -25,7 +25,7 @@ public class AbandonEffect extends SpellAbilityEffect {
|
||||
Player controller = source.getController();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.util.Arrays;
|
||||
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;
|
||||
|
||||
@@ -122,6 +121,12 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
||||
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));
|
||||
@@ -140,10 +145,8 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
||||
list = CardLists.getValidCards(list, valid, host.getController(), host, sa);
|
||||
|
||||
for (final Card c : list) {
|
||||
doAnimate(c, sa, power, toughness, types, removeTypes, finalColors,
|
||||
keywords, removeKeywords, hiddenKeywords,
|
||||
abilities, triggers, replacements, ImmutableList.of(),
|
||||
timestamp);
|
||||
doAnimate(c, sa, power, toughness, types, removeTypes, finalColors, keywords, removeKeywords,
|
||||
hiddenKeywords, abilities, triggers, replacements, stAbs, timestamp);
|
||||
|
||||
// give sVars
|
||||
if (!sVarsMap.isEmpty() ) {
|
||||
|
||||
@@ -164,7 +164,7 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
? TextUtil.fastReplace(sa.getParam("OptionQuestion"), "TARGETS", targets)
|
||||
: getStackDescription(sa);
|
||||
|
||||
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message)) {
|
||||
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message, null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ public class AttachEffect extends SpellAbilityEffect {
|
||||
// If Cast Targets will be checked on the Stack
|
||||
for (final Card attachment : attachments) {
|
||||
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
|
||||
continue;
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
||||
// 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
|
||||
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;
|
||||
}
|
||||
if (sa.hasParam("ChangeSingleTarget")) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message)) {
|
||||
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message, null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,18 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.game.*;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.GameCommand;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.CardType;
|
||||
import forge.game.*;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardState;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CardView;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.event.GameEventCombatChanged;
|
||||
import forge.game.player.DelayedReveal;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerView;
|
||||
import forge.game.player.*;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -43,13 +20,13 @@ import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.Zone;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.CardTranslation;
|
||||
import forge.util.Lang;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.MessageUtil;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.*;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
|
||||
@@ -492,7 +469,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
PlayerCollection deciders = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("AlternativeDecider"), sa);
|
||||
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"));
|
||||
altDest = true;
|
||||
}
|
||||
@@ -511,7 +488,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
|
||||
removeFromStack(tgtSA, sa, si, game, triggerList);
|
||||
removeFromStack(tgtSA, sa, si, game, triggerList, counterTable);
|
||||
} // End of change from stack
|
||||
|
||||
final String remember = sa.getParam("RememberChanged");
|
||||
@@ -564,7 +541,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()));
|
||||
if (optional && !chooser.getController().confirmAction(sa, null, prompt) )
|
||||
if (optional && !chooser.getController().confirmAction(sa, null, prompt, null) )
|
||||
continue;
|
||||
|
||||
final Zone originZone = game.getZoneOf(gameCard);
|
||||
@@ -689,7 +666,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
// need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger
|
||||
if (sa.hasParam("FaceDown")) {
|
||||
gameCard.turnFaceDown(true);
|
||||
setFaceDownState(gameCard, sa);
|
||||
CardFactoryUtil.setFaceDownState(gameCard, sa);
|
||||
}
|
||||
|
||||
movedCard = game.getAction().moveTo(gameCard.getController().getZone(destination), gameCard, sa, moveParams);
|
||||
@@ -956,7 +933,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
sb.append(sa.getParam("AlternativeMessage")).append(" ");
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -968,7 +945,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
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"));
|
||||
libraryPos = sa.hasParam("LibraryPositionAlternative") ? Integer.parseInt(sa.getParam("LibraryPositionAlternative")) : 0;
|
||||
}
|
||||
@@ -985,7 +962,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
prompt = Localizer.getInstance().getMessage("lblSearchPlayerZoneConfirm", "{player's}", Lang.joinHomogenous(origin, ZoneType.Accessors.GET_TRANSLATED_NAME).toLowerCase());
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1071,7 +1048,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
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;
|
||||
}
|
||||
tgtSA.setSVar("IsCastFromPlayEffect", "True");
|
||||
@@ -1187,7 +1164,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
String message = Localizer.getInstance().getMessage("lblCancelSearchUpToSelectNumCards", String.valueOf(num));
|
||||
|
||||
if (fetchList.isEmpty() || sa.hasParam("SkipCancelPrompt") ||
|
||||
decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneGeneral, message)) {
|
||||
decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneGeneral, message, null)) {
|
||||
break;
|
||||
}
|
||||
i--;
|
||||
@@ -1345,7 +1322,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
// need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger
|
||||
if (sa.hasParam("FaceDown")) {
|
||||
c.turnFaceDown(true);
|
||||
setFaceDownState(c, sa);
|
||||
CardFactoryUtil.setFaceDownState(c, sa);
|
||||
}
|
||||
movedCard = game.getAction().moveToPlay(c, c.getController(), sa, moveParams);
|
||||
|
||||
@@ -1448,6 +1425,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ZoneType.Exile.equals(destination) && sa.hasParam("WithCountersType")) {
|
||||
CounterType cType = CounterType.getType(sa.getParam("WithCountersType"));
|
||||
int cAmount = AbilityUtils.calculateAmount(sa.getOriginalHost(), sa.getParamOrDefault("WithCountersAmount", "1"), sa);
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
movedCard.addCounter(cType, cAmount, player, table);
|
||||
table.replaceCounterEffect(game, sa, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (((!ZoneType.Battlefield.equals(destination) && changeType != null && !defined && !changeType.equals("Card"))
|
||||
@@ -1496,39 +1480,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
&& sa.getParam("WithTotalCMC") == null;
|
||||
}
|
||||
|
||||
private static void setFaceDownState(Card c, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
CardState faceDown = c.getFaceDownState();
|
||||
|
||||
// set New Pt doesn't work because this values need to be copyable for clone effects
|
||||
if (sa.hasParam("FaceDownPower")) {
|
||||
faceDown.setBasePower(AbilityUtils.calculateAmount(
|
||||
source, sa.getParam("FaceDownPower"), sa));
|
||||
}
|
||||
if (sa.hasParam("FaceDownToughness")) {
|
||||
faceDown.setBaseToughness(AbilityUtils.calculateAmount(
|
||||
source, sa.getParam("FaceDownToughness"), sa));
|
||||
}
|
||||
|
||||
if (sa.hasParam("FaceDownSetType")) {
|
||||
faceDown.setType(new CardType(Arrays.asList(sa.getParam("FaceDownSetType").split(" & ")), false));
|
||||
}
|
||||
|
||||
if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")
|
||||
|| sa.hasParam("FaceDownSetType")) {
|
||||
final GameCommand unanimate = new GameCommand() {
|
||||
private static final long serialVersionUID = 8853789549297846163L;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
c.clearStates(CardStateName.FaceDown, true);
|
||||
}
|
||||
};
|
||||
|
||||
c.addFaceupCommand(unanimate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* removeFromStack.
|
||||
@@ -1543,7 +1494,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
* object.
|
||||
* @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 Zone originZone = tgtHost.getZone();
|
||||
game.getStack().remove(si);
|
||||
@@ -1555,7 +1506,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
Card movedCard = null;
|
||||
if (srcSA.hasParam("Destination")) {
|
||||
final boolean remember = srcSA.hasParam("RememberChanged");
|
||||
final boolean rememberSpell = srcSA.hasParam("RememberSpell");
|
||||
final boolean imprint = srcSA.hasParam("Imprint");
|
||||
if (tgtSA.isAbility()) {
|
||||
// Shouldn't be able to target Abilities but leaving this in for now
|
||||
@@ -1587,13 +1537,20 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
+ 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) {
|
||||
srcSA.getHostCard().addRemembered(tgtHost);
|
||||
// TODO or remember moved?
|
||||
}
|
||||
if (rememberSpell) {
|
||||
srcSA.getHostCard().addRemembered(tgtSA);
|
||||
}
|
||||
if (imprint) {
|
||||
srcSA.getHostCard().addImprintedCard(tgtHost);
|
||||
}
|
||||
|
||||
@@ -156,6 +156,11 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
public static boolean makeChoices(SpellAbility sa) {
|
||||
// CR 700.2g
|
||||
if (sa.isCopied()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//this resets all previous choices
|
||||
sa.setSubAbility(null);
|
||||
|
||||
@@ -180,7 +185,7 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
num = Math.min(num, choices.size());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import forge.game.player.DelayedReveal;
|
||||
import forge.game.player.PlayerView;
|
||||
import forge.util.CardTranslation;
|
||||
@@ -119,6 +118,23 @@ public class ChooseCardEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (sa.hasParam("ChooseParty")) {
|
||||
Set<String> partyTypes = Sets.newHashSet("Cleric", "Rogue", "Warrior", "Wizard");
|
||||
for (final String type : partyTypes) {
|
||||
CardCollection valids = CardLists.filter(p.getCardsIn(ZoneType.Battlefield),
|
||||
CardPredicates.isType(type));
|
||||
for (Card alreadyChosen : chosen) {
|
||||
valids.remove(alreadyChosen);
|
||||
}
|
||||
if (!valids.isEmpty()) {
|
||||
final String prompt = Localizer.getInstance().getMessage("lblChoose") + " " +
|
||||
Lang.nounWithNumeralExceptOne(1, type);
|
||||
Card c = p.getController().chooseSingleEntityForEffect(valids, sa, prompt, true, null);
|
||||
if (c != null) {
|
||||
chosen.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (sa.hasParam("WithTotalPower")) {
|
||||
final int totP = AbilityUtils.calculateAmount(host, sa.getParam("WithTotalPower"), sa);
|
||||
CardCollection negativeCreats = CardLists.filterLEPower(p.getCreaturesInPlay(), -1);
|
||||
@@ -131,7 +147,7 @@ public class ChooseCardEffect extends SpellAbilityEffect {
|
||||
Localizer.getInstance().getMessage("lblSelectCreatureWithTotalPowerLessOrEqualTo", (totP - chosenP - negativeNum))
|
||||
+ "\r\n(" + Localizer.getInstance().getMessage("lblSelected") + ":" + chosenPool + ")\r\n(" + Localizer.getInstance().getMessage("lblTotalPowerNum", chosenP) + ")", chosenP <= totP, 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;
|
||||
}
|
||||
} else {
|
||||
@@ -187,6 +203,18 @@ public class ChooseCardEffect extends SpellAbilityEffect {
|
||||
chosenPool.add(choice);
|
||||
}
|
||||
chosen.addAll(chosenPool);
|
||||
} else if (sa.hasParam("ControlAndNot")) {
|
||||
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseCreature");
|
||||
// Targeted player (p) chooses N creatures that belongs to them
|
||||
CardCollection tgtPlayerCtrl = CardLists.filterControlledBy(choices, p);
|
||||
chosen.addAll(p.getController().chooseCardsForEffect(tgtPlayerCtrl, sa, title + " " + "you control", minAmount, validAmount,
|
||||
!sa.hasParam("Mandatory"), null));
|
||||
// Targeted player (p) chooses N creatures that don't belong to them
|
||||
CardCollection notTgtPlayerCtrl = new CardCollection(choices);
|
||||
notTgtPlayerCtrl.removeAll(tgtPlayerCtrl);
|
||||
chosen.addAll(p.getController().chooseCardsForEffect(notTgtPlayerCtrl, sa, title + " " + "you don't control", minAmount, validAmount,
|
||||
!sa.hasParam("Mandatory"), null));
|
||||
|
||||
} else if ((tgt == null) || p.canBeTargetedBy(sa)) {
|
||||
if (sa.hasParam("AtRandom") && !choices.isEmpty()) {
|
||||
Aggregates.random(choices, validAmount, chosen);
|
||||
@@ -217,8 +245,13 @@ public class ChooseCardEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("Reveal")) {
|
||||
game.getAction().reveal(chosen, p, true, Localizer.getInstance().getMessage("lblChosenCards") + " ");
|
||||
if (sa.hasParam("Reveal") && !sa.hasParam("SecretlyChoose")) {
|
||||
game.getAction().reveal(chosen, p, true, sa.hasParam("RevealTitle") ? sa.getParam("RevealTitle") : Localizer.getInstance().getMessage("lblChosenCards") + " ");
|
||||
}
|
||||
}
|
||||
if(sa.hasParam("Reveal") && sa.hasParam("SecretlyChoose")) {
|
||||
for (final Player p : tgtPlayers) {
|
||||
game.getAction().reveal(chosen, p, true, sa.hasParam("RevealTitle") ? sa.getParam("RevealTitle") : Localizer.getInstance().getMessage("lblChosenCards") + " ");
|
||||
}
|
||||
}
|
||||
host.setChosenCards(chosen);
|
||||
|
||||
@@ -54,7 +54,6 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
boolean randomChoice = sa.hasParam("AtRandom");
|
||||
boolean draft = sa.hasParam("Draft"); //for digital "draft from spellbook" mechanic
|
||||
boolean chooseFromDefined = sa.hasParam("ChooseFromDefinedCards");
|
||||
boolean chooseFromList = sa.hasParam("ChooseFromList");
|
||||
boolean chooseFromOneTimeList = sa.hasParam("ChooseFromOneTimeList");
|
||||
@@ -62,8 +61,6 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
|
||||
if (!randomChoice) {
|
||||
if (sa.hasParam("SelectPrompt")) {
|
||||
message = sa.getParam("SelectPrompt");
|
||||
} else if (draft) {
|
||||
message = Localizer.getInstance().getMessage("lblChooseCardDraft");
|
||||
} else if (null == validDesc) {
|
||||
message = Localizer.getInstance().getMessage("lblChooseACardName");
|
||||
} else {
|
||||
@@ -108,12 +105,6 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
|
||||
chosen = p.getController().chooseCardName(sa, faces, message);
|
||||
} else if (chooseFromList) {
|
||||
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<>();
|
||||
for (String name : names) {
|
||||
// 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);
|
||||
if (!randomChoice) {
|
||||
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")) {
|
||||
p.addNoteForName(sa.getParam("NoteFor"), "Name:" + chosen);
|
||||
|
||||
@@ -50,7 +50,7 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
||||
List<SpellAbility> saToRemove = Lists.newArrayList();
|
||||
|
||||
for (SpellAbility saChoice : abilities) {
|
||||
if (!saChoice.getRestrictions().checkOtherRestrictions(host, saChoice, sa.getActivatingPlayer()) ) {
|
||||
if (saChoice.getRestrictions() != null && !saChoice.getRestrictions().checkOtherRestrictions(host, saChoice, sa.getActivatingPlayer())) {
|
||||
saToRemove.add(saChoice);
|
||||
} else if (saChoice.hasParam("UnlessCost")) {
|
||||
// generic check for if the cost can be paid
|
||||
|
||||
@@ -108,7 +108,7 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ public class ControlExchangeEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("Optional") && !sa.getActivatingPlayer().getController().confirmAction(sa, null,
|
||||
Localizer.getInstance().getMessage("lblExchangeControl",
|
||||
CardTranslation.getTranslatedName(object1.getName()),
|
||||
CardTranslation.getTranslatedName(object2.getName())))) {
|
||||
CardTranslation.getTranslatedName(object2.getName())), null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
TokenCreateTable tokenTable = new TokenCreateTable();
|
||||
|
||||
if (sa.hasParam("Optional") && !activator.getController().confirmAction(sa, null,
|
||||
Localizer.getInstance().getMessage("lblCopyPermanentConfirm"))) {
|
||||
Localizer.getInstance().getMessage("lblCopyPermanentConfirm"), null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
if (choosen != null) {
|
||||
tgtCards.add(choosen);
|
||||
choices = CardLists.filter(choices, Predicates.not(CardPredicates.sharesNameWith(choosen)));
|
||||
} else if (chooser.getController().confirmAction(sa, PlayerActionConfirmMode.OptionalChoose, Localizer.getInstance().getMessage("lblCancelChooseConfirm"))) {
|
||||
} else if (chooser.getController().confirmAction(sa, PlayerActionConfirmMode.OptionalChoose, Localizer.getInstance().getMessage("lblCancelChooseConfirm"), null)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
SpellAbility chosenSA = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa,
|
||||
Localizer.getInstance().getMessage("lblSelectASpellCopy"), ImmutableMap.of());
|
||||
|
||||
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoyouWantCopyTheSpell", CardTranslation.getTranslatedName(chosenSA.getHostCard().getName())))) {
|
||||
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoyouWantCopyTheSpell", CardTranslation.getTranslatedName(chosenSA.getHostCard().getName())), null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@ public class CounterEffect extends SpellAbilityEffect {
|
||||
for (final SpellAbility tgtSA : sas) {
|
||||
final Card tgtSACard = tgtSA.getHostCard();
|
||||
// should remember even that spell cannot be countered, e.g. Dovescape
|
||||
// TODO use LKI in case the spell gets countered before (else X amounts would be missing)
|
||||
if (sa.hasParam("RememberCounteredCMC")) {
|
||||
sa.getHostCard().addRemembered(Integer.valueOf(tgtSACard.getCMC()));
|
||||
}
|
||||
@@ -152,9 +153,7 @@ public class CounterEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberCountered")) {
|
||||
if (sa.getParam("RememberCountered").equals("True")) {
|
||||
sa.getHostCard().addRemembered(tgtSACard);
|
||||
}
|
||||
sa.getHostCard().addRemembered(tgtSACard);
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberSplicedOntoCounteredSpell")) {
|
||||
|
||||
@@ -67,8 +67,9 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
|
||||
stringBuilder.append(pronoun ? "they" : who).append(" ");
|
||||
|
||||
String desc = sa.getDescription();
|
||||
boolean forEach = desc.contains("for each");
|
||||
if (sa.hasParam("CounterTypes")) {
|
||||
String desc = sa.getDescription();
|
||||
if (desc.contains("Put ") && desc.contains(" on ")) {
|
||||
desc = desc.substring(desc.indexOf("Put "), desc.indexOf(" on ") + 4)
|
||||
.replaceFirst("Put ", "puts ");
|
||||
@@ -84,8 +85,8 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
final int amount = AbilityUtils.calculateAmount(card,
|
||||
sa.getParamOrDefault("CounterNum", "1"), sa);
|
||||
final String key = forEach ? "ForEachNum" : "CounterNum";
|
||||
final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault(key, "1"), sa);
|
||||
|
||||
if (sa.hasParam("Bolster")) {
|
||||
stringBuilder.append("bolsters ").append(amount).append(".");
|
||||
@@ -155,7 +156,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
}
|
||||
stringBuilder.append(".");
|
||||
stringBuilder.append(forEach ? desc.substring(desc.indexOf(" for each")) : ".");
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
@@ -174,7 +175,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
boolean putOnDefined = sa.hasParam("PutOnDefined");
|
||||
|
||||
if (sa.hasParam("Optional") && !pc.confirmAction
|
||||
(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPutCounter"))) {
|
||||
(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPutCounter"), null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -231,7 +232,9 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("CounterType", counterType);
|
||||
if (counterType != null) {
|
||||
params.put("CounterType", counterType);
|
||||
}
|
||||
if (sa.hasParam("DividedRandomly")) {
|
||||
tgtObjects.addAll(choices);
|
||||
} else {
|
||||
@@ -493,7 +496,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
// need to unfreeze tracker
|
||||
game.getTracker().unfreeze();
|
||||
|
||||
// check if it can recive the Tribute
|
||||
// check if it can receive the Tribute
|
||||
if (abort) {
|
||||
continue;
|
||||
}
|
||||
@@ -509,7 +512,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
Player chooser = pc.chooseSingleEntityForEffect(activator.getOpponents(), sa,
|
||||
Localizer.getInstance().getMessage("lblChooseAnOpponent"), params);
|
||||
|
||||
if (chooser.getController().confirmAction(sa, PlayerActionConfirmMode.Tribute, message)) {
|
||||
if (chooser.getController().confirmAction(sa, PlayerActionConfirmMode.Tribute, message, null)) {
|
||||
gameCard.setTributed(true);
|
||||
} else {
|
||||
continue;
|
||||
|
||||
@@ -76,7 +76,7 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
if (!eachExisting && sa.hasParam("Optional") && !pl.getController().confirmAction(sa, null,
|
||||
Localizer.getInstance().getMessage("lblWouldYouLikePutRemoveCounters", ctype.getName(),
|
||||
CardTranslation.getTranslatedName(gameCard.getName())))) {
|
||||
CardTranslation.getTranslatedName(gameCard.getName())), null)) {
|
||||
continue;
|
||||
}
|
||||
if (!sa.usesTargeting() || gameCard.canBeTargetedBy(sa)) {
|
||||
|
||||
@@ -83,7 +83,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
|
||||
if (sa.hasParam("Optional")) {
|
||||
String ctrs = cntToRemove > 1 ? Localizer.getInstance().getMessage("lblCounters") : num.equals("All") ? Localizer.getInstance().getMessage("lblAllCounters") : Localizer.getInstance().getMessage("lblACounters");
|
||||
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblRemove") + " " + ctrs + "?")) {
|
||||
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblRemove") + " " + ctrs + "?", null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
List<GameObject> tgts = getTargets(sa);
|
||||
if (sa.hasParam("OptionalDecider")) {
|
||||
Player decider = Iterables.getFirst(AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("OptionalDecider"), sa), null);
|
||||
if (decider != null && !decider.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoyouWantDealTargetDamageToTarget", String.valueOf(dmg), tgts.toString()))) {
|
||||
if (decider != null && !decider.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoyouWantDealTargetDamageToTarget", String.valueOf(dmg), tgts.toString()), null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,11 +44,11 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
final int numToChange = toChange.startsWith("All") ? numToDig : AbilityUtils.calculateAmount(host, sa.getParam("ChangeNum"), sa);
|
||||
|
||||
String verb = " looks at ";
|
||||
if (sa.hasParam("Reveal") && sa.getParam("Reveal").equals("True")) {
|
||||
verb = " reveals ";
|
||||
} else if (sa.hasParam("DestinationZone") && sa.getParam("DestinationZone").equals("Exile") &&
|
||||
numToDig == numToChange) {
|
||||
if (sa.hasParam("DestinationZone") && sa.getParam("DestinationZone").equals("Exile") &&
|
||||
numToDig == numToChange) {
|
||||
verb = " exiles ";
|
||||
} else if (sa.hasParam("Reveal") && sa.getParam("Reveal").equals("True")) {
|
||||
verb = " reveals ";
|
||||
}
|
||||
sb.append(host.getController()).append(verb).append("the top ");
|
||||
sb.append(numToDig == 1 ? "card" : Lang.getNumeral(numToDig) + " cards").append(" of ");
|
||||
@@ -203,7 +203,7 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
else if (sa.hasParam("RevealOptional")) {
|
||||
String question = TextUtil.concatWithSpace(Localizer.getInstance().getMessage("lblReveal") + ":", TextUtil.addSuffix(Lang.joinHomogenous(top),"?"));
|
||||
|
||||
hasRevealed = p.getController().confirmAction(sa, null, question);
|
||||
hasRevealed = p.getController().confirmAction(sa, null, question, null);
|
||||
if (hasRevealed) {
|
||||
game.getAction().reveal(top, p);
|
||||
}
|
||||
@@ -276,7 +276,7 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
// Optional abilities that use a dialog box to prompt the user to skip the ability (e.g. Explorer's Scope, Quest for Ula's Temple)
|
||||
if (optional && mayBeSkipped && !valid.isEmpty()) {
|
||||
String prompt = !optionalAbilityPrompt.isEmpty() ? optionalAbilityPrompt : Localizer.getInstance().getMessage("lblWouldYouLikeProceedWithOptionalAbility") + " " + host + "?\n\n(" + sa.getDescription() + ")";
|
||||
if (!p.getController().confirmAction(sa, null, TextUtil.fastReplace(prompt, "CARDNAME", CardTranslation.getTranslatedName(host.getName())))) {
|
||||
if (!p.getController().confirmAction(sa, null, TextUtil.fastReplace(prompt, "CARDNAME", CardTranslation.getTranslatedName(host.getName())), null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public class DigUntilEffect extends SpellAbilityEffect {
|
||||
if (revealed.equals(ZoneType.Exile)) {
|
||||
sb.append("and exile all other cards revealed this way.");
|
||||
}
|
||||
} else {
|
||||
} else if (revealed != null) {
|
||||
if (revealed.equals(ZoneType.Hand)) {
|
||||
sb.append("all cards revealed this way into their hand");
|
||||
}
|
||||
@@ -120,7 +120,7 @@ public class DigUntilEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
|
||||
if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantDigYourLibrary"))) {
|
||||
if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantDigYourLibrary"), null)) {
|
||||
continue;
|
||||
}
|
||||
CardCollection found = new CardCollection();
|
||||
@@ -171,7 +171,7 @@ public class DigUntilEffect extends SpellAbilityEffect {
|
||||
final Card c = itr.next();
|
||||
final ZoneType origin = c.getZone().getZoneType();
|
||||
if (optionalFound && !p.getController().confirmAction(sa, null,
|
||||
Localizer.getInstance().getMessage("lblDoYouWantPutCardToZone", foundDest.getTranslatedName()))) {
|
||||
Localizer.getInstance().getMessage("lblDoYouWantPutCardToZone", foundDest.getTranslatedName()), null)) {
|
||||
continue;
|
||||
}
|
||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
@@ -209,7 +209,9 @@ public class DigUntilEffect extends SpellAbilityEffect {
|
||||
Collections.shuffle(revealed, MyRandom.getRandom());
|
||||
}
|
||||
|
||||
if (sa.hasParam("NoneFoundDestination") && found.size() < untilAmount) {
|
||||
if (sa.hasParam("NoMoveRevealed")) {
|
||||
//don't do anything
|
||||
} else if (sa.hasParam("NoneFoundDestination") && found.size() < untilAmount) {
|
||||
// Allow ordering the revealed cards
|
||||
if (noneFoundDest.isKnown() && revealed.size() >= 2) {
|
||||
revealed = (CardCollection)p.getController().orderMoveToZoneList(revealed, noneFoundDest, sa);
|
||||
|
||||
@@ -153,7 +153,7 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
boolean runDiscard = !sa.hasParam("Optional")
|
||||
|| p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, sa.getParam("DiscardMessage"));
|
||||
|| p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, sa.getParam("DiscardMessage"), null);
|
||||
if (runDiscard) {
|
||||
toBeDiscarded = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedCards"), sa);
|
||||
|
||||
@@ -197,7 +197,7 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
String message = Localizer.getInstance().getMessage("lblWouldYouLikeRandomDiscardTargetCard", String.valueOf(numCards));
|
||||
boolean runDiscard = !sa.hasParam("Optional") || p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, message);
|
||||
boolean runDiscard = !sa.hasParam("Optional") || p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, message, null);
|
||||
|
||||
if (runDiscard) {
|
||||
final String valid = sa.getParamOrDefault("DiscardValid", "Card");
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.StaticData;
|
||||
import forge.card.ICardFace;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Localizer;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class DraftEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Player player = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa).get(0);
|
||||
final ZoneType zone = ZoneType.smartValueOf(sa.getParamOrDefault("Zone", "Hand"));
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(player).append(" drafts a card from ").append(source.getName()).append("'s spellbook");
|
||||
if (zone.equals("Hand")) {
|
||||
sb.append(".");
|
||||
} else if (zone.equals("Battlefield")) {
|
||||
sb.append(" and puts it onto the battlefield.");
|
||||
} else if (zone.equals("Exile")) {
|
||||
sb.append(", then exiles it.");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
|
||||
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
|
||||
final Card source = sa.getHostCard();
|
||||
final Player player = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa).get(0);
|
||||
final Game game = player.getGame();
|
||||
final ZoneType zone = ZoneType.smartValueOf(sa.getParamOrDefault("Zone", "Hand"));
|
||||
List<String> spellbook = Arrays.asList(sa.getParam("Spellbook").split(","));
|
||||
final int numToDraft = AbilityUtils.calculateAmount(source,
|
||||
sa.getParamOrDefault("DraftNum", "1"), sa);
|
||||
CardCollection drafted = new CardCollection();
|
||||
|
||||
for (int i = 0; i < numToDraft; i++) {
|
||||
String chosen = "";
|
||||
Collections.shuffle(spellbook);
|
||||
List<ICardFace> faces = new ArrayList<>();
|
||||
for (String name : spellbook.subList(0, 3)) {
|
||||
// Cardnames that include "," must use ";" instead in Spellbook$ (i.e. Tovolar; Dire Overlord)
|
||||
name = name.replace(";", ",");
|
||||
faces.add(StaticData.instance().getCommonCards().getFaceByName(name));
|
||||
}
|
||||
chosen = player.getController().chooseCardName(sa, faces,
|
||||
Localizer.getInstance().getMessage("lblChooseCardDraft"));
|
||||
if (!chosen.equals("")) {
|
||||
Card card = Card.fromPaperCard(StaticData.instance().getCommonCards().getUniqueByName(chosen), player);
|
||||
card.setTokenCard(true);
|
||||
game.getAction().moveTo(ZoneType.None, card, sa, moveParams);
|
||||
drafted.add(card);
|
||||
}
|
||||
}
|
||||
|
||||
final CardZoneTable triggerList = new CardZoneTable();
|
||||
for (final Card c : drafted) {
|
||||
Card made = game.getAction().moveTo(zone, c, sa, moveParams);
|
||||
if (c != null) {
|
||||
triggerList.put(ZoneType.None, made.getZone().getZoneType(), made);
|
||||
}
|
||||
if (sa.hasParam("RememberDrafted")) {
|
||||
source.addRemembered(made);
|
||||
}
|
||||
}
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
if (zone.equals(ZoneType.Library)) {
|
||||
player.shuffle(sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ public class DrawEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantDrawCards", Lang.nounWithAmount(numCards, " card")))) {
|
||||
if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantDrawCards", Lang.nounWithAmount(numCards, " card")), null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,13 @@ public class EffectEffect extends SpellAbilityEffect {
|
||||
effectKeywords = sa.getParam("Keywords").split(",");
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberSpell")) {
|
||||
rememberList = new FCollection<>();
|
||||
for (final String rem : sa.getParam("RememberSpell").split(",")) {
|
||||
rememberList.addAll(AbilityUtils.getDefinedSpellAbilities(hostCard, rem, sa));
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberObjects")) {
|
||||
rememberList = new FCollection<>();
|
||||
for (final String rem : sa.getParam("RememberObjects").split(",")) {
|
||||
|
||||
@@ -48,7 +48,7 @@ public class EncodeEffect extends SpellAbilityEffect {
|
||||
}
|
||||
// Handle choice of whether or not to encoded
|
||||
|
||||
if (!player.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantExileCardAndEncodeOntoYouCreature", CardTranslation.getTranslatedName(host.getName())))) {
|
||||
if (!player.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantExileCardAndEncodeOntoYouCreature", CardTranslation.getTranslatedName(host.getName())), null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ public class EndTurnEffect extends SpellAbilityEffect {
|
||||
public void resolve(SpellAbility sa) {
|
||||
final List<Player> enders = getDefinedPlayersOrTargeted(sa, "Defined");
|
||||
final Player ender = enders.isEmpty() ? sa.getActivatingPlayer() : enders.get(0);
|
||||
if (sa.hasParam("Optional") && !ender.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantEndTurn"))) {
|
||||
if (sa.hasParam("Optional") && !ender.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantEndTurn"), null)) {
|
||||
return;
|
||||
}
|
||||
Game game = ender.getGame();
|
||||
|
||||
@@ -59,7 +59,7 @@ public class FightEffect extends DamageBaseEffect {
|
||||
Player controller = host.getController();
|
||||
boolean isOptional = sa.hasParam("Optional");
|
||||
|
||||
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeFight", CardTranslation.getTranslatedName(fighters.get(0).getName()), CardTranslation.getTranslatedName(fighters.get(1).getName())))) {
|
||||
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeFight", CardTranslation.getTranslatedName(fighters.get(0).getName()), CardTranslation.getTranslatedName(fighters.get(1).getName())), null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,25 @@ import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Lang;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GoadEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final Player player = sa.getActivatingPlayer();
|
||||
List<Card> tgt = getTargetCards(sa);
|
||||
if (tgt.size() <= 0) {
|
||||
return "";
|
||||
} else {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(player).append(" goads ").append(Lang.joinHomogenous(tgt)).append(".");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Player player = sa.getActivatingPlayer();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user