diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 0920da8b4d8..e9247609619 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -682,6 +682,8 @@ public class AiAttackController { final boolean bAssault = this.doAssault(ai); // TODO: detect Lightmine Field by presence of a card with a specific trigger final boolean lightmineField = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Lightmine Field"); + // TODO: detect Season of the Witch by presence of a card with a specific trigger + final boolean seasonOfTheWitch = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Season of the Witch"); // Determine who will be attacked GameEntity defender = this.chooseDefender(combat, bAssault); @@ -715,6 +717,9 @@ public class AiAttackController { } else if (attacker.hasSVar("EndOfTurnLeavePlay") && isEffectiveAttacker(ai, attacker, combat)) { mustAttack = true; + } else if (seasonOfTheWitch) { + // TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack + mustAttack = true; } else { for (KeywordInterface inst : attacker.getKeywords()) { String s = inst.getOriginal(); diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index 2231323b1b9..19b57e42e65 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -17,6 +17,7 @@ import forge.game.ability.AbilityUtils; import forge.game.card.CardPredicates.Presets; import forge.game.combat.AttackingBand; import forge.game.combat.Combat; +import forge.game.combat.CombatUtil; import forge.game.keyword.Keyword; import forge.game.mana.Mana; import forge.game.player.Player; @@ -1622,6 +1623,9 @@ public class CardProperty { if (band == null || !band.getAttackers().contains(card)) { return false; } + } else if (property.equals("couldAttackButNotAttacking")) { + if (!game.getPhaseHandler().isPlayerTurn(controller)) return false; + return CombatUtil.couldAttackButNotAttacking(combat, card); } else if (property.startsWith("kicked")) { if (property.equals("kicked")) { if (card.getKickerMagnitude() == 0) { diff --git a/forge-game/src/main/java/forge/game/combat/CombatUtil.java b/forge-game/src/main/java/forge/game/combat/CombatUtil.java index 6391b442122..298816baf72 100644 --- a/forge-game/src/main/java/forge/game/combat/CombatUtil.java +++ b/forge-game/src/main/java/forge/game/combat/CombatUtil.java @@ -45,6 +45,7 @@ import forge.util.collect.FCollectionView; import forge.util.maps.MapToAmount; import org.apache.commons.lang3.tuple.Pair; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -82,6 +83,38 @@ public class CombatUtil { return myViolations <= bestAttack.getRight().intValue(); } + /** + * Check if attacker could attack without violating any constraints. + */ + public static boolean couldAttackButNotAttacking(Combat combat, final Card attacker) { + // If the player didn't declare attackers, combat here will be null + if (combat == null) { + combat = new Combat(attacker.getController()); + } else if (combat.isAttacking(attacker)) { + return false; + } + + final AttackConstraints constraints = combat.getAttackConstraints(); + final Pair, Integer> bestAttack = constraints.getLegalAttackers(); + final Map attackers = new HashMap<>(combat.getAttackersAndDefenders()); + final Game game = attacker.getGame(); + + return Iterables.any(getAllPossibleDefenders(attacker.getController()), new Predicate() { + @Override + public boolean apply(final GameEntity defender) { + if (!canAttack(attacker, defender) || getAttackCost(game, attacker, defender) != null) { + return false; + } + attackers.put(attacker, defender); + final int myViolations = constraints.countViolations(attackers); + if (myViolations == -1) { + return false; + } + return myViolations <= bestAttack.getRight().intValue(); + } + }); + } + /** *

* Check whether a player should be given the chance to attack this combat. diff --git a/forge-gui/res/cardsfolder/s/season_of_the_witch.txt b/forge-gui/res/cardsfolder/s/season_of_the_witch.txt new file mode 100644 index 00000000000..54aa551740f --- /dev/null +++ b/forge-gui/res/cardsfolder/s/season_of_the_witch.txt @@ -0,0 +1,13 @@ +Name:Season of the Witch +ManaCost:B B B +Types:Enchantment +K:UpkeepCost:PayLife<2> +T:Mode$ Phase | Mode$ Phase | Phase$ Declare Attackers | Execute$ TrigMarkCouldAttack | Static$ True +SVar:TrigMarkCouldAttack:DB$ PumpAll | ValidCards$ Creature.couldAttackButNotAttacking | RememberAllPumped$ True +T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | Execute$ TrigDestroyAll | TriggerDescription$ At the beginning of the end step, destroy all untapped creatures that didn't attack this turn, except for creatures that couldn't attack. +SVar:TrigDestroyAll:DB$ DestroyAll | ValidCards$ Creature.untapped+IsRemembered +T:Mode$ Phase | Phase$ Cleanup | Execute$ TrigCleanup | Static$ True +SVar:TrigCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:NonStackingEffect:True +AI:RemoveDeck:Random +Oracle:At the beginning of your upkeep, sacrifice Season of the Witch unless you pay 2 life.\nAt the beginning of the end step, destroy all untapped creatures that didn't attack this turn, except for creatures that couldn't attack.