From 2f96097f45a364d6b9ba1371e7a88054eae7fd31 Mon Sep 17 00:00:00 2001 From: Alumi Date: Mon, 1 Feb 2021 04:26:58 +0000 Subject: [PATCH] Add Sorrow's Path and General Jarkeld --- .../main/java/forge/game/ability/ApiType.java | 1 + .../ability/effects/SwitchBlockEffect.java | 168 ++++++++++++++++++ .../res/cardsfolder/g/general_jarkeld.txt | 8 + forge-gui/res/cardsfolder/s/sorrows_path.txt | 9 + 4 files changed, 186 insertions(+) create mode 100644 forge-game/src/main/java/forge/game/ability/effects/SwitchBlockEffect.java create mode 100644 forge-gui/res/cardsfolder/g/general_jarkeld.txt create mode 100644 forge-gui/res/cardsfolder/s/sorrows_path.txt diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java index 13c08677a42..365afb52c11 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -154,6 +154,7 @@ public enum ApiType { StoreSVar (StoreSVarEffect.class), StoreMap (StoreMapEffect.class), Surveil (SurveilEffect.class), + SwitchBlock (SwitchBlockEffect.class), Tap (TapEffect.class), TapAll (TapAllEffect.class), TapOrUntap (TapOrUntapEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/SwitchBlockEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SwitchBlockEffect.java new file mode 100644 index 00000000000..1605a7d24d4 --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/SwitchBlockEffect.java @@ -0,0 +1,168 @@ +package forge.game.ability.effects; + +import java.util.*; + +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.combat.Combat; +import forge.game.combat.CombatUtil; +import forge.game.event.GameEventCombatChanged; +import forge.game.spellability.SpellAbility; +import forge.game.trigger.TriggerType; + +public class SwitchBlockEffect extends SpellAbilityEffect { + + private void runTriggers(final Game game, final Card attacker, final Card blocker) { + final Map runParams = AbilityKey.newMap(); + runParams.put(AbilityKey.Attacker, attacker); + runParams.put(AbilityKey.Blocker, blocker); + game.getTriggerHandler().runTrigger(TriggerType.AttackerBlockedByCreature, runParams, false); + game.getTriggerHandler().runTrigger(TriggerType.Blocks, runParams, false); + } + + @Override + public void resolve(SpellAbility sa) { + final Card host = sa.getHostCard(); + final Game game = host.getGame(); + final Combat combat = game.getPhaseHandler().getCombat(); + boolean isTargetingAttacker = false; + + List attackers = new ArrayList<>(); + if (sa.hasParam("DefinedAttacker")) { + final String definedAttacker = sa.getParam("DefinedAttacker"); + if (definedAttacker.equals("Targeted")) { + isTargetingAttacker = true; + } + for (final Card attacker : AbilityUtils.getDefinedCards(host, definedAttacker, sa)) { + if (combat.isAttacking(attacker)) + attackers.add(attacker); + } + } + + List blockers = new ArrayList<>(); + if (sa.hasParam("DefinedBlocker")) { + final String definedBlocker = sa.getParam("DefinedBlocker"); + if (definedBlocker.equals("Targeted")) { + isTargetingAttacker = false; + } + for (final Card blocker : AbilityUtils.getDefinedCards(host, definedBlocker, sa)) { + if (combat.isBlocking(blocker)) + blockers.add(blocker); + } + } + + if (attackers.isEmpty() || blockers.isEmpty()) return; + + // Check if blockers can be switched, then switch them. + boolean isReblock = sa.hasParam("RemoveFromCombat"); + if (isTargetingAttacker) { // For General Jarkeld + // If targeting attackers but only one remains, this fizzles. + if (attackers.size() == 1) return; + + final Card attacker1 = attackers.get(0); + final Card attacker2 = attackers.get(1); + + // If both attackers are in the same attacking band, no need to switch + if (combat.getBandOfAttacker(attacker1) == combat.getBandOfAttacker(attacker2)) return; + + for (final Card blocker : blockers) { + if (combat.isBlocking(blocker, attacker1) && !CombatUtil.canBlock(attacker2, blocker) || + combat.isBlocking(blocker, attacker2) && !CombatUtil.canBlock(attacker1, blocker)) { + return; + } + } + + // Switch blockers + int blockingStates[] = new int[blockers.size()]; + // Remove all blockers first + for (int i = 0; i < blockers.size(); i++) { + final Card blocker = blockers.get(i); + final boolean blocking1 = combat.isBlocking(blocker, attacker1); + final boolean blocking2 = combat.isBlocking(blocker, attacker2); + blockingStates[i] = (blocking1 ? 1 : 0) + (blocking2 ? 2 : 0); + combat.removeFromCombat(blocker); + } + // Unregister so it won't ask for damage assignment order when adding each blocker + combat.unregisterAttacker(attacker1, combat.getBandOfAttacker(attacker1)); + combat.unregisterAttacker(attacker2, combat.getBandOfAttacker(attacker2)); + // Add blockers back to block the other attacker + for (int i = 0; i < blockers.size(); i++) { + final Card blocker = blockers.get(i); + if ((blockingStates[i] & 1) == 1) { + combat.addBlocker(attacker2, blocker); + if (isReblock) { + runTriggers(game, attacker2, blocker); + } + } + if((blockingStates[i] & 2) == 2) { + combat.addBlocker(attacker1, blocker); + if (isReblock) { + runTriggers(game, attacker1, blocker); + } + } + blocker.updateBlockingForView(); + } + // 509.6 + combat.orderBlockersForDamageAssignment(attacker1, combat.getBlockers(attacker1)); + combat.orderBlockersForDamageAssignment(attacker2, combat.getBlockers(attacker2)); + for (final Card blocker : blockers) { + combat.orderAttackersForDamageAssignment(blocker); + } + } else { // For Sorrow's Path + // If targeting blockers but only one remains, this fizzles. + if (blockers.size() == 1) return; + + final Card blocker1 = blockers.get(0); + final Card blocker2 = blockers.get(1); + + // If one blocker is currently blocking more creatures than the other blocker could possibly block, can't switch + if (combat.getAttackersBlockedBy(blocker1).size() > blocker2.canBlockAdditional() + 1 || + combat.getAttackersBlockedBy(blocker2).size() > blocker1.canBlockAdditional() + 1) { + return; + } else { + for (final Card attacker : attackers) { + if (combat.isBlocking(blocker1, attacker) && !CombatUtil.canBlock(attacker, blocker2) || + combat.isBlocking(blocker2, attacker) && !CombatUtil.canBlock(attacker, blocker1)) { + return; + } + } + } + + // Switch blockers + int blockingStates[] = new int[attackers.size()]; + // Remove all blockers first + for (int i = 0; i < attackers.size(); i++) { + final Card attacker = attackers.get(i); + final boolean blocking1 = combat.isBlocking(blocker1, attacker); + final boolean blocking2 = combat.isBlocking(blocker2, attacker); + blockingStates[i] = (blocking1 ? 1 : 0) + (blocking2 ? 2 : 0); + } + combat.removeFromCombat(blocker1); + combat.removeFromCombat(blocker2); + // Add blockers back to block the other attacker + for (int i = 0; i < attackers.size(); i++) { + final Card attacker = attackers.get(i); + if ((blockingStates[i] & 1) == 1) { + combat.addBlocker(attacker, blocker2); + if (isReblock) { + runTriggers(game, attacker, blocker2); + } + } + if ((blockingStates[i] & 2) == 2) { + combat.addBlocker(attacker, blocker1); + if (isReblock) { + runTriggers(game, attacker, blocker1); + } + } + } + combat.orderAttackersForDamageAssignment(blocker1); + combat.orderAttackersForDamageAssignment(blocker2); + } + + game.updateCombatForView(); + game.fireEvent(new GameEventCombatChanged()); + } +} \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/g/general_jarkeld.txt b/forge-gui/res/cardsfolder/g/general_jarkeld.txt new file mode 100644 index 00000000000..28ea82ad5c0 --- /dev/null +++ b/forge-gui/res/cardsfolder/g/general_jarkeld.txt @@ -0,0 +1,8 @@ +Name:General Jarkeld +ManaCost:3 W +Types:Legendary Creature Human Soldier +PT:1/2 +A:AB$ SwitchBlock | Cost$ T | ActivationPhases$ Declare Blockers | TargetMin$ 2 | TargetMax$ 2 | ValidTgts$ Creature.attacking+blocked | DefinedAttacker$ Targeted | DefinedBlocker$ Valid Creature.blockingTargeted | TgtPrompt$ Choose two target blocked attacking creatures | SpellDescription$ Choose two target blocked attacking creatures. If each of those creatures could be blocked by all creatures that the other is blocked by, each creature that's blocking exactly one of those attacking creatures stops blocking it and is blocking the other attacking creature. Activate this ability only during the declare blockers step. | StackDescription$ SpellDescription +AI:RemoveDeck:Random +AI:RemoveDeck:All +Oracle:{T}: Choose two target blocked attacking creatures. If each of those creatures could be blocked by all creatures that the other is blocked by, each creature that's blocking exactly one of those attacking creatures stops blocking it and is blocking the other attacking creature. Activate this ability only during the declare blockers step. diff --git a/forge-gui/res/cardsfolder/s/sorrows_path.txt b/forge-gui/res/cardsfolder/s/sorrows_path.txt new file mode 100644 index 00000000000..8c9de22e45b --- /dev/null +++ b/forge-gui/res/cardsfolder/s/sorrows_path.txt @@ -0,0 +1,9 @@ +Name:Sorrow's Path +ManaCost:no cost +Types:Land +A:AB$ SwitchBlock | Cost$ T | TargetMin$ 2 | TargetMax$ 2 | ValidTgts$ Creature.blocking+OppCtrl | TargetsFromSingleZone$ True | DefinedAttacker$ Valid Creature.blockedByValidThisTurn Targeted | DefinedBlocker$ Targeted | RemoveFromCombat$ True | TgtPrompt$ Choose two target blocking creatures an opponent controls | SpellDescription$ Choose two target blocking creatures an opponent controls. If each of those creatures could block all creatures that the other is blocking, remove both of them from combat. Each one then blocks all creatures the other was blocking. | StackDescription$ SpellDescription +T:Mode$ Taps | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigDamage | TriggerDescription$ Whenever CARDNAME becomes tapped, it deals 2 damage to you and each creature you control. +SVar:TrigDamage:DB$ DamageAll | ValidCards$ Creature.YouCtrl | ValidPlayers$ You | NumDmg$ 2 +AI:RemoveDeck:Random +AI:RemoveDeck:All +Oracle:{T}: Choose two target blocking creatures an opponent controls. If each of those creatures could block all creatures that the other is blocking, remove both of them from combat. Each one then blocks all creatures the other was blocking.\nWhenever Sorrow's Path becomes tapped, it deals 2 damage to you and each creature you control.