mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
Merge branch 'master' of https://github.com/jjayers99/forge-updates into master-local
This commit is contained in:
@@ -30,7 +30,6 @@ import com.google.common.collect.Lists;
|
|||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
import forge.ai.AiCardMemory.MemorySet;
|
import forge.ai.AiCardMemory.MemorySet;
|
||||||
import forge.ai.ability.ChooseGenericEffectAi;
|
|
||||||
import forge.ai.ability.ProtectAi;
|
import forge.ai.ability.ProtectAi;
|
||||||
import forge.ai.ability.TokenAi;
|
import forge.ai.ability.TokenAi;
|
||||||
import forge.card.CardStateName;
|
import forge.card.CardStateName;
|
||||||
@@ -290,7 +289,7 @@ public class ComputerUtil {
|
|||||||
SpellAbility newSA = sa.copyWithNoManaCost();
|
SpellAbility newSA = sa.copyWithNoManaCost();
|
||||||
newSA.setActivatingPlayer(ai, true);
|
newSA.setActivatingPlayer(ai, true);
|
||||||
|
|
||||||
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA) || !ComputerUtilMana.canPayManaCost(newSA, ai, 0, false)) {
|
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA, false) || !ComputerUtilMana.canPayManaCost(newSA, ai, 0, false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1176,7 +1175,7 @@ public class ComputerUtil {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cardState.hasKeyword(Keyword.RIOT) && ChooseGenericEffectAi.preferHasteForRiot(sa, ai)) {
|
if (cardState.hasKeyword(Keyword.RIOT) && SpecialAiLogic.preferHasteForRiot(sa, ai)) {
|
||||||
// Planning to choose Haste for Riot, so do this in Main 1
|
// Planning to choose Haste for Riot, so do this in Main 1
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -255,10 +255,25 @@ public class ComputerUtilAbility {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// deprioritize planar die roll marked with AIRollPlanarDieParams:LowPriority$ True
|
// deprioritize planar die roll marked with AIRollPlanarDieParams:LowPriority$ True
|
||||||
if (ApiType.RollPlanarDice == a.getApi() && a.getHostCard() != null && a.getHostCard().hasSVar("AIRollPlanarDieParams") && a.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) {
|
if (ApiType.RollPlanarDice == a.getApi() || ApiType.RollPlanarDice == b.getApi()) {
|
||||||
return 1;
|
Card hostCardForGame = a.getHostCard();
|
||||||
} else if (ApiType.RollPlanarDice == b.getApi() && b.getHostCard() != null && b.getHostCard().hasSVar("AIRollPlanarDieParams") && b.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) {
|
if (hostCardForGame == null) {
|
||||||
return -1;
|
if (b.getHostCard() != null) {
|
||||||
|
hostCardForGame = b.getHostCard();
|
||||||
|
} else {
|
||||||
|
return 0; // fallback if neither SA have a host card somehow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Game game = hostCardForGame.getGame();
|
||||||
|
for (Card c : game.getActivePlanes()) {
|
||||||
|
if (c.hasSVar("AIRollPlanarDieParams") && c.getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) {
|
||||||
|
if (ApiType.RollPlanarDice == a.getApi()) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// deprioritize pump spells with pure energy cost (can be activated last,
|
// deprioritize pump spells with pure energy cost (can be activated last,
|
||||||
|
|||||||
@@ -1257,7 +1257,7 @@ public class ComputerUtilCombat {
|
|||||||
sa.setActivatingPlayer(source.getController(), true);
|
sa.setActivatingPlayer(source.getController(), true);
|
||||||
|
|
||||||
if (sa.hasParam("Cost")) {
|
if (sa.hasParam("Cost")) {
|
||||||
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa, true)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1454,7 +1454,7 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (sa.hasParam("Cost")) {
|
if (sa.hasParam("Cost")) {
|
||||||
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa, true)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1488,7 +1488,7 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (sa.hasParam("Cost")) {
|
if (sa.hasParam("Cost")) {
|
||||||
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa, true)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -636,7 +636,7 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded, effect)
|
return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded, effect)
|
||||||
&& CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa);
|
&& CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa, effect);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView<Player> payers) {
|
public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView<Player> payers) {
|
||||||
@@ -760,6 +760,8 @@ public class ComputerUtilCost {
|
|||||||
int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList);
|
int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList);
|
||||||
|
|
||||||
return evalToken < evalCounter;
|
return evalToken < evalCounter;
|
||||||
|
} else if ("Riot".equals(aiLogic)) {
|
||||||
|
return !SpecialAiLogic.preferHasteForRiot(sa, payer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for shocklands and similar ETB replacement effects
|
// Check for shocklands and similar ETB replacement effects
|
||||||
|
|||||||
@@ -976,7 +976,7 @@ public class ComputerUtilMana {
|
|||||||
// Check if AI can still play this mana ability
|
// Check if AI can still play this mana ability
|
||||||
ma.setActivatingPlayer(ai, true);
|
ma.setActivatingPlayer(ai, true);
|
||||||
// if the AI can't pay the additional costs skip the mana ability
|
// if the AI can't pay the additional costs skip the mana ability
|
||||||
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) {
|
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma, false)) {
|
||||||
return false;
|
return false;
|
||||||
} else if (ma.getRestrictions() != null && ma.getRestrictions().isInstantSpeed()) {
|
} else if (ma.getRestrictions() != null && ma.getRestrictions().isInstantSpeed()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1517,7 +1517,7 @@ public class ComputerUtilMana {
|
|||||||
if (cost != null) {
|
if (cost != null) {
|
||||||
// if the AI can't pay the additional costs skip the mana ability
|
// if the AI can't pay the additional costs skip the mana ability
|
||||||
m.setActivatingPlayer(ai, true);
|
m.setActivatingPlayer(ai, true);
|
||||||
if (!CostPayment.canPayAdditionalCosts(m.getPayCosts(), m)) {
|
if (!CostPayment.canPayAdditionalCosts(m.getPayCosts(), m, false)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1063,6 +1063,9 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
|
final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
|
||||||
emptyAbility.setActivatingPlayer(player, true);
|
emptyAbility.setActivatingPlayer(player, true);
|
||||||
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
|
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
|
||||||
|
emptyAbility.setReplacingObjects(sa.getReplacingObjects());
|
||||||
|
emptyAbility.setTrigger(sa.getTrigger());
|
||||||
|
emptyAbility.setReplacementEffect(sa.getReplacementEffect());
|
||||||
emptyAbility.setSVars(sa.getSVars());
|
emptyAbility.setSVars(sa.getSVars());
|
||||||
emptyAbility.setCardState(sa.getCardState());
|
emptyAbility.setCardState(sa.getCardState());
|
||||||
emptyAbility.setXManaCostPaid(sa.getRootAbility().getXManaCostPaid());
|
emptyAbility.setXManaCostPaid(sa.getRootAbility().getXManaCostPaid());
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package forge.ai;
|
|||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
import forge.ai.ability.TokenAi;
|
import forge.ai.ability.TokenAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
@@ -13,6 +15,7 @@ import forge.game.phase.PhaseHandler;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.Expressions;
|
import forge.util.Expressions;
|
||||||
|
|
||||||
@@ -398,4 +401,48 @@ public class SpecialAiLogic {
|
|||||||
}
|
}
|
||||||
return willPlay;
|
return willPlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean preferHasteForRiot(SpellAbility sa, Player player) {
|
||||||
|
// returning true means preferring Haste, returning false means preferring a +1/+1 counter
|
||||||
|
final Card host = sa.getHostCard();
|
||||||
|
final Game game = host.getGame();
|
||||||
|
final Card copy = CardUtil.getLKICopy(host);
|
||||||
|
copy.setLastKnownZone(player.getZone(ZoneType.Battlefield));
|
||||||
|
|
||||||
|
// check state it would have on the battlefield
|
||||||
|
CardCollection preList = new CardCollection(copy);
|
||||||
|
game.getAction().checkStaticAbilities(false, Sets.newHashSet(copy), preList);
|
||||||
|
// reset again?
|
||||||
|
game.getAction().checkStaticAbilities(false);
|
||||||
|
|
||||||
|
// can't gain counters, use Haste
|
||||||
|
if (!copy.canReceiveCounters(CounterEnumType.P1P1)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// already has Haste, use counter
|
||||||
|
if (copy.hasKeyword(Keyword.HASTE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not AI turn
|
||||||
|
if (!game.getPhaseHandler().isPlayerTurn(player)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not before Combat
|
||||||
|
if (!game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO check other opponents too if able
|
||||||
|
final Player opp = player.getWeakestOpponent();
|
||||||
|
if (opp != null) {
|
||||||
|
// TODO add predict Combat Damage?
|
||||||
|
return opp.getLife() < copy.getNetPower();
|
||||||
|
}
|
||||||
|
|
||||||
|
// haste might not be good enough?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,6 @@ package forge.ai.ability;
|
|||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
@@ -11,9 +10,7 @@ import forge.ai.SpellApiToAi;
|
|||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.combat.Combat;
|
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.keyword.Keyword;
|
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -33,8 +30,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
if ("Khans".equals(aiLogic) || "Dragons".equals(aiLogic)) {
|
if ("Khans".equals(aiLogic) || "Dragons".equals(aiLogic)) {
|
||||||
return true;
|
return true;
|
||||||
} else if ("Riot".equals(aiLogic)) {
|
|
||||||
return true;
|
|
||||||
} else if ("Pump".equals(aiLogic) || "BestOption".equals(aiLogic)) {
|
} else if ("Pump".equals(aiLogic) || "BestOption".equals(aiLogic)) {
|
||||||
for (AbilitySub sb : sa.getAdditionalAbilityList("Choices")) {
|
for (AbilitySub sb : sa.getAdditionalAbilityList("Choices")) {
|
||||||
if (SpellApiToAi.Converter.get(sb.getApi()).canPlayAIWithSubs(ai, sb)) {
|
if (SpellApiToAi.Converter.get(sb.getApi()).canPlayAIWithSubs(ai, sb)) {
|
||||||
@@ -86,9 +81,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
|
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
|
||||||
Map<String, Object> params) {
|
Map<String, Object> params) {
|
||||||
Card host = sa.getHostCard();
|
Card host = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
|
||||||
final Game game = host.getGame();
|
final Game game = host.getGame();
|
||||||
final Combat combat = game.getCombat();
|
|
||||||
final String logic = sa.getParam("AILogic");
|
final String logic = sa.getParam("AILogic");
|
||||||
if (logic == null) {
|
if (logic == null) {
|
||||||
return spells.get(0);
|
return spells.get(0);
|
||||||
@@ -287,54 +280,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
if (!filtered.isEmpty()) {
|
if (!filtered.isEmpty()) {
|
||||||
return filtered.get(0);
|
return filtered.get(0);
|
||||||
}
|
}
|
||||||
} else if ("Riot".equals(logic)) {
|
|
||||||
SpellAbility counterSA = spells.get(0), hasteSA = spells.get(1);
|
|
||||||
return preferHasteForRiot(sa, player) ? hasteSA : counterSA;
|
|
||||||
}
|
}
|
||||||
return spells.get(0); // return first choice if no logic found
|
return spells.get(0); // return first choice if no logic found
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean preferHasteForRiot(SpellAbility sa, Player player) {
|
|
||||||
// returning true means preferring Haste, returning false means preferring a +1/+1 counter
|
|
||||||
final Card host = sa.getHostCard();
|
|
||||||
final Game game = host.getGame();
|
|
||||||
final Card copy = CardUtil.getLKICopy(host);
|
|
||||||
copy.setLastKnownZone(player.getZone(ZoneType.Battlefield));
|
|
||||||
|
|
||||||
// check state it would have on the battlefield
|
|
||||||
CardCollection preList = new CardCollection(copy);
|
|
||||||
game.getAction().checkStaticAbilities(false, Sets.newHashSet(copy), preList);
|
|
||||||
// reset again?
|
|
||||||
game.getAction().checkStaticAbilities(false);
|
|
||||||
|
|
||||||
// can't gain counters, use Haste
|
|
||||||
if (!copy.canReceiveCounters(CounterEnumType.P1P1)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// already has Haste, use counter
|
|
||||||
if (copy.hasKeyword(Keyword.HASTE)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// not AI turn
|
|
||||||
if (!game.getPhaseHandler().isPlayerTurn(player)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// not before Combat
|
|
||||||
if (!game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO check other opponents too if able
|
|
||||||
final Player opp = player.getWeakestOpponent();
|
|
||||||
if (opp != null) {
|
|
||||||
// TODO add predict Combat Damage?
|
|
||||||
return opp.getLife() < copy.getNetPower();
|
|
||||||
}
|
|
||||||
|
|
||||||
// haste might not be good enough?
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,16 @@ public class RollPlanarDiceAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
for (Card c : ai.getGame().getActivePlanes()) {
|
||||||
Card plane = sa.getHostCard();
|
if (willRollOnPlane(ai, c)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean willRollOnPlane(Player ai, Card plane) {
|
||||||
|
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
boolean decideToRoll = false;
|
boolean decideToRoll = false;
|
||||||
boolean rollInMain1 = false;
|
boolean rollInMain1 = false;
|
||||||
String modeName = "never";
|
String modeName = "never";
|
||||||
|
|||||||
@@ -253,7 +253,6 @@ public class Game {
|
|||||||
public List<Card> getLeftBattlefieldThisTurn() {
|
public List<Card> getLeftBattlefieldThisTurn() {
|
||||||
return leftBattlefieldThisTurn;
|
return leftBattlefieldThisTurn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Card> getLeftGraveyardThisTurn() {
|
public List<Card> getLeftGraveyardThisTurn() {
|
||||||
return leftGraveyardThisTurn;
|
return leftGraveyardThisTurn;
|
||||||
}
|
}
|
||||||
@@ -261,7 +260,6 @@ public class Game {
|
|||||||
public void clearLeftBattlefieldThisTurn() {
|
public void clearLeftBattlefieldThisTurn() {
|
||||||
leftBattlefieldThisTurn.clear();
|
leftBattlefieldThisTurn.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearLeftGraveyardThisTurn() {
|
public void clearLeftGraveyardThisTurn() {
|
||||||
leftGraveyardThisTurn.clear();
|
leftGraveyardThisTurn.clear();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -583,14 +583,19 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to apply any static effects to produce correct triggers
|
if (!table.isEmpty()) {
|
||||||
checkStaticAbilities();
|
// we don't want always trigger before counters are placed
|
||||||
|
game.getTriggerHandler().suppressMode(TriggerType.Always);
|
||||||
if (table.replaceCounterEffect(game, null, true, true, params)) {
|
// Need to apply any static effects to produce correct triggers
|
||||||
// update static abilities after etb counters have been placed
|
|
||||||
checkStaticAbilities();
|
checkStaticAbilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.replaceCounterEffect(game, null, true, true, params);
|
||||||
|
|
||||||
|
// update static abilities after etb counters have been placed
|
||||||
|
game.getTriggerHandler().clearSuppression(TriggerType.Always);
|
||||||
|
checkStaticAbilities();
|
||||||
|
|
||||||
// 400.7g try adding keyword back into card if it doesn't already have it
|
// 400.7g try adding keyword back into card if it doesn't already have it
|
||||||
if (zoneTo.is(ZoneType.Stack) && cause != null && cause.isSpell() && !cause.isIntrinsic() && c.equals(cause.getHostCard())) {
|
if (zoneTo.is(ZoneType.Stack) && cause != null && cause.isSpell() && !cause.isIntrinsic() && c.equals(cause.getHostCard())) {
|
||||||
if (cause.getKeyword() != null && !copied.getKeywords().contains(cause.getKeyword())) {
|
if (cause.getKeyword() != null && !copied.getKeywords().contains(cause.getKeyword())) {
|
||||||
@@ -2085,6 +2090,9 @@ public class GameAction {
|
|||||||
//<THIS CODE WILL WORK WITH PHASE = NULL>
|
//<THIS CODE WILL WORK WITH PHASE = NULL>
|
||||||
if (game.getRules().hasAppliedVariant(GameType.Planechase)) {
|
if (game.getRules().hasAppliedVariant(GameType.Planechase)) {
|
||||||
first.initPlane();
|
first.initPlane();
|
||||||
|
for (final Player p1 : game.getPlayers()) {
|
||||||
|
p1.createPlanechaseEffects(game);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
first = runOpeningHandActions(first);
|
first = runOpeningHandActions(first);
|
||||||
|
|||||||
@@ -87,6 +87,11 @@ public enum PlanarDice {
|
|||||||
runParams.put(AbilityKey.Result, Arrays.asList(0));
|
runParams.put(AbilityKey.Result, Arrays.asList(0));
|
||||||
roller.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDieOnce, runParams, false);
|
roller.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDieOnce, runParams, false);
|
||||||
|
|
||||||
|
if (res == Chaos) {
|
||||||
|
runParams = AbilityKey.mapFromPlayer(roller);
|
||||||
|
roller.getGame().getTriggerHandler().runTrigger(TriggerType.ChaosEnsues, runParams, false);
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1691,9 +1691,6 @@ public class AbilityUtils {
|
|||||||
|
|
||||||
if (root.isTrigger()) {
|
if (root.isTrigger()) {
|
||||||
Trigger t = root.getTrigger();
|
Trigger t = root.getTrigger();
|
||||||
if (t == null) {
|
|
||||||
return doXMath(0, expr, c, ctb);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImmediateTrigger should check for the Ability which created the trigger
|
// ImmediateTrigger should check for the Ability which created the trigger
|
||||||
if (t.getSpawningAbility() != null) {
|
if (t.getSpawningAbility() != null) {
|
||||||
@@ -2083,7 +2080,6 @@ public class AbilityUtils {
|
|||||||
for (Mana m : paidMana) {
|
for (Mana m : paidMana) {
|
||||||
if (m.toString().equals(type)) {
|
if (m.toString().equals(type)) {
|
||||||
count++;
|
count++;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return doXMath(count, expr, c, ctb);
|
return doXMath(count, expr, c, ctb);
|
||||||
@@ -2168,11 +2164,9 @@ public class AbilityUtils {
|
|||||||
if (sq[0].startsWith("RememberedSize")) {
|
if (sq[0].startsWith("RememberedSize")) {
|
||||||
return doXMath(c.getRememberedCount(), expr, c, ctb);
|
return doXMath(c.getRememberedCount(), expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sq[0].startsWith("ChosenSize")) {
|
if (sq[0].startsWith("ChosenSize")) {
|
||||||
return doXMath(c.getChosenCards().size(), expr, c, ctb);
|
return doXMath(c.getChosenCards().size(), expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sq[0].startsWith("ImprintedSize")) {
|
if (sq[0].startsWith("ImprintedSize")) {
|
||||||
return doXMath(c.getImprintedCards().size(), expr, c, ctb);
|
return doXMath(c.getImprintedCards().size(), expr, c, ctb);
|
||||||
}
|
}
|
||||||
@@ -2714,54 +2708,35 @@ public class AbilityUtils {
|
|||||||
return MyRandom.getRandom().nextInt(1+max-min) + min;
|
return MyRandom.getRandom().nextInt(1+max-min) + min;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String[] paidparts = l[0].split("\\$", 2);
|
||||||
|
Iterable<Card> someCards = null;
|
||||||
|
|
||||||
// Count$ThisTurnCast <Valid>
|
// Count$ThisTurnCast <Valid>
|
||||||
// Count$LastTurnCast <Valid>
|
// Count$LastTurnCast <Valid>
|
||||||
if (sq[0].startsWith("ThisTurnCast") || sq[0].startsWith("LastTurnCast")) {
|
if (sq[0].startsWith("ThisTurnCast") || sq[0].startsWith("LastTurnCast")) {
|
||||||
String[] paidparts = l[0].split("\\$", 2);
|
|
||||||
final String[] workingCopy = paidparts[0].split("_");
|
final String[] workingCopy = paidparts[0].split("_");
|
||||||
final String validFilter = workingCopy[1];
|
final String validFilter = workingCopy[1];
|
||||||
|
|
||||||
List<Card> res;
|
|
||||||
if (workingCopy[0].contains("This")) {
|
if (workingCopy[0].contains("This")) {
|
||||||
res = CardUtil.getThisTurnCast(validFilter, c, ctb, player);
|
someCards = CardUtil.getThisTurnCast(validFilter, c, ctb, player);
|
||||||
} else {
|
} else {
|
||||||
res = CardUtil.getLastTurnCast(validFilter, c, ctb, player);
|
someCards = CardUtil.getLastTurnCast(validFilter, c, ctb, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
int num;
|
|
||||||
if (paidparts.length > 1) {
|
|
||||||
num = handlePaid(res, paidparts[1], c, ctb);
|
|
||||||
} else {
|
|
||||||
num = res.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
return doXMath(num, expr, c, ctb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count$ThisTurnEntered <ZoneDestination> [from <ZoneOrigin>] <Valid>
|
// Count$ThisTurnEntered <ZoneDestination> [from <ZoneOrigin>] <Valid>
|
||||||
if (sq[0].startsWith("ThisTurnEntered")) {
|
if (sq[0].startsWith("ThisTurnEntered") || sq[0].startsWith("LastTurnEntered")) {
|
||||||
final String[] workingCopy = l[0].split("_", 5);
|
final String[] workingCopy = paidparts[0].split("_");
|
||||||
|
|
||||||
ZoneType destination = ZoneType.smartValueOf(workingCopy[1]);
|
ZoneType destination = ZoneType.smartValueOf(workingCopy[1]);
|
||||||
final boolean hasFrom = workingCopy[2].equals("from");
|
final boolean hasFrom = workingCopy[2].equals("from");
|
||||||
ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null;
|
ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null;
|
||||||
String validFilter = workingCopy[hasFrom ? 4 : 2];
|
String validFilter = workingCopy[hasFrom ? 4 : 2];
|
||||||
|
|
||||||
final List<Card> res = CardUtil.getThisTurnEntered(destination, origin, validFilter, c, ctb, player);
|
if (workingCopy[0].contains("This")) {
|
||||||
return doXMath(res.size(), expr, c, ctb);
|
someCards = CardUtil.getThisTurnEntered(destination, origin, validFilter, c, ctb, player);
|
||||||
}
|
} else {
|
||||||
|
someCards = CardUtil.getLastTurnEntered(destination, origin, validFilter, c, ctb, player);
|
||||||
// Count$LastTurnEntered <ZoneDestination> [from <ZoneOrigin>] <Valid>
|
}
|
||||||
if (sq[0].startsWith("LastTurnEntered")) {
|
|
||||||
final String[] workingCopy = l[0].split("_", 5);
|
|
||||||
|
|
||||||
ZoneType destination = ZoneType.smartValueOf(workingCopy[1]);
|
|
||||||
final boolean hasFrom = workingCopy[2].equals("from");
|
|
||||||
ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null;
|
|
||||||
String validFilter = workingCopy[hasFrom ? 4 : 2];
|
|
||||||
|
|
||||||
final List<Card> res = CardUtil.getLastTurnEntered(destination, origin, validFilter, c, ctb, player);
|
|
||||||
return doXMath(res.size(), expr, c, ctb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sq[0].startsWith("CountersAddedThisTurn")) {
|
if (sq[0].startsWith("CountersAddedThisTurn")) {
|
||||||
@@ -2779,7 +2754,6 @@ public class AbilityUtils {
|
|||||||
|
|
||||||
// count valid cards in any specified zone/s
|
// count valid cards in any specified zone/s
|
||||||
if (sq[0].startsWith("Valid")) {
|
if (sq[0].startsWith("Valid")) {
|
||||||
String[] paidparts = l[0].split("\\$", 2);
|
|
||||||
String[] lparts = paidparts[0].split(" ", 2);
|
String[] lparts = paidparts[0].split(" ", 2);
|
||||||
|
|
||||||
CardCollectionView cardsInZones = null;
|
CardCollectionView cardsInZones = null;
|
||||||
@@ -2805,13 +2779,7 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int cnt;
|
someCards = CardLists.getValidCards(cardsInZones, lparts[1], player, c, ctb);
|
||||||
if (paidparts.length > 1) {
|
|
||||||
cnt = handlePaid(CardLists.getValidCards(cardsInZones, lparts[1], player, c, ctb), paidparts[1], c, ctb);
|
|
||||||
} else {
|
|
||||||
cnt = CardLists.getValidCardCount(cardsInZones, lparts[1], player, c, ctb);
|
|
||||||
}
|
|
||||||
return doXMath(cnt, expr, c, ctb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sq[0].startsWith("MostCardName")) {
|
if (sq[0].startsWith("MostCardName")) {
|
||||||
@@ -2891,23 +2859,27 @@ public class AbilityUtils {
|
|||||||
return doXMath(Iterables.size(powers), expr, c, ctb);
|
return doXMath(Iterables.size(powers), expr, c, ctb);
|
||||||
}
|
}
|
||||||
if (sq[0].startsWith("DifferentCounterKinds_")) {
|
if (sq[0].startsWith("DifferentCounterKinds_")) {
|
||||||
final List<CounterType> kinds = Lists.newArrayList();
|
final Set<CounterType> kinds = Sets.newHashSet();
|
||||||
final String rest = l[0].substring(22);
|
final String rest = l[0].substring(22);
|
||||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb);
|
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb);
|
||||||
for (final Card card : list) {
|
for (final Card card : list) {
|
||||||
for (final Map.Entry<CounterType, Integer> map : card.getCounters().entrySet()) {
|
kinds.addAll(card.getCounters().keySet());
|
||||||
if (!kinds.contains(map.getKey())) {
|
|
||||||
kinds.add(map.getKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return doXMath(kinds.size(), expr, c, ctb);
|
return doXMath(kinds.size(), expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complex counting methods
|
// Complex counting methods
|
||||||
CardCollectionView someCards = getCardListForXCount(c, player, sq, ctb);
|
Integer num = null;
|
||||||
|
if (someCards == null) {
|
||||||
|
someCards = getCardListForXCount(c, player, sq, ctb);
|
||||||
|
} else if (paidparts.length > 1) {
|
||||||
|
num = handlePaid(someCards, paidparts[1], c, ctb);
|
||||||
|
}
|
||||||
|
if (num == null) {
|
||||||
|
num = Iterables.size(someCards);
|
||||||
|
}
|
||||||
|
|
||||||
return doXMath(someCards.size(), expr, c, ctb);
|
return doXMath(num, expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final void applyManaColorConversion(ManaConversionMatrix matrix, String conversion) {
|
public static final void applyManaColorConversion(ManaConversionMatrix matrix, String conversion) {
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
|||||||
tgtC.addChangedCardKeywords(kws, Lists.newArrayList(), false, tStamp, 0);
|
tgtC.addChangedCardKeywords(kws, Lists.newArrayList(), false, tStamp, 0);
|
||||||
game.fireEvent(new GameEventCardStatsChanged(tgtC));
|
game.fireEvent(new GameEventCardStatsChanged(tgtC));
|
||||||
}
|
}
|
||||||
if (hiddenKws.isEmpty()) {
|
if (!hiddenKws.isEmpty()) {
|
||||||
tgtC.addHiddenExtrinsicKeywords(tStamp, 0, hiddenKws);
|
tgtC.addHiddenExtrinsicKeywords(tStamp, 0, hiddenKws);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ import java.util.Arrays;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.GameCommand;
|
import forge.GameCommand;
|
||||||
|
import forge.card.CardType;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
@@ -129,8 +132,12 @@ public class ProtectEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<String> gainsKWList = Lists.newArrayList();
|
List<String> gainsKWList = Lists.newArrayList();
|
||||||
for (String color : gains) {
|
for (String type : gains) {
|
||||||
gainsKWList.add(TextUtil.concatWithSpace("Protection from", color));
|
if (isChoice && sa.getParam("Choices").equals("CardType")) {
|
||||||
|
gainsKWList.add("Protection:" + type);
|
||||||
|
} else {
|
||||||
|
gainsKWList.add(TextUtil.concatWithSpace("Protection from", type));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tgtCards.addAll(CardUtil.getRadiance(sa));
|
tgtCards.addAll(CardUtil.getRadiance(sa));
|
||||||
@@ -176,6 +183,8 @@ public class ProtectEffect extends SpellAbilityEffect {
|
|||||||
if (choices.contains("AnyColor")) {
|
if (choices.contains("AnyColor")) {
|
||||||
gains.addAll(MagicColor.Constant.ONLY_COLORS);
|
gains.addAll(MagicColor.Constant.ONLY_COLORS);
|
||||||
choices = choices.replaceAll("AnyColor,?", "");
|
choices = choices.replaceAll("AnyColor,?", "");
|
||||||
|
} else if (choices.contains("CardType")) {
|
||||||
|
choices = StringUtils.join(CardType.getAllCardTypes(), ",");
|
||||||
}
|
}
|
||||||
// Add any remaining choices
|
// Add any remaining choices
|
||||||
if (choices.length() > 0) {
|
if (choices.length() > 0) {
|
||||||
|
|||||||
@@ -5532,6 +5532,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
|||||||
// KeywordCollection#getAmount
|
// KeywordCollection#getAmount
|
||||||
|
|
||||||
final String[] parse = kw.contains(":") ? kw.split(":") : kw.split(" ");
|
final String[] parse = kw.contains(":") ? kw.split(":") : kw.split(" ");
|
||||||
|
if (parse.length < 2) {
|
||||||
|
count++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
final String s = parse[1];
|
final String s = parse[1];
|
||||||
if (StringUtils.isNumeric(s)) {
|
if (StringUtils.isNumeric(s)) {
|
||||||
count += Integer.parseInt(s);
|
count += Integer.parseInt(s);
|
||||||
|
|||||||
@@ -318,42 +318,11 @@ public class CardFactory {
|
|||||||
|
|
||||||
// ******************************************************************
|
// ******************************************************************
|
||||||
// ************** Link to different CardFactories *******************
|
// ************** Link to different CardFactories *******************
|
||||||
if (card.isPlane()) {
|
|
||||||
buildPlaneAbilities(card);
|
|
||||||
}
|
|
||||||
buildBattleAbilities(card);
|
buildBattleAbilities(card);
|
||||||
CardFactoryUtil.setupKeywordedAbilities(card); // Should happen AFTER setting left/right split abilities to set Fuse ability to both sides
|
CardFactoryUtil.setupKeywordedAbilities(card); // Should happen AFTER setting left/right split abilities to set Fuse ability to both sides
|
||||||
card.updateStateForView();
|
card.updateStateForView();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void buildPlaneAbilities(Card card) {
|
|
||||||
String trigger = "Mode$ PlanarDice | Result$ Planeswalk | TriggerZones$ Command | Secondary$ True | " +
|
|
||||||
"TriggerDescription$ Whenever you roll the Planeswalker symbol on the planar die, planeswalk.";
|
|
||||||
|
|
||||||
String rolledWalk = "DB$ Planeswalk | Cause$ PlanarDie";
|
|
||||||
|
|
||||||
Trigger planesWalkTrigger = TriggerHandler.parseTrigger(trigger, card, true);
|
|
||||||
planesWalkTrigger.setOverridingAbility(AbilityFactory.getAbility(rolledWalk, card));
|
|
||||||
card.addTrigger(planesWalkTrigger);
|
|
||||||
|
|
||||||
String chaosTrig = "Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Static$ True";
|
|
||||||
|
|
||||||
String rolledChaos = "DB$ ChaosEnsues";
|
|
||||||
|
|
||||||
Trigger chaosTrigger = TriggerHandler.parseTrigger(chaosTrig, card, true);
|
|
||||||
chaosTrigger.setOverridingAbility(AbilityFactory.getAbility(rolledChaos, card));
|
|
||||||
card.addTrigger(chaosTrigger);
|
|
||||||
|
|
||||||
String specialA = "ST$ RollPlanarDice | Cost$ X | SorcerySpeed$ True | Activator$ Player | SpecialAction$ True" +
|
|
||||||
" | ActivationZone$ Command | SpellDescription$ Roll the planar dice. X is equal to the number of " +
|
|
||||||
"times you have previously taken this action this turn. | CostDesc$ {X}: ";
|
|
||||||
|
|
||||||
SpellAbility planarRoll = AbilityFactory.getAbility(specialA, card);
|
|
||||||
planarRoll.setSVar("X", "Count$PlanarDiceSpecialActionThisTurn");
|
|
||||||
|
|
||||||
card.addSpellAbility(planarRoll);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void buildBattleAbilities(Card card) {
|
private static void buildBattleAbilities(Card card) {
|
||||||
if (!card.isBattle()) {
|
if (!card.isBattle()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -2445,21 +2445,11 @@ public class CardFactoryUtil {
|
|||||||
ReplacementEffect cardre = createETBReplacement(card, ReplacementLayer.Other, repeatSA, false, true, intrinsic, "Card.Self", "");
|
ReplacementEffect cardre = createETBReplacement(card, ReplacementLayer.Other, repeatSA, false, true, intrinsic, "Card.Self", "");
|
||||||
|
|
||||||
inst.addReplacement(cardre);
|
inst.addReplacement(cardre);
|
||||||
} else if (keyword.startsWith("Riot")) {
|
} else if (keyword.equals("Riot")) {
|
||||||
final String choose = "DB$ GenericChoice | AILogic$ Riot | SpellDescription$ Riot";
|
final String hasteStr = "DB$ Animate | Defined$ Self | Keywords$ Haste | Duration$ Permanent | UnlessCost$ AddCounter<1/P1P1> | UnlessPayer$ You | UnlessAI$ Riot | SpellDescription$ Riot";
|
||||||
|
|
||||||
final String counter = "DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | ETB$ True | CounterNum$ 1" +
|
final SpellAbility hasteSa = AbilityFactory.getAbility(hasteStr, card);
|
||||||
" | SpellDescription$ Put a +1/+1 counter on it.";
|
ReplacementEffect cardre = createETBReplacement(card, ReplacementLayer.Other, hasteSa, false, true, intrinsic, "Card.Self", "");
|
||||||
final String haste = "DB$ Animate | Defined$ Self | Keywords$ Haste | Duration$ Permanent | SpellDescription$ Haste";
|
|
||||||
|
|
||||||
SpellAbility saChoose = AbilityFactory.getAbility(choose, card);
|
|
||||||
|
|
||||||
List<AbilitySub> list = Lists.newArrayList();
|
|
||||||
list.add((AbilitySub)AbilityFactory.getAbility(counter, card));
|
|
||||||
list.add((AbilitySub)AbilityFactory.getAbility(haste, card));
|
|
||||||
saChoose.setAdditionalAbilityList("Choices", list);
|
|
||||||
|
|
||||||
ReplacementEffect cardre = createETBReplacement(card, ReplacementLayer.Other, saChoose, false, true, intrinsic, "Card.Self", "");
|
|
||||||
|
|
||||||
inst.addReplacement(cardre);
|
inst.addReplacement(cardre);
|
||||||
} else if (keyword.equals("Sunburst")) {
|
} else if (keyword.equals("Sunburst")) {
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ public class Cost implements Serializable {
|
|||||||
final String[] splitStr = abCostParse(parse, 5);
|
final String[] splitStr = abCostParse(parse, 5);
|
||||||
final String type = splitStr.length > 2 ? splitStr[2] : "CARDNAME";
|
final String type = splitStr.length > 2 ? splitStr[2] : "CARDNAME";
|
||||||
final String description = splitStr.length > 3 ? splitStr[3] : null;
|
final String description = splitStr.length > 3 ? splitStr[3] : null;
|
||||||
final ZoneType zone = splitStr.length > 4 ? ZoneType.smartValueOf(splitStr[4]) : ZoneType.Battlefield;
|
final List<ZoneType> zone = splitStr.length > 4 ? ZoneType.listValueOf(splitStr[4]) : Lists.newArrayList(ZoneType.Battlefield);
|
||||||
boolean oneOrMore = false;
|
boolean oneOrMore = false;
|
||||||
if (splitStr[0].equals("X1+")) {
|
if (splitStr[0].equals("X1+")) {
|
||||||
oneOrMore = true;
|
oneOrMore = true;
|
||||||
@@ -979,7 +979,7 @@ public class Cost implements Serializable {
|
|||||||
if (counters < 0) {
|
if (counters < 0) {
|
||||||
costParts.add(new CostPutCounter(String.valueOf(counters *-1), CounterType.get(CounterEnumType.LOYALTY), part.getType(), part.getTypeDescription()));
|
costParts.add(new CostPutCounter(String.valueOf(counters *-1), CounterType.get(CounterEnumType.LOYALTY), part.getType(), part.getTypeDescription()));
|
||||||
} else {
|
} else {
|
||||||
costParts.add(new CostRemoveCounter(String.valueOf(counters), CounterType.get(CounterEnumType.LOYALTY), part.getType(), part.getTypeDescription(), ZoneType.Battlefield, false));
|
costParts.add(new CostRemoveCounter(String.valueOf(counters), CounterType.get(CounterEnumType.LOYALTY), part.getType(), part.getTypeDescription(), Lists.newArrayList(ZoneType.Battlefield) , false));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -95,13 +95,13 @@ public class CostPayment extends ManaConversionMatrix {
|
|||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean canPayAdditionalCosts(Cost cost, final SpellAbility ability) {
|
public static boolean canPayAdditionalCosts(Cost cost, final SpellAbility ability, final boolean effect) {
|
||||||
if (cost == null) {
|
if (cost == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
cost = CostAdjustment.adjust(cost, ability);
|
cost = CostAdjustment.adjust(cost, ability);
|
||||||
return cost.canPay(ability, false);
|
return cost.canPay(ability, effect);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,13 +19,21 @@ package forge.game.cost;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
|
import forge.game.Game;
|
||||||
import forge.game.GameEntityCounterTable;
|
import forge.game.GameEntityCounterTable;
|
||||||
|
import forge.game.ability.AbilityKey;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
|
import forge.game.card.CardUtil;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.replacement.ReplacementEffect;
|
||||||
|
import forge.game.replacement.ReplacementType;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
@@ -140,15 +148,36 @@ public class CostPutCounter extends CostPartWithList {
|
|||||||
@Override
|
@Override
|
||||||
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
|
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
|
||||||
final Card source = ability.getHostCard();
|
final Card source = ability.getHostCard();
|
||||||
|
final Game game = source.getGame();
|
||||||
if (this.payCostFromSource()) {
|
if (this.payCostFromSource()) {
|
||||||
return source.isInPlay() && (getAbilityAmount(ability) == 0 || source.canReceiveCounters(this.counter));
|
if (isETBReplacement(ability, effect)) {
|
||||||
|
final Card copy = CardUtil.getLKICopy(source);
|
||||||
|
copy.setLastKnownZone(payer.getZone(ZoneType.Battlefield));
|
||||||
|
|
||||||
|
// check state it would have on the battlefield
|
||||||
|
CardCollection preList = new CardCollection(copy);
|
||||||
|
game.getAction().checkStaticAbilities(false, Sets.newHashSet(copy), preList);
|
||||||
|
// reset again?
|
||||||
|
game.getAction().checkStaticAbilities(false);
|
||||||
|
if (copy.canReceiveCounters(getCounter())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!source.isInPlay()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (source.canReceiveCounters(getCounter())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getAbilityAmount(ability) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3 Cards have Put a -1/-1 Counter on a Creature you control.
|
// 3 Cards have Put a -1/-1 Counter on a Creature you control.
|
||||||
List<Card> typeList = CardLists.getValidCards(source.getGame().getCardsIn(ZoneType.Battlefield),
|
List<Card> typeList = CardLists.getValidCards(source.getGame().getCardsIn(ZoneType.Battlefield),
|
||||||
this.getType().split(";"), payer, source, ability);
|
this.getType().split(";"), payer, source, ability);
|
||||||
|
|
||||||
typeList = CardLists.filter(typeList, CardPredicates.canReceiveCounters(this.counter));
|
typeList = CardLists.filter(typeList, CardPredicates.canReceiveCounters(getCounter()));
|
||||||
|
|
||||||
return !typeList.isEmpty();
|
return !typeList.isEmpty();
|
||||||
}
|
}
|
||||||
@@ -172,8 +201,12 @@ public class CostPutCounter extends CostPartWithList {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Card doPayment(Player payer, SpellAbility ability, Card targetCard, final boolean effect) {
|
protected Card doPayment(Player payer, SpellAbility ability, Card targetCard, final boolean effect) {
|
||||||
final int i = this.getAbilityAmount(ability);
|
final int i = getAbilityAmount(ability);
|
||||||
targetCard.addCounter(this.getCounter(), i, payer, counterTable);
|
if (isETBReplacement(ability, effect)) {
|
||||||
|
targetCard.addEtbCounter(getCounter(), i, payer);
|
||||||
|
} else {
|
||||||
|
targetCard.addCounter(getCounter(), i, payer, counterTable);
|
||||||
|
}
|
||||||
return targetCard;
|
return targetCard;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,4 +241,27 @@ public class CostPutCounter extends CostPartWithList {
|
|||||||
counterTable.clear();
|
counterTable.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean isETBReplacement(final SpellAbility ability, final boolean effect) {
|
||||||
|
if (!effect) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// only for itself?
|
||||||
|
if (!payCostFromSource()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ability == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ability.isReplacementAbility()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ReplacementEffect re = ability.getReplacementEffect();
|
||||||
|
if (re.getMode() != ReplacementType.Moved) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ability.getHostCard().equals(ability.getReplacingObject(AbilityKey.Card))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public class CostRemoveCounter extends CostPart {
|
|||||||
*/
|
*/
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
public final CounterType counter;
|
public final CounterType counter;
|
||||||
public final ZoneType zone;
|
public final List<ZoneType> zone;
|
||||||
public final Boolean oneOrMore;
|
public final Boolean oneOrMore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,7 +57,7 @@ public class CostRemoveCounter extends CostPart {
|
|||||||
* the description
|
* the description
|
||||||
* @param zone the zone.
|
* @param zone the zone.
|
||||||
*/
|
*/
|
||||||
public CostRemoveCounter(final String amount, final CounterType counter, final String type, final String description, final ZoneType zone, final boolean oneOrMore) {
|
public CostRemoveCounter(final String amount, final CounterType counter, final String type, final String description, final List<ZoneType> zone, final boolean oneOrMore) {
|
||||||
super(amount, type, description);
|
super(amount, type, description);
|
||||||
|
|
||||||
this.counter = counter;
|
this.counter = counter;
|
||||||
@@ -73,23 +73,24 @@ public class CostRemoveCounter extends CostPart {
|
|||||||
final CounterType cntrs = this.counter;
|
final CounterType cntrs = this.counter;
|
||||||
final Card source = ability.getHostCard();
|
final Card source = ability.getHostCard();
|
||||||
final String type = this.getType();
|
final String type = this.getType();
|
||||||
|
|
||||||
if (this.payCostFromSource()) {
|
if (this.payCostFromSource()) {
|
||||||
return source.getCounters(cntrs);
|
return source.getCounters(cntrs);
|
||||||
} else {
|
|
||||||
List<Card> typeList;
|
|
||||||
if (type.equals("OriginalHost")) {
|
|
||||||
typeList = Lists.newArrayList(ability.getOriginalHost());
|
|
||||||
} else {
|
|
||||||
typeList = CardLists.getValidCards(payer.getCardsIn(this.zone), type.split(";"), payer, source, ability);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Single Target
|
|
||||||
int maxcount = 0;
|
|
||||||
for (Card c : typeList) {
|
|
||||||
maxcount = Math.max(maxcount, c.getCounters(cntrs));
|
|
||||||
}
|
|
||||||
return maxcount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Card> typeList;
|
||||||
|
if (type.equals("OriginalHost")) {
|
||||||
|
typeList = Lists.newArrayList(ability.getOriginalHost());
|
||||||
|
} else {
|
||||||
|
typeList = CardLists.getValidCards(payer.getCardsIn(this.zone), type.split(";"), payer, source, ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single Target
|
||||||
|
int maxcount = 0;
|
||||||
|
for (Card c : typeList) {
|
||||||
|
maxcount = Math.max(maxcount, c.getCounters(cntrs));
|
||||||
|
}
|
||||||
|
return maxcount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -146,26 +147,24 @@ public class CostRemoveCounter extends CostPart {
|
|||||||
final int amount;
|
final int amount;
|
||||||
if (getAmount().equals("All")) {
|
if (getAmount().equals("All")) {
|
||||||
amount = source.getCounters(cntrs);
|
amount = source.getCounters(cntrs);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
amount = getAbilityAmount(ability);
|
amount = getAbilityAmount(ability);
|
||||||
}
|
}
|
||||||
if (this.payCostFromSource()) {
|
if (this.payCostFromSource()) {
|
||||||
return !source.isPhasedOut() && (source.getCounters(cntrs) - amount) >= 0;
|
return !source.isPhasedOut() && (source.getCounters(cntrs) - amount) >= 0;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
List<Card> typeList;
|
|
||||||
if (type.equals("OriginalHost")) {
|
|
||||||
typeList = Lists.newArrayList(ability.getOriginalHost());
|
|
||||||
} else {
|
|
||||||
typeList = CardLists.getValidCards(payer.getCardsIn(this.zone), type.split(";"), payer, source, ability);
|
|
||||||
}
|
|
||||||
|
|
||||||
// (default logic) remove X counters from a single permanent
|
List<Card> typeList;
|
||||||
for (Card c : typeList) {
|
if (type.equals("OriginalHost")) {
|
||||||
if (c.getCounters(cntrs) - amount >= 0) {
|
typeList = Lists.newArrayList(ability.getOriginalHost());
|
||||||
return true;
|
} else {
|
||||||
}
|
typeList = CardLists.getValidCards(payer.getCardsIn(this.zone), type.split(";"), payer, source, ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
// (default logic) remove X counters from a single permanent
|
||||||
|
for (Card c : typeList) {
|
||||||
|
if (c.getCounters(cntrs) - amount >= 0) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ public enum Keyword {
|
|||||||
STRIVE("Strive", KeywordWithCost.class, false, "CARDNAME costs %s more to cast for each target beyond the first."),
|
STRIVE("Strive", KeywordWithCost.class, false, "CARDNAME costs %s more to cast for each target beyond the first."),
|
||||||
SUNBURST("Sunburst", SimpleKeyword.class, false, "This enters the battlefield with either a +1/+1 or charge counter on it for each color of mana spent to cast it based on whether it's a creature."),
|
SUNBURST("Sunburst", SimpleKeyword.class, false, "This enters the battlefield with either a +1/+1 or charge counter on it for each color of mana spent to cast it based on whether it's a creature."),
|
||||||
SURGE("Surge", KeywordWithCost.class, false, "You may cast this spell for its surge cost if you or a teammate has cast another spell this turn."),
|
SURGE("Surge", KeywordWithCost.class, false, "You may cast this spell for its surge cost if you or a teammate has cast another spell this turn."),
|
||||||
SUSPEND("Suspend", Suspend.class, false, "Rather than cast this card from your hand, you may pay %s and exile it with {%d:time counter} on it. At the beginning of your upkeep, remove a time counter. When the last is removed, cast it without paying its mana cost."),
|
SUSPEND("Suspend", Suspend.class, false, "If you could begin to cast this card by putting it onto the stack from your hand, you may pay %s and exile it with {%d:time counter} on it. At the beginning of your upkeep, remove a time counter. When the last is removed, play it without paying its mana cost. If you cast a creature spell this way, it gains haste until you lose control of the spell or the permanent it becomes."),
|
||||||
TOTEM_ARMOR("Totem armor", SimpleKeyword.class, true, "If enchanted permanent would be destroyed, instead remove all damage marked on it and destroy this Aura."),
|
TOTEM_ARMOR("Totem armor", SimpleKeyword.class, true, "If enchanted permanent would be destroyed, instead remove all damage marked on it and destroy this Aura."),
|
||||||
TOXIC("Toxic", KeywordWithAmount.class, false, "Players dealt combat damage by this creature also get {%d:poison counter}."),
|
TOXIC("Toxic", KeywordWithAmount.class, false, "Players dealt combat damage by this creature also get {%d:poison counter}."),
|
||||||
TRAINING("Training", SimpleKeyword.class, false, "Whenever this creature attacks with another creature with greater power, put a +1/+1 counter on this creature."),
|
TRAINING("Training", SimpleKeyword.class, false, "Whenever this creature attacks with another creature with greater power, put a +1/+1 counter on this creature."),
|
||||||
|
|||||||
@@ -3176,6 +3176,36 @@ public class Player extends GameEntity implements Comparable<Player> {
|
|||||||
return eff;
|
return eff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void createPlanechaseEffects(Game game) {
|
||||||
|
final PlayerZone com = getZone(ZoneType.Command);
|
||||||
|
final String name = "Planar Dice";
|
||||||
|
final Card eff = new Card(game.nextCardId(), game);
|
||||||
|
eff.setTimestamp(game.getNextTimestamp());
|
||||||
|
eff.setName(name);
|
||||||
|
eff.setOwner(this);
|
||||||
|
eff.setImmutable(true);
|
||||||
|
String image = ImageKeys.getTokenKey("planechase");
|
||||||
|
eff.setImageKey(image);
|
||||||
|
|
||||||
|
String trigger = "Mode$ PlanarDice | Result$ Planeswalk | TriggerZones$ Command | ValidPlayer$ You | Secondary$ True | " +
|
||||||
|
"TriggerDescription$ Whenever you roll the Planeswalker symbol on the planar die, planeswalk.";
|
||||||
|
String rolledWalk = "DB$ Planeswalk | Cause$ PlanarDie";
|
||||||
|
Trigger planesWalkTrigger = TriggerHandler.parseTrigger(trigger, eff, true);
|
||||||
|
planesWalkTrigger.setOverridingAbility(AbilityFactory.getAbility(rolledWalk, eff));
|
||||||
|
eff.addTrigger(planesWalkTrigger);
|
||||||
|
|
||||||
|
String specialA = "ST$ RollPlanarDice | Cost$ X | SorcerySpeed$ True | Activator$ Player | SpecialAction$ True" +
|
||||||
|
" | ActivationZone$ Command | SpellDescription$ Roll the planar dice. X is equal to the number of " +
|
||||||
|
"times you have previously taken this action this turn. | CostDesc$ {X}: ";
|
||||||
|
SpellAbility planarRoll = AbilityFactory.getAbility(specialA, eff);
|
||||||
|
planarRoll.setSVar("X", "Count$PlanarDiceSpecialActionThisTurn");
|
||||||
|
eff.addSpellAbility(planarRoll);
|
||||||
|
|
||||||
|
eff.updateStateForView();
|
||||||
|
com.add(eff);
|
||||||
|
this.updateZoneForView(com);
|
||||||
|
}
|
||||||
|
|
||||||
public void createTheRing(Card host) {
|
public void createTheRing(Card host) {
|
||||||
final PlayerZone com = getZone(ZoneType.Command);
|
final PlayerZone com = getZone(ZoneType.Command);
|
||||||
if (theRing == null) {
|
if (theRing == null) {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ public abstract class AbilityActivated extends SpellAbility implements Cloneable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CostPayment.canPayAdditionalCosts(this.getPayCosts(), this);
|
return CostPayment.canPayAdditionalCosts(this.getPayCosts(), this, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CostPayment.canPayAdditionalCosts(this.getPayCosts(), this)) {
|
if (!CostPayment.canPayAdditionalCosts(this.getPayCosts(), this, false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -827,6 +827,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
public void setReplacingObject(final AbilityKey type, final Object o) {
|
public void setReplacingObject(final AbilityKey type, final Object o) {
|
||||||
replacingObjects.put(type, o);
|
replacingObjects.put(type, o);
|
||||||
}
|
}
|
||||||
|
public void setReplacingObjects(final Map<AbilityKey, Object> repParams) {
|
||||||
|
replacingObjects = AbilityKey.newMap(repParams);
|
||||||
|
}
|
||||||
public void setReplacingObjectsFrom(final Map<AbilityKey, Object> repParams, final AbilityKey... types) {
|
public void setReplacingObjectsFrom(final Map<AbilityKey, Object> repParams, final AbilityKey... types) {
|
||||||
int typesLength = types.length;
|
int typesLength = types.length;
|
||||||
for (int i = 0; i < typesLength; i += 1) {
|
for (int i = 0; i < typesLength; i += 1) {
|
||||||
|
|||||||
@@ -373,8 +373,14 @@ public class AdventureEventData implements Serializable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String restricted : Config.instance().getConfigData().restrictedEditions) {
|
ConfigData configData = Config.instance().getConfigData();
|
||||||
legalBlocks.removeIf(q -> q.getName().equals(restricted));
|
if (configData.allowedEditions != null) {
|
||||||
|
List<String> allowed = Arrays.asList(configData.allowedEditions);
|
||||||
|
legalBlocks.removeIf(q -> !allowed.contains(q.getName()));
|
||||||
|
} else {
|
||||||
|
for (String restricted : configData.restrictedEditions) {
|
||||||
|
legalBlocks.removeIf(q -> q.getName().equals(restricted));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return legalBlocks.isEmpty() ? null : Aggregates.random(legalBlocks);
|
return legalBlocks.isEmpty() ? null : Aggregates.random(legalBlocks);
|
||||||
}
|
}
|
||||||
@@ -388,8 +394,14 @@ public class AdventureEventData implements Serializable {
|
|||||||
legalBlocks.add(b);
|
legalBlocks.add(b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (String restricted : Config.instance().getConfigData().restrictedEditions) {
|
ConfigData configData = Config.instance().getConfigData();
|
||||||
legalBlocks.removeIf(q -> q.getName().equals(restricted));
|
if (configData.allowedEditions != null) {
|
||||||
|
List<String> allowed = Arrays.asList(configData.allowedEditions);
|
||||||
|
legalBlocks.removeIf(q -> !allowed.contains(q.getName()));
|
||||||
|
} else {
|
||||||
|
for (String restricted : configData.restrictedEditions) {
|
||||||
|
legalBlocks.removeIf(q -> q.getName().equals(restricted));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return legalBlocks.isEmpty()?null:Aggregates.random(legalBlocks);
|
return legalBlocks.isEmpty()?null:Aggregates.random(legalBlocks);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,4 +22,5 @@ public class ConfigData {
|
|||||||
public RewardData legalCards;
|
public RewardData legalCards;
|
||||||
public String[] restrictedCards;
|
public String[] restrictedCards;
|
||||||
public String[] restrictedEditions;
|
public String[] restrictedEditions;
|
||||||
|
public String[] allowedEditions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ public class RewardData implements Serializable {
|
|||||||
private static Iterable<PaperCard> allEnemyCards;
|
private static Iterable<PaperCard> allEnemyCards;
|
||||||
|
|
||||||
static private void initializeAllCards(){
|
static private void initializeAllCards(){
|
||||||
RewardData legals = Config.instance().getConfigData().legalCards;
|
ConfigData configData = Config.instance().getConfigData();
|
||||||
|
RewardData legals = configData.legalCards;
|
||||||
|
|
||||||
if(legals==null)
|
if(legals==null)
|
||||||
allCards = CardUtil.getFullCardPool(false); // we need unique cards only here, so that a unique card can be chosen before a set variant is determined
|
allCards = CardUtil.getFullCardPool(false); // we need unique cards only here, so that a unique card can be chosen before a set variant is determined
|
||||||
@@ -100,11 +101,14 @@ public class RewardData implements Serializable {
|
|||||||
return false;
|
return false;
|
||||||
if(input.getRules().getAiHints().getRemNonCommanderDecks())
|
if(input.getRules().getAiHints().getRemNonCommanderDecks())
|
||||||
return false;
|
return false;
|
||||||
if(Arrays.asList(Config.instance().getConfigData().restrictedEditions).contains(input.getEdition()))
|
if(configData.allowedEditions != null) {
|
||||||
|
if (!Arrays.asList(configData.allowedEditions).contains(input.getEdition()))
|
||||||
|
return false;
|
||||||
|
} else if(Arrays.asList(configData.restrictedEditions).contains(input.getEdition()))
|
||||||
return false;
|
return false;
|
||||||
if(input.getRules().isCustom())
|
if(input.getRules().isCustom())
|
||||||
return false;
|
return false;
|
||||||
return !Arrays.asList(Config.instance().getConfigData().restrictedCards).contains(input.getName());
|
return !Arrays.asList(configData.restrictedCards).contains(input.getName());
|
||||||
});
|
});
|
||||||
//Filter AI cards for enemies.
|
//Filter AI cards for enemies.
|
||||||
allEnemyCards=Iterables.filter(allCards, input -> {
|
allEnemyCards=Iterables.filter(allCards, input -> {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import com.github.tommyettinger.textra.TextraButton;
|
|||||||
import com.github.tommyettinger.textra.TextraLabel;
|
import com.github.tommyettinger.textra.TextraLabel;
|
||||||
import forge.Forge;
|
import forge.Forge;
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
|
import forge.adventure.data.ConfigData;
|
||||||
import forge.adventure.data.RewardData;
|
import forge.adventure.data.RewardData;
|
||||||
import forge.adventure.util.*;
|
import forge.adventure.util.*;
|
||||||
import forge.card.CardEdition;
|
import forge.card.CardEdition;
|
||||||
@@ -151,7 +152,10 @@ public class SpellSmithScene extends UIScene {
|
|||||||
.filter(input2 -> input2.getEdition().equals(input.getCode())).collect(Collectors.toList());
|
.filter(input2 -> input2.getEdition().equals(input.getCode())).collect(Collectors.toList());
|
||||||
if (it.size() == 0)
|
if (it.size() == 0)
|
||||||
return false;
|
return false;
|
||||||
return (!Arrays.asList(Config.instance().getConfigData().restrictedEditions).contains(input.getCode()));
|
ConfigData configData = Config.instance().getConfigData();
|
||||||
|
if (configData.allowedEditions != null)
|
||||||
|
return Arrays.asList(configData.allowedEditions).contains(input.getCode());
|
||||||
|
return (!Arrays.asList(configData.restrictedEditions).contains(input.getCode()));
|
||||||
}).sorted(new Comparator<CardEdition>() {
|
}).sorted(new Comparator<CardEdition>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(CardEdition e1, CardEdition e2) {
|
public int compare(CardEdition e1, CardEdition e2) {
|
||||||
|
|||||||
@@ -480,8 +480,11 @@ public class MapStage extends GameStage {
|
|||||||
if (enemy != null && !enemy.toString().isEmpty()) {
|
if (enemy != null && !enemy.toString().isEmpty()) {
|
||||||
EnemyData EN = WorldData.getEnemy(enemy.toString());
|
EnemyData EN = WorldData.getEnemy(enemy.toString());
|
||||||
if (EN == null) {
|
if (EN == null) {
|
||||||
System.err.printf("Enemy \"%s\" not found.", enemy);
|
System.err.printf("Enemy \"%s\" not found, choosing a random one for current biome\n", enemy);
|
||||||
break;
|
forge.adventure.world.World world = Current.world();
|
||||||
|
Vector2 poiPos = AdventureQuestController.instance().mostRecentPOI.getPosition();
|
||||||
|
int currentBiome = forge.adventure.world.World.highestBiome(world.getBiome((int) poiPos.x / world.getTileSize(), (int) poiPos.y / world.getTileSize()));
|
||||||
|
EN = world.getData().GetBiomes().get(currentBiome).getEnemy(1.0f);
|
||||||
}
|
}
|
||||||
EnemySprite mob = new EnemySprite(id, EN);
|
EnemySprite mob = new EnemySprite(id, EN);
|
||||||
Object dialogObject = prop.get("dialog"); //Check if the enemy has a dialogue attached to it.
|
Object dialogObject = prop.get("dialog"); //Check if the enemy has a dialogue attached to it.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
|
import forge.adventure.data.ConfigData;
|
||||||
import forge.adventure.data.GeneratedDeckData;
|
import forge.adventure.data.GeneratedDeckData;
|
||||||
import forge.adventure.data.GeneratedDeckTemplateData;
|
import forge.adventure.data.GeneratedDeckTemplateData;
|
||||||
import forge.adventure.data.RewardData;
|
import forge.adventure.data.RewardData;
|
||||||
@@ -23,7 +24,6 @@ import forge.item.generation.UnOpenedProduct;
|
|||||||
import forge.model.FModel;
|
import forge.model.FModel;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@@ -714,7 +714,7 @@ public class CardUtil {
|
|||||||
public static Deck getDeck(String path, boolean forAI, boolean isFantasyMode, String colors, boolean isTheme, boolean useGeneticAI, CardEdition starterEdition, boolean discourageDuplicates)
|
public static Deck getDeck(String path, boolean forAI, boolean isFantasyMode, String colors, boolean isTheme, boolean useGeneticAI, CardEdition starterEdition, boolean discourageDuplicates)
|
||||||
{
|
{
|
||||||
if(path.endsWith(".dck"))
|
if(path.endsWith(".dck"))
|
||||||
return DeckSerializer.fromFile(new File(Config.instance().getCommonFilePath(path)));
|
return DeckSerializer.fromFile(Config.instance().getFile(path).file());
|
||||||
|
|
||||||
if(forAI && (isFantasyMode||useGeneticAI)) {
|
if(forAI && (isFantasyMode||useGeneticAI)) {
|
||||||
Deck deck = DeckgenUtil.getRandomOrPreconOrThemeDeck(colors, forAI, isTheme, useGeneticAI);
|
Deck deck = DeckgenUtil.getRandomOrPreconOrThemeDeck(colors, forAI, isTheme, useGeneticAI);
|
||||||
@@ -755,8 +755,12 @@ public class CardUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Deck generateBoosterPackAsDeck(String code){
|
public static Deck generateBoosterPackAsDeck(String code){
|
||||||
|
ConfigData configData = Config.instance().getConfigData();
|
||||||
if (Arrays.asList(Config.instance().getConfigData().restrictedEditions).contains(code)){
|
if (configData.allowedEditions != null) {
|
||||||
|
if (!Arrays.asList(configData.allowedEditions).contains(code)){
|
||||||
|
System.err.println("Cannot generate booster pack, '" + code + "' is not an allowed edition");
|
||||||
|
}
|
||||||
|
} else if (Arrays.asList(configData.restrictedEditions).contains(code)){
|
||||||
System.err.println("Cannot generate booster pack, '" + code + "' is a restricted edition");
|
System.err.println("Cannot generate booster pack, '" + code + "' is a restricted edition");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigPutCounter | Tri
|
|||||||
SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Artifact.nonToken+YouCtrl+Other,Creature.YouCtrl+Other+nonToken | CounterType$ OIL | CounterNum$ 1
|
SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Artifact.nonToken+YouCtrl+Other,Creature.YouCtrl+Other+nonToken | CounterType$ OIL | CounterNum$ 1
|
||||||
A:AB$ Token | Cost$ 2 T | TokenScript$ r_1_1_phyrexian_goblin | CheckSVar$ CountCountersRemoved | CheckSVarCompare$ GE1 | SpellDescription$ Create a 1/1 red Phyrexian Goblin creature token. Activate only if an oil counter was removed from a permanent you controlled this turn or a permanent with an oil counter on it was put into a graveyard this turn.
|
A:AB$ Token | Cost$ 2 T | TokenScript$ r_1_1_phyrexian_goblin | CheckSVar$ CountCountersRemoved | CheckSVarCompare$ GE1 | SpellDescription$ Create a 1/1 red Phyrexian Goblin creature token. Activate only if an oil counter was removed from a permanent you controlled this turn or a permanent with an oil counter on it was put into a graveyard this turn.
|
||||||
SVar:CountCountersRemoved:Count$CountersRemovedThisTurn OIL Card.YouCtrl+inRealZoneBattlefield/Plus.X
|
SVar:CountCountersRemoved:Count$CountersRemovedThisTurn OIL Card.YouCtrl+inRealZoneBattlefield/Plus.X
|
||||||
SVar:X:Count$ThisTurnEntered_Graveyard_from_Battlefield_Permanent.YouCtrl+counters_GE1_OIL
|
SVar:X:Count$ThisTurnEntered_Graveyard_from_Battlefield_Permanent.counters_GE1_OIL
|
||||||
DeckHas:Ability$Token/Counters & Type$Goblin|Phyrexian
|
DeckHas:Ability$Token/Counters & Type$Goblin|Phyrexian
|
||||||
Oracle:At the beginning of your upkeep, put an oil counter on another target nontoken artifact or creature you control.\n{2}, {T}: Create a 1/1 red Phyrexian Goblin creature token. Activate only if an oil counter was removed from a permanent you controlled this turn or a permanent with an oil counter on it was put into a graveyard this turn.
|
Oracle:At the beginning of your upkeep, put an oil counter on another target nontoken artifact or creature you control.\n{2}, {T}: Create a 1/1 red Phyrexian Goblin creature token. Activate only if an oil counter was removed from a permanent you controlled this turn or a permanent with an oil counter on it was put into a graveyard this turn.
|
||||||
|
|||||||
@@ -3,11 +3,8 @@ ManaCost:2 B
|
|||||||
Types:Creature Rat
|
Types:Creature Rat
|
||||||
PT:1/1
|
PT:1/1
|
||||||
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, put an infection counter on it.
|
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, put an infection counter on it.
|
||||||
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ INFECTION | CounterNum$ 1 | SubAbility$ DBRemember
|
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ INFECTION | CounterNum$ 1
|
||||||
SVar:DBRemember:DB$ Pump | RememberObjects$ Opponent | StackDescription$ None
|
|
||||||
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ DBDisease | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, Diseased Vermin deals X damage to target opponent previously dealt damage by it, where X is the number of infection counters on it.
|
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ DBDisease | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, Diseased Vermin deals X damage to target opponent previously dealt damage by it, where X is the number of infection counters on it.
|
||||||
SVar:DBDisease:DB$ DealDamage | ValidTgts$ Opponent.IsRemembered | NumDmg$ X
|
SVar:DBDisease:DB$ DealDamage | ValidTgts$ Opponent.wasDealtDamageThisGameBy Self | NumDmg$ X
|
||||||
SVar:X:Count$CardCounters.INFECTION
|
SVar:X:Count$CardCounters.INFECTION
|
||||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True
|
|
||||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
|
||||||
Oracle:Whenever Diseased Vermin deals combat damage to a player, put an infection counter on it.\nAt the beginning of your upkeep, Diseased Vermin deals X damage to target opponent previously dealt damage by it, where X is the number of infection counters on it.
|
Oracle:Whenever Diseased Vermin deals combat damage to a player, put an infection counter on it.\nAt the beginning of your upkeep, Diseased Vermin deals X damage to target opponent previously dealt damage by it, where X is the number of infection counters on it.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | Tr
|
|||||||
SVar:TrigCharm:DB$ Charm | Choices$ DBTapUntap,DBDamage,DBCopy,DBChangeZone | ChoiceRestriction$ ThisGame | CharmNum$ 1
|
SVar:TrigCharm:DB$ Charm | Choices$ DBTapUntap,DBDamage,DBCopy,DBChangeZone | ChoiceRestriction$ ThisGame | CharmNum$ 1
|
||||||
SVar:DBTapUntap:DB$ TapOrUntap | ValidTgts$ Permanent | TgtPrompt$ Select target permanent to tap or untap | SpellDescription$ You may tap or untap target permanent.
|
SVar:DBTapUntap:DB$ TapOrUntap | ValidTgts$ Permanent | TgtPrompt$ Select target permanent to tap or untap | SpellDescription$ You may tap or untap target permanent.
|
||||||
SVar:DBDamage:DB$ DealDamage | Defined$ Opponent | NumDmg$ 3 | SpellDescription$ CARDNAME deals 3 damage to each opponent.
|
SVar:DBDamage:DB$ DealDamage | Defined$ Opponent | NumDmg$ 3 | SpellDescription$ CARDNAME deals 3 damage to each opponent.
|
||||||
SVar:DBCopy:DB$ CopySpellAbility | ValidTgts$ Instant.YouCtrl,Sorcery.YouCtrl | TgtPrompt$ Select target instant or sorcery spell you control | MayChooseTarget$ True | SpellDescription$ Copy target instant or sorcery spell you control. You may choose new targets for the copy.
|
SVar:DBCopy:DB$ CopySpellAbility | ValidTgts$ Instant.YouCtrl,Sorcery.YouCtrl | TargetType$ Spell | TgtPrompt$ Select target instant or sorcery spell you control | MayChooseTarget$ True | SpellDescription$ Copy target instant or sorcery spell you control. You may choose new targets for the copy.
|
||||||
SVar:DBChangeZone:DB$ ChangeZone | Origin$ Battlefield | Destination$ Library | LibraryPosition$ 0 | SpellDescription$ Put NICKNAME on top of its owner's library.
|
SVar:DBChangeZone:DB$ ChangeZone | Origin$ Battlefield | Destination$ Library | LibraryPosition$ 0 | SpellDescription$ Put NICKNAME on top of its owner's library.
|
||||||
DeckNeeds:Type$Instant|Sorcery
|
DeckNeeds:Type$Instant|Sorcery
|
||||||
Oracle:Whenever you cast an instant or sorcery spell, choose one that hasn't been chosen —\n• You may tap or untap target permanent.\n• Gandalf the Grey deals 3 damage to each opponent.\n• Copy target instant or sorcery spell you control. You may choose new targets for the copy.\n• Put Gandalf on top of its owner's library.
|
Oracle:Whenever you cast an instant or sorcery spell, choose one that hasn't been chosen —\n• You may tap or untap target permanent.\n• Gandalf the Grey deals 3 damage to each opponent.\n• Copy target instant or sorcery spell you control. You may choose new targets for the copy.\n• Put Gandalf on top of its owner's library.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Types:Artifact Vehicle
|
|||||||
PT:5/5
|
PT:5/5
|
||||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDamage | TriggerDescription$ When CARDNAME enters the battlefield, it deals 5 damage to target creature an opponent controls.
|
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDamage | TriggerDescription$ When CARDNAME enters the battlefield, it deals 5 damage to target creature an opponent controls.
|
||||||
SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | NumDmg$ 5
|
SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | NumDmg$ 5
|
||||||
T:Mode$ ExcessDamageAll | ValidTarget$ Creature.OppCtrl | CombatDamage$ False | Execute$ TrigTreasure | TriggerDescription$ Whenever one or more creatures your opponents control are dealt excess noncombat damage, create a Treasure token.
|
T:Mode$ ExcessDamageAll | ValidTarget$ Creature.OppCtrl | CombatDamage$ False | TriggerZones$ Battlefield | Execute$ TrigTreasure | TriggerDescription$ Whenever one or more creatures your opponents control are dealt excess noncombat damage, create a Treasure token.
|
||||||
SVar:TrigTreasure:DB$ Token | TokenScript$ c_a_treasure_sac
|
SVar:TrigTreasure:DB$ Token | TokenScript$ c_a_treasure_sac
|
||||||
K:Crew:2
|
K:Crew:2
|
||||||
Oracle:When Magmatic Galleon enters the battlefield, it deals 5 damage to target creature an opponent controls.\nWhenever one or more creatures your opponents control are dealt excess noncombat damage, create a Treasure token.\nCrew 2
|
Oracle:When Magmatic Galleon enters the battlefield, it deals 5 damage to target creature an opponent controls.\nWhenever one or more creatures your opponents control are dealt excess noncombat damage, create a Treasure token.\nCrew 2
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ ManaCost:2 G U R
|
|||||||
Types:Legendary Creature Human Rogue
|
Types:Legendary Creature Human Rogue
|
||||||
PT:3/3
|
PT:3/3
|
||||||
T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigCounter | TriggerDescription$ At the beginning of combat on your turn, put your choice of a +1/+1, first strike, vigilance, or menace counter on CARDNAME.
|
T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigCounter | TriggerDescription$ At the beginning of combat on your turn, put your choice of a +1/+1, first strike, vigilance, or menace counter on CARDNAME.
|
||||||
SVar:TrigCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1,First strike,Vigilance,Menace | CounterNum$ 1
|
SVar:TrigCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1,First Strike,Vigilance,Menace | CounterNum$ 1
|
||||||
K:Counters remain on CARDNAME as it moves to any zone other than a player's hand or library.
|
K:Counters remain on CARDNAME as it moves to any zone other than a player's hand or library.
|
||||||
SVar:AltCost:Cost$ 2 G U R Discard<2/Card> | ActivationZone$ Graveyard | Description$ You may cast NICKNAME from your graveyard by discarding two cards in addition to paying its other costs.
|
SVar:AltCost:Cost$ 2 G U R Discard<2/Card> | ActivationZone$ Graveyard | Description$ You may cast NICKNAME from your graveyard by discarding two cards in addition to paying its other costs.
|
||||||
DeckHas:Ability$Counters|Discard
|
DeckHas:Ability$Counters|Discard
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ Types:Legendary Creature Halfling Soldier
|
|||||||
PT:2/2
|
PT:2/2
|
||||||
K:Vigilance
|
K:Vigilance
|
||||||
K:Ward:1
|
K:Ward:1
|
||||||
A:AB$ Protection | Cost$ T | ValidTgts$ Creature.YouCtrl+Other | TgtPrompt$ Select another target creature you control | Gains$ Choice | Choices$ artifacts,enchantments,creatures,battles,instants,sorceries,planeswalkers,lands | SpellDescription$ Another target creature you control gains protection from the card type of your choice until end of turn. (It can't be blocked, targeted, dealt damage, enchanted, or equipped by anything of that type.)
|
A:AB$ Protection | Cost$ T | ValidTgts$ Creature.YouCtrl+Other | TgtPrompt$ Select another target creature you control | Gains$ Choice | Choices$ CardType | SpellDescription$ Another target creature you control gains protection from the card type of your choice until end of turn. (It can't be blocked, targeted, dealt damage, enchanted, or equipped by anything of that type.)
|
||||||
Oracle:Vigilance, ward {1}\n{T}: Another target creature you control gains protection from the card type of your choice until end of turn. (It can't be blocked, targeted, dealt damage, enchanted, or equipped by anything of that type.)
|
Oracle:Vigilance, ward {1}\n{T}: Another target creature you control gains protection from the card type of your choice until end of turn. (It can't be blocked, targeted, dealt damage, enchanted, or equipped by anything of that type.)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Name:Raven's Run
|
|||||||
ManaCost:no cost
|
ManaCost:no cost
|
||||||
Types:Plane Shadowmoor
|
Types:Plane Shadowmoor
|
||||||
S:Mode$ Continuous | EffectZone$ Command | Affected$ Creature | AddKeyword$ Wither | Description$ All Creatures have Wither (They deal damage to creatures in the form of -1/-1 counters.)
|
S:Mode$ Continuous | EffectZone$ Command | Affected$ Creature | AddKeyword$ Wither | Description$ All Creatures have Wither (They deal damage to creatures in the form of -1/-1 counters.)
|
||||||
T:Mode$ ChaosEnsues | OptionalDecider$ You | TriggerZones$ Command | Execute$ TrigPutCounter | TriggerDescription$ Whenever chaos ensues, put a -1/-1 counter on target creature, two -1/-1 counters on another target creature, and three -1/-1 counters on a third target creature.
|
T:Mode$ ChaosEnsues | TriggerZones$ Command | Execute$ TrigPutCounter | TriggerDescription$ Whenever chaos ensues, put a -1/-1 counter on target creature, two -1/-1 counters on another target creature, and three -1/-1 counters on a third target creature.
|
||||||
SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature | TargetUnique$ True | CounterType$ M1M1 | SubAbility$ DBPutTwo
|
SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature | TargetUnique$ True | CounterType$ M1M1 | SubAbility$ DBPutTwo
|
||||||
SVar:DBPutTwo:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select another target creature | TargetUnique$ True | CounterType$ M1M1 | CounterNum$ 2 | SubAbility$ DBPutThree
|
SVar:DBPutTwo:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select another target creature | TargetUnique$ True | CounterType$ M1M1 | CounterNum$ 2 | SubAbility$ DBPutThree
|
||||||
SVar:DBPutThree:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select a third target creature | TargetUnique$ True | CounterType$ M1M1 | CounterNum$ 3
|
SVar:DBPutThree:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select a third target creature | TargetUnique$ True | CounterType$ M1M1 | CounterNum$ 3
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ Types:Legendary Creature Goblin Rebel
|
|||||||
PT:2/2
|
PT:2/2
|
||||||
K:Trample
|
K:Trample
|
||||||
K:Haste
|
K:Haste
|
||||||
T:Mode$ Attacks | ValidCard$ Creature.equipped+Other | Execute$ TrigAttach | TriggerZones$ Battlefield | TriggerDescription$ Whenever an equipped creature you control other than CARDNAME attacks or dies, you may attach all Equipment attached to that creature to NICKNAME.
|
T:Mode$ Attacks | ValidCard$ Creature.equipped+YouCtrl+Other | Execute$ TrigAttach | TriggerZones$ Battlefield | TriggerDescription$ Whenever an equipped creature you control other than CARDNAME attacks or dies, you may attach all Equipment attached to that creature to NICKNAME.
|
||||||
T:Mode$ ChangesZone | ValidCard$ Creature.equipped+Other | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigAttach | TriggerZones$ Battlefield | OptionalDecider$ You | Secondary$ True | TriggerDescription$ Whenever an equipped creature you control other than CARDNAME attacks or dies, you may attach all Equipment attached to that creature to NICKNAME.
|
T:Mode$ ChangesZone | ValidCard$ Creature.equipped+YouCtrl+Other | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigAttach | TriggerZones$ Battlefield | OptionalDecider$ You | Secondary$ True | TriggerDescription$ Whenever an equipped creature you control other than CARDNAME attacks or dies, you may attach all Equipment attached to that creature to NICKNAME.
|
||||||
SVar:TrigAttach:DB$ Attach | Object$ AttachedTo TriggeredCard.Equipment | Defined$ Self
|
SVar:TrigAttach:DB$ Attach | Object$ AttachedTo TriggeredCard.Equipment | Defined$ Self
|
||||||
DeckNeeds:Type$Equipment
|
DeckNeeds:Type$Equipment
|
||||||
Oracle:Trample, haste\nWhenever an equipped creature you control other than Rhuk, Hexgold Nabber attacks or dies, you may attach all Equipment attached to that creature to Rhuk.
|
Oracle:Trample, haste\nWhenever an equipped creature you control other than Rhuk, Hexgold Nabber attacks or dies, you may attach all Equipment attached to that creature to Rhuk.
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ Name:Rift Elemental
|
|||||||
ManaCost:R
|
ManaCost:R
|
||||||
Types:Creature Elemental
|
Types:Creature Elemental
|
||||||
PT:1/1
|
PT:1/1
|
||||||
A:AB$ Pump | Cost$ 1 R SubCounter<1/TIME/Permanent/permanent you control> | Defined$ Self | NumAtt$ 2 | SpellDescription$ CARDNAME gets +2/+0 until end of turn.
|
A:AB$ Pump | Cost$ 1 R SubCounter<1/TIME/Permanent.inZoneBattlefield;Card.suspended/a permanent you control or suspended card you own/Battlefield,Exile> | Defined$ Self | NumAtt$ 2 | SpellDescription$ CARDNAME gets +2/+0 until end of turn.
|
||||||
A:AB$ Pump | Cost$ 1 R SubCounter<1/TIME/Card.suspended/suspended card you own/Exile> | Defined$ Self | NumAtt$ 2 | SpellDescription$ CARDNAME gets +2/+0 until end of turn.
|
|
||||||
AI:RemoveDeck:All
|
AI:RemoveDeck:All
|
||||||
Oracle:{1}{R}, Remove a time counter from a permanent you control or suspended card you own: Rift Elemental gets +2/+0 until end of turn.
|
Oracle:{1}{R}, Remove a time counter from a permanent you control or suspended card you own: Rift Elemental gets +2/+0 until end of turn.
|
||||||
|
|||||||
16
forge-gui/res/cardsfolder/s/sycorax_commander.txt
Normal file
16
forge-gui/res/cardsfolder/s/sycorax_commander.txt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
Name:Sycorax Commander
|
||||||
|
ManaCost:2 B R
|
||||||
|
Types:Creature Alien Soldier
|
||||||
|
PT:4/2
|
||||||
|
K:First Strike
|
||||||
|
K:Haste
|
||||||
|
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigChoice | TriggerDescription$ Sanctified Rules of Combat — When CARDNAME enters the battlefield, each opponent faces a villainous choice — That opponent discards all the cards in their hand, then draws that many cards minus one, or CARDNAME deals damage to that player equal to the number of cards in their hand.
|
||||||
|
SVar:TrigChoice:DB$ VillainousChoice | Defined$ Opponent | Choices$ DBDiscard,DBDamage
|
||||||
|
SVar:DBDiscard:DB$ Discard | Defined$ Player.IsRemembered | Mode$ Hand | RememberDiscarded$ True | SubAbility$ DBDraw | SpellDescription$ Opponent discards their hand, then draws that many cards minus one.
|
||||||
|
SVar:DBDraw:DB$ Draw | NumCards$ X | Defined$ Player.IsRemembered | SubAbility$ CleanDrawn
|
||||||
|
SVar:CleanDrawn:DB$ Cleanup | ClearRemembered$ True
|
||||||
|
# This calculation isn't considering the remembered player, only the remembered cards
|
||||||
|
SVar:X:Remembered$Amount/Minus.1
|
||||||
|
SVar:DBDamage:DB$ DealDamage | Defined$ Player.IsRemembered | NumDmg$ Y | SpellDescription$ CARDNAME deals damage to opponent equal to the number of cards in their hand.
|
||||||
|
SVar:Y:Count$ValidHand Card.OwnedBy Player.IsRemembered
|
||||||
|
Oracle:First strike, haste\nSanctified Rules of Combat — When Sycorax Commander enters the battlefield, each opponent faces a villainous choice — That opponent discards all the cards in their hand, then draws that many cards minus one, or Sycorax Commander deals damage to that player equal to the number of cards in their hand.
|
||||||
13
forge-gui/res/cardsfolder/t/the_dalek_emperor.txt
Normal file
13
forge-gui/res/cardsfolder/t/the_dalek_emperor.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Name:The Dalek Emperor
|
||||||
|
ManaCost:5 B R
|
||||||
|
Types:Legendary Artifact Creature Dalek
|
||||||
|
PT:6/6
|
||||||
|
K:Affinity:Dalek
|
||||||
|
S:Mode$ Continuous | Affected$ Dalek.Other+YouCtrl | AddKeyword$ Haste | Description$ Other Daleks you control have haste.
|
||||||
|
T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigChoice | TriggerDescription$ At the beginning of combat on your turn, each opponent faces a villainous choice — That player sacrifices a creature they control, or you create a 3/3 black Dalek artifact creature token with menace.
|
||||||
|
SVar:TrigChoice:DB$ VillainousChoice | Defined$ Opponent | Choices$ DBSacrifice,DBToken
|
||||||
|
SVar:DBSacrifice:DB$ Sacrifice | Defined$ Remembered | SacValid$ Creature | SacMessage$ Creature | SpellDescription$ Opponent sacrifices a creature.
|
||||||
|
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ b_3_3_a_dalek_menace | TokenOwner$ You | SpellDescription$ You create a 3/3 black Dalek artifact creature token with menace.
|
||||||
|
DeckHints:Type$Dalek
|
||||||
|
DeckHas:Ability$Token
|
||||||
|
Oracle:Affinity for Daleks (This spell costs {1} less to cast for each Dalek you control.)\nOther Daleks you control have haste.\nAt the beginning of combat on your turn, each opponent faces a villainous choice — That player sacrifices a creature they control, or you create a 3/3 black Dalek artifact creature token with menace.
|
||||||
15
forge-gui/res/cardsfolder/t/the_master_gallifreys_end.txt
Normal file
15
forge-gui/res/cardsfolder/t/the_master_gallifreys_end.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
Name:The Master, Gallifrey's End
|
||||||
|
ManaCost:2 B R
|
||||||
|
Types:Legendary Creature Time Lord Rogue
|
||||||
|
PT:4/3
|
||||||
|
T:Mode$ ChangesZone | ValidCard$ Creature.Artifact+nonToken+YouCtrl | Origin$ Battlefield | Destination$ Graveyard | OptionalDecider$ You | Execute$ TrigExile | TriggerZones$ Battlefield | TriggerDescription$ Make Them Pay — Whenever a nontoken artifact creature you control dies, you may exile it. If you do, choose an opponent with the most life among your opponents. That player faces a villainous choice — They lose 4 life, or you create a token that's a copy of that card.
|
||||||
|
SVar:TrigExile:DB$ ChangeZone | RememberChanged$ True | Origin$ Graveyard | Destination$ Exile | Defined$ TriggeredNewCardLKICopy | SubAbility$ DBChoosePlayer
|
||||||
|
SVar:DBChoosePlayer:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent+lifeEQX | SubAbility$ DBChoice
|
||||||
|
SVar:X:Count$OppGreatestLifeTotal
|
||||||
|
SVar:DBChoice:DB$ VillainousChoice | Defined$ ChosenPlayer | Choices$ DBLoseLife,DBCopy | SubAbility$ DBCleanup
|
||||||
|
SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 4 | Defined$ Player.IsRemembered | SpellDescription$ Opponent loses 4 life.
|
||||||
|
SVar:DBCopy:DB$ CopyPermanent | Defined$ Remembered | SubAbility$ DBCleanup | SpellDescription$ You create a token that's a copy of the exiled card.
|
||||||
|
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||||
|
DeckHints:Type$Artifact
|
||||||
|
DeckHas:Ability$Token
|
||||||
|
Oracle:Make Them Pay — Whenever a nontoken artifact creature you control dies, you may exile it. If you do, choose an opponent with the most life among your opponents. That player faces a villainous choice — They lose 4 life, or you create a token that's a copy of that card.
|
||||||
@@ -4,6 +4,6 @@ Types:Enchantment Saga
|
|||||||
K:Chapter:2:DBDestroyAll,DBChangeZone
|
K:Chapter:2:DBDestroyAll,DBChangeZone
|
||||||
SVar:DBDestroyAll:DB$ DestroyAll | ValidCards$ Creature | SpellDescription$ Destroy all creatures.
|
SVar:DBDestroyAll:DB$ DestroyAll | ValidCards$ Creature | SpellDescription$ Destroy all creatures.
|
||||||
SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.Legendary+YouCtrl | TgtPrompt$ Choose target legendary creature card in your graveyard | SubAbility$ DBPutCounter | RememberChanged$ True | SpellDescription$ Return target legendary creature card from your graveyard to the battlefield. Put your choice of a first strike, vigilance, or lifelink counter on it.
|
SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.Legendary+YouCtrl | TgtPrompt$ Choose target legendary creature card in your graveyard | SubAbility$ DBPutCounter | RememberChanged$ True | SpellDescription$ Return target legendary creature card from your graveyard to the battlefield. Put your choice of a first strike, vigilance, or lifelink counter on it.
|
||||||
SVar:DBPutCounter:DB$ PutCounter | Choices$ Creature.IsRemembered | CounterType$ First strike,Vigilance,Lifelink | CounterNum$ 1| SubAbility$ DBCleanup
|
SVar:DBPutCounter:DB$ PutCounter | Choices$ Creature.IsRemembered | CounterType$ First Strike,Vigilance,Lifelink | CounterNum$ 1| SubAbility$ DBCleanup
|
||||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||||
Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after II.)\nI — Destroy all creatures.\nII — Return target legendary creature card from your graveyard to the battlefield. Put your choice of a first strike, vigilance, or lifelink counter on it.
|
Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after II.)\nI — Destroy all creatures.\nII — Return target legendary creature card from your graveyard to the battlefield. Put your choice of a first strike, vigilance, or lifelink counter on it.
|
||||||
|
|||||||
8
forge-gui/res/cardsfolder/t/this_is_how_it_ends.txt
Normal file
8
forge-gui/res/cardsfolder/t/this_is_how_it_ends.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Name:This Is How It Ends
|
||||||
|
ManaCost:3 B
|
||||||
|
Types:Instant
|
||||||
|
A:SP$ ChangeZone | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Library | Shuffle$ True | SubAbility$ DBChoice | SpellDescription$ The owner of target creature shuffles it into their library,
|
||||||
|
SVar:DBChoice:DB$ VillainousChoice | Defined$ TargetedOwner | Choices$ DBLoseLife,DBShuffleAnother | SpellDescription$ then faces a villainous choice — They lose 5 life, or they shuffle another creature they own into their library.
|
||||||
|
SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 5 | Defined$ Remembered | SpellDescription$ Targeted creature's owner loses 5 life.
|
||||||
|
SVar:DBShuffleAnother:DB$ ChangeZone | ChangeType$ Creature.OwnedBy Remembered | DefinedPlayer$ Remembered | Chooser$ Remembered | ChangeNum$ 1 | Mandatory$ True | Origin$ Battlefield | Destination$ Library | Shuffle$ True | Hidden$ True | SpellDescription$ Targeted creature's owner shuffles another creature they own into their library.
|
||||||
|
Oracle:Target creature's owner shuffles it into their library, then faces a villainous choice — They lose 5 life, or they shuffle another creature they own into their library.
|
||||||
@@ -3,7 +3,7 @@ ManaCost:1 U
|
|||||||
Types:Instant Arcane
|
Types:Instant Arcane
|
||||||
K:Splice:Arcane:Return<1/Creature.Blue/blue creature>
|
K:Splice:Arcane:Return<1/Creature.Blue/blue creature>
|
||||||
A:SP$ Pump | ValidTgts$ Creature | KW$ Shroud | SubAbility$ DBUnblockable | StackDescription$ REP Target creature_{c:Targeted} | SpellDescription$ Target creature gains shroud until end of turn and can't be blocked this turn. (A creature with shroud can't be the target of spells or abilities.)
|
A:SP$ Pump | ValidTgts$ Creature | KW$ Shroud | SubAbility$ DBUnblockable | StackDescription$ REP Target creature_{c:Targeted} | SpellDescription$ Target creature gains shroud until end of turn and can't be blocked this turn. (A creature with shroud can't be the target of spells or abilities.)
|
||||||
SVar:DBUnblockable:DB$ Effect | RememberObjects$ Self | ExileOnMoved$ Battlefield | StaticAbilities$ Unblockable
|
SVar:DBUnblockable:DB$ Effect | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | StaticAbilities$ Unblockable
|
||||||
SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn.
|
SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn.
|
||||||
AI:RemoveDeck:All
|
AI:RemoveDeck:All
|
||||||
DeckHints:Type$Arcane
|
DeckHints:Type$Arcane
|
||||||
|
|||||||
@@ -1036,7 +1036,7 @@ public class HumanCostDecision extends CostDecisionMakerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, 1, 1, validCards, ability);
|
final InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, 1, 1, validCards, ability);
|
||||||
inp.setMessage(Localizer.getInstance().getMessage("lblRemoveCountersFromAInZoneCard", cost.zone.getTranslatedName()));
|
inp.setMessage(Localizer.getInstance().getMessage("lblRemoveCountersFromAInZoneCard", Lang.joinHomogenous(cost.zone, ZoneType.Accessors.GET_TRANSLATED_NAME)));
|
||||||
inp.setCancelAllowed(true);
|
inp.setCancelAllowed(true);
|
||||||
inp.showAndWait();
|
inp.showAndWait();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user