Merge branch 'propertyfix' into 'master'

ThisTargetedController

Closes #1909

See merge request core-developers/forge!4974
This commit is contained in:
Michael Kamensky
2021-07-10 03:42:11 +00:00
5 changed files with 71 additions and 70 deletions

View File

@@ -1,6 +1,7 @@
package forge.game.ability; package forge.game.ability;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@@ -131,7 +132,7 @@ public class AbilityUtils {
} }
else if (defined.equals("EffectSource")) { else if (defined.equals("EffectSource")) {
if (hostCard.isEmblem() || hostCard.getType().hasSubtype("Effect")) { if (hostCard.isEmblem() || hostCard.getType().hasSubtype("Effect")) {
c = AbilityUtils.findEffectRoot(hostCard); c = findEffectRoot(hostCard);
} }
} }
else if (defined.equals("Equipped")) { else if (defined.equals("Equipped")) {
@@ -510,13 +511,13 @@ public class AbilityUtils {
} }
// modify amount string for text changes // modify amount string for text changes
calcX[1] = AbilityUtils.applyAbilityTextChangeEffects(calcX[1], ability); calcX[1] = applyAbilityTextChangeEffects(calcX[1], ability);
Integer val = null; Integer val = null;
if (calcX[0].startsWith("Count")) { if (calcX[0].startsWith("Count")) {
val = AbilityUtils.xCount(card, calcX[1], ability); val = xCount(card, calcX[1], ability);
} else if (calcX[0].startsWith("Number")) { } else if (calcX[0].startsWith("Number")) {
val = AbilityUtils.xCount(card, svarval, ability); val = xCount(card, svarval, ability);
} else if (calcX[0].startsWith("SVar")) { } else if (calcX[0].startsWith("SVar")) {
final String[] l = calcX[1].split("/"); final String[] l = calcX[1].split("/");
final String m = CardFactoryUtil.extractOperators(calcX[1]); final String m = CardFactoryUtil.extractOperators(calcX[1]);
@@ -683,21 +684,15 @@ public class AbilityUtils {
val = objectXCount(objects, calcX[1], card, ability); val = objectXCount(objects, calcX[1], card, ability);
} }
else if (calcX[0].startsWith("TargetedController")) { else if (calcX[0].startsWith("TargetedController")) {
final List<Player> players = new ArrayList<>(); final PlayerCollection players = new PlayerCollection();
final CardCollection list = getDefinedCards(card, "Targeted", sa); final CardCollection list = getDefinedCards(card, "Targeted", sa);
final List<SpellAbility> sas = AbilityUtils.getDefinedSpellAbilities(card, "Targeted", sa); final List<SpellAbility> sas = getDefinedSpellAbilities(card, "Targeted", sa);
for (final Card c : list) { for (final Card c : list) {
final Player p = c.getController(); players.add(c.getController());
if (!players.contains(p)) {
players.add(p);
}
} }
for (final SpellAbility s : sas) { for (final SpellAbility s : sas) {
final Player p = s.getHostCard().getController(); players.add(s.getHostCard().getController());
if (!players.contains(p)) {
players.add(p);
}
} }
val = playerXCount(players, calcX[1], card, ability); val = playerXCount(players, calcX[1], card, ability);
} }
@@ -1052,6 +1047,14 @@ public class AbilityUtils {
players.add(((SpellAbility)sa).getActivatingPlayer()); players.add(((SpellAbility)sa).getActivatingPlayer());
} }
} }
else if (defined.equals("ThisTargetedController")) {
for (final Card c : getDefinedCards(card, "ThisTargetedCard", sa)) {
players.add(c.getController());
}
for (final SpellAbility s : getDefinedSpellAbilities(card, "ThisTargeted", sa)) {
players.add(s.getActivatingPlayer());
}
}
else if (defined.equals("ParentTargetedController")) { else if (defined.equals("ParentTargetedController")) {
for (final Card c : getDefinedCards(card, "ParentTarget", sa)) { for (final Card c : getDefinedCards(card, "ParentTarget", sa)) {
players.add(c.getController()); players.add(c.getController());
@@ -1304,22 +1307,29 @@ public class AbilityUtils {
else if (defined.equals("Parent") && sa instanceof SpellAbility) { else if (defined.equals("Parent") && sa instanceof SpellAbility) {
s = ((SpellAbility)sa).getRootAbility(); s = ((SpellAbility)sa).getRootAbility();
} }
else if (defined.equals("Targeted") && sa instanceof SpellAbility) { else if (defined.equals("Remembered")) {
for (TargetChoices tc : ((SpellAbility)sa).getAllTargetChoices()) { for (final Object o : card.getRemembered()) {
for (SpellAbility targetSpell : tc.getTargetSpells()) { if (o instanceof Card) {
SpellAbilityStackInstance stackInstance = game.getStack().getInstanceFromSpellAbility(targetSpell); final Card rem = (Card) o;
if (stackInstance != null) { sas.addAll(game.getCardState(rem).getSpellAbilities());
SpellAbility instanceSA = stackInstance.getSpellAbility(true); } else if (o instanceof SpellAbility) {
if (instanceSA != null) { sas.add((SpellAbility) o);
sas.add(instanceSA);
}
}
else {
sas.add(targetSpell);
}
} }
} }
} }
else if (defined.equals("Imprinted")) {
for (final Card imp : card.getImprintedCards()) {
sas.addAll(imp.getSpellAbilities());
}
}
else if (defined.equals("EffectSource")) {
if (card.getEffectSourceAbility() != null) {
sas.add(card.getEffectSourceAbility().getRootAbility());
}
}
else if (defined.equals("SourceFirstSpell")) {
sas.add(card.getFirstSpellAbility());
}
else if (defined.startsWith("Triggered") && sa instanceof SpellAbility) { else if (defined.startsWith("Triggered") && sa instanceof SpellAbility) {
final SpellAbility root = ((SpellAbility)sa).getRootAbility(); final SpellAbility root = ((SpellAbility)sa).getRootAbility();
@@ -1345,29 +1355,23 @@ public class AbilityUtils {
s = ((SpellAbilityStackInstance) o).getSpellAbility(true); s = ((SpellAbilityStackInstance) o).getSpellAbility(true);
} }
} }
else if (defined.equals("Remembered")) { else if (defined.endsWith("Targeted") && sa instanceof SpellAbility) {
for (final Object o : card.getRemembered()) { final List<TargetChoices> targets = defined.startsWith("This") ? Arrays.asList(((SpellAbility)sa).getTargets()) : ((SpellAbility)sa).getAllTargetChoices();
if (o instanceof Card) { for (TargetChoices tc : targets) {
final Card rem = (Card) o; for (SpellAbility targetSpell : tc.getTargetSpells()) {
sas.addAll(game.getCardState(rem).getSpellAbilities()); SpellAbilityStackInstance stackInstance = game.getStack().getInstanceFromSpellAbility(targetSpell);
} else if (o instanceof SpellAbility) { if (stackInstance != null) {
sas.add((SpellAbility) o); SpellAbility instanceSA = stackInstance.getSpellAbility(true);
if (instanceSA != null) {
sas.add(instanceSA);
}
}
else {
sas.add(targetSpell);
}
} }
} }
} }
else if (defined.equals("Imprinted")) {
for (final Card imp : card.getImprintedCards()) {
sas.addAll(imp.getSpellAbilities());
}
}
else if (defined.equals("EffectSource")) {
if (card.getEffectSourceAbility() != null) {
sas.add(card.getEffectSourceAbility().getRootAbility());
}
}
else if (defined.equals("SourceFirstSpell")) {
sas.add(card.getFirstSpellAbility());
}
if (s != null) { if (s != null) {
sas.add(s); sas.add(s);
@@ -1503,7 +1507,7 @@ public class AbilityUtils {
cost = new Cost(new ManaCost(new ManaCostParser(String.valueOf(source.getChosenNumber()))), true); cost = new Cost(new ManaCost(new ManaCostParser(String.valueOf(source.getChosenNumber()))), true);
} }
else if (unlessCost.startsWith("DefinedCost")) { else if (unlessCost.startsWith("DefinedCost")) {
CardCollection definedCards = AbilityUtils.getDefinedCards(sa.getHostCard(), unlessCost.split("_")[1], sa); CardCollection definedCards = getDefinedCards(sa.getHostCard(), unlessCost.split("_")[1], sa);
if (definedCards.isEmpty()) { if (definedCards.isEmpty()) {
sa.resolve(); sa.resolve();
resolveSubAbilities(sa, game); resolveSubAbilities(sa, game);
@@ -1523,7 +1527,7 @@ public class AbilityUtils {
cost = new Cost(newCost.toManaCost(), true); cost = new Cost(newCost.toManaCost(), true);
} }
else if (unlessCost.startsWith("DefinedSACost")) { else if (unlessCost.startsWith("DefinedSACost")) {
FCollection<SpellAbility> definedSAs = AbilityUtils.getDefinedSpellAbilities(sa.getHostCard(), unlessCost.split("_")[1], sa); FCollection<SpellAbility> definedSAs = getDefinedSpellAbilities(sa.getHostCard(), unlessCost.split("_")[1], sa);
if (definedSAs.isEmpty()) { if (definedSAs.isEmpty()) {
sa.resolve(); sa.resolve();
resolveSubAbilities(sa, game); resolveSubAbilities(sa, game);
@@ -1654,7 +1658,7 @@ public class AbilityUtils {
* @return a int. * @return a int.
*/ */
public static int xCount(final Card c, final String s, final CardTraitBase ctb) { public static int xCount(final Card c, final String s, final CardTraitBase ctb) {
final String s2 = AbilityUtils.applyAbilityTextChangeEffects(s, ctb); final String s2 = applyAbilityTextChangeEffects(s, ctb);
final String[] l = s2.split("/"); final String[] l = s2.split("/");
final String expr = CardFactoryUtil.extractOperators(s2); final String expr = CardFactoryUtil.extractOperators(s2);
final Player player = ctb == null ? null : ctb instanceof SpellAbility ? ((SpellAbility)ctb).getActivatingPlayer() : ctb.getHostCard().getController(); final Player player = ctb == null ? null : ctb instanceof SpellAbility ? ((SpellAbility)ctb).getActivatingPlayer() : ctb.getHostCard().getController();
@@ -1770,7 +1774,7 @@ public class AbilityUtils {
//Count$HasNumChosenColors.<DefinedCards related to spellability> //Count$HasNumChosenColors.<DefinedCards related to spellability>
if (sq[0].contains("HasNumChosenColors")) { if (sq[0].contains("HasNumChosenColors")) {
int sum = 0; int sum = 0;
for (Card card : AbilityUtils.getDefinedCards(c, sq[1], sa)) { for (Card card : getDefinedCards(c, sq[1], sa)) {
sum += CardUtil.getColors(card).getSharedColors(ColorSet.fromNames(c.getChosenColors())).countColors(); sum += CardUtil.getColors(card).getSharedColors(ColorSet.fromNames(c.getChosenColors())).countColors();
} }
return sum; return sum;
@@ -1876,7 +1880,7 @@ public class AbilityUtils {
// Not optimal but since xCount doesn't take SAs, we need to replicate while we have it // Not optimal but since xCount doesn't take SAs, we need to replicate while we have it
// Probably would be best if xCount took an optional SA to use in these circumstances // Probably would be best if xCount took an optional SA to use in these circumstances
if (sq[0].contains("TargetedLifeTotal")) { if (sq[0].contains("TargetedLifeTotal")) {
for (Player tgtP : AbilityUtils.getDefinedPlayers(c, "TargetedPlayer", ctb)) { for (Player tgtP : getDefinedPlayers(c, "TargetedPlayer", ctb)) {
return doXMath(tgtP.getLife(), expr, c, ctb); return doXMath(tgtP.getLife(), expr, c, ctb);
} }
} }
@@ -1907,7 +1911,7 @@ public class AbilityUtils {
} // end ctb != null } // end ctb != null
if (sq[0].contains("OppsAtLifeTotal")) { if (sq[0].contains("OppsAtLifeTotal")) {
final int lifeTotal = AbilityUtils.calculateAmount(c, sq[1], ctb); final int lifeTotal = calculateAmount(c, sq[1], ctb);
int number = 0; int number = 0;
for (final Player opp : player.getOpponents()) { for (final Player opp : player.getOpponents()) {
if (opp.getLife() == lifeTotal) { if (opp.getLife() == lifeTotal) {
@@ -1920,7 +1924,7 @@ public class AbilityUtils {
//Count$SearchedLibrary.<DefinedPlayer> //Count$SearchedLibrary.<DefinedPlayer>
if (sq[0].contains("SearchedLibrary")) { if (sq[0].contains("SearchedLibrary")) {
int sum = 0; int sum = 0;
for (Player p : AbilityUtils.getDefinedPlayers(c, sq[1], ctb)) { for (Player p : getDefinedPlayers(c, sq[1], ctb)) {
sum += p.getLibrarySearched(); sum += p.getLibrarySearched();
} }
return doXMath(sum, expr, c, ctb); return doXMath(sum, expr, c, ctb);
@@ -2021,7 +2025,7 @@ public class AbilityUtils {
if (sq[0].startsWith("DamageDoneByPlayerThisTurn")) { if (sq[0].startsWith("DamageDoneByPlayerThisTurn")) {
int sum = 0; int sum = 0;
for (Player p : AbilityUtils.getDefinedPlayers(c, sq[1], ctb)) { for (Player p : getDefinedPlayers(c, sq[1], ctb)) {
sum += c.getReceivedDamageByPlayerThisTurn(p); sum += c.getReceivedDamageByPlayerThisTurn(p);
} }
return doXMath(sum, expr, c, ctb); return doXMath(sum, expr, c, ctb);
@@ -2047,8 +2051,8 @@ public class AbilityUtils {
final String type = sq[0].split("_")[1]; final String type = sq[0].split("_")[1];
boolean found = false; boolean found = false;
if (c.getCastFrom() != null && c.getCastSA() != null) { if (c.getCastFrom() != null && c.getCastSA() != null) {
int revealed = AbilityUtils.calculateAmount(c, "Revealed$Valid " + type, c.getCastSA()); int revealed = calculateAmount(c, "Revealed$Valid " + type, c.getCastSA());
int ctrl = AbilityUtils.calculateAmount(c, "Count$Valid " + type + ".inZoneBattlefield+YouCtrl", c.getCastSA()); int ctrl = calculateAmount(c, "Count$Valid " + type + ".inZoneBattlefield+YouCtrl", c.getCastSA());
if (revealed + ctrl >= 1) { if (revealed + ctrl >= 1) {
found = true; found = true;
} }
@@ -2078,7 +2082,7 @@ public class AbilityUtils {
if (sq[0].startsWith("AttachedTo")) { if (sq[0].startsWith("AttachedTo")) {
final String[] k = l[0].split(" "); final String[] k = l[0].split(" ");
int sum = 0; int sum = 0;
for (Card card : AbilityUtils.getDefinedCards(c, k[1], ctb)) { for (Card card : getDefinedCards(c, k[1], ctb)) {
// Hateful Eidolon: the script uses LKI so that the attached cards have to be defined // Hateful Eidolon: the script uses LKI so that the attached cards have to be defined
// This card needs the spellability ("Auras You control", you refers to the activating player) // This card needs the spellability ("Auras You control", you refers to the activating player)
// CardFactoryUtils.xCount doesn't have the sa parameter, SVar:X:TriggeredCard$Valid <restriction> cannot handle this // CardFactoryUtils.xCount doesn't have the sa parameter, SVar:X:TriggeredCard$Valid <restriction> cannot handle this
@@ -2448,7 +2452,7 @@ public class AbilityUtils {
// Count$UrzaLands.<numHB>.<numNotHB> // Count$UrzaLands.<numHB>.<numNotHB>
if (sq[0].startsWith("UrzaLands")) { if (sq[0].startsWith("UrzaLands")) {
return doXMath(AbilityUtils.calculateAmount(c, sq[player.hasUrzaLands() ? 1 : 2], ctb), expr, c, ctb); return doXMath(calculateAmount(c, sq[player.hasUrzaLands() ? 1 : 2], ctb), expr, c, ctb);
} }
///////////////// /////////////////
@@ -2540,8 +2544,8 @@ public class AbilityUtils {
//Count$Random.<Min>.<Max> //Count$Random.<Min>.<Max>
if (sq[0].equals("Random")) { if (sq[0].equals("Random")) {
int min = AbilityUtils.calculateAmount(c, sq[1], ctb); int min = calculateAmount(c, sq[1], ctb);
int max = AbilityUtils.calculateAmount(c, sq[2], ctb); int max = calculateAmount(c, sq[2], ctb);
return MyRandom.getRandom().nextInt(1+max-min) + min; return MyRandom.getRandom().nextInt(1+max-min) + min;
} }
@@ -3048,7 +3052,7 @@ public class AbilityUtils {
secondaryNum = Integer.parseInt(s[1]); secondaryNum = Integer.parseInt(s[1]);
} }
} catch (final Exception e) { } catch (final Exception e) {
secondaryNum = AbilityUtils.calculateAmount(c, s[1], ctb); secondaryNum = calculateAmount(c, s[1], ctb);
} }
if (s[0].contains("Plus")) { if (s[0].contains("Plus")) {
@@ -3498,7 +3502,7 @@ public class AbilityUtils {
if (string.startsWith("FilterControlledBy")) { if (string.startsWith("FilterControlledBy")) {
final String pString = filter[0].substring(18); final String pString = filter[0].substring(18);
FCollectionView<Player> controllers = AbilityUtils.getDefinedPlayers(source, pString, ctb); FCollectionView<Player> controllers = getDefinedPlayers(source, pString, ctb);
filteredList = CardLists.filterControlledByAsList(filteredList, controllers); filteredList = CardLists.filterControlledByAsList(filteredList, controllers);
filteredString = TextUtil.fastReplace(filteredString, pString, ""); filteredString = TextUtil.fastReplace(filteredString, pString, "");
filteredString = TextUtil.fastReplace(filteredString, "FilterControlledBy_", ""); filteredString = TextUtil.fastReplace(filteredString, "FilterControlledBy_", "");
@@ -3603,14 +3607,14 @@ public class AbilityUtils {
// Count$InTargetedHand (targeted player's cards in hand) // Count$InTargetedHand (targeted player's cards in hand)
if (sq[0].contains("InTargetedHand")) { if (sq[0].contains("InTargetedHand")) {
for (Player tgtP : AbilityUtils.getDefinedPlayers(c, "TargetedPlayer", ctb)) { for (Player tgtP : getDefinedPlayers(c, "TargetedPlayer", ctb)) {
someCards.addAll(tgtP.getCardsIn(ZoneType.Hand)); someCards.addAll(tgtP.getCardsIn(ZoneType.Hand));
} }
} }
// Count$InTargetedHand (targeted player's cards in hand) // Count$InTargetedHand (targeted player's cards in hand)
if (sq[0].contains("InTargetedLibrary")) { if (sq[0].contains("InTargetedLibrary")) {
for (Player tgtP : AbilityUtils.getDefinedPlayers(c, "TargetedPlayer", ctb)) { for (Player tgtP : getDefinedPlayers(c, "TargetedPlayer", ctb)) {
someCards.addAll(tgtP.getCardsIn(ZoneType.Library)); someCards.addAll(tgtP.getCardsIn(ZoneType.Library));
} }
} }

View File

@@ -15,7 +15,6 @@ public class LifeLoseEffect extends SpellAbilityEffect {
*/ */
@Override @Override
protected String getStackDescription(SpellAbility sa) { protected String getStackDescription(SpellAbility sa) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("LifeAmount"), sa); final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("LifeAmount"), sa);

View File

@@ -2203,8 +2203,7 @@ public class Player extends GameEntity implements Comparable<Player> {
if (incR.length > 1) { if (incR.length > 1) {
final String excR = incR[1]; final String excR = incR[1];
final String[] exR = excR.split("\\+"); // Exclusive Restrictions final String[] exR = excR.split("\\+"); // Exclusive Restrictions are ...
// are ...
for (int j = 0; j < exR.length; j++) { for (int j = 0; j < exR.length; j++) {
if (!hasProperty(exR[j], sourceController, source, spellAbility)) { if (!hasProperty(exR[j], sourceController, source, spellAbility)) {
return false; return false;

View File

@@ -2,6 +2,6 @@ Name:Ram Through
ManaCost:1 G ManaCost:1 G
Types:Instant Types:Instant
A:SP$ Pump | Cost$ 1 G | ValidTgts$ Creature.YouCtrl | AILogic$ PowerDmg | TgtPrompt$ Select target creature you control | SubAbility$ SoulsDamage | StackDescription$ SpellDescription | SpellDescription$ Target creature you control deals damage equal to its power to target creature you don't control. If the creature you control has trample, excess damage is dealt to that creature's controller instead. A:SP$ Pump | Cost$ 1 G | ValidTgts$ Creature.YouCtrl | AILogic$ PowerDmg | TgtPrompt$ Select target creature you control | SubAbility$ SoulsDamage | StackDescription$ SpellDescription | SpellDescription$ Target creature you control deals damage equal to its power to target creature you don't control. If the creature you control has trample, excess damage is dealt to that creature's controller instead.
SVar:SoulsDamage:DB$ DealDamage | ValidTgts$ Creature.YouDontCtrl | AILogic$ PowerDmg | TgtPrompt$ Select target creature you don't control | NumDmg$ X | DamageSource$ ParentTarget | ExcessDamage$ TargetedController | ExcessDamageCondition$ Card.withTrample | StackDescription$ None SVar:SoulsDamage:DB$ DealDamage | ValidTgts$ Creature.YouDontCtrl | AILogic$ PowerDmg | TgtPrompt$ Select target creature you don't control | NumDmg$ X | DamageSource$ ParentTarget | ExcessDamage$ ThisTargetedController | ExcessDamageCondition$ Card.withTrample | StackDescription$ None
SVar:X:ParentTargeted$CardPower SVar:X:ParentTargeted$CardPower
Oracle:Target creature you control deals damage equal to its power to target creature you don't control. If the creature you control has trample, excess damage is dealt to that creature's controller instead. Oracle:Target creature you control deals damage equal to its power to target creature you don't control. If the creature you control has trample, excess damage is dealt to that creature's controller instead.

View File

@@ -10,4 +10,3 @@ SVar:DBLoseLife:DB$ LoseLife | ValidTgts$ Player.Opponent | TgtPrompt$ Select ta
SVar:DBGainLife:DB$ GainLife | LifeAmount$ 2 SVar:DBGainLife:DB$ GainLife | LifeAmount$ 2
DeckHas:Ability$LifeGain & Ability$Mill DeckHas:Ability$LifeGain & Ability$Mill
Oracle:Choose two —\n• Target player mills three cards, then you return a land card from your graveyard to your hand.\n• Destroy target noncreature, nonland permanent with mana value 2 or less.\n• Target creature gets -3/-1 until end of turn.\n• Target opponent loses 2 life and you gain 2 life. Oracle:Choose two —\n• Target player mills three cards, then you return a land card from your graveyard to your hand.\n• Destroy target noncreature, nonland permanent with mana value 2 or less.\n• Target creature gets -3/-1 until end of turn.\n• Target opponent loses 2 life and you gain 2 life.