Merge remote-tracking branch 'upstream/master' into editions-type-review

This commit is contained in:
leriomaggio
2021-06-21 11:45:15 +01:00
553 changed files with 1603 additions and 1140 deletions

1
.gitignore vendored
View File

@@ -17,6 +17,7 @@
.vscode/settings.json
.vscode/launch.json
.factorypath
# Ignore NetBeans config files

View File

@@ -1819,7 +1819,7 @@ public class AiController {
// AI would play everything. But limits to one copy of (Leyline of Singularity) and (Gemstone Caverns)
List<SpellAbility> result = Lists.newArrayList();
for(SpellAbility sa : usableFromOpeningHand) {
for (SpellAbility sa : usableFromOpeningHand) {
// Is there a better way for the AI to decide this?
if (doTrigger(sa, false)) {
result.add(sa);
@@ -1830,7 +1830,7 @@ public class AiController {
SpellAbility saGemstones = null;
List<SpellAbility> toRemove = Lists.newArrayList();
for(SpellAbility sa : result) {
for (SpellAbility sa : result) {
String srcName = sa.getHostCard().getName();
if ("Gemstone Caverns".equals(srcName)) {
if (saGemstones == null)
@@ -2227,11 +2227,11 @@ public class AiController {
private boolean checkAiSpecificRestrictions(final SpellAbility sa) {
// AI-specific restrictions specified as activation parameters in spell abilities
if (sa.hasParam("AILifeThreshold")) {
return player.getLife() > Integer.parseInt(sa.getParam("AILifeThreshold"));
}
return true;
}

View File

@@ -354,7 +354,6 @@ public class AiCostDecision extends CostDecisionMakerBase {
return PaymentDecision.number(c);
}
@Override
public PaymentDecision visit(CostPutCardToLib cost) {
if (cost.payCostFromSource()) {

View File

@@ -331,11 +331,14 @@ public class ComputerUtil {
}
public static Card getCardPreference(final Player ai, final Card activate, final String pref, final CardCollection typeList) {
return getCardPreference(ai, activate, pref, typeList, null);
}
public static Card getCardPreference(final Player ai, final Card activate, final String pref, final CardCollection typeList, SpellAbility sa) {
final Game game = ai.getGame();
String prefDef = "";
if (activate != null) {
prefDef = activate.getSVar("AIPreference");
final String[] prefGroups = activate.getSVar("AIPreference").split("\\|");
final String[] prefGroups = prefDef.split("\\|");
for (String prefGroup : prefGroups) {
final String[] prefValid = prefGroup.trim().split("\\$");
if (prefValid[0].equals(pref) && !prefValid[1].startsWith("Special:")) {
@@ -346,8 +349,8 @@ public class ComputerUtil {
for (String validItem : prefValid[1].split(",")) {
final CardCollection prefList = CardLists.getValidCards(typeList, validItem, activate.getController(), activate, null);
int threshold = getAIPreferenceParameter(activate, "CreatureEvalThreshold");
int minNeeded = getAIPreferenceParameter(activate, "MinCreaturesBelowThreshold");
int threshold = getAIPreferenceParameter(activate, "CreatureEvalThreshold", sa);
int minNeeded = getAIPreferenceParameter(activate, "MinCreaturesBelowThreshold", sa);
if (threshold != -1) {
List<Card> toRemove = Lists.newArrayList();
@@ -390,7 +393,7 @@ public class ComputerUtil {
final CardCollection sacMeList = CardLists.filter(typeList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (c.hasSVar("SacMe") && (Integer.parseInt(c.getSVar("SacMe")) == priority));
return c.hasSVar("SacMe") && (Integer.parseInt(c.getSVar("SacMe")) == priority);
}
});
if (!sacMeList.isEmpty()) {
@@ -419,6 +422,7 @@ public class ComputerUtil {
if (!nonCreatures.isEmpty()) {
return ComputerUtilCard.getWorstAI(nonCreatures);
} else if (!typeList.isEmpty()) {
// TODO make sure survival is possible in case the creature blocks a trampler
return ComputerUtilCard.getWorstAI(typeList);
}
}
@@ -505,7 +509,7 @@ public class ComputerUtil {
return null;
}
public static int getAIPreferenceParameter(final Card c, final String paramName) {
public static int getAIPreferenceParameter(final Card c, final String paramName, SpellAbility sa) {
if (!c.hasSVar("AIPreferenceParams")) {
return -1;
}
@@ -520,7 +524,21 @@ public class ComputerUtil {
case "CreatureEvalThreshold":
// Threshold of 150 is just below the level of a 1/1 mana dork or a 2/2 baseline creature with no keywords
if (paramName.equals(parName)) {
return Integer.parseInt(parValue);
int num = 0;
try {
num = Integer.parseInt(parValue);
} catch (NumberFormatException nfe) {
String[] valParts = StringUtils.split(parValue, "/");
CardCollection foundCards = AbilityUtils.getDefinedCards(c, valParts[0], sa);
if (!foundCards.isEmpty()) {
num = ComputerUtilCard.evaluateCreature(foundCards.get(0));
}
valParts[0] = Integer.toString(num);
if (valParts.length > 1) {
num = AbilityUtils.doXMath(num, valParts[1], c, sa);
}
}
return num;
}
break;
case "MinCreaturesBelowThreshold":
@@ -543,9 +561,8 @@ public class ComputerUtil {
typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability));
if ((target != null) && target.getController() == ai) {
typeList.remove(target); // don't sacrifice the card we're pumping
}
// don't sacrifice the card we're pumping
typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, ability, ai);
if (typeList.size() < amount) {
return null;
@@ -573,9 +590,8 @@ public class ComputerUtil {
final Card target, final int amount, SpellAbility sa) {
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa);
if ((target != null) && target.getController() == ai) {
typeList.remove(target); // don't exile the card we're pumping
}
// don't exile the card we're pumping
typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai);
if (typeList.size() < amount) {
return null;
@@ -594,9 +610,8 @@ public class ComputerUtil {
final Card target, final int amount, SpellAbility sa) {
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa);
if ((target != null) && target.getController() == ai) {
typeList.remove(target); // don't move the card we're pumping
}
// don't move the card we're pumping
typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai);
if (typeList.size() < amount) {
return null;
@@ -718,12 +733,10 @@ public class ComputerUtil {
}
public static CardCollection chooseReturnType(final Player ai, final String type, final Card activate, final Card target, final int amount, SpellAbility sa) {
final CardCollection typeList =
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
if ((target != null) && target.getController() == ai) {
// don't bounce the card we're pumping
typeList.remove(target);
}
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
// don't bounce the card we're pumping
typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai);
if (typeList.size() < amount) {
return new CardCollection();
@@ -743,7 +756,7 @@ public class ComputerUtil {
CardCollection remaining = new CardCollection(cardlist);
final CardCollection sacrificed = new CardCollection();
final Card host = source.getHostCard();
final int considerSacThreshold = getAIPreferenceParameter(host, "CreatureEvalThreshold");
final int considerSacThreshold = getAIPreferenceParameter(host, "CreatureEvalThreshold", source);
if ("OpponentOnly".equals(source.getParam("AILogic"))) {
if(!source.getActivatingPlayer().isOpponentOf(ai)) {
@@ -3026,6 +3039,6 @@ public class ComputerUtil {
}
}
return false;
}
}

View File

@@ -8,6 +8,8 @@ import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@@ -20,6 +22,7 @@ import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardUtil;
import forge.game.card.CounterEnumType;
@@ -270,7 +273,10 @@ public class ComputerUtilCost {
}
final CardCollection sacList = new CardCollection();
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility);
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility);
// don't sacrifice the card we're pumping
typeList = paymentChoicesWithoutTargets(typeList, sourceAbility, ai);
int count = 0;
while (count < amount) {
@@ -320,11 +326,14 @@ public class ComputerUtilCost {
}
final CardCollection sacList = new CardCollection();
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility);
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility);
// don't sacrifice the card we're pumping
typeList = paymentChoicesWithoutTargets(typeList, sourceAbility, ai);
int count = 0;
while (count < amount) {
Card prefCard = ComputerUtil.getCardPreference(ai, source, "SacCost", typeList);
Card prefCard = ComputerUtil.getCardPreference(ai, source, "SacCost", typeList, sourceAbility);
if (prefCard == null) {
return false;
}
@@ -407,7 +416,7 @@ public class ComputerUtilCost {
* @return true, if successful
*/
public static boolean checkSacrificeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sourceAbility) {
return checkSacrificeCost(ai, cost, source, sourceAbility,true);
return checkSacrificeCost(ai, cost, source, sourceAbility, true);
}
/**
@@ -420,8 +429,8 @@ public class ComputerUtilCost {
* @param cost
* @return a boolean.
*/
@Deprecated
public static boolean shouldPayCost(final Player ai, final Card hostCard, final Cost cost) {
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostPayLife) {
if (!ai.cantLoseForZeroOrLessLife()) {
@@ -741,4 +750,12 @@ public class ComputerUtilCost {
}
return ObjectUtils.defaultIfNull(val, 0);
}
public static CardCollection paymentChoicesWithoutTargets(Iterable<Card> choices, SpellAbility source, Player ai) {
if (source.usesTargeting()) {
final CardCollection targets = new CardCollection(source.getTargets().getTargetCards());
choices = Iterables.filter(choices, Predicates.not(Predicates.and(CardPredicates.isController(ai), Predicates.in(targets))));
}
return new CardCollection(choices);
}
}

View File

@@ -788,7 +788,6 @@ public class ComputerUtilMana {
String manaProduced = ignoreColor || ignoreType ? MagicColor.toShortString(toPay.getColorMask())
: predictManafromSpellAbility(saPayment, ai, toPay);
// System.out.println(manaProduced);
payMultipleMana(cost, manaProduced, ai);
// remove from available lists
@@ -1072,7 +1071,6 @@ public class ComputerUtilMana {
getComboManaChoice(ai, saPayment, sa, cost);
}
else if (saPayment.getApi() == ApiType.ManaReflected) {
//System.out.println("Evaluate reflected mana of: " + saPayment.getHostCard());
Set<String> reflected = CardUtil.getReflectableManaColors(saPayment);
for (byte c : MagicColor.WUBRG) {
@@ -1281,7 +1279,7 @@ public class ComputerUtilMana {
final AbilityManaPart abMana = manaAb.getManaPart();
if (abMana.isComboMana()) {
int amount = manaAb.hasParam("Amount") ? AbilityUtils.calculateAmount(source, manaAb.getParam("Amount"), saRoot) : 1;
int amount = manaAb.hasParam("Amount") ? AbilityUtils.calculateAmount(source, manaAb.getParam("Amount"), manaAb) : 1;
final ManaCostBeingPaid testCost = new ManaCostBeingPaid(cost);
final String[] comboColors = abMana.getComboColors().split(" ");
for (int nMana = 1; nMana <= amount; nMana++) {
@@ -1301,7 +1299,7 @@ public class ComputerUtilMana {
if (!testCost.isPaid()) {
// Loop over combo colors
for (String color : comboColors) {
if (satisfiesColorChoice(abMana, choiceString, choice) && testCost.isAnyPartPayableWith(ManaAtom.fromName(color), ai.getManaPool())) {
if (satisfiesColorChoice(abMana, choiceString, choice) && testCost.needsColor(ManaAtom.fromName(color), ai.getManaPool())) {
payMultipleMana(testCost, color, ai);
if (nMana != 1) {
choiceString.append(" ");

View File

@@ -137,6 +137,23 @@ public class PlayerControllerAi extends PlayerController {
return new HashMap<>();
}
@Override
public Map<Byte, Integer> specifyManaCombo(SpellAbility sa, ColorSet colorSet, int manaAmount, boolean different) {
Map<Byte, Integer> result = new HashMap<>();
for (int i = 0; i < manaAmount; ++i) {
Byte chosen = chooseColor("", sa, colorSet);
if (result.containsKey(chosen)) {
result.put(chosen, result.get(chosen) + 1);
} else {
result.put(chosen, 1);
}
if (different) {
colorSet = ColorSet.fromMask(colorSet.getColor() - chosen);
}
}
return result;
}
@Override
public Integer announceRequirements(SpellAbility ability, String announce) {
// For now, these "announcements" are made within the AI classes of the appropriate SA effects

View File

@@ -289,7 +289,6 @@ public abstract class SpellAbilityAi {
if (sa.isSpell() && !sa.isBuyBackAbility()) {
return false;
}
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
}

View File

@@ -984,7 +984,7 @@ public class AttachAi extends SpellAbilityAi {
List<GameObject> targets = new ArrayList<>();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
targets = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
targets = AbilityUtils.getDefinedObjects(card, sa.getParam("Defined"), sa);
} else {
AttachAi.attachPreference(sa, tgt, mandatory);
targets = sa.getTargets();
@@ -1344,7 +1344,7 @@ public class AttachAi extends SpellAbilityAi {
CardCollection list = null;
if (tgt == null) {
list = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
list = AbilityUtils.getDefinedCards(attachSource, sa.getParam("Defined"), sa);
} else {
list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(tgt.getZone()), tgt.getValidTgts(), sa.getActivatingPlayer(), attachSource, sa);

View File

@@ -246,7 +246,6 @@ public class ChooseCardAi extends SpellAbilityAi {
return true;
}
});
System.out.println("Tangle Wire" + options + " - " + betterList);
if (!betterList.isEmpty()) {
choice = betterList.get(0);
} else {

View File

@@ -68,7 +68,6 @@ import forge.util.Aggregates;
public class ControlGainAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
final List<String> lose = Lists.newArrayList();
if (sa.hasParam("LoseControl")) {

View File

@@ -29,7 +29,6 @@ import forge.util.collect.FCollection;
public class CountersMoveAi extends SpellAbilityAi {
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if (sa.usesTargeting()) {
sa.resetTargets();
if (!moveTgtAI(ai, sa)) {
@@ -83,8 +82,7 @@ public class CountersMoveAi extends SpellAbilityAi {
return true;
}
// something you can't block, try to reduce its
// attack
// something you can't block, try to reduce its attack
if (!ComputerUtilCard.canBeBlockedProfitably(ai, cpy)) {
return true;
}
@@ -119,7 +117,6 @@ public class CountersMoveAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
sa.resetTargets();
@@ -237,7 +234,6 @@ public class CountersMoveAi extends SpellAbilityAi {
}
private boolean moveTgtAI(final Player ai, final SpellAbility sa) {
final Card host = sa.getHostCard();
final Game game = ai.getGame();
final String type = sa.getParam("CounterType");
@@ -283,8 +279,7 @@ public class CountersMoveAi extends SpellAbilityAi {
// cant use substract on Copy
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
// do not steal a P1P1 from Undying if it would die
// this way
// do not steal a P1P1 from Undying if it would die this way
if (cType != null && cType.is(CounterEnumType.P1P1) && srcCardCpy.getNetToughness() <= 0) {
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken();
}
@@ -373,8 +368,13 @@ public class CountersMoveAi extends SpellAbilityAi {
}
Card lki = CardUtil.getLKICopy(src);
lki.clearCounters();
// go for opponent when value implies debuff
if (cType == null) {
lki.clearCounters();
}
else {
lki.setCounters(cType, 0);
}
// go for opponent when higher value implies debuff
if (ComputerUtilCard.evaluateCreature(src) > ComputerUtilCard.evaluateCreature(lki)) {
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
if (!aiList.isEmpty()) {
@@ -419,6 +419,12 @@ public class CountersMoveAi extends SpellAbilityAi {
return true;
}
}
final boolean isMandatoryTrigger = (sa.isTrigger() && !sa.isOptionalTrigger())
|| (sa.getRootAbility().isTrigger() && !sa.getRootAbility().isOptionalTrigger());
if (!isMandatoryTrigger) {
// no good target
return false;
}
}
// move counter to opponents creature but only if you can not steal them

View File

@@ -64,11 +64,9 @@ public class CountersPutAi extends SpellAbilityAi {
*/
@Override
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
final String type = sa.getParam("CounterType");
final String aiLogic = sa.getParamOrDefault("AILogic", "");
// TODO Auto-generated method stub
if (!super.willPayCosts(ai, sa, cost, source)) {
return false;
}
@@ -225,8 +223,7 @@ public class CountersPutAi extends SpellAbilityAi {
}
if (sa.canTarget(ai)) {
// don't target itself when its forced to add poison
// counters too
// don't target itself when its forced to add poison counters too
if (!ai.getCounters().isEmpty()) {
if (!eachExisting || ai.getPoisonCounters() < 5) {
sa.getTargets().add(ai);
@@ -480,7 +477,6 @@ public class CountersPutAi extends SpellAbilityAi {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
// don't put the counter on the dead creature
if (sacSelf && c.equals(source)) {
return false;
@@ -493,6 +489,8 @@ public class CountersPutAi extends SpellAbilityAi {
Card sacTarget = ComputerUtil.getCardPreference(ai, source, "SacCost", list);
// this card is planned to be sacrificed during cost payment, so don't target it
// (otherwise the AI can cheat by activating this SA and not paying the sac cost, e.g. Extruder)
// TODO needs update if amount > 1 gets printed,
// maybe also check putting the counter on that exact creature is more important than sacrificing it (though unlikely?)
list.remove(sacTarget);
}
@@ -617,7 +615,7 @@ public class CountersPutAi extends SpellAbilityAi {
// Instant +1/+1
if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa)) {
if (!(ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN) && abCost.isReusuableResource())) {
return false; // only if next turn and cost is reusable
return false; // only if next turn and cost is reusable
}
}
}

View File

@@ -195,7 +195,6 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
*/
@Override
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
if (options.size() > 1) {
final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();

View File

@@ -81,7 +81,6 @@ public class CountersRemoveAi extends SpellAbilityAi {
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final String type = sa.getParam("CounterType");
if (sa.usesTargeting()) {

View File

@@ -46,7 +46,7 @@ public class DamageAllAi extends SpellAbilityAi {
int x = -1;
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
if (damage.equals("X") && sa.getSVar(damage).equals("Count$Converge")) {
dmg = ComputerUtilMana.getConvergeCount(sa, ai);
}
@@ -202,7 +202,7 @@ public class DamageAllAi extends SpellAbilityAi {
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
sa.setXManaCostPaid(dmg);
} else {
dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
dmg = AbilityUtils.calculateAmount(source, damage, sa);
}
if (sa.hasParam("ValidPlayers")) {
@@ -286,7 +286,7 @@ public class DamageAllAi extends SpellAbilityAi {
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
sa.setXManaCostPaid(dmg);
} else {
dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
dmg = AbilityUtils.calculateAmount(source, damage, sa);
}
if (sa.hasParam("ValidPlayers")) {

View File

@@ -39,6 +39,7 @@ import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices;
@@ -52,10 +53,9 @@ public class DamageDealAi extends DamageAiBase {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
final String logic = sa.getParam("AILogic");
Card source = sa.getHostCard();
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
final String logic = sa.getParam("AILogic");
if ("MadSarkhanDigDmg".equals(logic)) {
return SpecialCardAi.SarkhanTheMad.considerDig(ai, sa);
@@ -105,7 +105,7 @@ public class DamageDealAi extends DamageAiBase {
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
if (damage.equals("X")) {
if (sa.getSVar(damage).equals("Count$xPaid") || sourceName.equals("Crater's Claws")) {
@@ -531,7 +531,7 @@ public class DamageDealAi extends DamageAiBase {
&& "P1P1".equals(sa.getParent().getParam("CounterType"))) {
// assuming the SA parent is of PutCounter type. Perhaps it's possible to predict counter multipliers here somehow?
final String amountStr = sa.getParent().getParamOrDefault("CounterNum", "1");
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
final int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
dmg += amount;
}
@@ -844,7 +844,7 @@ public class DamageDealAi extends DamageAiBase {
// this is for Triggered targets that are mandatory
final boolean noPrevention = sa.hasParam("NoPrevention");
final boolean divided = sa.isDividedAsYouChoose();
final Player opp = ai.getWeakestOpponent();
PlayerCollection opps = ai.getOpponents();
while (sa.canAddMoreTarget()) {
if (tgt.canTgtPlaneswalker()) {
@@ -872,13 +872,17 @@ public class DamageDealAi extends DamageAiBase {
}
}
if (sa.canTarget(opp)) {
if (sa.getTargets().add(opp)) {
if (divided) {
sa.addDividedAllocation(opp, dmg);
break;
if (!opps.isEmpty()) {
Player opp = opps.getFirst();
opps.remove(opp);
if (sa.canTarget(opp)) {
if (sa.getTargets().add(opp)) {
if (divided) {
sa.addDividedAllocation(opp, dmg);
break;
}
continue;
}
continue;
}
}

View File

@@ -244,7 +244,7 @@ public class DrawAi extends SpellAbilityAi {
int numCards = 1;
if (sa.hasParam("NumCards")) {
numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
numCards = AbilityUtils.calculateAmount(source, sa.getParam("NumCards"), sa);
}
boolean xPaid = false;

View File

@@ -67,8 +67,7 @@ public class FightAi extends SpellAbilityAi {
for (Card humanCreature : humCreatures) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= fighter1.getNetPower()
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(fighter1)) {
// todo: check min/max targets; see if we picked the best
// matchup
// todo: check min/max targets; see if we picked the best matchup
sa.getTargets().add(humanCreature);
return true;
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
@@ -85,8 +84,7 @@ public class FightAi extends SpellAbilityAi {
for (Card aiCreature : aiCreatures) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(aiCreature)) {
// todo: check min/max targets; see if we picked the
// best matchup
// todo: check min/max targets; see if we picked the best matchup
sa.getTargets().add(humanCreature);
sa.getTargets().add(aiCreature);
return true;

View File

@@ -114,7 +114,7 @@ public class LifeSetAi extends SpellAbilityAi {
sa.setXManaCostPaid(xPay);
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
// special cases when amount can't be calculated without targeting first

View File

@@ -78,7 +78,7 @@ public class MustBlockAi extends SpellAbilityAi {
Card attacker = null;
if (sa.hasParam("DefinedAttacker")) {
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedAttacker"), sa);
final List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedAttacker"), sa);
if (cards.isEmpty()) {
return false;
}

View File

@@ -33,7 +33,6 @@ public class PermanentAi extends SpellAbilityAi {
*/
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Card card = sa.getHostCard();
if (card.hasKeyword("MayFlashSac") && !ai.couldCastSorcery(sa)) {
@@ -51,7 +50,6 @@ public class PermanentAi extends SpellAbilityAi {
*/
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
final Card card = sa.getHostCard();
final Game game = ai.getGame();

View File

@@ -58,7 +58,6 @@ public class PermanentCreatureAi extends PermanentAi {
*/
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Card card = sa.getHostCard();
final Game game = ai.getGame();

View File

@@ -27,7 +27,6 @@ public class PermanentNoncreatureAi extends PermanentAi {
*/
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if (!super.checkApiLogic(ai, sa))
return false;

View File

@@ -63,7 +63,7 @@ public class PlayAi extends SpellAbilityAi {
return false;
}
} else if (!sa.hasParam("Valid")) {
cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
cards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
if (cards.isEmpty()) {
return false;
}

View File

@@ -202,11 +202,10 @@ public class ProtectAi extends SpellAbilityAi {
sa.resetTargets();
CardCollection list = getProtectCreatures(ai, sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), source, sa);
if (game.getStack().isEmpty()) {
// If the cost is tapping, don't activate before declare
// attack/block
// If the cost is tapping, don't activate before declare attack/block
if (sa.getPayCosts().hasTapCost()) {
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)) {
@@ -341,7 +340,7 @@ public class ProtectAi extends SpellAbilityAi {
sa.getTargets().add(c);
}
if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
if (sa.getTargets().size() < tgt.getMinTargets(source, sa)) {
sa.resetTargets();
return false;
}

View File

@@ -540,8 +540,7 @@ public class PumpAi extends PumpAiBase {
list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, source, sa);
if (game.getStack().isEmpty()) {
// If the cost is tapping, don't activate before declare
// attack/block
// If the cost is tapping, don't activate before declare attack/block
if (sa.getPayCosts().hasTapCost()) {
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)) {

View File

@@ -73,8 +73,8 @@ public class PumpAllAi extends PumpAiBase {
return true;
}
final int power = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumAtt"), sa);
final int defense = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumDef"), sa);
final int power = AbilityUtils.calculateAmount(source, sa.getParam("NumAtt"), sa);
final int defense = AbilityUtils.calculateAmount(source, sa.getParam("NumDef"), sa);
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<>();
final PhaseType phase = game.getPhaseHandler().getPhase();

View File

@@ -23,7 +23,6 @@ public class SacrificeAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
return sacrificeTgtAI(ai, sa);
}

View File

@@ -1,5 +1,6 @@
package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
@@ -8,16 +9,33 @@ import forge.game.spellability.SpellAbility;
public class SkipPhaseAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return true;
return targetPlayer(aiPlayer, sa, false);
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return mandatory || canPlayAI(aiPlayer, sa);
return targetPlayer(aiPlayer, sa, mandatory);
}
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;
}
public boolean targetPlayer(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
sa.resetTargets();
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
}
else if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai);
}
else {
return false;
}
}
return true;
}
}

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;

View File

@@ -50,7 +50,6 @@ public class TokenAi extends SpellAbilityAi {
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Card source = sa.getHostCard();
// Planeswalker-related flags
boolean pwMinus = false;

View File

@@ -24,7 +24,7 @@ public class TokenDb implements ITokenDatabase {
// colors_power_toughness_cardtypes_sub_types_keywords
// Some examples:
// c_3_3_a_wurm_lifelink
// c_3_3_a_phyrexian_wurm_lifelink
// w_2_2_knight_first_strike
// The image names should be the same as the script name + _set

View File

@@ -125,7 +125,6 @@ public class ForgeScript {
}
public static boolean spellAbilityHasProperty(SpellAbility sa, String property, Player sourceController,
Card source, CardTraitBase spellAbility) {
if (property.equals("ManaAbility")) {

View File

@@ -205,7 +205,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
public boolean canBeAttached(final Card attach) {
return canBeAttached(attach, false);
}
public boolean canBeAttached(final Card attach, boolean checkSBA) {
// master mode
if (!attach.isAttachment() || attach.isCreature() || equals(attach)) {
@@ -250,7 +249,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
}
protected boolean canBeEnchantedBy(final Card aura) {
SpellAbility sa = aura.getFirstAttachSpell();
TargetRestrictions tgt = null;
if (sa != null) {
@@ -263,7 +261,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
public boolean hasProtectionFrom(final Card source) {
return hasProtectionFrom(source, false);
}
public abstract boolean hasProtectionFrom(final Card source, final boolean checkSBA);
// Counters!
@@ -280,7 +277,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
Integer value = counters.get(counterName);
return value == null ? 0 : value;
}
public final int getCounters(final CounterEnumType counterType) {
return getCounters(CounterType.get(counterType));
}
@@ -292,7 +288,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
counters.put(counterType, num);
}
}
public void setCounters(final CounterEnumType counterType, final Integer num) {
setCounters(CounterType.get(counterType), num);
}

View File

@@ -349,7 +349,7 @@ public class Match {
if (!lostOwnership.isEmpty()) {
List<PaperCard> lostPaperOwnership = new ArrayList<>();
for(Card c : lostOwnership) {
for (Card c : lostOwnership) {
lostPaperOwnership.add((PaperCard)c.getPaperCard());
}
outcome.addAnteLost(registered, lostPaperOwnership);
@@ -357,7 +357,7 @@ public class Match {
if (!gainedOwnership.isEmpty()) {
List<PaperCard> gainedPaperOwnership = new ArrayList<>();
for(Card c : gainedOwnership) {
for (Card c : gainedOwnership) {
gainedPaperOwnership.add((PaperCard)c.getPaperCard());
}
outcome.addAnteWon(registered, gainedPaperOwnership);

View File

@@ -1286,8 +1286,7 @@ public class AbilityUtils {
SpellAbility s = null;
// TODO - this probably needs to be fleshed out a bit, but the basics
// work
// TODO - this probably needs to be fleshed out a bit, but the basics work
if (defined.equals("Self") && sa instanceof SpellAbility) {
s = (SpellAbility)sa;
}
@@ -2731,7 +2730,6 @@ public class AbilityUtils {
return doXMath(powers.size(), expr, c, ctb);
}
if (sq[0].startsWith("MostProminentCreatureType")) {
String restriction = l[0].split(" ")[1];
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);

View File

@@ -617,8 +617,29 @@ public abstract class SpellAbilityEffect {
combat.addBlocker(attacker, c);
combat.orderAttackersForDamageAssignment(c);
{
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Attacker, attacker);
runParams.put(AbilityKey.Blocker, c);
game.getTriggerHandler().runTrigger(TriggerType.AttackerBlockedByCreature, runParams, false);
}
{
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Attackers, attacker);
game.getTriggerHandler().runTrigger(TriggerType.AttackerBlockedOnce, runParams, false);
}
// Run triggers for new blocker and add it to damage assignment order
if (!wasBlocked) {
final CardCollection blockers = combat.getBlockers(attacker);
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Attacker, attacker);
runParams.put(AbilityKey.Blockers, blockers);
runParams.put(AbilityKey.NumBlockers, blockers.size());
runParams.put(AbilityKey.Defender, combat.getDefenderByAttacker(attacker));
runParams.put(AbilityKey.DefendingPlayer, combat.getDefenderPlayerByAttacker(attacker));
game.getTriggerHandler().runTrigger(TriggerType.AttackerBlocked, runParams, false);
combat.setBlocked(attacker, true);
combat.addBlockerToDamageAssignmentOrder(attacker, c);
}

View File

@@ -59,6 +59,9 @@ public class AnimateAllEffect extends AnimateEffectBase {
if (types.hasSubtype("ChosenType")) {
types.clear();
types.add(host.getChosenType());
} else if (types.hasSubtype("ChosenType2")) {
types.clear();
types.add(host.getChosenType2());
}
final List<String> keywords = new ArrayList<>();
@@ -125,7 +128,7 @@ public class AnimateAllEffect extends AnimateEffectBase {
}
CardCollectionView list;
if (!sa.usesTargeting() && !sa.hasParam("Defined")) {
list = game.getCardsIn(ZoneType.Battlefield);
} else {

View File

@@ -71,6 +71,9 @@ public class AnimateEffect extends AnimateEffectBase {
if (types.hasSubtype("ChosenType")) {
types.clear();
types.add(source.getChosenType());
} else if (types.hasSubtype("ChosenType2")) {
types.clear();
types.add(source.getChosenType2());
}
final List<String> keywords = Lists.newArrayList();
@@ -227,7 +230,7 @@ public class AnimateEffect extends AnimateEffectBase {
}
// allow SVar substitution for keywords
for (int i = 0; i < keywords.size(); i++) {
final String k = keywords.get(i);
final String k = keywords.get(i);
if (sa.hasSVar(k)) {
keywords.add("\"" + k + "\"");
keywords.remove(k);

View File

@@ -291,7 +291,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
* @return a {@link java.lang.String} object.
*/
private static String changeKnownOriginStackDescription(final SpellAbility sa) {
final StringBuilder sb = new StringBuilder();
final Card host = sa.getHostCard();
@@ -1047,7 +1046,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// only multi-select if player can select more than one
if (changeNum > 1 && allowMultiSelect(decider, sa)) {
List<Card> selectedCards;
if (! sa.hasParam("SelectPrompt")) {
if (!sa.hasParam("SelectPrompt")) {
// new default messaging for multi select
if (fetchList.size() > changeNum) {
//Select up to %changeNum cards from %players %origin

View File

@@ -65,7 +65,7 @@ public class ChooseTypeEffect extends SpellAbilityEffect {
for (final Player p : tgtPlayers) {
if ((tgt == null) || p.canBeTargetedBy(sa)) {
String choice = p.getController().chooseSomeType(type, sa, validTypes, invalidTypes);
if (!sa.hasParam("Secondary")) {
if (!sa.hasParam("ChooseType2")) {
card.setChosenType(choice);
} else {
card.setChosenType2(choice);

View File

@@ -143,7 +143,7 @@ public class CloneEffect extends SpellAbilityEffect {
tgtCard.updateStateForView();
// when clone is itself, cleanup from old abilities
if (host.equals(tgtCard)) {
if (host.equals(tgtCard) && !sa.hasParam("ImprintRememberedNoCleanup")) {
tgtCard.clearImprintedCards();
tgtCard.clearRemembered();
}

View File

@@ -4,13 +4,14 @@ import java.util.List;
import forge.GameCommand;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.Lang;
import forge.util.TextUtil;
/**
/**
* TODO: Write javadoc for this type.
*
*/
@@ -18,16 +19,18 @@ public class ControlPlayerEffect extends SpellAbilityEffect {
@Override
protected String getStackDescription(SpellAbility sa) {
List<Player> tgtPlayers = getTargetPlayers(sa);
return TextUtil.concatWithSpace(sa.getActivatingPlayer().toString(),"controls", Lang.joinHomogenous(tgtPlayers),"during their next turn");
}
@SuppressWarnings("serial")
@Override
public void resolve(SpellAbility sa) {
final Player activator = sa.getActivatingPlayer();
final Game game = activator.getGame();
final Player controller = sa.hasParam("Controller") ? AbilityUtils.getDefinedPlayers(
sa.getHostCard(), sa.getParam("Controller"), sa).get(0) : activator;
List<Player> tgtPlayers = getTargetPlayers(sa);
@@ -37,13 +40,13 @@ public class ControlPlayerEffect extends SpellAbilityEffect {
@Override
public void run() {
// CR 800.4b
if (activator.hasLost()) {
if (controller.hasLost()) {
return;
}
long ts = game.getNextTimestamp();
pTarget.addController(ts, activator);
pTarget.addController(ts, controller);
// on following cleanup release control
game.getEndOfTurn().addUntil(new GameCommand() {
@Override

View File

@@ -73,10 +73,8 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
controllers = AbilityUtils.getDefinedPlayers(card, sa.getParam("Controller"), sa);
}
final List<SpellAbility> tgtSpells = getTargetSpells(sa);
if (tgtSpells.size() == 0 || amount == 0) {
return;
}

View File

@@ -193,7 +193,7 @@ public class CounterEffect extends SpellAbilityEffect {
params.put(AbilityKey.StackSi, si);
String destination = srcSA.hasParam("Destination") ? srcSA.getParam("Destination") : tgtSA.isAftermath() ? "Exile" : "Graveyard";
if (srcSA.hasParam("DestinationChoice")) {//Hinder
if (srcSA.hasParam("DestinationChoice")) { //Hinder
List<String> pos = Arrays.asList(srcSA.getParam("DestinationChoice").split(","));
destination = srcSA.getActivatingPlayer().getController().chooseSomeType(Localizer.getInstance().getMessage("lblRemoveDestination"), tgtSA, pos, null);
}
@@ -211,7 +211,6 @@ public class CounterEffect extends SpellAbilityEffect {
} else if (destination.equals("Battlefield")) {
if (tgtSA instanceof SpellPermanent) {
Card c = tgtSA.getHostCard();
System.out.println(c + " is SpellPermanent");
c.setController(srcSA.getActivatingPlayer(), 0);
game.getAction().moveToPlay(c, srcSA.getActivatingPlayer(), srcSA, params);
} else {

View File

@@ -147,8 +147,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
Map<CounterType, Integer> countersToAdd = Maps.newHashMap();
for (Card src : srcCards) {
// rule 121.5: If the first and second objects are the same object, nothing
// happens
// rule 121.5: If the first and second objects are the same object, nothing happens
if (src.equals(dest)) {
continue;
}
@@ -199,8 +198,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
boolean updateSource = false;
for (final Card dest : tgtCards) {
// rule 121.5: If the first and second objects are the same object, nothing
// happens
// rule 121.5: If the first and second objects are the same object, nothing happens
if (source.equals(dest)) {
continue;
}
@@ -262,8 +260,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
for (final Card dest : tgtCards) {
if (null != dest) {
// rule 121.5: If the first and second objects are the same object, nothing
// happens
// rule 121.5: If the first and second objects are the same object, nothing happens
if (source.equals(dest)) {
continue;
}
@@ -329,8 +326,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
final PlayerController pc = player.getController();
final Game game = host.getGame();
// rule 121.5: If the first and second objects are the same object, nothing
// happens
// rule 121.5: If the first and second objects are the same object, nothing happens
if (src.equals(dest)) {
return;
}

View File

@@ -173,26 +173,25 @@ public class DigUntilEffect extends SpellAbilityEffect {
if (optionalFound && !p.getController().confirmAction(sa, null,
Localizer.getInstance().getMessage("lblDoYouWantPutCardToZone", foundDest.getTranslatedName()))) {
continue;
}
Card m = null;
if (sa.hasParam("GainControl") && foundDest.equals(ZoneType.Battlefield)) {
c.setController(sa.getActivatingPlayer(), game.getNextTimestamp());
m = game.getAction().moveTo(c.getController().getZone(foundDest), c, sa);
if (sa.hasParam("Tapped")) {
c.setTapped(true);
}
if (addToCombat(c, c.getController(), sa, "Attacking", "Blocking")) {
combatChanged = true;
}
} else if (sa.hasParam("NoMoveFound") && foundDest.equals(ZoneType.Library)) {
//Don't do anything
} else {
Card m = null;
if (sa.hasParam("GainControl") && foundDest.equals(ZoneType.Battlefield)) {
c.setController(sa.getActivatingPlayer(), game.getNextTimestamp());
m = game.getAction().moveTo(c.getController().getZone(foundDest), c, sa);
if (sa.hasParam("Tapped")) {
c.setTapped(true);
}
if (addToCombat(c, c.getController(), sa, "Attacking", "Blocking")) {
combatChanged = true;
}
} else if (sa.hasParam("NoMoveFound") && foundDest.equals(ZoneType.Library)) {
//Don't do anything
} else {
m = game.getAction().moveTo(foundDest, c, foundLibPos, sa);
}
revealed.remove(c);
if (m != null && !origin.equals(m.getZone().getZoneType())) {
table.put(origin, m.getZone().getZoneType(), m);
}
m = game.getAction().moveTo(foundDest, c, foundLibPos, sa);
}
revealed.remove(c);
if (m != null && !origin.equals(m.getZone().getZoneType())) {
table.put(origin, m.getZone().getZoneType(), m);
}
}
}

View File

@@ -247,9 +247,12 @@ public class EffectEffect extends SpellAbilityEffect {
}
// Set Chosen Type
if (!hostCard.getChosenType().isEmpty()) {
if (hostCard.hasChosenType()) {
eff.setChosenType(hostCard.getChosenType());
}
if (hostCard.hasChosenType2()) {
eff.setChosenType2(hostCard.getChosenType2());
}
// Set Chosen name
if (!hostCard.getNamedCard().isEmpty()) {

View File

@@ -65,19 +65,18 @@ public class FightEffect extends DamageBaseEffect {
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeFight", CardTranslation.getTranslatedName(fighters.get(0).getName()), CardTranslation.getTranslatedName(fighters.get(1).getName())))) {
return;
} else {
dealDamage(sa, fighters.get(0), fighters.get(1));
for (Card c : fighters) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Fighter, c);
game.getTriggerHandler().runTrigger(TriggerType.Fight, runParams, false);
}
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Fighters, fighters);
game.getTriggerHandler().runTrigger(TriggerType.FightOnce, runParams, false);
}
dealDamage(sa, fighters.get(0), fighters.get(1));
for (Card c : fighters) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Fighter, c);
game.getTriggerHandler().runTrigger(TriggerType.Fight, runParams, false);
}
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Fighters, fighters);
}
private static List<Card> getFighters(SpellAbility sa) {

View File

@@ -58,8 +58,8 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
Card lki = CardUtil.getLKICopy(gameCard);
lki.clearControllers();
lki.setOwner(sa.getActivatingPlayer());
// if this trigger is part of ETBReplacement it shouldn't run with LKI from incomplete zone change (Wall of Stolen Identity)
final Card trigHost = sa.getRootAbility().getReplacementEffect() != null && sa.getRootAbility().getReplacementEffect().getMode().equals(ReplacementType.Moved) ? gameCard : lki;
// if this trigger is part of ETBReplacement it shouldn't run with LKI from incomplete zone change (Mimic Vat + Wall of Stolen Identity)
final Card trigHost = sa.getRootAbility().getReplacementEffect() != null && sa.getRootAbility().getReplacementEffect().getMode().equals(ReplacementType.Moved) && gameCard.getZone() == null ? gameCard : lki;
final Trigger immediateTrig = TriggerHandler.parseTrigger(mapParams, trigHost, sa.isIntrinsic(), null);
immediateTrig.setSpawningAbility(sa.copy(lki, sa.getActivatingPlayer(), true));

View File

@@ -3,6 +3,7 @@ package forge.game.ability.effects;
import static forge.util.TextUtil.toManaString;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
@@ -61,34 +62,51 @@ public class ManaEffect extends SpellAbilityEffect {
String[] colorsNeeded = express.isEmpty() ? null : express.split(" ");
boolean differentChoice = abMana.getOrigProduced().contains("Different");
ColorSet fullOptions = colorOptions;
for (int nMana = 0; nMana < amount; nMana++) {
String choice = "";
if (colorsNeeded != null && colorsNeeded.length > nMana) { // select from express choices if possible
colorOptions = ColorSet
.fromMask(fullOptions.getColor() & ManaAtom.fromName(colorsNeeded[nMana]));
}
if (colorOptions.isColorless() && colorsProduced.length > 0) {
// If we just need generic mana, no reason to ask the controller for a choice,
// just use the first possible color.
choice = colorsProduced[differentChoice ? nMana : 0];
} else {
byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa,
differentChoice && (colorsNeeded == null || colorsNeeded.length <= nMana) ? fullOptions : colorOptions);
if (chosenColor == 0)
throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + p + " color mana choice is empty for " + card.getName());
if (differentChoice) {
fullOptions = ColorSet.fromMask(fullOptions.getColor() - chosenColor);
// Use specifyManaCombo if possible
if (colorsNeeded == null && amount > 1 && !sa.hasParam("TwoEach")) {
Map<Byte, Integer> choices = p.getController().specifyManaCombo(sa, colorOptions, amount, differentChoice);
for (Map.Entry<Byte, Integer> e : choices.entrySet()) {
Byte chosenColor = e.getKey();
String choice = MagicColor.toShortString(chosenColor);
Integer count = e.getValue();
while (count > 0) {
if (choiceString.length() > 0) {
choiceString.append(" ");
}
choiceString.append(choice);
--count;
}
choice = MagicColor.toShortString(chosenColor);
}
} else {
for (int nMana = 0; nMana < amount; nMana++) {
String choice = "";
if (colorsNeeded != null && colorsNeeded.length > nMana) { // select from express choices if possible
colorOptions = ColorSet
.fromMask(fullOptions.getColor() & ManaAtom.fromName(colorsNeeded[nMana]));
}
if (colorOptions.isColorless() && colorsProduced.length > 0) {
// If we just need generic mana, no reason to ask the controller for a choice,
// just use the first possible color.
choice = colorsProduced[differentChoice ? nMana : 0];
} else {
byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa,
differentChoice && (colorsNeeded == null || colorsNeeded.length <= nMana) ? fullOptions : colorOptions);
if (chosenColor == 0)
throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + p + " color mana choice is empty for " + card.getName());
if (nMana > 0) {
choiceString.append(" ");
}
choiceString.append(choice);
if (sa.hasParam("TwoEach")) {
choiceString.append(" ").append(choice);
if (differentChoice) {
fullOptions = ColorSet.fromMask(fullOptions.getColor() - chosenColor);
}
choice = MagicColor.toShortString(chosenColor);
}
if (nMana > 0) {
choiceString.append(" ");
}
choiceString.append(choice);
if (sa.hasParam("TwoEach")) {
choiceString.append(" ").append(choice);
}
}
}
@@ -128,7 +146,7 @@ public class ManaEffect extends SpellAbilityEffect {
if (type.equals("EnchantedManaCost")) {
Card enchanted = card.getEnchantingCard();
if (enchanted == null )
if (enchanted == null )
continue;
StringBuilder sb = new StringBuilder();
@@ -234,7 +252,7 @@ public class ManaEffect extends SpellAbilityEffect {
* a {@link forge.card.spellability.AbilityMana} object.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
*
* @return a {@link java.lang.String} object.
*/

View File

@@ -59,7 +59,7 @@ public class RevealEffect extends SpellAbilityEffect {
if (valid.isEmpty())
continue;
if( cnt > valid.size() )
if (cnt > valid.size())
cnt = valid.size();
int min = cnt;

View File

@@ -1240,7 +1240,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final boolean hasTrigger(final Trigger t) {
return currentState.hasTrigger(t);
}
public final boolean hasTrigger(final int id) {
return currentState.hasTrigger(id);
}
@@ -1495,7 +1494,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public boolean removeCounterTimestamp(CounterType counterType) {
return removeCounterTimestamp(counterType, true);
}
public boolean removeCounterTimestamp(CounterType counterType, boolean updateView) {
Long old = counterTypeTimestamps.remove(counterType);
if (old != null) {
@@ -1581,6 +1579,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
}
public final int sumAllCounters() {
int count = 0;
for (final Integer value2 : counters.values()) {
count += value2;
}
return count;
}
public final String getSVar(final String var) {
return currentState.getSVar(var);
}
@@ -1611,18 +1617,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
currentState.removeSVar(var);
}
public final int sumAllCounters() {
int count = 0;
for (final Integer value2 : counters.values()) {
count += value2;
}
return count;
}
public final int getTurnInZone() {
return turnInZone;
}
public final void setTurnInZone(final int turn) {
turnInZone = turn;
}
@@ -1630,7 +1627,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final Player getTurnInController() {
return turnInController;
}
public final void setTurnInController(final Player p) {
turnInController = p;
}
@@ -1638,7 +1634,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final void setManaCost(final ManaCost s) {
currentState.setManaCost(s);
}
public final ManaCost getManaCost() {
return currentState.getManaCost();
}

View File

@@ -1273,7 +1273,7 @@ public class CardFactoryUtil {
sbTrig.append("Living Weapon (").append(inst.getReminderText()).append(")");
final StringBuilder sbGerm = new StringBuilder();
sbGerm.append("DB$ Token | TokenAmount$ 1 | TokenScript$ b_0_0_germ |TokenOwner$ You | RememberTokens$ True");
sbGerm.append("DB$ Token | TokenAmount$ 1 | TokenScript$ b_0_0_phyrexian_germ |TokenOwner$ You | RememberTokens$ True");
final SpellAbility saGerm = AbilityFactory.getAbility(sbGerm.toString(), card);

View File

@@ -221,9 +221,9 @@ public final class CardUtil {
newCopy.getCurrentState().copyFrom(in.getState(in.getFaceupCardStateName()), true);
if (in.isFaceDown()) {
// prevent StackDescription from revealing face
newCopy.setName(in.toString());
newCopy.turnFaceDownNoUpdate();
// prevent StackDescription from revealing face
newCopy.updateStateForView();
}
if (in.isAdventureCard() && in.getFaceupCardStateName().equals(CardStateName.Original)) {
@@ -275,7 +275,7 @@ public final class CardUtil {
newCopy.addRemembered(in.getRemembered());
newCopy.addImprintedCards(in.getImprintedCards());
for(Table.Cell<Player, CounterType, Integer> cl : in.getEtbCounters()) {
for (Table.Cell<Player, CounterType, Integer> cl : in.getEtbCounters()) {
newCopy.addEtbCounter(cl.getColumnKey(), cl.getValue(), cl.getRowKey());
}

View File

@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -59,7 +59,7 @@ import forge.util.collect.FCollectionView;
* <p>
* Combat class.
* </p>
*
*
* @author Forge
* @version $Id$
*/
@@ -78,7 +78,7 @@ public class Combat {
private Map<GameEntity, CombatLki> lkiCache = Maps.newHashMap();
private CardDamageMap damageMap = new CardDamageMap();
// List holds creatures who have dealt 1st strike damage to disallow them deal damage on regular basis (unless they have double-strike KW)
// List holds creatures who have dealt 1st strike damage to disallow them deal damage on regular basis (unless they have double-strike KW)
private CardCollection combatantsThatDealtFirstStrikeDamage = new CardCollection();
public Combat(final Player attacker) {
@@ -110,7 +110,7 @@ public class Combat {
for (Entry<AttackingBand, Card> entry : combat.blockedBands.entries()) {
blockedBands.put(bandsMap.get(entry.getKey()), map.map(entry.getValue()));
}
for (Entry<Card, CardCollection> entry : combat.attackersOrderedForDamageAssignment.entrySet()) {
attackersOrderedForDamageAssignment.put(map.map(entry.getKey()), map.mapCollection(entry.getValue()));
}
@@ -277,7 +277,7 @@ public class Combat {
}
}
return null;
}
}
public final Player getDefenderPlayerByAttacker(final Card c) {
GameEntity defender = getDefenderByAttacker(c);
@@ -300,10 +300,10 @@ public class Combat {
return ab;
}
}
CombatLki lki = lkiCache.get(c);
CombatLki lki = lkiCache.get(c);
return lki == null || !lki.isAttacker ? null : lki.getFirstBand();
}
public final AttackingBand getBandOfAttackerNotNull(final Card c) {
AttackingBand band = getBandOfAttacker(c);
if (band == null) {
@@ -314,8 +314,8 @@ public class Combat {
public final List<AttackingBand> getAttackingBands() {
return Lists.newArrayList(attackedByBands.values());
}
}
/**
* Checks if a card is attacking, returns true if the card was attacking when it left the battlefield
*/
@@ -333,7 +333,7 @@ public class Combat {
}
return false;
}
/**
* Checks if a card is currently attacking, returns false if the card is not currently attacking, even if its LKI was.
*/
@@ -355,7 +355,7 @@ public class Combat {
}
public final CardCollection getBlockers(final Card card) {
// If requesting the ordered blocking list pass true, directly.
// If requesting the ordered blocking list pass true, directly.
AttackingBand band = getBandOfAttacker(card);
Collection<Card> blockers = blockedBands.get(band);
return blockers == null ? new CardCollection() : new CardCollection(blockers);
@@ -456,7 +456,7 @@ public class Combat {
}
/** If there are multiple blockers, the Attacker declares the Assignment Order */
public void orderBlockersForDamageAssignment() { // this method performs controller's role
public void orderBlockersForDamageAssignment() { // this method performs controller's role
List<Pair<Card, CardCollection>> blockersNeedManualOrdering = new ArrayList<>();
for (AttackingBand band : attackedByBands.values()) {
if (band.isEmpty()) continue;
@@ -475,15 +475,15 @@ public class Combat {
}
}
}
// brought this out of iteration on bands to avoid concurrency problems
// brought this out of iteration on bands to avoid concurrency problems
for (Pair<Card, CardCollection> pair : blockersNeedManualOrdering) {
orderBlockersForDamageAssignment(pair.getLeft(), pair.getRight());
}
}
/** If there are multiple blockers, the Attacker declares the Assignment Order */
public void orderBlockersForDamageAssignment(Card attacker, CardCollection blockers) { // this method performs controller's role
public void orderBlockersForDamageAssignment(Card attacker, CardCollection blockers) { // this method performs controller's role
if (blockers.size() <= 1) {
blockersOrderedForDamageAssignment.put(attacker, new CardCollection(blockers));
return;
@@ -513,7 +513,7 @@ public class Combat {
* Add a blocker to the damage assignment order of an attacker. The
* relative order of creatures already blocking the attacker may not be
* changed. Performs controller's role.
*
*
* @param attacker the attacking creature.
* @param blocker the blocking creature.
*/
@@ -527,7 +527,7 @@ public class Combat {
blockersOrderedForDamageAssignment.put(attacker, orderedBlockers);
}
}
public void orderAttackersForDamageAssignment() { // this method performs controller's role
// If there are multiple blockers, the Attacker declares the Assignment Order
for (final Card blocker : getAllBlockers()) {
@@ -538,7 +538,7 @@ public class Combat {
public void orderAttackersForDamageAssignment(Card blocker) { // this method performs controller's role
CardCollection attackers = getAttackersBlockedBy(blocker);
// They need a reverse map here: Blocker => List<Attacker>
Player blockerCtrl = blocker.getController();
CardCollection orderedAttacker = attackers.size() <= 1 ? attackers : blockerCtrl.getController().orderAttackers(blocker, attackers);
@@ -549,11 +549,11 @@ public class Combat {
// removes references to this attacker from all indices and orders
public void unregisterAttacker(final Card c, AttackingBand ab) {
blockersOrderedForDamageAssignment.remove(c);
Collection<Card> blockers = blockedBands.get(ab);
if (blockers != null) {
for (Card b : blockers) {
// Clear removed attacker from assignment order
// Clear removed attacker from assignment order
if (attackersOrderedForDamageAssignment.containsKey(b)) {
attackersOrderedForDamageAssignment.get(b).remove(c);
}
@@ -655,7 +655,7 @@ public class Combat {
}
return true;
}
// Call this method right after turn-based action of declare blockers has been performed
public final void fireTriggersForUnblockedAttackers(final Game game) {
boolean bFlag = false;
@@ -698,7 +698,7 @@ public class Combat {
if (!dealDamageThisPhase(blocker, firstStrikeDamage)) {
continue;
}
if (firstStrikeDamage) {
combatantsThatDealtFirstStrikeDamage.add(blocker);
}
@@ -737,7 +737,7 @@ public class Combat {
if (!dealDamageThisPhase(attacker, firstStrikeDamage)) {
continue;
}
if (firstStrikeDamage) {
combatantsThatDealtFirstStrikeDamage.add(attacker);
}
@@ -750,7 +750,7 @@ public class Combat {
if (damageDealt <= 0) {
continue;
}
AttackingBand band = getBandOfAttacker(attacker);
if (band == null) {
continue;
@@ -764,6 +764,13 @@ public class Combat {
CardTranslation.getTranslatedName(attacker.getName()))));
boolean trampler = attacker.hasKeyword(Keyword.TRAMPLE);
orderedBlockers = blockersOrderedForDamageAssignment.get(attacker);
boolean assignCombatDamageToCreature = ((orderedBlockers == null || orderedBlockers.isEmpty()) &&
getDefendersCreatures().size() > 0 &&
attacker.hasKeyword("If CARDNAME is unblocked, you may have it assign its combat damage to " +
"a creature defending player controls.") &&
attacker.getController().getController().confirmAction(null, null,
Localizer.getInstance().getMessage("lblAssignCombatDamageToCreature",
CardTranslation.getTranslatedName(attacker.getName()))));
if (divideCombatDamageAsChoose) {
if (orderedBlockers == null || orderedBlockers.isEmpty()) {
orderedBlockers = getDefendersCreatures();
@@ -790,7 +797,11 @@ public class Combat {
defender = getDefenderPlayerByAttacker(attacker);
}
if (orderedBlockers == null || orderedBlockers.isEmpty()) {
if (trampler || !band.isBlocked()) { // this is called after declare blockers, no worries 'bout nulls in isBlocked
if (assignCombatDamageToCreature) {
Card chosen = attacker.getController().getController().chooseCardsForEffect(getDefendersCreatures(),
null, Localizer.getInstance().getMessage("lblChooseCreature"), 1, 1, false, null).get(0);
damageMap.put(attacker, chosen, damageDealt);
} else if (trampler || !band.isBlocked()) { // this is called after declare blockers, no worries 'bout nulls in isBlocked
damageMap.put(attacker, defender, damageDealt);
} // No damage happens if blocked but no blockers left
}
@@ -827,7 +838,7 @@ public class Combat {
} // for
return assignedDamage;
}
private final boolean dealDamageThisPhase(Card combatant, boolean firstStrikeDamage) {
// During first strike damage, double strike and first strike deal damage
// During regular strike damage, double strike and anyone who hasn't dealt damage deal damage
@@ -885,7 +896,7 @@ public class Combat {
public boolean isPlayerAttacked(Player who) {
for (GameEntity defender : attackedByBands.keySet()) {
Card defenderAsCard = defender instanceof Card ? (Card)defender : null;
if ((null != defenderAsCard && defenderAsCard.getController() != who) ||
if ((null != defenderAsCard && defenderAsCard.getController() != who) ||
(null == defenderAsCard && defender != who)) {
continue; // defender is not related to player 'who'
}

View File

@@ -112,13 +112,11 @@ public class CostPartMana extends CostPart {
@Override
public boolean isUndoable() { return true; }
@Override
public final String toString() {
return cost.toString();
}
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
// For now, this will always return true. But this should probably be

View File

@@ -58,7 +58,6 @@ public class PaymentDecision {
return res;
}
public static PaymentDecision number(int c) {
return new PaymentDecision(c);
}
@@ -77,7 +76,6 @@ public class PaymentDecision {
return new PaymentDecision(null, manas, null, null, null);
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/

View File

@@ -100,7 +100,7 @@ public enum Keyword {
LANDWALK("Landwalk", KeywordWithType.class, false, "This creature is unblockable as long as defending player controls a %s."),
LEVEL_UP("Level up", KeywordWithCost.class, false, "%s: Put a level counter on this. Level up only as a sorcery."),
LIFELINK("Lifelink", SimpleKeyword.class, true, "Damage dealt by this creature also causes its controller to gain that much life."),
LIVING_WEAPON("Living weapon", SimpleKeyword.class, true, "When this Equipment enters the battlefield, create a 0/0 black Germ creature token, then attach this to it."),
LIVING_WEAPON("Living Weapon", SimpleKeyword.class, true, "When this Equipment enters the battlefield, create a 0/0 black Phyrexian Germ creature token, then attach this to it."),
MADNESS("Madness", KeywordWithCost.class, false, "If you discard this card, discard it into exile. When you do, cast it for %s or put it into your graveyard."),
MELEE("Melee", SimpleKeyword.class, false, "Whenever this creature attacks, it gets +1/+1 until end of turn for each opponent you attacked this combat."),
MENTOR("Mentor", SimpleKeyword.class, false, "Whenever this creature attacks, put a +1/+1 counter on target attacking creature with lesser power."),

View File

@@ -505,7 +505,7 @@ public class PhaseHandler implements java.io.Serializable {
eventEndCombat = new GameEventCombatEnded(attackers, blockers);
}
endCombat();
for(Player player : game.getPlayers()) {
for (Player player : game.getPlayers()) {
player.resetCombatantsThisCombat();
}

View File

@@ -430,7 +430,6 @@ public class Player extends GameEntity implements Comparable<Player> {
public boolean isOpponentOf(Player other) {
return other != this && other != null && (other.teamNumber < 0 || other.teamNumber != teamNumber);
}
public boolean isOpponentOf(String other) {
Player otherPlayer = null;
for (Player p : game.getPlayers()) {
@@ -474,9 +473,7 @@ public class Player extends GameEntity implements Comparable<Player> {
public final boolean gainLife(int lifeGain, final Card source) {
return gainLife(lifeGain, source, null);
}
public final boolean gainLife(int lifeGain, final Card source, final SpellAbility sa) {
// Run any applicable replacement effects.
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
repParams.put(AbilityKey.LifeGained, lifeGain);
@@ -544,7 +541,6 @@ public class Player extends GameEntity implements Comparable<Player> {
public final int loseLife(final int toLose) {
return loseLife(toLose, false);
}
public final int loseLife(final int toLose, final boolean manaBurn) {
int lifeLost = 0;
if (!canLoseLife()) {
@@ -870,7 +866,6 @@ public class Player extends GameEntity implements Comparable<Player> {
public final int addCounter(final CounterType counterType, final int n, final Player source, final SpellAbility cause, final boolean applyMultiplier, GameEntityCounterTable table) {
return addCounter(counterType, n, source, cause, applyMultiplier, true, table);
}
@Override
public int addCounter(CounterType counterType, int n, final Player source, final SpellAbility cause, boolean applyMultiplier, boolean fireEvents, GameEntityCounterTable table) {
int addAmount = n;
@@ -1000,7 +995,6 @@ public class Player extends GameEntity implements Comparable<Player> {
public final void addChangedKeywords(final String[] addKeywords, final String[] removeKeywords, final Long timestamp) {
addChangedKeywords(ImmutableList.copyOf(addKeywords), ImmutableList.copyOf(removeKeywords), timestamp);
}
public final void addChangedKeywords(final List<String> addKeywords, final List<String> removeKeywords, final Long timestamp) {
// if the key already exists - merge entries
KeywordsChange cks = null;
@@ -1050,8 +1044,6 @@ public class Player extends GameEntity implements Comparable<Player> {
public final void removeKeyword(final String keyword) {
removeKeyword(keyword, true);
}
public final void removeKeyword(final String keyword, final boolean allInstances) {
boolean keywordRemoved = false;
@@ -1082,7 +1074,6 @@ public class Player extends GameEntity implements Comparable<Player> {
public final boolean hasKeyword(final String keyword) {
return keywords.contains(keyword);
}
@Override
public final boolean hasKeyword(final Keyword keyword) {
return keywords.contains(keyword);
@@ -1172,7 +1163,6 @@ public class Player extends GameEntity implements Comparable<Player> {
public boolean hasProtectionFrom(final Card source, final boolean checkSBA) {
return hasProtectionFrom(source, checkSBA, false);
}
public boolean hasProtectionFrom(final Card source, final boolean checkSBA, final boolean damageSource) {
final boolean colorlessDamage = damageSource && source.hasKeyword("Colorless Damage Source");
for (KeywordInterface ki : keywords) {
@@ -1638,6 +1628,7 @@ public class Player extends GameEntity implements Comparable<Player> {
public final int getNumDiscardedThisTurn() {
return numDiscardedThisTurn;
}
public final void resetNumDiscardedThisTurn() {
numDiscardedThisTurn = 0;
}
@@ -1925,7 +1916,6 @@ public class Player extends GameEntity implements Comparable<Player> {
public boolean hasTappedLandForManaThisTurn() {
return tappedLandForManaThisTurn;
}
public void setTappedLandForManaThisTurn(boolean tappedLandForManaThisTurn) {
this.tappedLandForManaThisTurn = tappedLandForManaThisTurn;
}
@@ -2004,7 +1994,6 @@ public class Player extends GameEntity implements Comparable<Player> {
boolean isAnyOppLoseProof = false;
for (Player p : game.getPlayers()) {
if (p == this || p.getOutcome() != null) {
continue; // except self and already dead
}
isAnyOppLoseProof |= p.hasKeyword("You can't lose the game.");
@@ -2083,7 +2072,6 @@ public class Player extends GameEntity implements Comparable<Player> {
public final boolean hasRevolt() {
return revolt;
}
public final void setRevolt(final boolean val) {
revolt = val;
}
@@ -2143,7 +2131,6 @@ public class Player extends GameEntity implements Comparable<Player> {
public final void setLibrarySearched(final int l) {
numLibrarySearchedOwn = l;
}
public final int getLibrarySearched() {
return numLibrarySearchedOwn;
}
@@ -2167,7 +2154,6 @@ public class Player extends GameEntity implements Comparable<Player> {
@Override
public final boolean isValid(final String restriction, final Player sourceController, final Card source, CardTraitBase spellAbility) {
final String[] incR = restriction.split("\\.", 2);
if (incR[0].equals("Opponent")) {
@@ -2320,6 +2306,7 @@ public class Player extends GameEntity implements Comparable<Player> {
public final void resetSpellCastThisGame() {
spellsCastThisGame = 0;
}
public final int getLifeGainedByTeamThisTurn() {
return lifeGainedByTeamThisTurn;
}
@@ -2647,7 +2634,6 @@ public class Player extends GameEntity implements Comparable<Player> {
public int getStartingHandSize() {
return startingHandSize;
}
public void setStartingHandSize(int shs) {
startingHandSize = shs;
}
@@ -2687,7 +2673,6 @@ public class Player extends GameEntity implements Comparable<Player> {
* Puts my currently active planes, if any, at the bottom of my planar deck.
*/
public void leaveCurrentPlane() {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Cards, new CardCollection(currentPlanes));
game.getTriggerHandler().runTrigger(TriggerType.PlaneswalkedFrom, runParams, false);
@@ -2746,11 +2731,9 @@ public class Player extends GameEntity implements Comparable<Player> {
public CardCollectionView getInboundTokens() {
return inboundTokens;
}
public void addInboundToken(Card c) {
inboundTokens.add(c);
}
public void removeInboundToken(Card c) {
inboundTokens.remove(c);
}
@@ -2791,7 +2774,6 @@ public class Player extends GameEntity implements Comparable<Player> {
ColorSet identity = ColorSet.fromMask(ci);
return identity;
}
public ColorSet getNotCommanderColorID() {
if (commanders.isEmpty()) {
return null;
@@ -2804,7 +2786,6 @@ public class Player extends GameEntity implements Comparable<Player> {
Integer cast = commanderCast.get(commander);
return cast == null ? 0 : cast.intValue();
}
public void incCommanderCast(Card commander) {
commanderCast.put(commander, getCommanderCast(commander) + 1);
getView().updateCommanderCast(this, commander);
@@ -2842,9 +2823,11 @@ public class Player extends GameEntity implements Comparable<Player> {
public void setExtraTurnCount(final int val) {
view.setExtraTurnCount(val);
}
public void setHasPriority(final boolean val) {
view.setHasPriority(val);
}
public boolean isAI() {
return view.isAI();
}
@@ -3137,7 +3120,6 @@ public class Player extends GameEntity implements Comparable<Player> {
public CardCollectionView getLostOwnership() {
return lostOwnership;
}
public CardCollectionView getGainedOwnership() {
return gainedOwnership;
}
@@ -3209,6 +3191,7 @@ public class Player extends GameEntity implements Comparable<Player> {
this.updateZoneForView(com);
}
}
public void updateKeywordCardAbilityText() {
if(getKeywordCard() == null)
return;
@@ -3252,10 +3235,10 @@ public class Player extends GameEntity implements Comparable<Player> {
keywordEffect = null;
}
}
public boolean hasBlessing() {
return blessingEffect != null;
}
public void setBlessing(boolean bless) {
// no need to to change
if ((blessingEffect != null) == bless) {
@@ -3357,7 +3340,6 @@ public class Player extends GameEntity implements Comparable<Player> {
getView().updateAdditionalVote(this);
getGame().fireEvent(new GameEventPlayerStatsChanged(this, false));
}
public void removeAdditionalVote(long timestamp) {
if (additionalVotes.remove(timestamp) != null) {
getView().updateAdditionalVote(this);
@@ -3420,7 +3402,6 @@ public class Player extends GameEntity implements Comparable<Player> {
public Set<Long> getControlVote() {
return controlVotes;
}
public void setControlVote(Set<Long> value) {
controlVotes.clear();
controlVotes.addAll(value);

View File

@@ -48,9 +48,9 @@ import forge.item.PaperCard;
import forge.util.ITriggerEvent;
import forge.util.collect.FCollectionView;
/**
/**
* A prototype for player controller class
*
*
* Handles phase skips for now.
*/
public abstract class PlayerController {
@@ -109,21 +109,22 @@ public abstract class PlayerController {
public abstract Map<Card, Integer> assignCombatDamage(Card attacker, CardCollectionView blockers, int damageDealt, GameEntity defender, boolean overrideOrder);
public abstract Map<GameEntity, Integer> divideShield(Card effectSource, Map<GameEntity, Integer> affected, int shieldAmount);
public abstract Map<Byte, Integer> specifyManaCombo(SpellAbility sa, ColorSet colorSet, int manaAmount, boolean different);
public abstract Integer announceRequirements(SpellAbility ability, String announce);
public abstract CardCollectionView choosePermanentsToSacrifice(SpellAbility sa, int min, int max, CardCollectionView validTargets, String message);
public abstract CardCollectionView choosePermanentsToDestroy(SpellAbility sa, int min, int max, CardCollectionView validTargets, String message);
public abstract TargetChoices chooseNewTargetsFor(SpellAbility ability, Predicate<GameObject> filter, boolean optional);
public abstract boolean chooseTargetsFor(SpellAbility currentAbility); // this is bad a function for it assigns targets to sa inside its body
public abstract boolean chooseTargetsFor(SpellAbility currentAbility); // this is bad a function for it assigns targets to sa inside its body
// Specify a target of a spell (Spellskite)
public abstract Pair<SpellAbilityStackInstance, GameObject> chooseTarget(SpellAbility sa, List<Pair<SpellAbilityStackInstance, GameObject>> allTargets);
// Q: why is there min/max and optional at once? A: This is to handle cases like 'choose 3 to 5 cards or none at all'
// Q: why is there min/max and optional at once? A: This is to handle cases like 'choose 3 to 5 cards or none at all'
public abstract CardCollectionView chooseCardsForEffect(CardCollectionView sourceList, SpellAbility sa, String title, int min, int max, boolean isOptional, Map<String, Object> params);
public final <T extends GameEntity> T chooseSingleEntityForEffect(FCollectionView<T> optionList, SpellAbility sa, String title, Map<String, Object> params) { return chooseSingleEntityForEffect(optionList, null, sa, title, false, null, params); }
public final <T extends GameEntity> T chooseSingleEntityForEffect(FCollectionView<T> optionList, SpellAbility sa, String title, boolean isOptional, Map<String, Object> params) { return chooseSingleEntityForEffect(optionList, null, sa, title, isOptional, null, params); }
public final <T extends GameEntity> T chooseSingleEntityForEffect(FCollectionView<T> optionList, SpellAbility sa, String title, boolean isOptional, Map<String, Object> params) { return chooseSingleEntityForEffect(optionList, null, sa, title, isOptional, null, params); }
public abstract <T extends GameEntity> T chooseSingleEntityForEffect(FCollectionView<T> optionList, DelayedReveal delayedReveal, SpellAbility sa, String title, boolean isOptional, Player relatedPlayer, Map<String, Object> params);
public abstract List<SpellAbility> chooseSpellAbilitiesForEffect(List<SpellAbility> spells, SpellAbility sa, String title, int num, Map<String, Object> params);
@@ -209,7 +210,7 @@ public abstract class PlayerController {
public final boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice) { return chooseBinary(sa, question, kindOfChoice, (Boolean) null); }
public abstract boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Boolean defaultChioce);
public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Map<String, Object> params) { return chooseBinary(sa, question, kindOfChoice); }
public abstract boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call);
public abstract Card chooseProtectionShield(GameEntity entityBeingDamaged, List<String> options, Map<String, Card> choiceMap);
@@ -260,7 +261,7 @@ public abstract class PlayerController {
public abstract String chooseCardName(SpellAbility sa, Predicate<ICardFace> cpp, String valid, String message);
public abstract String chooseCardName(SpellAbility sa, List<ICardFace> faces, String message);
// better to have this odd method than those if playerType comparison in ChangeZone
// better to have this odd method than those if playerType comparison in ChangeZone
public abstract Card chooseSingleCardForZoneChange(ZoneType destination, List<ZoneType> origin, SpellAbility sa, CardCollection fetchList, DelayedReveal delayedReveal, String selectPrompt, boolean isOptional, Player decider);
public abstract List<Card> chooseCardsForZoneChange(ZoneType destination, List<ZoneType> origin, SpellAbility sa, CardCollection fetchList, int min, int max, DelayedReveal delayedReveal, String selectPrompt, Player decider);

View File

@@ -44,7 +44,6 @@ public class ReplaceDiscard extends ReplacementEffect {
*/
@Override
public boolean canReplace(Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) {
return false;
}

View File

@@ -28,7 +28,6 @@ public class ReplaceMoved extends ReplacementEffect {
*/
@Override
public boolean canReplace(Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Affected))) {
return false;
}

View File

@@ -281,7 +281,7 @@ public class ReplacementHandler {
// Updated Replacements need to be logged elsewhere because its otherwise in the wrong order
if (res != ReplacementResult.Updated) {
String message = chosenRE.getDescription();
if ( !StringUtils.isEmpty(message))
if (!StringUtils.isEmpty(message))
if (chosenRE.getHostCard() != null) {
message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName());
}

View File

@@ -385,7 +385,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
manaPart = manaPart0;
}
// Spell, and Ability, and other Ability objects override this method
public abstract boolean canPlay();

View File

@@ -73,7 +73,6 @@ public class TriggerDiscarded extends Trigger {
return true;
}
/** {@inheritDoc} */
@Override
public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) {

View File

@@ -10,11 +10,14 @@ import com.google.common.collect.Maps;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityKey;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardDamageMap;
import forge.game.card.CardState;
import forge.game.card.CardZoneTable;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.Ability;
@@ -505,6 +508,39 @@ public class WrappedAbility extends Ability {
// TODO: CardCollection
}
@Override
public CardDamageMap getDamageMap() {
return sa.getDamageMap();
}
@Override
public CardDamageMap getPreventMap() {
return sa.getPreventMap();
}
@Override
public GameEntityCounterTable getCounterTable() {
return sa.getCounterTable();
}
@Override
public CardZoneTable getChangeZoneTable() {
return sa.getChangeZoneTable();
}
@Override
public void setDamageMap(final CardDamageMap map) {
sa.setDamageMap(map);
}
@Override
public void setPreventMap(final CardDamageMap map) {
sa.setPreventMap(map);
}
@Override
public void setCounterTable(final GameEntityCounterTable table) {
sa.setCounterTable(table);
}
@Override
public void setChangeZoneTable(final CardZoneTable table) {
sa.setChangeZoneTable(table);
}
public boolean isAlternativeCost(AlternativeCost ac) {
return sa.isAlternativeCost(ac);
}

View File

@@ -279,7 +279,7 @@ public final class CMatchUI
}
}
private SkinImage getPlayerAvatar(final PlayerView p, final int defaultIndex) {
public SkinImage getPlayerAvatar(final PlayerView p, final int defaultIndex) {
if (avatarImages.containsKey(p.getLobbyPlayerName())) {
return ImageCache.getIcon(avatarImages.get(p.getLobbyPlayerName()));
}
@@ -1029,13 +1029,13 @@ public final class CMatchUI
}
@Override
public Map<GameEntityView, Integer> assignGenericAmount(final CardView effectSource, final Map<GameEntityView, Integer> target,
public Map<Object, Integer> assignGenericAmount(final CardView effectSource, final Map<Object, Integer> target,
final int amount, final boolean atLeastOne, final String amountLabel) {
if (amount <= 0) {
return Collections.emptyMap();
}
final AtomicReference<Map<GameEntityView, Integer>> result = new AtomicReference<>();
final AtomicReference<Map<Object, Integer>> result = new AtomicReference<>();
FThreads.invokeInEdtAndWait(new Runnable() {
@Override
public void run() {

View File

@@ -44,11 +44,13 @@ import forge.toolbox.FButton;
import forge.toolbox.FLabel;
import forge.toolbox.FScrollPane;
import forge.toolbox.FSkin;
import forge.toolbox.FSkin.SkinImage;
import forge.toolbox.FSkin.SkinnedPanel;
import forge.util.Localizer;
import forge.util.TextUtil;
import forge.view.FDialog;
import forge.view.arcane.CardPanel;
import forge.view.arcane.MiscCardPanel;
import net.miginfocom.swing.MigLayout;
/**
@@ -116,21 +118,28 @@ public class VAssignCombatDamage {
private final MouseAdapter mad = new MouseAdapter() {
@Override
public void mouseEntered(final MouseEvent evt) {
CardView source = ((CardPanel) evt.getSource()).getCard();
if (!damage.containsKey(source)) source = null; // to get player instead of fake card
SkinnedPanel panel = (SkinnedPanel)evt.getSource();
CardView source = null;
if (panel instanceof CardPanel) {
source = ((CardPanel)panel).getCard();
}
final FSkin.Colors brdrColor = VAssignCombatDamage.this.canAssignTo(source) ? FSkin.Colors.CLR_ACTIVE : FSkin.Colors.CLR_INACTIVE;
((CardPanel) evt.getSource()).setBorder(new FSkin.LineSkinBorder(FSkin.getColor(brdrColor), 2));
panel.setBorder(new FSkin.LineSkinBorder(FSkin.getColor(brdrColor), 2));
}
@Override
public void mouseExited(final MouseEvent evt) {
((CardPanel) evt.getSource()).setBorder((Border)null);
((SkinnedPanel) evt.getSource()).setBorder((Border)null);
}
@Override
public void mousePressed(final MouseEvent evt) {
CardView source = ((CardPanel) evt.getSource()).getCard(); // will be NULL for player
SkinnedPanel panel = (SkinnedPanel)evt.getSource();
CardView source = null;
if (panel instanceof CardPanel) {
source = ((CardPanel)panel).getCard();
}
boolean meta = evt.isControlDown();
boolean isLMB = SwingUtilities.isLeftMouseButton(evt);
@@ -192,14 +201,7 @@ public class VAssignCombatDamage {
final DamageTarget dt = new DamageTarget(null, new FLabel.Builder().text("0").fontSize(18).fontAlign(SwingConstants.CENTER).build());
damage.put(null, dt);
defenders.add(dt);
CardView fakeCard = null;
if (defender instanceof CardView) {
fakeCard = (CardView)defender;
} else if (defender instanceof PlayerView) {
final PlayerView p = (PlayerView)defender;
fakeCard = new CardView(-1, null, defender.toString(), p, matchUI.getAvatarImage(p.getLobbyPlayerName()));
}
addPanelForDefender(pnlDefenders, fakeCard);
addPanelForDefender(pnlDefenders, defender);
}
// Add "opponent placeholder" card if trample allowed
@@ -257,12 +259,21 @@ public class VAssignCombatDamage {
* @param pnlDefenders
* @param defender
*/
private void addPanelForDefender(final JPanel pnlDefenders, final CardView defender) {
final CardPanel cp = new CardPanel(matchUI, defender);
cp.setCardBounds(0, 0, 105, 150);
cp.setOpaque(true);
pnlDefenders.add(cp, "w 145px!, h 170px!, gap 5px 5px 3px 3px, ax center");
cp.addMouseListener(mad);
private void addPanelForDefender(final JPanel pnlDefenders, final GameEntityView defender) {
if (defender instanceof CardView) {
final CardPanel cp = new CardPanel(matchUI, (CardView)defender);
cp.setCardBounds(0, 0, 105, 150);
cp.setOpaque(true);
pnlDefenders.add(cp, "w 145px!, h 170px!, gap 5px 5px 3px 3px, ax center");
cp.addMouseListener(mad);
} else if (defender instanceof PlayerView) {
final PlayerView p = (PlayerView)defender;
SkinImage playerAvatar = matchUI.getPlayerAvatar(p, 0);
final MiscCardPanel mp = new MiscCardPanel(matchUI, p.getName(), playerAvatar);
mp.setCardBounds(0, 0, 105, 150);
pnlDefenders.add(mp, "w 145px!, h 170px!, gap 5px 5px 3px 3px, ax center");
mp.addMouseListener(mad);
}
}
/**

View File

@@ -34,27 +34,26 @@ import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import forge.game.GameEntityView;
import forge.card.MagicColor;
import forge.game.card.CardView;
import forge.game.player.PlayerView;
import forge.gui.SOverlayUtils;
import forge.localinstance.skin.FSkinProp;
import forge.toolbox.FButton;
import forge.toolbox.FLabel;
import forge.toolbox.FScrollPane;
import forge.toolbox.FSkin;
import forge.toolbox.FSkin.SkinImage;
import forge.toolbox.FSkin.SkinnedPanel;
import forge.util.Localizer;
import forge.util.TextUtil;
import forge.view.FDialog;
import forge.view.arcane.CardPanel;
import forge.view.arcane.MiscCardPanel;
import net.miginfocom.swing.MigLayout;
/**
* Assembles Swing components of assign damage dialog.
*
* This needs a JDialog to maintain a modal state.
* Without the modal state, the PhaseHandler automatically
* moves forward to phase Main2 without assigning damage.
* Assembles Swing components of assign generic amount dialog.
*
* <br><br><i>(V at beginning of class name denotes a view class.)</i>
*/
@@ -72,17 +71,18 @@ public class VAssignGenericAmount {
private final String lblAmount;
private final JLabel lblTotalAmount;
private final boolean atLeastOne;
// Label Buttons
private final FButton btnOK = new FButton(localizer.getMessage("lblOk"));
private final FButton btnReset = new FButton(localizer.getMessage("lblReset"));
private static class AssignTarget {
public final GameEntityView entity;
public final Object entity;
public final JLabel label;
public final int max;
public int amount;
public AssignTarget(final GameEntityView e, final JLabel lbl, int max0) {
public AssignTarget(final Object e, final JLabel lbl, int max0) {
entity = e;
label = lbl;
max = max0;
@@ -91,38 +91,40 @@ public class VAssignGenericAmount {
}
private final List<AssignTarget> targetsList = new ArrayList<>();
private final Map<GameEntityView, AssignTarget> targetsMap = new HashMap<>();
private final Map<SkinnedPanel, AssignTarget> targetsMap = new HashMap<>();
// Mouse actions
private final MouseAdapter mad = new MouseAdapter() {
@Override
public void mouseEntered(final MouseEvent evt) {
((CardPanel) evt.getSource()).setBorder(new FSkin.LineSkinBorder(FSkin.getColor(FSkin.Colors.CLR_ACTIVE), 2));
((SkinnedPanel) evt.getSource()).setBorder(new FSkin.LineSkinBorder(FSkin.getColor(FSkin.Colors.CLR_ACTIVE), 2));
}
@Override
public void mouseExited(final MouseEvent evt) {
((CardPanel) evt.getSource()).setBorder((Border)null);
((SkinnedPanel) evt.getSource()).setBorder((Border)null);
}
@Override
public void mousePressed(final MouseEvent evt) {
CardView source = ((CardPanel) evt.getSource()).getCard(); // will be NULL for player
SkinnedPanel panel = (SkinnedPanel)evt.getSource();
AssignTarget at = targetsMap.get(panel);
boolean meta = evt.isControlDown();
boolean isLMB = SwingUtilities.isLeftMouseButton(evt);
boolean isRMB = SwingUtilities.isRightMouseButton(evt);
if ( isLMB || isRMB)
assignAmountTo(source, meta, isLMB);
assignAmountTo(at, meta, isLMB);
}
};
public VAssignGenericAmount(final CMatchUI matchUI, final CardView effectSource, final Map<GameEntityView, Integer> targets, final int amount, final boolean atLeastOne, final String amountLabel) {
public VAssignGenericAmount(final CMatchUI matchUI, final CardView effectSource, final Map<Object, Integer> targets, final int amount, final boolean atLeastOne, final String amountLabel) {
this.matchUI = matchUI;
dlg.setTitle(localizer.getMessage("lbLAssignAmountForEffect", amountLabel, effectSource.toString()));
totalAmountToAssign = amount;
this.atLeastOne = atLeastOne;
lblAmount = amountLabel;
lblTotalAmount = new FLabel.Builder().text(localizer.getMessage("lblTotalAmountText", lblAmount)).build();
@@ -153,7 +155,7 @@ public class VAssignGenericAmount {
final FScrollPane scrTargets = new FScrollPane(pnlTargets, false);
// Top row of cards...
for (final Map.Entry<GameEntityView, Integer> e : targets.entrySet()) {
for (final Map.Entry<Object, Integer> e : targets.entrySet()) {
int maxAmount = e.getValue() != null ? e.getValue() : amount;
final AssignTarget at = new AssignTarget(e.getKey(), new FLabel.Builder().text("0").fontSize(18).fontAlign(SwingConstants.CENTER).build(), maxAmount);
addPanelForTarget(pnlTargets, at);
@@ -161,13 +163,17 @@ public class VAssignGenericAmount {
// ... bottom row of labels.
for (final AssignTarget l : targetsList) {
pnlTargets.add(l.label, "w 145px!, h 30px!, gap 5px 5px 0 5px");
if (l.entity instanceof Byte) {
pnlTargets.add(l.label, "w 100px!, h 30px!, gap 5px 5px 0 5px");
} else {
pnlTargets.add(l.label, "w 145px!, h 30px!, gap 5px 5px 0 5px");
}
}
btnOK.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent arg0) { finish(); } });
btnReset.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent arg0) { resetAssignedAmount(); initialAssignAmount(atLeastOne); } });
@Override public void actionPerformed(ActionEvent arg0) { resetAssignedAmount(); initialAssignAmount(); } });
// Final UI layout
pnlMain.setLayout(new MigLayout("insets 0, gap 0, wrap 2, ax center"));
@@ -185,7 +191,7 @@ public class VAssignGenericAmount {
pnlMain.getRootPane().setDefaultButton(btnOK);
initialAssignAmount(atLeastOne);
initialAssignAmount();
SOverlayUtils.showOverlay();
dlg.setUndecorated(true);
@@ -197,26 +203,47 @@ public class VAssignGenericAmount {
}
private void addPanelForTarget(final JPanel pnlTargets, final AssignTarget at) {
CardView cv = null;
if (at.entity instanceof CardView) {
cv = (CardView)at.entity;
final CardPanel cp = new CardPanel(matchUI, (CardView)at.entity);
cp.setCardBounds(0, 0, 105, 150);
cp.setOpaque(true);
pnlTargets.add(cp, "w 145px!, h 170px!, gap 5px 5px 3px 3px, ax center");
cp.addMouseListener(mad);
targetsMap.put(cp, at);
} else if (at.entity instanceof PlayerView) {
final PlayerView p = (PlayerView)at.entity;
cv = new CardView(-1, null, at.entity.toString(), p, matchUI.getAvatarImage(p.getLobbyPlayerName()));
} else {
return;
SkinImage playerAvatar = matchUI.getPlayerAvatar(p, 0);
final MiscCardPanel mp = new MiscCardPanel(matchUI, p.getName(), playerAvatar);
mp.setCardBounds(0, 0, 105, 150);
pnlTargets.add(mp, "w 145px!, h 170px!, gap 5px 5px 3px 3px, ax center");
mp.addMouseListener(mad);
targetsMap.put(mp, at);
} else if (at.entity instanceof Byte) {
SkinImage manaSymbol;
Byte color = (Byte) at.entity;
if (color == MagicColor.WHITE) {
manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_W);
} else if (color == MagicColor.BLUE) {
manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_U);
} else if (color == MagicColor.BLACK) {
manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_B);
} else if (color == MagicColor.RED) {
manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_R);
} else if (color == MagicColor.GREEN) {
manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_G);
} else { // Should never come here, but add this to avoid compile error
manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_COLORLESS);
}
final MiscCardPanel mp = new MiscCardPanel(matchUI, "", manaSymbol);
mp.setCardBounds(0, 0, 70, 70);
pnlTargets.add(mp, "w 100px!, h 150px!, gap 5px 5px 3px 3px, ax center");
mp.addMouseListener(mad);
targetsMap.put(mp, at);
}
final CardPanel cp = new CardPanel(matchUI, cv);
cp.setCardBounds(0, 0, 105, 150);
cp.setOpaque(true);
pnlTargets.add(cp, "w 145px!, h 170px!, gap 5px 5px 3px 3px, ax center");
cp.addMouseListener(mad);
targetsMap.put(cv, at);
targetsList.add(at);
}
private void assignAmountTo(CardView source, final boolean meta, final boolean isAdding) {
AssignTarget at = targetsMap.get(source);
private void assignAmountTo(AssignTarget at, final boolean meta, final boolean isAdding) {
int assigned = at.amount;
int leftToAssign = Math.max(0, at.max - assigned);
int amountToAdd = isAdding ? 1 : -1;
@@ -234,6 +261,9 @@ public class VAssignGenericAmount {
if (amountToAdd > remainingAmount) {
amountToAdd = remainingAmount;
}
if (atLeastOne && assigned + amountToAdd < 1) {
amountToAdd = 1 - assigned;
}
if (0 == amountToAdd || amountToAdd + assigned < 0) {
return;
@@ -243,7 +273,7 @@ public class VAssignGenericAmount {
updateLabels();
}
private void initialAssignAmount(boolean atLeastOne) {
private void initialAssignAmount() {
if (!atLeastOne) {
updateLabels();
return;
@@ -305,8 +335,8 @@ public class VAssignGenericAmount {
SOverlayUtils.hideOverlay();
}
public Map<GameEntityView, Integer> getAssignedMap() {
Map<GameEntityView, Integer> result = new HashMap<>(targetsList.size());
public Map<Object, Integer> getAssignedMap() {
Map<Object, Integer> result = new HashMap<>(targetsList.size());
for (AssignTarget at : targetsList)
result.put(at.entity, at.amount);
return result;

View File

@@ -0,0 +1,136 @@
package forge.view.arcane;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import forge.localinstance.properties.ForgePreferences.FPref;
import forge.model.FModel;
import forge.screens.match.CMatchUI;
import forge.toolbox.FLabel;
import forge.toolbox.FSkin.SkinImage;
import forge.toolbox.FSkin.SkinnedPanel;
import forge.view.arcane.util.OutlinedLabel;
public class MiscCardPanel extends SkinnedPanel {
private static final float ROT_CENTER_TO_TOP_CORNER = 1.0295630140987000315797369464196f;
private static final float ROT_CENTER_TO_BOTTOM_CORNER = 0.7071067811865475244008443621048f;
private final CMatchUI matchUI;
private final String label;
private final FLabel image;
private OutlinedLabel titleText;
private int cardXOffset, cardYOffset, cardWidth, cardHeight;
public MiscCardPanel(final CMatchUI matchUI, final String label, final SkinImage image) {
this.matchUI = matchUI;
this.label = label;
this.image = new FLabel.Builder().icon(image).build();
setBackground(Color.black);
setOpaque(true);
add(this.image);
createCardNameOverlay();
}
public CMatchUI getMatchUI() {
return matchUI;
}
private void createCardNameOverlay() {
titleText = new OutlinedLabel();
titleText.setFont(getFont().deriveFont(Font.BOLD, 13f));
titleText.setForeground(Color.white);
titleText.setGlow(Color.black);
titleText.setWrap(true);
titleText.setText(label);
add(titleText);
}
@Override
public final void paint(final Graphics g) {
if (!isValid()) {
super.validate();
}
super.paint(g);
}
@Override
public final void doLayout() {
final Point imgPos = new Point(cardXOffset, cardYOffset);
final Dimension imgSize = new Dimension(cardWidth, cardHeight);
image.setLocation(imgPos);
image.setSize(imgSize);
displayCardNameOverlay(showCardNameOverlay(), imgSize, imgPos);
}
private void displayCardNameOverlay(final boolean isVisible, final Dimension imgSize, final Point imgPos) {
if (isVisible) {
final int titleX = Math.round(imgSize.width * (24f / 480));
final int titleY = Math.round(imgSize.height * (54f / 640)) - 15;
final int titleH = Math.round(imgSize.height * (360f / 640));
titleText.setBounds(imgPos.x + titleX, imgPos.y + titleY + 2, imgSize.width - 2 * titleX, titleH - titleY);
}
titleText.setVisible(isVisible);
}
@Override
public final String toString() {
return label;
}
public final void setCardBounds(final int x, final int y, int width, int height) {
cardWidth = width;
cardHeight = height;
final int rotCenterX = Math.round(width / 2f);
final int rotCenterY = height - rotCenterX;
final int rotCenterToTopCorner = Math.round(width * ROT_CENTER_TO_TOP_CORNER);
final int rotCenterToBottomCorner = Math.round(width * ROT_CENTER_TO_BOTTOM_CORNER);
final int xOffset = rotCenterX - rotCenterToBottomCorner;
final int yOffset = rotCenterY - rotCenterToTopCorner;
cardXOffset = -xOffset;
cardYOffset = -yOffset;
width = -xOffset + rotCenterX + rotCenterToTopCorner;
height = -yOffset + rotCenterY + rotCenterToBottomCorner;
setBounds(x + xOffset, y + yOffset, width, height);
}
@Override
public final void repaint() {
final Rectangle b = getBounds();
final JRootPane rootPane = SwingUtilities.getRootPane(this);
if (rootPane == null) {
return;
}
final Point p = SwingUtilities.convertPoint(getParent(), b.x, b.y, rootPane);
rootPane.repaint(p.x, p.y, b.width, b.height);
}
private static boolean isPreferenceEnabled(final FPref preferenceName) {
return FModel.getPreferences().getPrefBoolean(preferenceName);
}
private boolean isShowingOverlays() {
return isPreferenceEnabled(FPref.UI_SHOW_CARD_OVERLAYS);
}
private boolean showCardNameOverlay() {
return isShowingOverlays() && isPreferenceEnabled(FPref.UI_OVERLAY_CARD_NAME);
}
public void repaintOverlays() {
repaint();
doLayout();
}
}

View File

@@ -80,7 +80,7 @@ import forge.util.collect.FCollectionView;
/**
* Default harmless implementation for tests.
* Test-specific behaviour can easily be added by mocking (parts of) this class.
*
*
* Note that the current PlayerController implementations seem to be responsible for handling some game logic,
* and even aside from that, they are theoretically capable of making illegal choices (which are then not blocked by the real game logic).
* Test cases that need to override the default behaviour of this class should make sure to do so in a way that does not invalidate their correctness.
@@ -151,6 +151,10 @@ public class PlayerControllerForTests extends PlayerController {
throw new IllegalStateException("Erring on the side of caution here...");
}
@Override
public Map<Byte, Integer> specifyManaCombo(SpellAbility sa, ColorSet colorSet, int manaAmount, boolean different) {
throw new IllegalStateException("Erring on the side of caution here...");
}
@Override
public Integer announceRequirements(SpellAbility ability, String announce) {
@@ -422,7 +426,7 @@ public class PlayerControllerForTests extends PlayerController {
@Override
public List<SpellAbility> chooseSpellAbilityToPlay() {
//TODO: This method has to return the spellability chosen by player
// It should not play the sa right from here. The code has been left as it is to quickly adapt to changed playercontroller interface
// It should not play the sa right from here. The code has been left as it is to quickly adapt to changed playercontroller interface
if (playerActions != null) {
CastSpellFromHandAction castSpellFromHand = playerActions.getNextActionIfApplicable(player, getGame(), CastSpellFromHandAction.class);
if (castSpellFromHand != null) {
@@ -476,7 +480,7 @@ public class PlayerControllerForTests extends PlayerController {
public byte chooseColor(String message, SpellAbility sa, ColorSet colors) {
return Iterables.getFirst(colors, MagicColor.WHITE);
}
@Override
public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) {
return Iterables.getFirst(colors, (byte)0);
@@ -551,7 +555,7 @@ public class PlayerControllerForTests extends PlayerController {
ComputerUtil.playStack(sa, player, getGame());
}
}
private void prepareSingleSa(final Card host, final SpellAbility sa, boolean isMandatory){
if (sa.hasParam("TargetingPlayer")) {
Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0);
@@ -583,7 +587,7 @@ public class PlayerControllerForTests extends PlayerController {
} else {
ComputerUtil.playStack(tgtSA, player, getGame());
}
} else
} else
return false; // didn't play spell
}
return true;

View File

@@ -397,9 +397,9 @@ public class MatchController extends AbstractGuiGame {
}
@Override
public Map<GameEntityView, Integer> assignGenericAmount(final CardView effectSource, final Map<GameEntityView, Integer> targets,
public Map<Object, Integer> assignGenericAmount(final CardView effectSource, final Map<Object, Integer> targets,
final int amount, final boolean atLeastOne, final String amountLabel) {
return new WaitCallback<Map<GameEntityView, Integer>>() {
return new WaitCallback<Map<Object, Integer>>() {
@Override
public void run() {
final VAssignGenericAmount v = new VAssignGenericAmount(effectSource, targets, amount, atLeastOne, amountLabel, this);

View File

@@ -1,6 +1,6 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
* Copyright (C) 2021 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -32,7 +32,7 @@ import forge.assets.FSkinColor.Colors;
import forge.assets.FSkinFont;
import forge.assets.FSkinImage;
import forge.card.CardZoom;
import forge.game.GameEntityView;
import forge.card.MagicColor;
import forge.game.card.CardView;
import forge.game.player.PlayerView;
import forge.screens.match.MatchController;
@@ -56,17 +56,18 @@ public class VAssignGenericAmount extends FDialog {
private static final float CARD_GAP_X = Utils.scale(10);
private static final float ADD_BTN_HEIGHT = Utils.AVG_FINGER_HEIGHT * 0.75f;
private final Callback<Map<GameEntityView, Integer>> callback;
private final Callback<Map<Object, Integer>> callback;
private final int totalAmountToAssign;
private final String lblAmount;
private final FLabel lblTotalAmount;
private final boolean atLeastOne;
private final EffectSourcePanel pnlSource;
private final TargetsPanel pnlTargets;
private final List<AssignTarget> targetsList = new ArrayList<>();
private final Map<GameEntityView, AssignTarget> targetsMap = new HashMap<>();
private final Map<Object, AssignTarget> targetsMap = new HashMap<>();
/** Constructor.
*
@@ -75,11 +76,12 @@ public class VAssignGenericAmount extends FDialog {
* @param amount Total amount to be assigned
* @param atLeastOne Must assign at least one amount to each target
*/
public VAssignGenericAmount(final CardView effectSource, final Map<GameEntityView, Integer> targets, final int amount, final boolean atLeastOne, final String amountLabel, final WaitCallback<Map<GameEntityView, Integer>> waitCallback) {
public VAssignGenericAmount(final CardView effectSource, final Map<Object, Integer> targets, final int amount, final boolean atLeastOne, final String amountLabel, final WaitCallback<Map<Object, Integer>> waitCallback) {
super(Localizer.getInstance().getMessage("lbLAssignAmountForEffect", amountLabel, CardTranslation.getTranslatedName(effectSource.getName())) , 2);
callback = waitCallback;
totalAmountToAssign = amount;
this.atLeastOne = atLeastOne;
lblAmount = amountLabel;
lblTotalAmount = add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblTotalAmountText", lblAmount)).align(Align.center).build());
@@ -97,11 +99,11 @@ public class VAssignGenericAmount extends FDialog {
@Override
public void handleEvent(FEvent e) {
resetAssignedDamage();
initialAssignAmount(atLeastOne);
initialAssignAmount();
}
});
initialAssignAmount(atLeastOne);
initialAssignAmount();
}
@Override
@@ -130,13 +132,13 @@ public class VAssignGenericAmount extends FDialog {
}
private class TargetsPanel extends FScrollPane {
private TargetsPanel(final Map<GameEntityView, Integer> targets) {
for (final Map.Entry<GameEntityView, Integer> e : targets.entrySet()) {
private TargetsPanel(final Map<Object, Integer> targets) {
for (final Map.Entry<Object, Integer> e : targets.entrySet()) {
addDamageTarget(e.getKey(), e.getValue());
}
}
private void addDamageTarget(GameEntityView entity, int max) {
private void addDamageTarget(Object entity, int max) {
AssignTarget at = add(new AssignTarget(entity, max));
targetsMap.put(entity, at);
targetsList.add(at);
@@ -162,23 +164,38 @@ public class VAssignGenericAmount extends FDialog {
}
private class AssignTarget extends FContainer {
private final GameEntityView entity;
private final Object entity;
private final FDisplayObject obj;
private final FLabel label, btnSubtract, btnAdd;
private final int max;
private int amount;
public AssignTarget(GameEntityView entity0, int max0) {
public AssignTarget(Object entity0, int max0) {
entity = entity0;
max = max0;
if (entity instanceof CardView) {
obj = add(new EffectSourcePanel((CardView)entity));
}
else if (entity instanceof PlayerView) {
} else if (entity instanceof PlayerView) {
PlayerView player = (PlayerView)entity;
obj = add(new MiscTargetPanel(player.getName(), MatchController.getPlayerAvatar(player)));
}
else {
} else if (entity instanceof Byte) {
FSkinImage manaSymbol;
Byte color = (Byte) entity;
if (color == MagicColor.WHITE) {
manaSymbol = FSkinImage.MANA_W;
} else if (color == MagicColor.BLUE) {
manaSymbol = FSkinImage.MANA_U;
} else if (color == MagicColor.BLACK) {
manaSymbol = FSkinImage.MANA_B;
} else if (color == MagicColor.RED) {
manaSymbol = FSkinImage.MANA_R;
} else if (color == MagicColor.GREEN) {
manaSymbol = FSkinImage.MANA_G;
} else { // Should never come here, but add this to avoid compile error
manaSymbol = FSkinImage.MANA_COLORLESS;
}
obj = add(new MiscTargetPanel("", manaSymbol));
} else {
obj = add(new MiscTargetPanel(entity.toString(), FSkinImage.UNKNOWN));
}
label = add(new FLabel.Builder().text("0").font(FSkinFont.get(18)).align(Align.center).build());
@@ -254,7 +271,7 @@ public class VAssignGenericAmount extends FDialog {
}
}
private void assignAmountTo(GameEntityView source, boolean isAdding) {
private void assignAmountTo(Object source, boolean isAdding) {
AssignTarget at = targetsMap.get(source);
int assigned = at.amount;
int leftToAssign = Math.max(0, at.max - assigned);
@@ -264,6 +281,9 @@ public class VAssignGenericAmount extends FDialog {
if (amountToAdd > remainingAmount) {
amountToAdd = remainingAmount;
}
if (atLeastOne && assigned + amountToAdd < 1) {
amountToAdd = 1 - assigned;
}
if (0 == amountToAdd || amountToAdd + assigned < 0) {
return;
@@ -273,7 +293,7 @@ public class VAssignGenericAmount extends FDialog {
updateLabels();
}
private void initialAssignAmount(boolean atLeastOne) {
private void initialAssignAmount() {
if (!atLeastOne) {
updateLabels();
return;
@@ -339,8 +359,8 @@ public class VAssignGenericAmount extends FDialog {
callback.run(getAssignedMap());
}
public Map<GameEntityView, Integer> getAssignedMap() {
Map<GameEntityView, Integer> result = new HashMap<>(targetsList.size());
public Map<Object, Integer> getAssignedMap() {
Map<Object, Integer> result = new HashMap<>(targetsList.size());
for (AssignTarget at : targetsList)
result.put(at.entity, at.amount);
return result;

View File

@@ -1,6 +1,6 @@
Name:Aesthir Glider
ManaCost:3
Types:Artifact Creature Bird
Types:Artifact Creature Bird Construct
PT:2/1
K:CARDNAME can't block.
K:Flying

View File

@@ -3,8 +3,8 @@ ManaCost:2 G G G
Types:Legendary Creature Ooze
PT:2/2
K:Storm
S:Mode$ Continuous | Affected$ Card.token+Self | RemoveType$ Legendary | Description$ CARDNAME is not legendary if it's a token.
S:Mode$ Continuous | Affected$ Card.token+Self | RemoveType$ Legendary | Description$ CARDNAME isn't legendary as long as it's a token.
K:etbCounter:P1P1:X:no condition:CARDNAME enters the battlefield with a +1/+1 counter on it for each other Ooze you control.
SVar:X:Count$LastStateBattlefield Ooze.YouCtrl+Other
DeckHas:Ability$Counters
Oracle:Storm (When you cast this spell, copy it for each spell cast before it this turn. The copies become tokens.)\nAeve, Progenitor Ooze is not legendary if it's a token.\nAeve enters the battlefield with a +1/+1 counter on it for each other Ooze you control.
Oracle:Storm (When you cast this spell, copy it for each spell cast before it this turn. Copies become tokens.)\nAeve, Progenitor Ooze isn't legendary as long as it's a token.\nAeve enters the battlefield with a +1/+1 counter on it for each other Ooze you control.

View File

@@ -1,9 +1,9 @@
Name:Altar of the Goyf
ManaCost:5
Types:Tribal Artifact Lhurgoyf
T:Mode$ Attacks | ValidCard$ Creature.YouCtrl | Alone$ True | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of card types among cards in all graveyards.
T:Mode$ Attacks | ValidCard$ Creature.YouCtrl | Alone$ True | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of card types among cards in all graveyard.
SVar:TrigPump:DB$ Pump | Defined$ TriggeredAttacker | NumAtt$ +X | NumDef$ +X
S:Mode$ Continuous | Affected$ Creature.Lhurgoyf+YouCtrl | AddKeyword$ Trample | Description$ Lhurgoyf creatures you control have trample.
SVar:X:Count$CardTypes.Graveyard
SVar:PlayMain1:TRUE
Oracle:Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of card types among cards in all graveyards.\nLhurgoyf creatures you control have trample.
Oracle:Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of card types among cards in all graveyard.\nLhurgoyf creatures you control have trample.

View File

@@ -1,6 +1,6 @@
Name:Angelic Rocket
ManaCost:8
Types:Artifact Creature Angel
Types:Host Artifact Creature Angel
PT:4/4
K:Flying
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDestroy | OptionalDecider$ You | Host$ True | TriggerDescription$ When this creature enters the battlefield, you may destroy target nonland permanent.

View File

@@ -8,4 +8,4 @@ SVar:TrigPutCounter:DB$ PutCounterAll | ValidCards$ Creature.Artifact+Other+YouC
K:Modular:2
DeckHas:Ability$Counters
SVar:PlayMain1:TRUE
Oracle:First strike\nWhen Arcbound Shikari enters the battlefield, put a +1/+1 counter on each other artifact creature you control.\nModular 2 (This creature enters the battlefield with two +1/+1 counters on it. When it does, you may put its counters on target artifact creature.)
Oracle:First strike\nWhen Arcbound Shikari enters the battlefield, put a +1/+1 counter on each other artifact creature you control.\nModular 2 (This creature enters the battlefield with two +1/+1 counters on it. When it dies, you may put its +1/+1 counters on target artifact creature.)

View File

@@ -5,4 +5,4 @@ PT:0/0
K:Modular:4
K:Riot
DeckHas:Ability$Counters
Oracle:Modular 4 (This creature enters the battlefield with four +1/+1 counters on it. When it dies, you may put its +1/+1 counters on target artifact creature.)\nRiot (This creature enters the battlefield with your choice of an additional +1/+1 counter or haste.)
Oracle:Modular 4 (This creature enters the battlefield with four +1/+1 counters on it. When it dies, you may put its +1/+1 counters on target artifact creature.)\nRiot (This creature enters the battlefield with your choice of a +1/+1 counter or haste.)

View File

@@ -7,4 +7,4 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S
SVar:TrigMassacre:DB$ PumpAll | NumAtt$ -2 | NumDef$ -2 | ValidCards$ Creature.OppCtrl | IsCurse$ True
K:Unearth:3 B B
SVar:PlayMain1:TRUE
Oracle:When Archfiend of Sorrows enters the battlefield, creatures your opponents control get -2/-2 until end of turn.\nUnearth {3}{B}{B} ({3}{B}{B}: Return this card from your graveyard to the battlefield. It gains haste. Exile it at the beginning of the next end step or if it would leave the battlefield. Unearth only as a sorcery.)
Oracle:Flying\nWhen Archfiend of Sorrows enters the battlefield, creatures your opponents control get -2/-2 until end of turn.\nUnearth {3}{B}{B} ({3}{B}{B}: Return this card from your graveyard to the battlefield. It gains haste. Exile it at the beginning of the next end step or if it would leave the battlefield. Unearth only as a sorcery.)

View File

@@ -7,4 +7,4 @@ K:Lifelink
K:Outlast:GW
S:Mode$ Continuous | Affected$ Creature.YouCtrl+Other+counters_LT1_P1P1 | AddKeyword$ Outlast:GW | Description$ Each other creature you control without a +1/+1 counter on it has outlast {G/W}.
DeckHas:Ability$Counters
Oracle:Outlast {G/W} ({G/W}, {T}: Put a +1/+1 counter on this creature. Outlast only as a sorcery.)\nEach other creature you control without a +1/+1 counter on it has outlast {G/W}.
Oracle:Reach, lifelink\nOutlast {G/W} ({G/W}, {T}: Put a +1/+1 counter on this creature. Outlast only as a sorcery.)\nEach other creature you control without a +1/+1 counter on it has outlast {G/W}.

View File

@@ -1,6 +1,6 @@
Name:Ascendant Evincar
ManaCost:4 B B
Types:Legendary Creature Vampire Noble
Types:Legendary Creature Phyrexian Vampire Noble
PT:3/3
K:Flying
S:Mode$ Continuous | Affected$ Creature.Black+Other | AddPower$ 1 | AddToughness$ 1 | Description$ Other black creatures get +1/+1.

View File

@@ -1,6 +1,6 @@
Name:Atraxa, Praetors' Voice
ManaCost:G W U B
Types:Legendary Creature Angel Horror
Types:Legendary Creature Phyrexian Angel Horror
PT:4/4
K:Flying
K:Vigilance

View File

@@ -20,7 +20,7 @@ ALTERNATE
Name:Sanctum of the Sun
ManaCost:no cost
Colors:colorless
Types:Land
Types:Legendary Land
A:AB$ Mana | Cost$ T | Produced$ Any | Amount$ X | SpellDescription$ Add X mana of any one color, where X is your life total.
SVar:X:Count$YourLifeTotal
SVar:Picture:http://www.wizards.com/global/images/magic/general/sanctum_of_the_sun.jpg

View File

@@ -6,4 +6,4 @@ K:Trample
K:Reinforce:2:1 G
K:Scavenge:5 G G
DeckHas:Ability$Counters
Oracle:Trample\nReinforce 2{1}{G} ({1}{G}, Discard this card: Put two +1/+1 counters on target creature.)\nScavenge {5}{G}{G} ({5}{G}{G}, Exile this card from your graveyard: Put a number of +1/+1 counters equal to this card's power on target creature. Scavenge only as a sorcery.)
Oracle:Trample\nReinforce 2{1}{G} ({1}{G}, Discard this card: Put two +1/+1 counters on target creature.)\nScavenge {5}{G}{G} ({5}{G}{G}, Exile this card from your graveyard: Put a number of +1/+1 counters equal to this card's power on target creature. Scavenge only as a sorcery.)

View File

@@ -1,11 +1,11 @@
Name:Barbed Spike
ManaCost:1 W
Types:Artifact Equipment
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a 1/1 colorless Thopter artifact creature token with flying, then attach CARDNAME to it.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a 1/1 colorless Thopter artifact creature token with flying and attach CARDNAME to it.
SVar:TrigToken:DB$ Token | TokenScript$ c_1_1_a_thopter_flying | RememberTokens$ True | SubAbility$ DBAttach
SVar:DBAttach:DB$ Attach | Defined$ Remembered | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | Description$ Equipped creature gets +1/+0.
K:Equip:2
DeckHas:Ability$Token
Oracle:When Barbed Spike enters the battlefield, create a 1/1 colorless Thopter artifact creature token with flying, then attach Barbed Spike to it.\nEquipped creature gets +1/+0.\nEquip {2} ({2}: Attach to target creature you control. Equip only as a sorcery.)
Oracle:When Barbed Spike enters the battlefield, create a 1/1 colorless Thopter artifact creature token with flying and attach Barbed Spike to it.\nEquipped creature gets +1/+0.\nEquip {2}

View File

@@ -7,4 +7,4 @@ S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 4 | AddToughness$
A:AB$ ChangeZone | Cost$ 3 | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return CARDNAME to its owner's hand.
DeckHas:Ability$Token
SVar:Picture:http://www.wizards.com/global/images/magic/general/batterskull.jpg
Oracle:Living weapon (When this Equipment enters the battlefield, create a 0/0 black Germ creature token, then attach this to it.)\nEquipped creature gets +4/+4 and has vigilance and lifelink.\n{3}: Return Batterskull to its owner's hand.\nEquip {5}
Oracle:Living weapon (When this Equipment enters the battlefield, create a 0/0 black Phyrexian Germ creature token, then attach this to it.)\nEquipped creature gets +4/+4 and has vigilance and lifelink.\n{3}: Return Batterskull to its owner's hand.\nEquip {5}

View File

@@ -1,6 +1,6 @@
Name:Belbe, Corrupted Observer
ManaCost:B G
Types:Legendary Creature Elf Zombie
Types:Legendary Creature Phyrexian Zombie Elf
PT:2/2
T:Mode$ Phase | Phase$ Main2 | TriggerZones$ Battlefield | Execute$ TrigMana | TriggerDescription$ At the beginning of each player's postcombat main phase, that player adds {C}{C} for each of your opponents who lost life this turn. (Damage causes loss of life.)
SVar:TrigMana:DB$ Mana | Produced$ C | Amount$ X | Defined$ TriggeredPlayer

View File

@@ -1,6 +1,6 @@
Name:Blackcleave Goblin
ManaCost:3 B
Types:Creature Goblin Zombie
Types:Creature Phyrexian Goblin Zombie
PT:2/1
K:Haste
K:Infect

View File

@@ -1,9 +1,8 @@
Name:Blade Splicer
ManaCost:2 W
Types:Creature Human Artificer
Types:Creature Phyrexian Human Artificer
PT:1/1
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a 3/3 colorless Golem artifact creature token.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_3_3_a_golem | TokenOwner$ You | LegacyImage$ c 3 3 a golem nph
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a 3/3 colorless Phyrexian Golem artifact creature token.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_3_3_a_phyrexian_golem | TokenOwner$ You
S:Mode$ Continuous | Affected$ Creature.Golem+YouCtrl | AddKeyword$ First Strike | Description$ Golems you control have first strike.
SVar:Picture:http://www.wizards.com/global/images/magic/general/blade_splicer.jpg
Oracle:When Blade Splicer enters the battlefield, create a 3/3 colorless Golem artifact creature token.\nGolems you control have first strike.
Oracle:When Blade Splicer enters the battlefield, create a 3/3 colorless Phyrexian Golem artifact creature token.\nGolems you control have first strike.

View File

@@ -2,7 +2,7 @@ Name:Blazing Rootwalla
ManaCost:R
Types:Creature Lizard
PT:1/1
A:AB$ Pump | Cost$ R | NumAtt$ +2 | ActivationLimit$ 1 | SpellDescription$ CARDNAME gets +2/+0 until end of turn. Activate only once per turn.
A:AB$ Pump | Cost$ R | NumAtt$ +2 | ActivationLimit$ 1 | SpellDescription$ CARDNAME gets +2/+0 until end of turn. Activate only once each turn.
K:Madness:0
DeckHints:Ability$Discard
Oracle:{R}: Blazing Rootwalla gets +2/+0 until end of turn. Activate only once per turn.\nMadness {0} (If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.)
Oracle:{R}: Blazing Rootwalla gets +2/+0 until end of turn. Activate only once each turn.\nMadness {0} (If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.)

View File

@@ -1,6 +1,6 @@
Name:Blight Mamba
ManaCost:1 G
Types:Creature Snake
Types:Creature Phyrexian Snake
PT:1/1
K:Infect
A:AB$ Regenerate | Cost$ 1 G | SpellDescription$ Regenerate CARDNAME.

View File

@@ -1,6 +1,6 @@
Name:Blighted Agent
ManaCost:1 U
Types:Creature Human Rogue
Types:Creature Phyrexian Human Rogue
PT:1/1
K:Infect
K:Unblockable

View File

@@ -1,6 +1,6 @@
Name:Blightsteel Colossus
ManaCost:12
Types:Artifact Creature Golem
Types:Artifact Creature Phyrexian Golem
PT:11/11
K:Trample
K:Infect

View File

@@ -1,6 +1,6 @@
Name:Blightwidow
ManaCost:3 G
Types:Creature Spider
Types:Creature Phyrexian Spider
PT:2/4
K:Reach
K:Infect

View File

@@ -1,6 +1,6 @@
Name:Blind Zealot
ManaCost:1 B B
Types:Creature Human Cleric
Types:Creature Phyrexian Human Cleric
PT:2/2
K:Intimidate
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDestroy | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, you may sacrifice it. If you do, destroy target creature that player controls.

View File

@@ -1,6 +1,6 @@
Name:Blinding Souleater
ManaCost:3
Types:Artifact Creature Cleric
Types:Artifact Creature Phyrexian Cleric
PT:1/3
A:AB$ Tap | Cost$ PW T | ValidTgts$ Creature | TgtPrompt$ Select target creature | AIPhyrexianPayment$ Never | SpellDescription$ Tap target creature.
AI:RemoveDeck:Random

Some files were not shown because too many files have changed in this diff Show More