mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28:01 +00:00
Resolve "ZNR: Charm Effect tweaks"
This commit is contained in:
committed by
Michael Kamensky
parent
2ce9f9a5b2
commit
daf70f7e60
@@ -2103,9 +2103,9 @@ public class AiController {
|
||||
return filterList(list, CardTraitPredicates.hasParam("AiLogic", logic));
|
||||
}
|
||||
|
||||
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num, boolean allowRepeat) {
|
||||
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, List<AbilitySub> possible, int min, int num, boolean allowRepeat) {
|
||||
if (simPicker != null) {
|
||||
return simPicker.chooseModeForAbility(sa, min, num, allowRepeat);
|
||||
return simPicker.chooseModeForAbility(sa, possible, min, num, allowRepeat);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,9 @@ public class ComputerUtil {
|
||||
sa = GameActionUtil.addExtraKeywordCost(sa);
|
||||
|
||||
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
||||
CharmEffect.makeChoices(sa);
|
||||
if (!CharmEffect.makeChoices(sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (chooseTargets != null) {
|
||||
chooseTargets.run();
|
||||
@@ -261,7 +263,9 @@ public class ComputerUtil {
|
||||
newSA.setHostCard(game.getAction().moveToStack(source, sa));
|
||||
|
||||
if (newSA.getApi() == ApiType.Charm && !newSA.isWrapper()) {
|
||||
CharmEffect.makeChoices(newSA);
|
||||
if (!CharmEffect.makeChoices(sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -800,8 +800,8 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num, boolean allowRepeat) {
|
||||
List<AbilitySub> result = brains.chooseModeForAbility(sa, min, num, allowRepeat);
|
||||
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, List<AbilitySub> possible, int min, int num, boolean allowRepeat) {
|
||||
List<AbilitySub> result = brains.chooseModeForAbility(sa, possible, min, num, allowRepeat);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -21,8 +23,11 @@ public class CharmAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
|
||||
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
final int num = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("CharmNum", "1"), sa);
|
||||
final int min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParamOrDefault("MinCharmNum", "1"), sa) : num;
|
||||
|
||||
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
|
||||
|
||||
// Reset the chosen list otherwise it will be locked in forever by earlier calls
|
||||
|
||||
@@ -9,7 +9,6 @@ import forge.ai.ability.ExploreAi;
|
||||
import forge.ai.simulation.GameStateEvaluator.Score;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.card.*;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
@@ -371,14 +370,12 @@ public class SpellAbilityPicker {
|
||||
return bestScore;
|
||||
}
|
||||
|
||||
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num, boolean allowRepeat) {
|
||||
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, List<AbilitySub> choices, int min, int num, boolean allowRepeat) {
|
||||
if (interceptor != null) {
|
||||
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
|
||||
return interceptor.chooseModesForAbility(choices, min, num, allowRepeat);
|
||||
}
|
||||
if (plan != null && plan.getSelectedDecision() != null && plan.getSelectedDecision().modes != null) {
|
||||
Plan.Decision decision = plan.getSelectedDecision();
|
||||
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
|
||||
// TODO: Validate that there's no discrepancies between choices and modes?
|
||||
List<AbilitySub> plannedModes = SpellAbilityChoicesIterator.getModeCombination(choices, decision.modes);
|
||||
if (plan.getSelectedDecision().targets != null) {
|
||||
|
||||
@@ -286,7 +286,7 @@ public final class AbilityFactory {
|
||||
|
||||
spellAbility.setDescription(sb.toString());
|
||||
} else if (api == ApiType.Charm) {
|
||||
spellAbility.setDescription(CharmEffect.makeSpellDescription(spellAbility));
|
||||
spellAbility.setDescription(CharmEffect.makeFormatedDescription(spellAbility));
|
||||
} else {
|
||||
spellAbility.setDescription("");
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@ public class AbilityUtils {
|
||||
if (StringUtils.isBlank(amount)) { return 0; }
|
||||
if (card == null) { return 0; }
|
||||
final Player player = card.getController();
|
||||
final Game game = player.getGame();
|
||||
final Game game = player == null ? card.getGame() : player.getGame();
|
||||
|
||||
// Strip and save sign for calculations
|
||||
final boolean startsWithPlus = amount.charAt(0) == '+';
|
||||
|
||||
@@ -20,12 +20,14 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
|
||||
public static List<AbilitySub> makePossibleOptions(final SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
Iterable<Object> restriction = null;
|
||||
List<String> restriction = null;
|
||||
|
||||
if (sa.hasParam("ChoiceRestriction")) {
|
||||
String rest = sa.getParam("ChoiceRestriction");
|
||||
if (rest.equals("NotRemembered")) {
|
||||
restriction = source.getRemembered();
|
||||
if (rest.equals("ThisGame")) {
|
||||
restriction = source.getChosenModesGame(sa);
|
||||
} else if (rest.equals("ThisTurn")) {
|
||||
restriction = source.getChosenModesTurn(sa);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,14 +35,9 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
List<AbilitySub> choices = Lists.newArrayList(sa.getAdditionalAbilityList("Choices"));
|
||||
if (restriction != null) {
|
||||
List<AbilitySub> toRemove = Lists.newArrayList();
|
||||
for (Object o : restriction) {
|
||||
if (o instanceof AbilitySub) {
|
||||
String abText = ((AbilitySub)o).getDescription();
|
||||
for (AbilitySub ch : choices) {
|
||||
if (ch.getDescription().equals(abText)) {
|
||||
toRemove.add(ch);
|
||||
}
|
||||
}
|
||||
for (AbilitySub ch : choices) {
|
||||
if (restriction.contains(ch.getDescription())) {
|
||||
toRemove.add(ch);
|
||||
}
|
||||
}
|
||||
choices.removeAll(toRemove);
|
||||
@@ -54,55 +51,24 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
return choices;
|
||||
}
|
||||
|
||||
public static String makeSpellDescription(SpellAbility sa) {
|
||||
int num = Integer.parseInt(sa.getParamOrDefault("CharmNum", "1"));
|
||||
int min = Integer.parseInt(sa.getParamOrDefault("MinCharmNum", String.valueOf(num)));
|
||||
boolean repeat = sa.hasParam("CanRepeatModes");
|
||||
boolean random = sa.hasParam("Random");
|
||||
boolean oppChooses = "Opponent".equals(sa.getParam("Chooser"));
|
||||
|
||||
List<AbilitySub> list = CharmEffect.makePossibleOptions(sa);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(sa.getCostDescription());
|
||||
sb.append(oppChooses ? "An opponent chooses " : "Choose ");
|
||||
|
||||
if (num == min) {
|
||||
sb.append(Lang.getNumeral(num));
|
||||
} else if (min == 0) {
|
||||
sb.append("up to ").append(Lang.getNumeral(num));
|
||||
} else {
|
||||
sb.append(Lang.getNumeral(min)).append(" or ").append(list.size() == 2 ? "both" : "more");
|
||||
}
|
||||
|
||||
if (random) {
|
||||
sb.append("at random.");
|
||||
}
|
||||
if (repeat) {
|
||||
sb.append(". You may choose the same mode more than once.");
|
||||
}
|
||||
sb.append(" - ");
|
||||
int i = 0;
|
||||
for (AbilitySub sub : list) {
|
||||
if (i > 0) {
|
||||
sb.append("; ");
|
||||
}
|
||||
sb.append(sub.getParam("SpellDescription"));
|
||||
++i;
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String makeFormatedDescription(SpellAbility sa) {
|
||||
int num = Integer.parseInt(sa.getParamOrDefault("CharmNum", "1"));
|
||||
int min = Integer.parseInt(sa.getParamOrDefault("MinCharmNum", String.valueOf(num)));
|
||||
Card source = sa.getHostCard();
|
||||
|
||||
List<AbilitySub> list = CharmEffect.makePossibleOptions(sa);
|
||||
final int num;
|
||||
// hotfix for Vindictive Lich when using getCardForUi
|
||||
if (source.getController() == null && sa.getParamOrDefault("CharmNum", "1").contains("MaxUniqueOpponents")) {
|
||||
// using getCardForUi game is not set, so can't guess max charm
|
||||
num = Integer.MAX_VALUE;
|
||||
} else {
|
||||
num = Math.min(AbilityUtils.calculateAmount(source, sa.getParamOrDefault("CharmNum", "1"), sa), list.size());
|
||||
}
|
||||
final int min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParamOrDefault("MinCharmNum", "1"), sa) : num;
|
||||
|
||||
boolean repeat = sa.hasParam("CanRepeatModes");
|
||||
boolean random = sa.hasParam("Random");
|
||||
boolean oppChooses = "Opponent".equals(sa.getParam("Chooser"));
|
||||
|
||||
List<AbilitySub> list = CharmEffect.makePossibleOptions(sa);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(sa.getCostDescription());
|
||||
sb.append(oppChooses ? "An opponent chooses " : "Choose ");
|
||||
@@ -117,8 +83,10 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
|
||||
if (sa.hasParam("ChoiceRestriction")) {
|
||||
String rest = sa.getParam("ChoiceRestriction");
|
||||
if (rest.equals("NotRemembered")) {
|
||||
if (rest.equals("ThisGame")) {
|
||||
sb.append(" that hasn't been chosen");
|
||||
} else if (rest.equals("ThisTurn")) {
|
||||
sb.append(" that hasn't been chosen this turn");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,24 +131,30 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
//this resets all previous choices
|
||||
sa.setSubAbility(null);
|
||||
|
||||
List<AbilitySub> choices = makePossibleOptions(sa);
|
||||
|
||||
// Entwine does use all Choices
|
||||
if (sa.isEntwine()) {
|
||||
chainAbilities(sa, makePossibleOptions(sa));
|
||||
return true;
|
||||
}
|
||||
|
||||
final int num = sa.hasParam("CharmNumOnResolve") ?
|
||||
AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CharmNumOnResolve"), sa)
|
||||
: Integer.parseInt(sa.getParamOrDefault("CharmNum", "1"));
|
||||
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
|
||||
|
||||
if (sa.hasParam("Random")) {
|
||||
chainAbilities(sa, Aggregates.random(makePossibleOptions(sa), num));
|
||||
chainAbilities(sa, choices);
|
||||
return true;
|
||||
}
|
||||
|
||||
Card source = sa.getHostCard();
|
||||
Player activator = sa.getActivatingPlayer();
|
||||
|
||||
final int num = Math.min(AbilityUtils.calculateAmount(source, sa.getParamOrDefault("CharmNum", "1"), sa), choices.size());
|
||||
final int min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParamOrDefault("MinCharmNum", "1"), sa) : num;
|
||||
|
||||
// if the amount of choices is smaller than min then they can't be chosen
|
||||
if (min > choices.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.hasParam("Random")) {
|
||||
chainAbilities(sa, Aggregates.random(choices, num));
|
||||
return true;
|
||||
}
|
||||
|
||||
Player chooser = sa.getActivatingPlayer();
|
||||
|
||||
if (sa.hasParam("Chooser")) {
|
||||
@@ -193,9 +167,16 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
source.setChosenPlayer(chooser);
|
||||
}
|
||||
|
||||
List<AbilitySub> chosen = chooser.getController().chooseModeForAbility(sa, min, num, sa.hasParam("CanRepeatModes"));
|
||||
List<AbilitySub> chosen = chooser.getController().chooseModeForAbility(sa, choices, min, num, sa.hasParam("CanRepeatModes"));
|
||||
chainAbilities(sa, chosen);
|
||||
return chosen != null && !chosen.isEmpty();
|
||||
|
||||
// trigger without chosen modes are removed from stack
|
||||
if (sa.isTrigger()) {
|
||||
return chosen != null && !chosen.isEmpty();
|
||||
}
|
||||
|
||||
// for spells and activated abilities it is possible to chose zero if minCharmNum allows it
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void chainAbilities(SpellAbility sa, List<AbilitySub> chosen) {
|
||||
|
||||
@@ -34,7 +34,6 @@ import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostSacrifice;
|
||||
@@ -280,6 +279,11 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
private final Table<SpellAbility, StaticAbility, Integer> numberTurnActivationsStatic = HashBasedTable.create();
|
||||
private final Table<SpellAbility, StaticAbility, Integer> numberGameActivationsStatic = HashBasedTable.create();
|
||||
|
||||
private final Map<SpellAbility, List<String>> chosenModesTurn = Maps.newHashMap();
|
||||
private final Map<SpellAbility, List<String>> chosenModesGame = Maps.newHashMap();
|
||||
|
||||
private final Table<SpellAbility, StaticAbility, List<String>> chosenModesTurnStatic = HashBasedTable.create();
|
||||
private final Table<SpellAbility, StaticAbility, List<String>> chosenModesGameStatic = HashBasedTable.create();
|
||||
|
||||
// Enumeration for CMC request types
|
||||
public enum SplitCMCMode {
|
||||
@@ -2326,14 +2330,7 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
|
||||
private String formatSpellAbility(final SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final String elementText = sa.toString();
|
||||
|
||||
//Determine if a card has multiple choices, then format it in an easier to read list.
|
||||
if (ApiType.Charm.equals(sa.getApi())) {
|
||||
sb.append(CharmEffect.makeFormatedDescription(sa));
|
||||
} else {
|
||||
sb.append(elementText).append("\r\n");
|
||||
}
|
||||
sb.append(sa.toString()).append("\r\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -5916,6 +5913,7 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
clearBlockedThisTurn();
|
||||
resetMayPlayTurn();
|
||||
resetExtertedThisTurn();
|
||||
resetChosenModeTurn();
|
||||
}
|
||||
|
||||
public boolean hasETBTrigger(final boolean drawbackOnly) {
|
||||
@@ -6583,6 +6581,94 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
numberTurnActivationsStatic.clear();
|
||||
}
|
||||
|
||||
public List<String> getChosenModesTurn(SpellAbility ability) {
|
||||
SpellAbility original = null;
|
||||
SpellAbility root = ability.getRootAbility();
|
||||
|
||||
// because trigger spell abilities are copied, try to get original one
|
||||
if (root.isTrigger()) {
|
||||
original = root.getTrigger().getOverridingAbility();
|
||||
} else {
|
||||
original = ability.getOriginalAbility();
|
||||
if (original == null) {
|
||||
original = ability;
|
||||
}
|
||||
}
|
||||
|
||||
if (ability.getGrantorStatic() != null) {
|
||||
return chosenModesTurnStatic.get(original, ability.getGrantorStatic());
|
||||
}
|
||||
return chosenModesTurn.get(original);
|
||||
}
|
||||
public List<String> getChosenModesGame(SpellAbility ability) {
|
||||
SpellAbility original = null;
|
||||
SpellAbility root = ability.getRootAbility();
|
||||
|
||||
// because trigger spell abilities are copied, try to get original one
|
||||
if (root.isTrigger()) {
|
||||
original = root.getTrigger().getOverridingAbility();
|
||||
} else {
|
||||
original = ability.getOriginalAbility();
|
||||
if (original == null) {
|
||||
original = ability;
|
||||
}
|
||||
}
|
||||
|
||||
if (ability.getGrantorStatic() != null) {
|
||||
return chosenModesGameStatic.get(original, ability.getGrantorStatic());
|
||||
}
|
||||
return chosenModesGame.get(original);
|
||||
}
|
||||
|
||||
public void addChosenModes(SpellAbility ability, String mode) {
|
||||
SpellAbility original = null;
|
||||
SpellAbility root = ability.getRootAbility();
|
||||
|
||||
// because trigger spell abilities are copied, try to get original one
|
||||
if (root.isTrigger()) {
|
||||
original = root.getTrigger().getOverridingAbility();
|
||||
} else {
|
||||
original = ability.getOriginalAbility();
|
||||
if (original == null) {
|
||||
original = ability;
|
||||
}
|
||||
}
|
||||
|
||||
if (ability.getGrantorStatic() != null) {
|
||||
List<String> result = chosenModesTurnStatic.get(original, ability.getGrantorStatic());
|
||||
if (result == null) {
|
||||
result = Lists.newArrayList();
|
||||
chosenModesTurnStatic.put(original, ability.getGrantorStatic(), result);
|
||||
}
|
||||
result.add(mode);
|
||||
result = chosenModesGameStatic.get(original, ability.getGrantorStatic());
|
||||
if (result == null) {
|
||||
result = Lists.newArrayList();
|
||||
chosenModesGameStatic.put(original, ability.getGrantorStatic(), result);
|
||||
}
|
||||
result.add(mode);
|
||||
} else {
|
||||
List<String> result = chosenModesTurn.get(original);
|
||||
if (result == null) {
|
||||
result = Lists.newArrayList();
|
||||
chosenModesTurn.put(original, result);
|
||||
}
|
||||
result.add(mode);
|
||||
|
||||
result = chosenModesGame.get(original);
|
||||
if (result == null) {
|
||||
result = Lists.newArrayList();
|
||||
chosenModesGame.put(original, result);
|
||||
}
|
||||
result.add(mode);
|
||||
}
|
||||
}
|
||||
|
||||
public void resetChosenModeTurn() {
|
||||
chosenModesTurn.clear();
|
||||
chosenModesTurnStatic.clear();
|
||||
}
|
||||
|
||||
public int getPlaneswalkerAbilityActivated() {
|
||||
return planeswalkerAbilityActivated;
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ public abstract class PlayerController {
|
||||
public abstract boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call);
|
||||
public abstract Card chooseProtectionShield(GameEntity entityBeingDamaged, List<String> options, Map<String, Card> choiceMap);
|
||||
|
||||
public abstract List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num, boolean allowRepeat);
|
||||
public abstract List<AbilitySub> chooseModeForAbility(SpellAbility sa, List<AbilitySub> possible, int min, int num, boolean allowRepeat);
|
||||
|
||||
public abstract byte chooseColor(String message, SpellAbility sa, ColorSet colors);
|
||||
public abstract byte chooseColorAllowColorless(String message, Card c, ColorSet colors);
|
||||
|
||||
@@ -575,6 +575,8 @@ public class TriggerHandler {
|
||||
|
||||
sa.setStackDescription(sa.toString());
|
||||
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
||||
// need to be set for demonic pact to look for chosen modes
|
||||
sa.setTrigger(regtrig);
|
||||
if (!CharmEffect.makeChoices(sa)) {
|
||||
// 603.3c If no mode is chosen, the ability is removed from the stack.
|
||||
return;
|
||||
|
||||
@@ -246,9 +246,9 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
}
|
||||
}
|
||||
|
||||
if (sp.getApi() == ApiType.Charm && sp.hasParam("RememberChoice")) {
|
||||
if (sp.getApi() == ApiType.Charm && sp.hasParam("ChoiceRestriction")) {
|
||||
// Remember the Choice here for later handling
|
||||
source.addRemembered(sp.getSubAbility());
|
||||
source.addChosenModes(sp, sp.getSubAbility().getDescription());
|
||||
}
|
||||
|
||||
//cancel auto-pass for all opponents of activating player
|
||||
|
||||
@@ -446,7 +446,7 @@ public class PlayerControllerForTests extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num, boolean allowRepeat) {
|
||||
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, List<AbilitySub> possible, int min, int num, boolean allowRepeat) {
|
||||
throw new IllegalStateException("Erring on the side of caution here...");
|
||||
}
|
||||
|
||||
|
||||
@@ -5,14 +5,11 @@ R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplaceWith$
|
||||
SVar:DBChooseOpp:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | ChoiceTitle$ Choose an opponent to give control to: | AILogic$ Curse | SubAbility$ MoveToPlay
|
||||
SVar:MoveToPlay:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Battlefield | Defined$ ReplacedCard | GainControl$ True | NewController$ ChosenPlayer | SubAbility$ ClearRemembered
|
||||
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigCharm | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, ABILITY
|
||||
SVar:TrigCharm:DB$ Charm | Choices$ LifePact,DiscardPact,ZombiesPact | ChoiceRestriction$ NotRemembered | RememberChoice$ True | CharmNum$ 1
|
||||
SVar:LifePact:DB$ SetLife | Defined$ You | LifeAmount$ 4 | ChoiceName$ LifePact | SpellDescription$ Your life total becomes 4.
|
||||
SVar:DiscardPact:DB$ Discard | Defined$ You | Mode$ Hand | ChoiceName$ DiscardPact | SpellDescription$ Discard your hand.
|
||||
SVar:ZombiesPact:DB$ RepeatEach | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ MakeZombies | ChoiceName$ ZombiesPact | ChangeZoneTable$ True | SpellDescription$ Each opponent creates five 2/2 black Zombie creature tokens.
|
||||
SVar:TrigCharm:DB$ Charm | Choices$ LifePact,DiscardPact,ZombiesPact | ChoiceRestriction$ ThisGame | CharmNum$ 1
|
||||
SVar:LifePact:DB$ SetLife | Defined$ You | LifeAmount$ 4 | SpellDescription$ Your life total becomes 4.
|
||||
SVar:DiscardPact:DB$ Discard | Defined$ You | Mode$ Hand | SpellDescription$ Discard your hand.
|
||||
SVar:ZombiesPact:DB$ RepeatEach | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ MakeZombies | ChangeZoneTable$ True | SpellDescription$ Each opponent creates five 2/2 black Zombie creature tokens.
|
||||
SVar:MakeZombies:DB$ Token | LegacyImage$ b 2 2 zombie rna | TokenAmount$ 5 | TokenScript$ b_2_2_zombie | TokenOwner$ Remembered | SpellDescription$ Each opponent creates five 2/2 black Zombie creature tokens.
|
||||
# Clear RememberChoice just in case it's not getting cleared by Zone changes
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ ClearRemembered | Static$ True
|
||||
SVar:ClearRemembered:DB$ Cleanup | ClearRemembered$ True | ClearChosenPlayer$ True
|
||||
AI:RemoveDeck:Random
|
||||
DeckHas:Ability$Token
|
||||
Oracle:Captive Audience enters the battlefield under the control of an opponent of your choice.\nAt the beginning of your upkeep, choose one that hasn't been chosen —\n• Your life total becomes 4.\n• Discard your hand.\n• Each opponent creates five 2/2 black Zombie creature tokens.
|
||||
|
||||
@@ -2,15 +2,11 @@ Name:Demonic Pact
|
||||
ManaCost:2 B B
|
||||
Types:Enchantment
|
||||
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigCharm | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, ABILITY
|
||||
SVar:TrigCharm:DB$ Charm | Choices$ DrainPact,DiscardPact,DrawPact,DeathPact | ChoiceRestriction$ NotRemembered | RememberChoice$ True | CharmNum$ 1
|
||||
SVar:DrainPact:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 4 | SubAbility$ DBGainLife | ChoiceName$ DrainPact | SpellDescription$ CARDNAME deals 4 damage to any target and you gain 4 life.
|
||||
SVar:TrigCharm:DB$ Charm | Choices$ DrainPact,DiscardPact,DrawPact,DeathPact | ChoiceRestriction$ ThisGame | CharmNum$ 1
|
||||
SVar:DrainPact:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 4 | SubAbility$ DBGainLife | SpellDescription$ CARDNAME deals 4 damage to any target and you gain 4 life.
|
||||
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 4
|
||||
SVar:DiscardPact:DB$ Discard | ValidTgts$ Player | NumCards$ 2 | Mode$ TgtChoose | ChoiceName$ DiscardPact | SpellDescription$ Target player discards two cards.
|
||||
SVar:DrawPact:DB$ Draw | NumCards$ 2 | ChoiceName$ DrawPact | SpellDescription$ Draw two cards.
|
||||
SVar:DeathPact:DB$ LosesGame | Defined$ You | ChoiceName$ DeathPact | SpellDescription$ You lose the game.
|
||||
# Clear RememberChoice just in case it's not getting cleared by Zone changes
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ ClearRemembered | Static$ True
|
||||
SVar:ClearRemembered:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:DiscardPact:DB$ Discard | ValidTgts$ Player | NumCards$ 2 | Mode$ TgtChoose | SpellDescription$ Target player discards two cards.
|
||||
SVar:DrawPact:DB$ Draw | NumCards$ 2 | SpellDescription$ Draw two cards.
|
||||
SVar:DeathPact:DB$ LosesGame | Defined$ You | SpellDescription$ You lose the game.
|
||||
AI:RemoveDeck:All
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/demonic_pact.jpg
|
||||
Oracle:At the beginning of your upkeep, choose one that hasn't been chosen —\n• Demonic Pact deals 4 damage to any target and you gain 4 life.\n• Target opponent discards two cards.\n• Draw two cards.\n• You lose the game.
|
||||
|
||||
14
forge-gui/res/cardsfolder/i/inscription_of_abundance.txt
Normal file
14
forge-gui/res/cardsfolder/i/inscription_of_abundance.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
Name:Inscription of Abundance
|
||||
ManaCost:1 G
|
||||
Types:Instant
|
||||
K:Kicker:2 G
|
||||
A:SP$ Charm | Cost$ 1 G | MinCharmNum$ X | CharmNum$ Y | References$ X,Y | Choices$ DBPutCounter,DBGainLife,DBPump | AdditionalDescription$ If this spell was kicked, choose any number instead.
|
||||
SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 2 | SpellDescription$ Put two +1/+1 counters on target creature.
|
||||
SVar:DBGainLife:DB$ GainLife | ValidTgts$ Player | TgtPrompt$ Select target player | LifeAmount$ Z | References$ Z | SpellDescription$ Target player gains X life, where X is the greatest power among creatures they control.
|
||||
SVar:DBPump:DB$ Pump | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | AILogic$ Fight | SubAbility$ DBFight | SpellDescription$ Target creature you control fights target creature you don't control.
|
||||
SVar:DBFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature you don't control
|
||||
SVar:X:Count$Kicked.0.1
|
||||
SVar:Y:Count$Kicked.3.1
|
||||
SVar:Z:Count$GreatestPower_Creature.TargetedPlayerCtrl
|
||||
Oracle:Kicker {2}{G}\nChoose one. If this spell was kicked, choose any number instead.\n• Put two +1/+1 counters on target creature.\n• Target player gains X life, where X is the greatest power among creatures they control.\n• Target creature you control fights target creature you don't control.
|
||||
|
||||
14
forge-gui/res/cardsfolder/i/inscription_of_insight.txt
Normal file
14
forge-gui/res/cardsfolder/i/inscription_of_insight.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
Name:Inscription of Insight
|
||||
ManaCost:3 U
|
||||
Types:Sorcery
|
||||
K:Kicker:2 U U
|
||||
A:SP$ Charm | Cost$ 3 U | MinCharmNum$ X | CharmNum$ Y | References$ X,Y | Choices$ DBReturn,DBScry,DBToken | AdditionalDescription$ If this spell was kicked, choose any number instead.
|
||||
SVar:DBReturn:DB$ ChangeZone | TargetMin$ 0 | TargetMax$ 2 | ValidTgts$ Creature | TgtPrompt$ Select up to two target creatures | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return up to two target creatures to their owners' hands.
|
||||
SVar:DBScry:DB$ Scry | ScryNum$ 2 | SubAbility$ DBDraw | SpellDescription$ Scry 2, then draw two cards.
|
||||
SVar:DBDraw:DB$ Draw | NumCards$ 2
|
||||
SVar:DBToken:DB$ Token | ValidTgts$ Player | TgtPrompt$ Select target player | TokenAmount$ 1 | TokenScript$ u_x_x_illusion | TokenOwner$ TargetedPlayer | TokenPower$ Z | TokenToughness$ Z | References$ Z | SpellDescription$ Target player creates an X/X blue Illusion creature token, where X is the number of cards in their hand.
|
||||
SVar:X:Count$Kicked.0.1
|
||||
SVar:Y:Count$Kicked.3.1
|
||||
SVar:Z:TargetedPlayer$CardsInHand
|
||||
Oracle:Kicker {2}{U}{U}\nChoose one. If this spell was kicked, choose any number instead.\n• Return up to two target creatures to their owners’ hands.\n• Scry 2, then draw two cards.\n• Target player creates an X/X blue Illusion creature token, where X is the number of cards in their hand.
|
||||
|
||||
12
forge-gui/res/cardsfolder/i/inscription_of_ruin.txt
Normal file
12
forge-gui/res/cardsfolder/i/inscription_of_ruin.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
Name:Inscription of Ruin
|
||||
ManaCost:2 B
|
||||
Types:Sorcery
|
||||
K:Kicker:2 B B
|
||||
A:SP$ Charm | Cost$ 2 B | MinCharmNum$ X | CharmNum$ Y | References$ X,Y | Choices$ DBDiscard,DBReturn,DBDestroy | AdditionalDescription$ If this spell was kicked, choose any number instead.
|
||||
SVar:DBDiscard:DB$ Discard | ValidTgts$ Opponent | NumCards$ 2 | Mode$ TgtChoose | SpellDescription$ Target opponent discards two cards.
|
||||
SVar:DBReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Card.Creature+cmcLE2+YouOwn | TgtPrompt$ Select target creature card with converted mana cost 2 or less | SpellDescription$ Return target creature card with converted mana cost 2 or less from your graveyard to the battlefield.
|
||||
SVar:DBDestroy:DB$ Destroy | ValidTgts$ Creature.cmcLE3 | TgtPrompt$ Select target creature with converted mana cost 3 or less | SpellDescription$ Destroy target creature with converted mana cost 3 or less.
|
||||
SVar:X:Count$Kicked.0.1
|
||||
SVar:Y:Count$Kicked.3.1
|
||||
Oracle:Kicker {2}{B}{B}\nChoose one. If this spell was kicked, choose any number instead.\n• Target opponent discards two cards.\n• Return target creature card with converted mana cost 2 or less from your graveyard to the battlefield.\n• Destroy target creature with converted mana cost 3 or less.
|
||||
|
||||
@@ -4,9 +4,7 @@ Types:Creature Human Warrior
|
||||
PT:3/1
|
||||
S:Mode$ Continuous | Affected$ Creature.Coward | AddHiddenKeyword$ CantBlock Creature.Warrior:Warriors | Description$ Cowards can't block Warriors.
|
||||
SVar:PlayMain1:TRUE
|
||||
T:Mode$ TurnBegin | ValidPlayer$ Player | Static$ True | TriggerZones$ Battlefield | Execute$ CharmReset
|
||||
SVar:CharmReset:DB$ Cleanup | ClearRemembered$ True
|
||||
A:AB$ Charm | Cost$ 1 | Choices$ Pump,Coward,Trample | ChoiceRestriction$ NotRemembered | RememberChoice$ True | CharmNum$ 1
|
||||
A:AB$ Charm | Cost$ 1 | Choices$ Pump,Coward,Trample | ChoiceRestriction$ ThisTurn | CharmNum$ 1
|
||||
SVar:Pump:DB$ Pump | Defined$ Self | NumAtt$ 1 | NumDef$ 1 | SpellDescription$ CARDNAME gets +1/+1 until end of turn.
|
||||
SVar:Coward:DB$ Animate | ValidTgts$ Creature | TgtPrompt$ Select target creature | Types$ Coward | RemoveCreatureTypes$ True | SpellDescription$ Target creature becomes a Coward until end of turn.
|
||||
SVar:Trample:DB$ Pump | ValidTgts$ Warrior | TgtPrompt$ Select target Warrior | KW$ Trample | SpellDescription$ Target Warrior gains trample until end of turn.
|
||||
|
||||
@@ -3,11 +3,9 @@ ManaCost:3 B
|
||||
Types:Creature Zombie Wizard
|
||||
PT:4/1
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, ABILITY
|
||||
SVar:TrigCharm:DB$ Charm | MinCharmNum$ 1 | CharmNum$ 3 | CharmNumOnResolve$ MaxUniqueOpponents | Choices$ SacCreature,DiscardCards,LoseLife | References$ MaxUniqueOpponents | AdditionalDescription$ Each mode must target a different player.
|
||||
SVar:TrigCharm:DB$ Charm | MinCharmNum$ 1 | CharmNum$ MaxUniqueOpponents | Choices$ SacCreature,DiscardCards,LoseLife | References$ MaxUniqueOpponents | AdditionalDescription$ Each mode must target a different player.
|
||||
SVar:SacCreature:DB$ Sacrifice | ValidTgts$ Opponent | TargetUnique$ True | SacValid$ Creature | SacMessage$ Creature | SpellDescription$ Target opponent sacrifices a creature.
|
||||
SVar:DiscardCards:DB$ Discard | ValidTgts$ Opponent | TargetUnique$ True | NumCards$ 2 | Mode$ TgtChoose | SpellDescription$ Target opponent discards two cards.
|
||||
SVar:LoseLife:DB$ LoseLife | ValidTgts$ Opponent | TargetUnique$ True | LifeAmount$ 5 | SpellDescription$ Target opponent loses 5 life.
|
||||
SVar:MaxUniqueOpponents:PlayerCountOpponents$Amount
|
||||
#TODO: The AI is able to target the same player with multiple modes, usually all three. This should not happen.
|
||||
AI:RemoveDeck:All
|
||||
Oracle:When Vindictive Lich dies, choose one or more. Each mode must target a different player.\n• Target opponent sacrifices a creature.\n• Target opponent discards two cards.\n• Target opponent loses 5 life.
|
||||
|
||||
@@ -85,7 +85,9 @@ public class HumanPlay {
|
||||
}
|
||||
|
||||
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
||||
CharmEffect.makeChoices(sa);
|
||||
if (!CharmEffect.makeChoices(sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
sa = AbilityUtils.addSpliceEffects(sa);
|
||||
@@ -150,7 +152,9 @@ public class HumanPlay {
|
||||
|
||||
if (!sa.isCopied()) {
|
||||
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
||||
CharmEffect.makeChoices(sa);
|
||||
if (!CharmEffect.makeChoices(sa)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
sa = AbilityUtils.addSpliceEffects(sa);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
package forge.player;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileWriter;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.Range;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -62,7 +50,6 @@ import forge.game.PlanarDice;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
@@ -1591,14 +1578,13 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
||||
* spellability.SpellAbility, java.util.List, int, int)
|
||||
*/
|
||||
@Override
|
||||
public List<AbilitySub> chooseModeForAbility(final SpellAbility sa, final int min, final int num,
|
||||
public List<AbilitySub> chooseModeForAbility(final SpellAbility sa, List<AbilitySub> possible, final int min, final int num,
|
||||
boolean allowRepeat) {
|
||||
boolean trackerFrozen = game.getTracker().isFrozen();
|
||||
if (trackerFrozen) {
|
||||
// The view tracker needs to be unfrozen to update the SpellAbilityViews at this point, or it may crash
|
||||
game.getTracker().unfreeze();
|
||||
}
|
||||
final List<AbilitySub> possible = CharmEffect.makePossibleOptions(sa);
|
||||
Map<SpellAbilityView, AbilitySub> spellViewCache = SpellAbilityView.getMap(possible);
|
||||
if (trackerFrozen) {
|
||||
game.getTracker().freeze(); // refreeze if the tracker was frozen prior to this update
|
||||
|
||||
Reference in New Issue
Block a user