SpellAbilityEffect: unify Attacking options

This commit is contained in:
Hans Mackowiak
2020-11-04 20:43:40 +01:00
parent b664c2393e
commit feefe62047
7 changed files with 101 additions and 231 deletions

View File

@@ -8,7 +8,9 @@ import forge.card.MagicColor;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.GameCommand;
import forge.card.CardType;
@@ -18,6 +20,7 @@ import forge.game.GameObject;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardFactoryUtil;
import forge.game.combat.Combat;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler;
@@ -28,8 +31,11 @@ import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
/**
* <p>
@@ -523,4 +529,58 @@ public abstract class SpellAbilityEffect {
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
}
}
protected static boolean addToCombat(Card c, Player controller, SpellAbility sa, String attackingParam, String blockingParam) {
final Card host = sa.getHostCard();
final Game game = controller.getGame();
if (!game.getPhaseHandler().inCombat()) {
return false;
}
boolean combatChanged = false;
final Combat combat = game.getCombat();
if (sa.hasParam(attackingParam) && combat.getAttackingPlayer().equals(controller)) {
String attacking = sa.getParam(attackingParam);
GameEntity defender = null;
FCollectionView<GameEntity> defs = null;
if ("True".equalsIgnoreCase(attacking)) {
defs = combat.getDefenders();
} else if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
Player defendingPlayer = Iterables.getFirst(AbilityUtils.getDefinedPlayers(host, attacking, sa), null);
if (defendingPlayer != null) {
defs = game.getCombat().getDefendersControlledBy(defendingPlayer);
}
} else {
defs = AbilityUtils.getDefinedEntities(host, attacking, sa);
}
if (defs != null) {
Map<String, Object> params = Maps.newHashMap();
params.put("Attacker", c);
defender = controller.getController().chooseSingleEntityForEffect(defs, sa,
Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(c.getName())), false, params);
}
if (defender != null) {
combat.addAttacker(c, defender);
combatChanged = true;
}
}
if (sa.hasParam(blockingParam)) {
final Card attacker = Iterables.getFirst(AbilityUtils.getDefinedCards(host, sa.getParam(blockingParam), sa), null);
if (attacker != null) {
final boolean wasBlocked = combat.isBlocked(attacker);
combat.addBlocker(attacker, c);
combat.orderAttackersForDamageAssignment(c);
// Run triggers for new blocker and add it to damage assignment order
if (!wasBlocked) {
combat.setBlocked(attacker, true);
combat.addBlockerToDamageAssignmentOrder(attacker, c);
}
combatChanged = true;
}
}
return combatChanged;
}
}

View File

@@ -11,12 +11,10 @@ import forge.card.CardStateName;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameLogEntryType;
import forge.game.GameObject;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.*;
import forge.game.combat.Combat;
import forge.game.event.GameEventCombatChanged;
import forge.game.player.DelayedReveal;
import forge.game.player.Player;
@@ -483,6 +481,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
final boolean optional = sa.hasParam("Optional");
final long ts = game.getNextTimestamp();
boolean combatChanged = false;
for (final Card tgtC : tgtCards) {
if (tgt != null && tgtC.isInPlay() && !tgtC.canBeTargetedBy(sa)) {
@@ -616,42 +615,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (sa.hasParam("FaceDown")) {
movedCard.turnFaceDown(true);
}
if (sa.hasParam("Attacking")) {
final Combat combat = game.getCombat();
if (null != combat) {
FCollectionView<GameEntity> defs = null;
String attacking = sa.getParam("Attacking");
if ("True".equalsIgnoreCase(attacking)) {
defs = combat.getDefenders();
} else if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
Player defendingPlayer = Iterables.getFirst(AbilityUtils.getDefinedPlayers(hostCard,
attacking, sa), null);
if (defendingPlayer != null) {
defs = combat.getDefendersControlledBy(defendingPlayer);
}
}
GameEntity defender = null;
if (sa.hasParam("DefinedDefender")) {
FCollection<GameObject> objs = AbilityUtils.getDefinedObjects(hostCard, sa.getParam("DefinedDefender"), sa);
for(GameObject obj : objs) {
if (obj instanceof GameEntity) {
defender = (GameEntity)obj;
break;
}
}
} else {
String title = Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(movedCard.getName()));
Map<String, Object> params = Maps.newHashMap();
params.put("Attacker", movedCard);
defender = player.getController().chooseSingleEntityForEffect(defs, sa, title,false,
params);
}
if (defender != null) {
combat.addAttacker(movedCard, defender);
game.getCombat().getBandOfAttacker(movedCard).setBlocked(false);
game.fireEvent(new GameEventCombatChanged());
}
}
if (addToCombat(movedCard, movedCard.getController(), sa, "Attacking", "Blocking")) {
combatChanged = true;
}
if (sa.hasParam("Ninjutsu")) {
// Ninjutsu need to get the Defender of the Returned Creature
@@ -659,7 +624,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
final GameEntity defender = game.getCombat().getDefenderByAttacker(returned);
game.getCombat().addAttacker(tgtC, defender);
game.getCombat().getBandOfAttacker(tgtC).setBlocked(false);
game.fireEvent(new GameEventCombatChanged());
combatChanged = true;
}
if (sa.hasParam("Tapped") || sa.hasParam("Ninjutsu")) {
tgtC.setTapped(true);
@@ -727,6 +692,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
}
if (combatChanged) {
game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged());
}
//reveal command cards that changes zone from command zone to player's hand
if (!commandCards.isEmpty()) {
game.getAction().reveal(commandCards, player, true, "Revealed cards in ");
@@ -1066,6 +1036,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
CardCollection movedCards = new CardCollection();
long ts = game.getNextTimestamp();
final CardZoneTable triggerList = new CardZoneTable();
boolean combatChanged = false;
for (final Card c : chosenCards) {
Card movedCard = null;
final Zone originZone = game.getZoneOf(c);
@@ -1148,59 +1119,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
}
if (sa.hasParam("Attacking")) {
final Combat combat = game.getCombat();
if (null != combat) {
FCollectionView<GameEntity> defs = null;
String attacking = sa.getParam("Attacking");
if ("True".equalsIgnoreCase(attacking)) {
defs = combat.getDefenders();
} else if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
Player defendingPlayer = Iterables.getFirst(AbilityUtils.getDefinedPlayers(source,
attacking, sa), null);
if (defendingPlayer != null) {
defs = combat.getDefendersControlledBy(defendingPlayer);
}
}
GameEntity defender = null;
if (sa.hasParam("DefinedDefender")) {
FCollection<GameObject> objs = AbilityUtils.getDefinedObjects(source,
sa.getParam("DefinedDefender"), sa);
for (GameObject obj : objs) {
if (obj instanceof GameEntity) {
defender = (GameEntity) obj;
break;
}
}
} else {
String title = Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard",
CardTranslation.getTranslatedName(c.getName()));
Map<String, Object> params = Maps.newHashMap();
params.put("Attacker", c);
defender = decider.getController().chooseSingleEntityForEffect(defs, sa, title,false,
params);
}
if (defender != null) {
combat.addAttacker(c, defender);
game.getCombat().getBandOfAttacker(c).setBlocked(false);
game.fireEvent(new GameEventCombatChanged());
}
}
}
if (sa.hasParam("Blocking")) {
final Combat combat = game.getCombat();
if ( null != combat ) {
CardCollection attackers = AbilityUtils.getDefinedCards(source, sa.getParam("Blocking"), sa);
if (!attackers.isEmpty()) {
Card attacker = attackers.get(0);
if (combat.isAttacking(attacker)) {
combat.addBlocker(attacker, c);
combat.orderAttackersForDamageAssignment(c);
game.fireEvent(new GameEventCombatChanged());
}
}
}
if (addToCombat(c, c.getController(), sa, "Attacking", "Blocking")) {
combatChanged = true;
}
// need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger
if (sa.hasParam("FaceDown") && ZoneType.Battlefield.equals(destination)) {
c.turnFaceDown(true);
@@ -1302,6 +1224,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
player.shuffle(sa);
}
if (combatChanged) {
game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged());
}
triggerList.triggerChangesZoneAll(game);
}

View File

@@ -2,28 +2,21 @@ package forge.game.ability.effects;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.GameCommand;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.combat.Combat;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.event.GameEventCombatChanged;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.collect.FCollectionView;
import forge.util.Localizer;
import forge.util.CardTranslation;
public class ControlGainEffect extends SpellAbilityEffect {
@@ -86,7 +79,6 @@ public class ControlGainEffect extends SpellAbilityEffect {
final boolean bTapOnLose = sa.hasParam("TapOnLose");
final boolean remember = sa.hasParam("RememberControlled");
final boolean forget = sa.hasParam("ForgetControlled");
final boolean attacking = sa.hasParam("Attacking");
final List<String> keywords = sa.hasParam("AddKWs") ? Arrays.asList(sa.getParam("AddKWs").split(" & ")) : null;
final List<String> lose = sa.hasParam("LoseControl") ? Arrays.asList(sa.getParam("LoseControl").split(",")) : null;
@@ -109,6 +101,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
return;
}
boolean combatChanged = false;
for (Card tgtC : tgtCards) {
if (!tgtC.isInPlay() || !tgtC.canBeControlledBy(newController)) {
@@ -209,23 +202,15 @@ public class ControlGainEffect extends SpellAbilityEffect {
game.getAction().controllerChangeZoneCorrection(tgtC);
if (attacking) {
final Combat combat = game.getCombat();
if ( null != combat ) {
final FCollectionView<GameEntity> e = combat.getDefenders();
String title = Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(tgtC.getName()));
Map<String, Object> params = Maps.newHashMap();
params.put("Attacker", tgtC);
final GameEntity defender = sa.getActivatingPlayer().getController().chooseSingleEntityForEffect(e, sa, title, params);
if (defender != null) {
combat.addAttacker(tgtC, defender);
game.getCombat().getBandOfAttacker(tgtC).setBlocked(false);
game.fireEvent(new GameEventCombatChanged());
}
}
if (addToCombat(tgtC, tgtC.getController(), sa, "Attacking", "Blocking")) {
combatChanged = true;
}
} // end foreach target
if (combatChanged) {
game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged());
}
}
/**

View File

@@ -1,11 +1,8 @@
package forge.game.ability.effects;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
@@ -16,7 +13,6 @@ import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardZoneTable;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
import forge.game.event.GameEventCombatChanged;
import forge.game.player.DelayedReveal;
import forge.game.player.Player;
@@ -109,6 +105,7 @@ public class DigEffect extends SpellAbilityEffect {
CardZoneTable table = new CardZoneTable();
GameEntityCounterTable counterTable = new GameEntityCounterTable();
boolean combatChanged = false;
for (final Player p : tgtPlayers) {
if (tgt != null && !p.canBeTargetedBy(sa)) {
continue;
@@ -317,35 +314,8 @@ public class DigEffect extends SpellAbilityEffect {
if (sa.hasParam("Tapped")) {
c.setTapped(true);
}
if (sa.hasParam("Attacking")) {
final Combat combat = game.getCombat();
String attacking = sa.getParam("Attacking");
GameEntity defender = null;
FCollectionView<GameEntity> defs = null;
if (null != combat) {
if ("True".equalsIgnoreCase(attacking)) {
defs = combat.getDefenders();
} else if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
Player defendingPlayer = Iterables.getFirst(AbilityUtils.getDefinedPlayers(host,
attacking, sa), null);
if (defendingPlayer != null) {
defs = game.getCombat().getDefendersControlledBy(defendingPlayer);
}
}
}
if (defs != null) {
Map<String, Object> params = Maps.newHashMap();
params.put("Attacker", c);
defender = player.getController().chooseSingleEntityForEffect(defs, sa,
Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard",
CardTranslation.getTranslatedName(c.getName())),false,
params);
}
if (defender != null) {
combat.addAttacker(c, defender);
game.getCombat().getBandOfAttacker(c).setBlocked(false);
game.fireEvent(new GameEventCombatChanged());
}
if (addToCombat(c, c.getController(), sa, "Attacking", "Blocking")) {
combatChanged = true;
}
} else if (destZone1.equals(ZoneType.Exile)) {
c.setExiledWith(effectHost);
@@ -423,6 +393,10 @@ public class DigEffect extends SpellAbilityEffect {
}
}
}
if (combatChanged) {
game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged());
}
//table trigger there
table.triggerChangesZoneAll(game);
counterTable.triggerCountersPutAll(game);

View File

@@ -1,27 +1,21 @@
package forge.game.ability.effects;
import forge.game.Game;
import forge.game.GameEntity;
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.combat.Combat;
import forge.game.event.GameEventCombatChanged;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.MyRandom;
import forge.util.Localizer;
import forge.util.collect.FCollectionView;
import java.util.*;
import com.google.common.collect.Maps;
public class DigUntilEffect extends SpellAbilityEffect {
/* (non-Javadoc)
@@ -116,6 +110,7 @@ public class DigUntilEffect extends SpellAbilityEffect {
final boolean optionalFound = sa.hasParam("OptionalFoundMove");
CardZoneTable table = new CardZoneTable();
boolean combatChanged = false;
for (final Player p : getTargetPlayers(sa)) {
if (p == null) {
@@ -178,23 +173,8 @@ public class DigUntilEffect extends SpellAbilityEffect {
if (sa.hasParam("Tapped")) {
c.setTapped(true);
}
if (sa.hasParam("Attacking")) {
final Combat combat = game.getCombat();
if (null != combat) {
final FCollectionView<GameEntity> e = combat.getDefenders();
Map<String, Object> params = Maps.newHashMap();
params.put("Attacker", c);
final GameEntity defender = sa.getActivatingPlayer().getController().chooseSingleEntityForEffect(e, sa,
Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(c.getName())), params);
if (defender != null) {
combat.addAttacker(c, defender);
combat.getBandOfAttacker(c).setBlocked(false);
game.fireEvent(new GameEventCombatChanged());
}
}
if (addToCombat(c, c.getController(), sa, "Attacking", "Blocking")) {
combatChanged = true;
}
} else if (sa.hasParam("NoMoveFound") && foundDest.equals(ZoneType.Library)) {
//Don't do anything
@@ -268,6 +248,10 @@ public class DigUntilEffect extends SpellAbilityEffect {
}
} // end foreach player
}
if (combatChanged) {
game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged());
}
table.triggerChangesZoneAll(game);
} // end resolve

View File

@@ -2,13 +2,11 @@ package forge.game.ability.effects;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.mutable.MutableBoolean;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import forge.GameCommand;
@@ -22,14 +20,10 @@ import forge.game.card.CardCollection;
import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable;
import forge.game.card.token.TokenInfo;
import forge.game.combat.Combat;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.Localizer;
import forge.util.collect.FCollectionView;
public abstract class TokenEffectBase extends SpellAbilityEffect {
@@ -80,7 +74,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
addSelfTrigger(sa, sa.getParam("AtEOTTrig"), c);
}
if (addTokenToCombat(game, c, tok.getController(), sa, host)) {
if (addToCombat(c, tok.getController(), sa, "TokenAttacking", "TokenBlocking")) {
combatChanged.setTrue();
}
@@ -115,58 +109,6 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
return allTokens;
}
private boolean addTokenToCombat(Game game, Card c, Player controller, SpellAbility sa, Card host) {
if (!game.getPhaseHandler().inCombat()) {
return false;
}
boolean combatChanged = false;
final Combat combat = game.getCombat();
if (sa.hasParam("TokenAttacking") && combat.getAttackingPlayer().equals(controller)) {
String attacking = sa.getParam("TokenAttacking");
GameEntity defender = null;
FCollectionView<GameEntity> defs = null;
if ("True".equalsIgnoreCase(attacking)) {
defs = combat.getDefenders();
} else if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
Player defendingPlayer = Iterables.getFirst(AbilityUtils.getDefinedPlayers(host, attacking, sa), null);
if (defendingPlayer != null) {
defs = game.getCombat().getDefendersControlledBy(defendingPlayer);
}
} else {
defs = AbilityUtils.getDefinedEntities(host, attacking, sa);
}
if (defs != null) {
Map<String, Object> params = Maps.newHashMap();
params.put("Attacker", c);
defender = controller.getController().chooseSingleEntityForEffect(defs, sa,
Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(c.getName())), false, params);
}
if (defender != null) {
combat.addAttacker(c, defender);
combatChanged = true;
}
}
if (sa.hasParam("TokenBlocking")) {
final Card attacker = Iterables.getFirst(AbilityUtils.getDefinedCards(host, sa.getParam("TokenBlocking"), sa), null);
if (attacker != null) {
final boolean wasBlocked = combat.isBlocked(attacker);
combat.addBlocker(attacker, c);
combat.orderAttackersForDamageAssignment(c);
// Run triggers for new blocker and add it to damage assignment order
if (!wasBlocked) {
combat.setBlocked(attacker, true);
combat.addBlockerToDamageAssignmentOrder(attacker, c);
}
combatChanged = true;
}
}
return combatChanged;
}
private boolean attachTokenTo(Card tok, SpellAbility sa) {
final Card host = sa.getHostCard();
final Game game = host.getGame();

View File

@@ -4,6 +4,5 @@ Types:Legendary Creature Human Cleric
PT:2/2
K:Flying
T:Mode$ Attacks | ValidCard$ Card.Self | Attacked$ Opponent | Execute$ TrigChange | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME attacks an opponent, you may put an Angel, Demon, or Dragon creature card from your hand onto the battlefield tapped and attacking that opponent.
SVar:TrigChange:DB$ChangeZone | Origin$ Hand | Destination$ Battlefield | ChangeType$ Creature.Angel+YouCtrl,Creature.Demon+YouCtrl,Creature.Dragon+YouCtrl | Tapped$ True | Attacking$ True | DefinedDefender$ TriggeredDefender
SVar:Picture:http://www.wizards.com/global/images/magic/general/kaalia_of_the_vast.jpg
SVar:TrigChange:DB$ChangeZone | Origin$ Hand | Destination$ Battlefield | ChangeType$ Creature.Angel+YouCtrl,Creature.Demon+YouCtrl,Creature.Dragon+YouCtrl | Tapped$ True | Attacking$ TriggeredDefender
Oracle:Flying\nWhenever Kaalia of the Vast attacks an opponent, you may put an Angel, Demon, or Dragon creature card from your hand onto the battlefield tapped and attacking that opponent.