mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28:01 +00:00
Merge remote-tracking branch 'upstream/master' into editions-type-review
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,6 +17,7 @@
|
||||
|
||||
.vscode/settings.json
|
||||
.vscode/launch.json
|
||||
.factorypath
|
||||
|
||||
|
||||
# Ignore NetBeans config files
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -354,7 +354,6 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
return PaymentDecision.number(c);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostPutCardToLib cost) {
|
||||
if (cost.payCostFromSource()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(" ");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
|
||||
return sacrificeTgtAI(ai, sa);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
*/
|
||||
|
||||
@@ -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."),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -73,7 +73,6 @@ public class TriggerDiscarded extends Trigger {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.)
|
||||
|
||||
@@ -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.)
|
||||
|
||||
@@ -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.)
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name:Blightwidow
|
||||
ManaCost:3 G
|
||||
Types:Creature Spider
|
||||
Types:Creature Phyrexian Spider
|
||||
PT:2/4
|
||||
K:Reach
|
||||
K:Infect
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user