Merge branch 'sorrows_path_general_jarkeld' into 'master'

Add Sorrow's Path and General Jarkeld

See merge request core-developers/forge!3685
This commit is contained in:
Michael Kamensky
2021-02-01 04:26:59 +00:00
4 changed files with 186 additions and 0 deletions

View File

@@ -154,6 +154,7 @@ public enum ApiType {
StoreSVar (StoreSVarEffect.class), StoreSVar (StoreSVarEffect.class),
StoreMap (StoreMapEffect.class), StoreMap (StoreMapEffect.class),
Surveil (SurveilEffect.class), Surveil (SurveilEffect.class),
SwitchBlock (SwitchBlockEffect.class),
Tap (TapEffect.class), Tap (TapEffect.class),
TapAll (TapAllEffect.class), TapAll (TapAllEffect.class),
TapOrUntap (TapOrUntapEffect.class), TapOrUntap (TapOrUntapEffect.class),

View File

@@ -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<AbilityKey, Object> 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<Card> 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<Card> 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());
}
}

View File

@@ -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.

View File

@@ -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.