mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-12 16:58:57 +00:00
Merge remote-tracking branch 'upstream/master' into collector-number-in-card-list-and-card-db-refactoring
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.43-SNAPSHOT</version>
|
||||
<version>1.6.44-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
|
||||
@@ -510,7 +510,6 @@ public class AiController {
|
||||
landList = unreflectedLands;
|
||||
}
|
||||
|
||||
|
||||
//try to skip lands that enter the battlefield tapped
|
||||
if (!nonLandsInHand.isEmpty()) {
|
||||
CardCollection nonTappedLands = new CardCollection();
|
||||
@@ -534,6 +533,7 @@ public class AiController {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO if this is the only source for a color we need badly prioritize it instead
|
||||
if (foundTapped) {
|
||||
continue;
|
||||
}
|
||||
@@ -813,7 +813,7 @@ public class AiController {
|
||||
}
|
||||
else {
|
||||
Cost payCosts = sa.getPayCosts();
|
||||
if(payCosts != null) {
|
||||
if (payCosts != null) {
|
||||
ManaCost mana = payCosts.getTotalMana();
|
||||
if (mana != null) {
|
||||
if (mana.countX() > 0) {
|
||||
@@ -879,7 +879,7 @@ public class AiController {
|
||||
public boolean isNonDisabledCardInPlay(final String cardName) {
|
||||
for (Card card : player.getCardsIn(ZoneType.Battlefield)) {
|
||||
if (card.getName().equals(cardName)) {
|
||||
// TODO - Better logic to detemine if a permanent is disabled by local effects
|
||||
// TODO - Better logic to determine if a permanent is disabled by local effects
|
||||
// currently assuming any permanent enchanted by another player
|
||||
// is disabled and a second copy is necessary
|
||||
// will need actual logic that determines if the enchantment is able
|
||||
@@ -1916,7 +1916,7 @@ public class AiController {
|
||||
if (sa.hasParam("AIMaxAmount")) {
|
||||
max = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("AIMaxAmount"), sa);
|
||||
}
|
||||
switch(sa.getApi()) {
|
||||
switch (sa.getApi()) {
|
||||
case TwoPiles:
|
||||
// TODO: improve AI
|
||||
Card biggest = null;
|
||||
@@ -2046,11 +2046,6 @@ public class AiController {
|
||||
}
|
||||
}
|
||||
|
||||
// check
|
||||
for (int i = 0; i < library.size(); i++) {
|
||||
System.out.println(library.get(i));
|
||||
}
|
||||
|
||||
return library;
|
||||
} // smoothComputerManaCurve()
|
||||
|
||||
|
||||
@@ -78,15 +78,13 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
return null;
|
||||
}
|
||||
return PaymentDecision.card(player.getLastDrawnCard());
|
||||
}
|
||||
else if (cost.payCostFromSource()) {
|
||||
} else if (cost.payCostFromSource()) {
|
||||
if (!hand.contains(source)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return PaymentDecision.card(source);
|
||||
}
|
||||
else if (type.equals("Hand")) {
|
||||
} else if (type.equals("Hand")) {
|
||||
if (hand.size() > 1 && ability.getActivatingPlayer() != null) {
|
||||
hand = ability.getActivatingPlayer().getController().orderMoveToZoneList(hand, ZoneType.Graveyard, ability);
|
||||
}
|
||||
@@ -107,8 +105,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
randomSubset = ability.getActivatingPlayer().getController().orderMoveToZoneList(randomSubset, ZoneType.Graveyard, ability);
|
||||
}
|
||||
return PaymentDecision.card(randomSubset);
|
||||
}
|
||||
else if (type.equals("DifferentNames")) {
|
||||
} else if (type.equals("DifferentNames")) {
|
||||
CardCollection differentNames = new CardCollection();
|
||||
CardCollection discardMe = CardLists.filter(hand, CardPredicates.hasSVar("DiscardMe"));
|
||||
while (c > 0) {
|
||||
@@ -125,8 +122,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
c--;
|
||||
}
|
||||
return PaymentDecision.card(differentNames);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
||||
|
||||
CardCollection result = aic.getCardsToDiscard(c, type.split(";"), ability, discarded);
|
||||
@@ -183,8 +179,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
else if (cost.sameZone) {
|
||||
// TODO Determine exile from same zone for AI
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
CardCollectionView chosen = ComputerUtil.chooseExileFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c, ability);
|
||||
return null == chosen ? null : PaymentDecision.card(chosen);
|
||||
}
|
||||
@@ -267,6 +262,15 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
return PaymentDecision.number(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostRollDice cost) {
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
return PaymentDecision.number(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostGainControl cost) {
|
||||
if (cost.payCostFromSource()) {
|
||||
@@ -366,8 +370,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
|
||||
if (cost.isSameZone()) {
|
||||
list = new CardCollection(game.getCardsIn(cost.getFrom()));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
list = new CardCollection(player.getCardsIn(cost.getFrom()));
|
||||
}
|
||||
|
||||
@@ -862,4 +865,3 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2832,7 +2832,6 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
public static boolean lifegainPositive(final Player player, final Card source) {
|
||||
|
||||
if (!player.canGainLife()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -716,6 +716,7 @@ public class ComputerUtilCard {
|
||||
|
||||
int bigCMC = -1;
|
||||
for (final Card card : all) {
|
||||
// TODO when PlayAi can consider MDFC this should also look at the back face (if not on stack or battlefield)
|
||||
int curCMC = card.getCMC();
|
||||
|
||||
// Add all cost of all auras with the same controller
|
||||
|
||||
@@ -1899,8 +1899,7 @@ public class ComputerUtilMana {
|
||||
if (!res.contains(a)) {
|
||||
if (cost.isReusuableResource()) {
|
||||
res.add(0, a);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
res.add(res.size(), a);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.ReplaceEffect, AlwaysPlayAi.class)
|
||||
.put(ApiType.ReplaceDamage, AlwaysPlayAi.class)
|
||||
.put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class)
|
||||
.put(ApiType.ReplaceToken, AlwaysPlayAi.class)
|
||||
.put(ApiType.RestartGame, RestartGameAi.class)
|
||||
.put(ApiType.Reveal, RevealAi.class)
|
||||
.put(ApiType.RevealHand, RevealHandAi.class)
|
||||
|
||||
@@ -34,7 +34,7 @@ public class AmassAi extends SpellAbilityAi {
|
||||
final String tokenScript = "b_0_0_zombie_army";
|
||||
final int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Num", "1"), sa);
|
||||
|
||||
Card token = TokenInfo.getProtoType(tokenScript, sa, false);
|
||||
Card token = TokenInfo.getProtoType(tokenScript, sa, ai, false);
|
||||
|
||||
if (token == null) {
|
||||
return false;
|
||||
|
||||
@@ -34,6 +34,7 @@ public class ChooseTypeAi extends SpellAbilityAi {
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
|
||||
return doMirrorEntityLogic(aiPlayer, sa);
|
||||
}
|
||||
return !chooseType(sa, aiPlayer.getCardsIn(ZoneType.Battlefield)).isEmpty();
|
||||
} else if ("MostProminentOppControls".equals(sa.getParam("AILogic"))) {
|
||||
return !chooseType(sa, aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield)).isEmpty();
|
||||
}
|
||||
|
||||
@@ -1,42 +1,27 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiPlayDecision;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.CardTypeView;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameType;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.Spell;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityPredicates;
|
||||
import forge.game.spellability.SpellPermanent;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.spellability.*;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class PlayAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
@@ -70,13 +55,12 @@ public class PlayAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("ValidSA")) {
|
||||
if (cards != null & sa.hasParam("ValidSA")) {
|
||||
final String valid[] = {sa.getParam("ValidSA")};
|
||||
final Iterator<Card> itr = cards.iterator();
|
||||
while (itr.hasNext()) {
|
||||
final Card c = itr.next();
|
||||
final List<SpellAbility> validSA = Lists.newArrayList(Iterables.filter(AbilityUtils.getBasicSpellsFromPlayEffect(c, ai), SpellAbilityPredicates.isValid(valid, ai , c, sa)));
|
||||
if (validSA.size() == 0) {
|
||||
if (!Iterables.any(AbilityUtils.getBasicSpellsFromPlayEffect(c, ai), SpellAbilityPredicates.isValid(valid, ai , c, sa))) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
@@ -120,6 +104,23 @@ public class PlayAi extends SpellAbilityAi {
|
||||
}
|
||||
validOpts = CardLists.filter(validOpts, CardPredicates.greaterCMC(minCMC));
|
||||
return chooseSingleCard(ai, sa, validOpts, sa.hasParam("Optional"), null, null) != null;
|
||||
} else if ("WithTotalCMC".equals(logic)) {
|
||||
// Try to play only when there are more than three playable cards.
|
||||
if (cards.size() < 3)
|
||||
return false;
|
||||
ManaCost mana = sa.getPayCosts().getTotalMana();
|
||||
if (mana.countX() > 0) {
|
||||
int amount = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
if (amount < ComputerUtilCard.getBestAI(cards).getCMC())
|
||||
return false;
|
||||
int totalCMC = 0;
|
||||
for (Card c : cards) {
|
||||
totalCMC += c.getCMC();
|
||||
}
|
||||
if (amount > totalCMC)
|
||||
amount = totalCMC;
|
||||
sa.setXManaCostPaid(amount);
|
||||
}
|
||||
}
|
||||
|
||||
if (source != null && source.hasKeyword(Keyword.HIDEAWAY) && source.hasRemembered()) {
|
||||
@@ -173,12 +174,19 @@ public class PlayAi extends SpellAbilityAi {
|
||||
List<Card> tgtCards = CardLists.filter(options, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
// TODO needs to be aligned for MDFC along with getAbilityToPlay so the knowledge
|
||||
// of which spell was the reason for the choice can be used there
|
||||
for (SpellAbility s : c.getBasicSpells(c.getState(CardStateName.Original))) {
|
||||
Spell spell = (Spell) s;
|
||||
s.setActivatingPlayer(ai);
|
||||
// timing restrictions still apply
|
||||
if (!s.getRestrictions().checkTimingRestrictions(c, s))
|
||||
continue;
|
||||
if (params != null && params.containsKey("CMCLimit")) {
|
||||
Integer cmcLimit = (Integer) params.get("CMCLimit");
|
||||
if (spell.getPayCosts().getTotalMana().getCMC() > cmcLimit)
|
||||
continue;
|
||||
}
|
||||
if (sa.hasParam("WithoutManaCost")) {
|
||||
// Try to avoid casting instants and sorceries with X in their cost, since X will be assumed to be 0.
|
||||
if (!(spell instanceof SpellPermanent)) {
|
||||
|
||||
@@ -95,7 +95,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
if (sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
x = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
sa.setXManaCostPaid(x);
|
||||
sa.getRootAbility().setXManaCostPaid(x);
|
||||
}
|
||||
if (x <= 0) {
|
||||
return false; // 0 tokens or 0 toughness token(s)
|
||||
@@ -358,7 +358,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
if (!sa.hasParam("TokenScript")) {
|
||||
throw new RuntimeException("Spell Ability has no TokenScript: " + sa);
|
||||
}
|
||||
Card result = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa);
|
||||
Card result = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa, ai);
|
||||
|
||||
if (result == null) {
|
||||
throw new RuntimeException("don't find Token for TokenScript: " + sa.getParam("TokenScript"));
|
||||
|
||||
@@ -229,12 +229,12 @@ public class GameCopier {
|
||||
|
||||
private static final boolean USE_FROM_PAPER_CARD = true;
|
||||
private Card createCardCopy(Game newGame, Player newOwner, Card c) {
|
||||
if (c.isToken() && !c.isEmblem()) {
|
||||
if (c.isToken() && !c.isImmutable()) {
|
||||
Card result = new TokenInfo(c).makeOneToken(newOwner);
|
||||
CardFactory.copyCopiableCharacteristics(c, result);
|
||||
return result;
|
||||
}
|
||||
if (USE_FROM_PAPER_CARD && !c.isEmblem() && c.getPaperCard() != null) {
|
||||
if (USE_FROM_PAPER_CARD && !c.isImmutable() && c.getPaperCard() != null) {
|
||||
Card newCard = Card.fromPaperCard(c.getPaperCard(), newOwner);
|
||||
newCard.setCommander(c.isCommander());
|
||||
return newCard;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.43-SNAPSHOT</version>
|
||||
<version>1.6.44-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-core</artifactId>
|
||||
|
||||
@@ -424,9 +424,9 @@ public final class CardRules implements ICardCharacteristics {
|
||||
value = colonPos > 0 ? value.substring(1+colonPos) : null;
|
||||
|
||||
if ( "RemoveDeck".equals(variable) ) {
|
||||
this.removedFromAIDecks = "All".equalsIgnoreCase(value);
|
||||
this.removedFromRandomDecks = "Random".equalsIgnoreCase(value);
|
||||
this.removedFromNonCommanderDecks = "NonCommander".equalsIgnoreCase(value);
|
||||
this.removedFromAIDecks |= "All".equalsIgnoreCase(value);
|
||||
this.removedFromRandomDecks |= "Random".equalsIgnoreCase(value);
|
||||
this.removedFromNonCommanderDecks |= "NonCommander".equalsIgnoreCase(value);
|
||||
}
|
||||
} else if ("AlternateMode".equals(key)) {
|
||||
this.altMode = CardSplitType.smartValueOf(value);
|
||||
|
||||
@@ -58,7 +58,6 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
Conspiracy(false, "conspiracies"),
|
||||
Creature(true, "creatures"),
|
||||
Dungeon(false, "dungeons"),
|
||||
Emblem(false, "emblems"),
|
||||
Enchantment(true, "enchantments"),
|
||||
Instant(false, "instants"),
|
||||
Land(true, "lands"),
|
||||
@@ -437,11 +436,6 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
return coreTypes.contains(CoreType.Phenomenon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmblem() {
|
||||
return coreTypes.contains(CoreType.Emblem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTribal() {
|
||||
return coreTypes.contains(CoreType.Tribal);
|
||||
@@ -547,7 +541,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
if (!isInstant() && !isSorcery()) {
|
||||
Iterables.removeIf(subtypes, Predicates.IS_SPELL_TYPE);
|
||||
}
|
||||
if (!isPlaneswalker() && !isEmblem()) {
|
||||
if (!isPlaneswalker()) {
|
||||
Iterables.removeIf(subtypes, Predicates.IS_WALKER_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ public interface CardTypeView extends Iterable<String>, Serializable {
|
||||
boolean isBasicLand();
|
||||
boolean isPlane();
|
||||
boolean isPhenomenon();
|
||||
boolean isEmblem();
|
||||
boolean isTribal();
|
||||
boolean isDungeon();
|
||||
CardTypeView getTypeWithChanges(Iterable<CardChangedType> changedCardTypes);
|
||||
|
||||
@@ -87,7 +87,6 @@ public class DeckGeneratorMonoColor extends DeckGeneratorBase {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final CardPool getDeck(final int size, final boolean forAi) {
|
||||
addCreaturesAndSpells(size, cmcLevels, forAi);
|
||||
|
||||
@@ -61,7 +61,6 @@ import forge.util.TextUtil;
|
||||
*/
|
||||
public class BoosterGenerator {
|
||||
|
||||
|
||||
private final static Map<String, PrintSheet> cachedSheets = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
private static synchronized PrintSheet getPrintSheet(String key) {
|
||||
if( !cachedSheets.containsKey(key) )
|
||||
|
||||
@@ -31,7 +31,6 @@ public class UnOpenedProduct implements IUnOpenedProduct {
|
||||
this.poolLimited = considerNumbersInPool; // TODO: Add 0 to parameter's name.
|
||||
}
|
||||
|
||||
|
||||
// Means to select from all unique cards (from base game, ie. no schemes or avatars)
|
||||
public UnOpenedProduct(SealedProduct.Template template) {
|
||||
tpl = template;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.43-SNAPSHOT</version>
|
||||
<version>1.6.44-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
@@ -705,17 +705,20 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
if (zoneTo.is(ZoneType.Stack)) {
|
||||
// zoneFrom maybe null if the spell is cast from "Ouside the game", ex. ability of Garth One-Eye
|
||||
if (zoneFrom == null) {
|
||||
c.setCastFrom(null);
|
||||
c.setCastSA(null);
|
||||
} else if (zoneTo.is(ZoneType.Stack)) {
|
||||
} else {
|
||||
c.setCastFrom(zoneFrom.getZoneType());
|
||||
}
|
||||
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard()) && !c.isCopiedSpell()) {
|
||||
c.setCastSA(cause);
|
||||
} else {
|
||||
c.setCastSA(null);
|
||||
}
|
||||
} else if (!(zoneTo.is(ZoneType.Battlefield) && zoneFrom.is(ZoneType.Stack))) {
|
||||
} else if (zoneFrom == null || !(zoneFrom.is(ZoneType.Stack) &&
|
||||
(zoneTo.is(ZoneType.Battlefield) || zoneTo.is(ZoneType.Merged)))) {
|
||||
c.setCastFrom(null);
|
||||
c.setCastSA(null);
|
||||
}
|
||||
|
||||
@@ -153,8 +153,7 @@ public final class GameActionUtil {
|
||||
final StringBuilder sb = new StringBuilder(sa.getDescription());
|
||||
if (!source.equals(host)) {
|
||||
sb.append(" by ");
|
||||
if ((host.isEmblem() || host.getType().hasSubtype("Effect"))
|
||||
&& host.getEffectSource() != null) {
|
||||
if ((host.isImmutable()) && host.getEffectSource() != null) {
|
||||
sb.append(host.getEffectSource());
|
||||
} else {
|
||||
sb.append(host);
|
||||
@@ -542,7 +541,6 @@ public final class GameActionUtil {
|
||||
final Card eff = new Card(game.nextCardId(), game);
|
||||
eff.setTimestamp(game.getNextTimestamp());
|
||||
eff.setName(sourceCard.getName() + "'s Effect");
|
||||
eff.addType("Effect");
|
||||
eff.setOwner(controller);
|
||||
|
||||
eff.setImageKey(sourceCard.getImageKey());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.game;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -37,11 +38,23 @@ public enum PlanarDice {
|
||||
trigRes = Chaos;
|
||||
}
|
||||
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, roller);
|
||||
runParams.put(AbilityKey.Result, trigRes);
|
||||
roller.getGame().getTriggerHandler().runTrigger(TriggerType.PlanarDice, runParams,false);
|
||||
|
||||
// Also run normal RolledDie and RolledDieOnce triggers
|
||||
runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, roller);
|
||||
runParams.put(AbilityKey.Sides, 6);
|
||||
runParams.put(AbilityKey.Result, 0);
|
||||
roller.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDie, runParams, false);
|
||||
|
||||
runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, roller);
|
||||
runParams.put(AbilityKey.Sides, 6);
|
||||
runParams.put(AbilityKey.Result, Arrays.asList(0));
|
||||
roller.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDieOnce, runParams, false);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostParser;
|
||||
import forge.card.mana.ManaCostShard;
|
||||
import forge.game.CardTraitBase;
|
||||
import forge.game.Direction;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameObject;
|
||||
@@ -111,9 +112,9 @@ public class AbilityUtils {
|
||||
// Probably will move to One function solution sometime in the future
|
||||
public static CardCollection getDefinedCards(final Card hostCard, final String def, final CardTraitBase sa) {
|
||||
CardCollection cards = new CardCollection();
|
||||
String defined = (def == null) ? "Self" : applyAbilityTextChangeEffects(def, sa); // default to Self
|
||||
final String[] incR = defined.split("\\.", 2);
|
||||
defined = incR[0];
|
||||
String changedDef = (def == null) ? "Self" : applyAbilityTextChangeEffects(def, sa); // default to Self
|
||||
final String[] incR = changedDef.split("\\.", 2);
|
||||
String defined = incR[0];
|
||||
final Game game = hostCard.getGame();
|
||||
|
||||
Card c = null;
|
||||
@@ -131,7 +132,7 @@ public class AbilityUtils {
|
||||
}
|
||||
}
|
||||
else if (defined.equals("EffectSource")) {
|
||||
if (hostCard.isEmblem() || hostCard.getType().hasSubtype("Effect")) {
|
||||
if (hostCard.isImmutable()) {
|
||||
c = findEffectRoot(hostCard);
|
||||
}
|
||||
}
|
||||
@@ -313,6 +314,10 @@ public class AbilityUtils {
|
||||
for (final Card imprint : hostCard.getImprintedCards()) {
|
||||
cards.add(game.getCardState(imprint));
|
||||
}
|
||||
} else if (defined.equals("UntilLeavesBattlefield")) {
|
||||
for (final Card ulb : hostCard.getUntilLeavesBattlefield()) {
|
||||
cards.add(game.getCardState(ulb));
|
||||
}
|
||||
} else if (defined.startsWith("ThisTurnEntered")) {
|
||||
final String[] workingCopy = defined.split("_");
|
||||
ZoneType destination, origin;
|
||||
@@ -345,6 +350,23 @@ public class AbilityUtils {
|
||||
cards.add(game.getCardState(cardByID));
|
||||
}
|
||||
}
|
||||
} else if (defined.startsWith("Valid")) {
|
||||
Iterable<Card> candidates;
|
||||
String validDefined;
|
||||
if (defined.startsWith("Valid ")) {
|
||||
candidates = game.getCardsIn(ZoneType.Battlefield);
|
||||
validDefined = changedDef.substring("Valid ".length());
|
||||
} else if (defined.startsWith("ValidAll ")) {
|
||||
candidates = game.getCardsInGame();
|
||||
validDefined = changedDef.substring("ValidAll ".length());
|
||||
} else {
|
||||
String[] s = changedDef.split(" ", 2);
|
||||
String zone = s[0].substring("Valid".length());
|
||||
candidates = game.getCardsIn(ZoneType.smartValueOf(zone));
|
||||
validDefined = s[1];
|
||||
}
|
||||
cards.addAll(CardLists.getValidCards(candidates, validDefined.split(","), hostCard.getController(), hostCard, sa));
|
||||
return cards;
|
||||
} else {
|
||||
CardCollection list = null;
|
||||
if (sa instanceof SpellAbility) {
|
||||
@@ -376,19 +398,6 @@ public class AbilityUtils {
|
||||
}
|
||||
}
|
||||
|
||||
if (defined.startsWith("Valid ")) {
|
||||
String validDefined = defined.substring("Valid ".length());
|
||||
list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), validDefined.split(","), hostCard.getController(), hostCard, sa);
|
||||
} else if (defined.startsWith("ValidAll ")) {
|
||||
String validDefined = defined.substring("ValidAll ".length());
|
||||
list = CardLists.getValidCards(game.getCardsInGame(), validDefined.split(","), hostCard.getController(), hostCard, sa);
|
||||
} else if (defined.startsWith("Valid")) {
|
||||
String[] s = defined.split(" ");
|
||||
String zone = s[0].substring("Valid".length());
|
||||
String validDefined = s[1];
|
||||
list = CardLists.getValidCards(game.getCardsIn(ZoneType.smartValueOf(zone)), validDefined.split(","), hostCard.getController(), hostCard, sa);
|
||||
}
|
||||
|
||||
if (list != null) {
|
||||
cards.addAll(list);
|
||||
}
|
||||
@@ -420,7 +429,7 @@ public class AbilityUtils {
|
||||
private static Card findEffectRoot(Card startCard) {
|
||||
Card cc = startCard.getEffectSource();
|
||||
if (cc != null) {
|
||||
if (cc.isEmblem() || cc.getType().hasSubtype("Effect")) {
|
||||
if (cc.isImmutable()) {
|
||||
return findEffectRoot(cc);
|
||||
}
|
||||
return cc;
|
||||
@@ -1268,6 +1277,9 @@ public class AbilityUtils {
|
||||
}
|
||||
else if (defined.equals("Opponent")) {
|
||||
players.addAll(player.getOpponents());
|
||||
} else if (defined.startsWith("NextPlayerToYour")) {
|
||||
Direction dir = defined.substring(16).equals("Left") ? Direction.Left : Direction.Right;
|
||||
players.add(game.getNextPlayerAfter(player, dir));
|
||||
}
|
||||
else {
|
||||
for (Player p : game.getPlayersInTurnOrder()) {
|
||||
|
||||
@@ -142,6 +142,7 @@ public enum ApiType {
|
||||
ReplaceEffect (ReplaceEffect.class),
|
||||
ReplaceMana (ReplaceManaEffect.class),
|
||||
ReplaceDamage (ReplaceDamageEffect.class),
|
||||
ReplaceToken (ReplaceTokenEffect.class),
|
||||
ReplaceSplitDamage (ReplaceSplitDamageEffect.class),
|
||||
RestartGame (RestartGameEffect.class),
|
||||
Reveal (RevealEffect.class),
|
||||
|
||||
@@ -13,7 +13,6 @@ import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
import forge.GameCommand;
|
||||
import forge.card.CardType;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
@@ -457,25 +456,19 @@ public abstract class SpellAbilityEffect {
|
||||
final Card eff = new Card(game.nextCardId(), game);
|
||||
eff.setTimestamp(game.getNextTimestamp());
|
||||
eff.setName(name);
|
||||
eff.setColor(hostCard.determineColor().getColor());
|
||||
// if name includes emblem then it should be one
|
||||
eff.addType(name.startsWith("Emblem") ? "Emblem" : "Effect");
|
||||
// add Planeswalker types into Emblem for fun
|
||||
if (name.startsWith("Emblem") && hostCard.isPlaneswalker()) {
|
||||
for (final String type : hostCard.getType().getSubtypes()) {
|
||||
if (CardType.isAPlaneswalkerType(type)) {
|
||||
eff.addType(type);
|
||||
}
|
||||
}
|
||||
if (name.startsWith("Emblem")) {
|
||||
eff.setEmblem(true);
|
||||
// Emblem needs to be colorless
|
||||
eff.setColor(MagicColor.COLORLESS);
|
||||
}
|
||||
|
||||
eff.setOwner(controller);
|
||||
eff.setSVars(sa.getSVars());
|
||||
|
||||
eff.setImageKey(image);
|
||||
if (eff.getType().hasType(CardType.CoreType.Emblem)) {
|
||||
eff.setColor(MagicColor.COLORLESS);
|
||||
} else {
|
||||
eff.setColor(hostCard.determineColor().getColor());
|
||||
}
|
||||
|
||||
eff.setImmutable(true);
|
||||
eff.setEffectSource(sa);
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.event.GameEventCombatChanged;
|
||||
import forge.game.event.GameEventTokenCreated;
|
||||
import forge.game.player.Player;
|
||||
@@ -53,37 +52,24 @@ public class AmassEffect extends TokenEffectBase {
|
||||
final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa);
|
||||
final boolean remember = sa.hasParam("RememberAmass");
|
||||
|
||||
boolean useZoneTable = true;
|
||||
CardZoneTable triggerList = sa.getChangeZoneTable();
|
||||
if (triggerList == null) {
|
||||
triggerList = new CardZoneTable();
|
||||
useZoneTable = false;
|
||||
}
|
||||
if (sa.hasParam("ChangeZoneTable")) {
|
||||
sa.setChangeZoneTable(triggerList);
|
||||
useZoneTable = true;
|
||||
}
|
||||
|
||||
MutableBoolean combatChanged = new MutableBoolean(false);
|
||||
// create army token if needed
|
||||
if (CardLists.count(activator.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Army")) == 0) {
|
||||
final String tokenScript = "b_0_0_zombie_army";
|
||||
CardZoneTable triggerList = new CardZoneTable();
|
||||
MutableBoolean combatChanged = new MutableBoolean(false);
|
||||
|
||||
final Card prototype = TokenInfo.getProtoType(tokenScript, sa, false);
|
||||
makeTokenTable(makeTokenTableInternal(activator, "b_0_0_zombie_army", 1, sa), false, triggerList, combatChanged, sa);
|
||||
|
||||
makeTokens(prototype, activator, sa, 1, true, false, triggerList, combatChanged);
|
||||
|
||||
if (!useZoneTable) {
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
triggerList.clear();
|
||||
}
|
||||
|
||||
game.fireEvent(new GameEventTokenCreated());
|
||||
}
|
||||
|
||||
if (combatChanged.isTrue()) {
|
||||
game.updateCombatForView();
|
||||
game.fireEvent(new GameEventCombatChanged());
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("CounterType", CounterType.get(CounterEnumType.P1P1));
|
||||
params.put("Amount", 1);
|
||||
|
||||
@@ -190,11 +190,6 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
}
|
||||
}
|
||||
|
||||
// Restore immutable to effect
|
||||
if (sa.hasParam("Immutable")) {
|
||||
c.setImmutable(true);
|
||||
}
|
||||
|
||||
game.fireEvent(new GameEventCardStatsChanged(c));
|
||||
}
|
||||
|
||||
|
||||
@@ -164,14 +164,6 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), c, false, sa);
|
||||
addedTriggers.add(parsedTrigger);
|
||||
}
|
||||
if (sa.hasParam("GainsTriggeredAbilitiesOf")) {
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("GainsTriggeredAbilitiesOf"), sa);
|
||||
for (final Card card : cards) {
|
||||
for (Trigger t : card.getTriggers()) {
|
||||
addedTriggers.add(t.copy(c, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// give replacement effects
|
||||
final List<ReplacementEffect> addedReplacements = Lists.newArrayList();
|
||||
|
||||
@@ -119,6 +119,12 @@ public class ChooseCardEffect extends SpellAbilityEffect {
|
||||
Aggregates.random(choices, validAmount, chosen);
|
||||
} else {
|
||||
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseaCard") + " ";
|
||||
if (sa.hasParam ("ChoiceTitleAppendDefined")) {
|
||||
String defined = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("ChoiceTitleAppendDefined"), sa).toString();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(title).append(" ").append(defined);
|
||||
title = sb.toString();
|
||||
}
|
||||
chosen.addAll(p.getController().chooseCardsForEffect(choices, sa, title, minAmount, validAmount, !sa.hasParam("Mandatory"), null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +72,7 @@ public class ChooseTypeEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw new InvalidParameterException(sa.getHostCard() + "'s ability resulted in no types to choose from");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
if (tapOnLose) {
|
||||
c.tap();
|
||||
}
|
||||
} // if
|
||||
}
|
||||
host.removeGainControlTargets(c);
|
||||
}
|
||||
|
||||
@@ -93,6 +93,11 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
tgtCards = CardLists.filterControlledBy(tgtCards, getTargetPlayers(sa));
|
||||
}
|
||||
|
||||
// in case source was LKI or still resolving
|
||||
if (source.isLKI() || source.getZone().is(ZoneType.Stack)) {
|
||||
source = game.getCardState(source);
|
||||
}
|
||||
|
||||
// check for lose control criteria right away
|
||||
if (lose != null && lose.contains("LeavesPlay") && !source.isInZone(ZoneType.Battlefield)) {
|
||||
return;
|
||||
@@ -103,7 +108,6 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
|
||||
boolean combatChanged = false;
|
||||
for (Card tgtC : tgtCards) {
|
||||
|
||||
if (!tgtC.isInPlay() || !tgtC.canBeControlledBy(newController)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import forge.game.card.CardFactory;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.card.TokenCreateTable;
|
||||
import forge.game.event.GameEventCombatChanged;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
@@ -177,15 +178,18 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
}
|
||||
|
||||
MutableBoolean combatChanged = new MutableBoolean(false);
|
||||
TokenCreateTable tokenTable = new TokenCreateTable();
|
||||
|
||||
for (final Card c : tgtCards) {
|
||||
// if it only targets player, it already got all needed cards from defined
|
||||
if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer() && !c.canBeTargetedBy(sa)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
makeTokens(getProtoType(sa, c), controller, sa, numCopies, true, true, triggerList, combatChanged);
|
||||
tokenTable.put(controller, getProtoType(sa, c, controller), numCopies);
|
||||
} // end foreach Card
|
||||
|
||||
makeTokenTable(tokenTable, true, triggerList, combatChanged, sa);
|
||||
|
||||
if (!useZoneTable) {
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
triggerList.clear();
|
||||
@@ -196,9 +200,8 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
}
|
||||
} // end resolve
|
||||
|
||||
private Card getProtoType(final SpellAbility sa, final Card original) {
|
||||
private Card getProtoType(final SpellAbility sa, final Card original, final Player newOwner) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player newOwner = sa.getActivatingPlayer();
|
||||
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
|
||||
final Card copy = new Card(id, original.getPaperCard(), host.getGame());
|
||||
copy.setOwner(newOwner);
|
||||
|
||||
@@ -12,7 +12,6 @@ public class DetachedCardEffect extends Card {
|
||||
card = card0;
|
||||
|
||||
setName(name0);
|
||||
addType("Effect");
|
||||
setOwner(card0.getOwner());
|
||||
setImmutable(true);
|
||||
|
||||
|
||||
@@ -140,10 +140,6 @@ public class EffectEffect extends SpellAbilityEffect {
|
||||
final Card eff = createEffect(sa, controller, name, image);
|
||||
eff.setSetCode(sa.getHostCard().getSetCode());
|
||||
eff.setRarity(sa.getHostCard().getRarity());
|
||||
// For Raging River effect to add attacker "left" or "right" pile later
|
||||
if (sa.hasParam("Mutable")) {
|
||||
eff.setImmutable(false);
|
||||
}
|
||||
|
||||
// Abilities and triggers work the same as they do for Token
|
||||
// Grant abilities
|
||||
|
||||
@@ -38,7 +38,6 @@ public class FightEffect extends DamageBaseEffect {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.ability.SpellAbilityEffect#resolve(forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@@ -77,6 +76,7 @@ public class FightEffect extends DamageBaseEffect {
|
||||
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Fighters, fighters);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.FightOnce, runParams, false);
|
||||
}
|
||||
|
||||
private static List<Card> getFighters(SpellAbility sa) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.event.GameEventCombatChanged;
|
||||
import forge.game.event.GameEventTokenCreated;
|
||||
import forge.game.player.Player;
|
||||
@@ -36,14 +35,13 @@ public class InvestigateEffect extends TokenEffectBase {
|
||||
|
||||
final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa);
|
||||
|
||||
final String tokenScript = "c_a_clue_draw";
|
||||
final Card prototype = TokenInfo.getProtoType(tokenScript, sa, false);
|
||||
|
||||
// Investigate in Sequence
|
||||
for (final Player p : getTargetPlayers(sa)) {
|
||||
for (int i = 0; i < amount; i++) {
|
||||
CardZoneTable triggerList = new CardZoneTable();
|
||||
MutableBoolean combatChanged = new MutableBoolean(false);
|
||||
makeTokens(prototype, p, sa, 1, true, false, triggerList, combatChanged);
|
||||
|
||||
makeTokenTable(makeTokenTableInternal(p, "c_a_clue_draw", 1, sa), false, triggerList, combatChanged, sa);
|
||||
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
p.addInvestigatedThisTurn();
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -69,6 +72,8 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
final boolean optional = sa.hasParam("Optional");
|
||||
boolean remember = sa.hasParam("RememberPlayed");
|
||||
int amount = 1;
|
||||
boolean hasTotalCMCLimit = sa.hasParam("WithTotalCMC");
|
||||
int totalCMCLimit = Integer.MAX_VALUE;
|
||||
if (sa.hasParam("Amount") && !sa.getParam("Amount").equals("All")) {
|
||||
amount = AbilityUtils.calculateAmount(source, sa.getParam("Amount"), sa);
|
||||
}
|
||||
@@ -163,13 +168,15 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
|
||||
if (sa.hasParam("ValidSA")) {
|
||||
final String valid[] = {sa.getParam("ValidSA")};
|
||||
List<Card> toRemove = Lists.newArrayList();
|
||||
for (Card c : tgtCards) {
|
||||
Iterator<Card> it = tgtCards.iterator();
|
||||
while (it.hasNext()) {
|
||||
Card c = it.next();
|
||||
if (!Iterables.any(AbilityUtils.getBasicSpellsFromPlayEffect(c, controller), SpellAbilityPredicates.isValid(valid, controller , c, sa))) {
|
||||
toRemove.add(c);
|
||||
// it.remove will only remove item from the list part of CardCollection
|
||||
tgtCards.asSet().remove(c);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
tgtCards.removeAll(toRemove);
|
||||
if (tgtCards.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -179,15 +186,37 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
amount = tgtCards.size();
|
||||
}
|
||||
|
||||
if (hasTotalCMCLimit) {
|
||||
totalCMCLimit = AbilityUtils.calculateAmount(source, sa.getParam("WithTotalCMC"), sa);
|
||||
}
|
||||
|
||||
if (controlledByPlayer != null) {
|
||||
activator.addController(controlledByTimeStamp, controlledByPlayer);
|
||||
}
|
||||
|
||||
boolean singleOption = tgtCards.size() == 1 && amount == 1 && optional;
|
||||
Map<String, Object> params = hasTotalCMCLimit ? new HashMap<>() : null;
|
||||
|
||||
while (!tgtCards.isEmpty() && amount > 0 && totalCMCLimit >= 0) {
|
||||
if (hasTotalCMCLimit) {
|
||||
// filter out cards with mana value greater than limit
|
||||
Iterator<Card> it = tgtCards.iterator();
|
||||
final String [] valid = {"Spell.cmcLE"+totalCMCLimit};
|
||||
while (it.hasNext()) {
|
||||
Card c = it.next();
|
||||
if (!Iterables.any(AbilityUtils.getBasicSpellsFromPlayEffect(c, controller), SpellAbilityPredicates.isValid(valid, controller , c, sa))) {
|
||||
// it.remove will only remove item from the list part of CardCollection
|
||||
tgtCards.asSet().remove(c);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
if (tgtCards.isEmpty())
|
||||
break;
|
||||
params.put("CMCLimit", totalCMCLimit);
|
||||
}
|
||||
|
||||
while (!tgtCards.isEmpty() && amount > 0) {
|
||||
activator.getController().tempShowCards(showCards);
|
||||
Card tgtCard = controller.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblSelectCardToPlay"), !singleOption && optional, null);
|
||||
Card tgtCard = controller.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblSelectCardToPlay"), !singleOption && optional, params);
|
||||
activator.getController().endTempShowCards();
|
||||
if (tgtCard == null) {
|
||||
break;
|
||||
@@ -248,6 +277,14 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
final String valid[] = {sa.getParam("ValidSA")};
|
||||
sas = Lists.newArrayList(Iterables.filter(sas, SpellAbilityPredicates.isValid(valid, controller , source, sa)));
|
||||
}
|
||||
if (hasTotalCMCLimit) {
|
||||
Iterator<SpellAbility> it = sas.iterator();
|
||||
while (it.hasNext()) {
|
||||
SpellAbility s = it.next();
|
||||
if (s.getPayCosts().getTotalMana().getCMC() > totalCMCLimit)
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (sas.isEmpty()) {
|
||||
continue;
|
||||
@@ -276,6 +313,8 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
|
||||
final int tgtCMC = tgtSA.getPayCosts().getTotalMana().getCMC();
|
||||
|
||||
if (sa.hasParam("WithoutManaCost")) {
|
||||
tgtSA = tgtSA.copyWithNoManaCost();
|
||||
} else if (sa.hasParam("PlayCost")) {
|
||||
@@ -341,6 +380,7 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
amount--;
|
||||
totalCMCLimit -= tgtCMC;
|
||||
}
|
||||
|
||||
// Remove controlled by player if any
|
||||
|
||||
@@ -121,7 +121,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
final Card host = sa.getHostCard();
|
||||
//if host is not on the battlefield don't apply
|
||||
// Suspend should does Affect the Stack
|
||||
if ((sa.hasParam("UntilLoseControlOfHost") || sa.hasParam("UntilHostLeavesPlay"))
|
||||
if (("UntilHostLeavesPlay".equals(sa.getParam("Duration")) || "UntilLoseControlOfHost".equals(sa.getParam("Duration")))
|
||||
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
|
||||
return;
|
||||
}
|
||||
@@ -158,7 +158,6 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (tgts.size() > 0) {
|
||||
|
||||
for (final GameEntity c : tgts) {
|
||||
sb.append(c).append(" ");
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class RegenerationEffect extends SpellAbilityEffect {
|
||||
// Play the Regen sound
|
||||
game.fireEvent(new GameEventCardRegenerated());
|
||||
|
||||
if (host.getType().hasStringType("Effect")) {
|
||||
if (host.isImmutable()) {
|
||||
c.subtractShield(host);
|
||||
host.removeRemembered(c);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ public class RepeatEffect extends SpellAbilityEffect {
|
||||
// TODO Replace Infinite Loop Break with a game draw. Here are the scenarios that can cause this:
|
||||
// Helm of Obedience vs Graveyard to Library replacement effect
|
||||
|
||||
if(source.getName().equals("Helm of Obedience")) {
|
||||
if (source.getName().equals("Helm of Obedience")) {
|
||||
StringBuilder infLoop = new StringBuilder(sa.getHostCard().toString());
|
||||
infLoop.append(" - To avoid an infinite loop, this repeat has been broken ");
|
||||
infLoop.append(" and the game will now continue in the current state, ending the loop early. ");
|
||||
|
||||
@@ -47,7 +47,7 @@ public class ReplaceDamageEffect extends SpellAbilityEffect {
|
||||
prevent -= n;
|
||||
|
||||
if (!StringUtils.isNumeric(varValue) && card.getSVar(varValue).startsWith("Number$")) {
|
||||
if (card.getType().hasStringType("Effect") && prevent <= 0) {
|
||||
if (card.isImmutable() && prevent <= 0) {
|
||||
game.getAction().exile(card, null);
|
||||
} else {
|
||||
card.setSVar(varValue, "Number$" + prevent);
|
||||
|
||||
@@ -3,12 +3,6 @@ package forge.game.ability.effects;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameLogEntryType;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -17,25 +11,21 @@ import forge.game.card.Card;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementResult;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
|
||||
public class ReplaceEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Game game = card.getGame();
|
||||
|
||||
final AbilityKey varName = AbilityKey.fromString(sa.getParam("VarName"));
|
||||
final String varValue = sa.getParam("VarValue");
|
||||
final String type = sa.getParamOrDefault("VarType", "amount");
|
||||
final ReplacementType retype = sa.getReplacementEffect().getMode();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
|
||||
Map<AbilityKey, Object> params = Maps.newHashMap(originalParams);
|
||||
Map<AbilityKey, Object> params = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
|
||||
|
||||
if ("Card".equals(type)) {
|
||||
List<Card> list = AbilityUtils.getDefinedCards(card, varValue, sa);
|
||||
@@ -53,7 +43,7 @@ public class ReplaceEffect extends SpellAbilityEffect {
|
||||
params.put(varName, list.get(0));
|
||||
}
|
||||
} else if ("TokenScript".equals(type)) {
|
||||
final Card protoType = TokenInfo.getProtoType(varValue, sa);
|
||||
final Card protoType = TokenInfo.getProtoType(varValue, sa, sa.getActivatingPlayer());
|
||||
if (protoType != null) {
|
||||
params.put(varName, protoType);
|
||||
}
|
||||
@@ -61,42 +51,7 @@ public class ReplaceEffect extends SpellAbilityEffect {
|
||||
params.put(varName, AbilityUtils.calculateAmount(card, varValue, sa));
|
||||
}
|
||||
|
||||
if (params.containsKey(AbilityKey.EffectOnly)) {
|
||||
params.put(AbilityKey.EffectOnly, true);
|
||||
}
|
||||
|
||||
if (retype == ReplacementType.DamageDone) {
|
||||
for (Map.Entry<AbilityKey, Object> e : params.entrySet()) {
|
||||
originalParams.put(e.getKey(), e.getValue());
|
||||
}
|
||||
originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
|
||||
return;
|
||||
}
|
||||
|
||||
// need to log Updated events there, or the log is wrong order
|
||||
String message = sa.getReplacementEffect().toString();
|
||||
if ( !StringUtils.isEmpty(message)) {
|
||||
message = TextUtil.fastReplace(message, "CARDNAME", card.getName());
|
||||
game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
|
||||
}
|
||||
|
||||
//try to call replacementHandler with new Params
|
||||
ReplacementResult result = game.getReplacementHandler().run(retype, params);
|
||||
switch (result) {
|
||||
case NotReplaced:
|
||||
case Updated: {
|
||||
for (Map.Entry<AbilityKey, Object> e : params.entrySet()) {
|
||||
originalParams.put(e.getKey(), e.getValue());
|
||||
}
|
||||
// effect was updated
|
||||
originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// effect was replaced with something else
|
||||
originalParams.put(AbilityKey.ReplacementResult, result);
|
||||
break;
|
||||
}
|
||||
params.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,20 +4,15 @@ import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameLogEntryType;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementResult;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
public class ReplaceManaEffect extends SpellAbilityEffect {
|
||||
|
||||
@@ -25,17 +20,14 @@ public class ReplaceManaEffect extends SpellAbilityEffect {
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Player player = sa.getActivatingPlayer();
|
||||
final Game game = card.getGame();
|
||||
|
||||
// outside of Replacement Effect, unwanted result
|
||||
if (!sa.isReplacementAbility()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ReplacementType event = sa.getReplacementEffect().getMode();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
|
||||
Map<AbilityKey, Object> params = Maps.newHashMap(originalParams);
|
||||
Map<AbilityKey, Object> params = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
|
||||
|
||||
String replaced = (String)sa.getReplacingObject(AbilityKey.Mana);
|
||||
if (sa.hasParam("ReplaceMana")) {
|
||||
@@ -79,31 +71,8 @@ public class ReplaceManaEffect extends SpellAbilityEffect {
|
||||
replaced = StringUtils.repeat(replaced, " ", Integer.valueOf(sa.getParam("ReplaceAmount")));
|
||||
}
|
||||
params.put(AbilityKey.Mana, replaced);
|
||||
|
||||
// need to log Updated events there, or the log is wrong order
|
||||
String message = sa.getReplacementEffect().toString();
|
||||
if (!StringUtils.isEmpty(message)) {
|
||||
message = TextUtil.fastReplace(message, "CARDNAME", card.getName());
|
||||
game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
|
||||
}
|
||||
|
||||
//try to call replacementHandler with new Params
|
||||
ReplacementResult result = game.getReplacementHandler().run(event, params);
|
||||
switch (result) {
|
||||
case NotReplaced:
|
||||
case Updated: {
|
||||
for (Map.Entry<AbilityKey, Object> e : params.entrySet()) {
|
||||
originalParams.put(e.getKey(), e.getValue());
|
||||
}
|
||||
// effect was updated
|
||||
originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// effect was replaced with something else
|
||||
originalParams.put(AbilityKey.ReplacementResult, result);
|
||||
break;
|
||||
}
|
||||
params.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect {
|
||||
dmg -= n;
|
||||
prevent -= n;
|
||||
|
||||
if (card.getType().hasStringType("Effect") && prevent <= 0) {
|
||||
if (card.isImmutable() && prevent <= 0) {
|
||||
game.getAction().exile(card, null);
|
||||
} else if (!StringUtils.isNumeric(varValue)) {
|
||||
sa.setSVar(varValue, "Number$" + prevent);
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.TokenCreateTable;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementResult;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class ReplaceTokenEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Player p = sa.getActivatingPlayer();
|
||||
final Game game = card.getGame();
|
||||
|
||||
// ReplaceToken Effect only applies to one Player
|
||||
Player affected = (Player) sa.getReplacingObject(AbilityKey.Player);
|
||||
TokenCreateTable table = (TokenCreateTable) sa.getReplacingObject(AbilityKey.Token);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa
|
||||
.getReplacingObject(AbilityKey.OriginalParams);
|
||||
|
||||
// currently the only ones that changes the amount does double it
|
||||
if ("Amount".equals(sa.getParam("Type"))) {
|
||||
for (Map.Entry<Card, Integer> e : table.row(affected).entrySet()) {
|
||||
if (!sa.matchesValidParam("ValidCard", e.getKey())) {
|
||||
continue;
|
||||
}
|
||||
// currently the amount is only doubled
|
||||
table.put(affected, e.getKey(), e.getValue() * 2);
|
||||
}
|
||||
} else if ("AddToken".equals(sa.getParam("Type"))) {
|
||||
long timestamp = game.getNextTimestamp();
|
||||
|
||||
Map<Player, Integer> byController = Maps.newHashMap();
|
||||
for (Map.Entry<Card, Integer> e : table.row(affected).entrySet()) {
|
||||
if (!sa.matchesValidParam("ValidCard", e.getKey())) {
|
||||
continue;
|
||||
}
|
||||
Player contoller = e.getKey().getController();
|
||||
int old = ObjectUtils.defaultIfNull(byController.get(contoller), 0);
|
||||
byController.put(contoller, old + e.getValue());
|
||||
}
|
||||
|
||||
if (!byController.isEmpty()) {
|
||||
// for Xorn, might matter if you could somehow create Treasure under multiple players control
|
||||
if (sa.hasParam("Amount")) {
|
||||
int i = AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa);
|
||||
for (Map.Entry<Player, Integer> e : byController.entrySet()) {
|
||||
e.setValue(i);
|
||||
}
|
||||
}
|
||||
for (Map.Entry<Player, Integer> e : byController.entrySet()) {
|
||||
for (String script : sa.getParam("TokenScript").split(",")) {
|
||||
final Card token = TokenInfo.getProtoType(script, sa, p);
|
||||
|
||||
if (token == null) {
|
||||
throw new RuntimeException("don't find Token for TokenScript: " + script);
|
||||
}
|
||||
token.setController(e.getKey(), timestamp);
|
||||
table.put(p, token, e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("ReplaceToken".equals(sa.getParam("Type"))) {
|
||||
long timestamp = game.getNextTimestamp();
|
||||
|
||||
Map<Player, Integer> toInsertMap = Maps.newHashMap();
|
||||
Set<Card> toRemoveSet = Sets.newHashSet();
|
||||
for (Map.Entry<Card, Integer> e : table.row(affected).entrySet()) {
|
||||
if (!sa.matchesValidParam("ValidCard", e.getKey())) {
|
||||
continue;
|
||||
}
|
||||
Player controller = e.getKey().getController();
|
||||
int old = ObjectUtils.defaultIfNull(toInsertMap.get(controller), 0);
|
||||
toInsertMap.put(controller, old + e.getValue());
|
||||
toRemoveSet.add(e.getKey());
|
||||
}
|
||||
// remove replaced tokens
|
||||
table.row(affected).keySet().removeAll(toRemoveSet);
|
||||
|
||||
// insert new tokens
|
||||
for (Map.Entry<Player, Integer> pe : toInsertMap.entrySet()) {
|
||||
if (pe.getValue() <= 0) {
|
||||
continue;
|
||||
}
|
||||
for (String script : sa.getParam("TokenScript").split(",")) {
|
||||
final Card token = TokenInfo.getProtoType(script, sa, pe.getKey());
|
||||
|
||||
if (token == null) {
|
||||
throw new RuntimeException("don't find Token for TokenScript: " + script);
|
||||
}
|
||||
|
||||
token.setController(pe.getKey(), timestamp);
|
||||
table.put(affected, token, pe.getValue());
|
||||
}
|
||||
}
|
||||
} else if ("ReplaceController".equals(sa.getParam("Type"))) {
|
||||
long timestamp = game.getNextTimestamp();
|
||||
Player newController = sa.getActivatingPlayer();
|
||||
if (sa.hasParam("NewController")) {
|
||||
newController = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("NewController"), sa).get(0);
|
||||
}
|
||||
for (Map.Entry<Card, Integer> c : table.row(affected).entrySet()) {
|
||||
if (!sa.matchesValidParam("ValidCard", c.getKey())) {
|
||||
continue;
|
||||
}
|
||||
c.getKey().setController(newController, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
// effect was updated
|
||||
originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,10 +23,8 @@ import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.event.GameEventCombatChanged;
|
||||
import forge.game.event.GameEventTokenCreated;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class TokenEffect extends TokenEffectBase {
|
||||
@@ -36,20 +34,6 @@ public class TokenEffect extends TokenEffectBase {
|
||||
return sa.getDescription();
|
||||
}
|
||||
|
||||
public Card loadTokenPrototype(SpellAbility sa) {
|
||||
if (!sa.hasParam("TokenScript")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Card result = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa);
|
||||
|
||||
if (result == null) {
|
||||
throw new RuntimeException("don't find Token for TokenScript: " + sa.getParam("TokenScript"));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
@@ -67,9 +51,8 @@ public class TokenEffect extends TokenEffectBase {
|
||||
}
|
||||
}
|
||||
|
||||
Card prototype = loadTokenPrototype(sa);
|
||||
|
||||
final int finalAmount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("TokenAmount", "1"), sa);
|
||||
MutableBoolean combatChanged = new MutableBoolean(false);
|
||||
|
||||
boolean useZoneTable = true;
|
||||
CardZoneTable triggerList = sa.getChangeZoneTable();
|
||||
@@ -82,10 +65,8 @@ public class TokenEffect extends TokenEffectBase {
|
||||
useZoneTable = true;
|
||||
}
|
||||
|
||||
MutableBoolean combatChanged = new MutableBoolean(false);
|
||||
for (final Player owner : AbilityUtils.getDefinedPlayers(host, sa.getParamOrDefault("TokenOwner", "You"), sa)) {
|
||||
makeTokens(prototype, owner, sa, finalAmount, true, false, triggerList, combatChanged);
|
||||
}
|
||||
makeTokenTable(AbilityUtils.getDefinedPlayers(host, sa.getParamOrDefault("TokenOwner", "You"), sa),
|
||||
sa.getParam("TokenScript").split(","), finalAmount, false, triggerList, combatChanged, sa);
|
||||
|
||||
if (!useZoneTable) {
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
|
||||
@@ -2,45 +2,125 @@ package forge.game.ability.effects;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.mutable.MutableBoolean;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
import forge.GameCommand;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardFactory;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.TokenCreateTable;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.event.GameEventCardStatsChanged;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public abstract class TokenEffectBase extends SpellAbilityEffect {
|
||||
|
||||
protected List<Card> makeTokens(final Card prototype, final Player creator, final SpellAbility sa, int finalAmount,
|
||||
boolean applyMultiplier, boolean clone, CardZoneTable triggerList, MutableBoolean combatChanged) {
|
||||
protected TokenCreateTable createTokenTable(Iterable<Player> players, String[] tokenScripts, final int finalAmount, final SpellAbility sa) {
|
||||
|
||||
TokenCreateTable tokenTable = new TokenCreateTable();
|
||||
for (final Player owner : players) {
|
||||
for (String script : tokenScripts) {
|
||||
final Card result = TokenInfo.getProtoType(script, sa, owner);
|
||||
|
||||
if (result == null) {
|
||||
throw new RuntimeException("don't find Token for TokenScript: " + script);
|
||||
}
|
||||
// set owner
|
||||
result.setOwner(owner);
|
||||
tokenTable.put(owner, result, finalAmount);
|
||||
}
|
||||
}
|
||||
return tokenTable;
|
||||
}
|
||||
|
||||
protected TokenCreateTable makeTokenTableInternal(Player owner, String script, final int finalAmount, final SpellAbility sa) {
|
||||
TokenCreateTable tokenTable = new TokenCreateTable();
|
||||
final Card result = TokenInfo.getProtoType(script, sa, owner, false);
|
||||
|
||||
if (result == null) {
|
||||
throw new RuntimeException("don't find Token for TokenScript: " + script);
|
||||
}
|
||||
// set owner
|
||||
result.setOwner(owner);
|
||||
tokenTable.put(owner, result, finalAmount);
|
||||
|
||||
return tokenTable;
|
||||
}
|
||||
|
||||
protected TokenCreateTable makeTokenTable(Iterable<Player> players, String[] tokenScripts, final int finalAmount, final boolean clone,
|
||||
CardZoneTable triggerList, MutableBoolean combatChanged, final SpellAbility sa) {
|
||||
return makeTokenTable(createTokenTable(players, tokenScripts, finalAmount, sa), clone, triggerList, combatChanged, sa);
|
||||
}
|
||||
|
||||
protected TokenCreateTable makeTokenTable(TokenCreateTable tokenTable, final boolean clone, CardZoneTable triggerList, MutableBoolean combatChanged, final SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
final long timestamp = game.getNextTimestamp();
|
||||
long timestamp = game.getNextTimestamp();
|
||||
|
||||
// support PlayerCollection for affected
|
||||
Set<Player> toRemove = Sets.newHashSet();
|
||||
for (Player p : tokenTable.rowKeySet()) {
|
||||
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(p);
|
||||
repParams.put(AbilityKey.Token, tokenTable);
|
||||
repParams.put(AbilityKey.EffectOnly, true); // currently only effects can create tokens?
|
||||
|
||||
switch (game.getReplacementHandler().run(ReplacementType.CreateToken, repParams)) {
|
||||
case NotReplaced:
|
||||
break;
|
||||
case Updated: {
|
||||
tokenTable = (TokenCreateTable) repParams.get(AbilityKey.Token);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
toRemove.add(p);
|
||||
}
|
||||
}
|
||||
tokenTable.rowKeySet().removeAll(toRemove);
|
||||
|
||||
final List<String> pumpKeywords = Lists.newArrayList();
|
||||
if (sa.hasParam("PumpKeywords")) {
|
||||
pumpKeywords.addAll(Arrays.asList(sa.getParam("PumpKeywords").split(" & ")));
|
||||
}
|
||||
|
||||
List<Card> allTokens = Lists.newArrayList();
|
||||
for (Card tok : TokenInfo.makeTokensFromPrototype(prototype, creator, finalAmount, applyMultiplier)) {
|
||||
for (final Table.Cell<Player, Card, Integer> c : tokenTable.cellSet()) {
|
||||
Card prototype = c.getColumnKey();
|
||||
Player creator = c.getRowKey();
|
||||
Player controller = prototype.getController();
|
||||
int cellAmount = c.getValue();
|
||||
for (int i = 0; i < cellAmount; i++) {
|
||||
Card tok = CardFactory.copyCard(prototype, true);
|
||||
// Crafty Cutpurse would change under which control it does enter,
|
||||
// but it shouldn't change who creates the token
|
||||
tok.setOwner(creator);
|
||||
if (creator != controller) {
|
||||
tok.setController(controller, timestamp);
|
||||
}
|
||||
tok.setTimestamp(timestamp);
|
||||
tok.setToken(true);
|
||||
|
||||
// do effect stuff with the token
|
||||
if (sa.hasParam("TokenTapped")) {
|
||||
tok.setTapped(true);
|
||||
}
|
||||
@@ -48,7 +128,6 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
|
||||
if (!sa.hasParam("AttachAfter") && sa.hasParam("AttachedTo") && !attachTokenTo(tok, sa)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sa.hasParam("WithCounters")) {
|
||||
String[] parse = sa.getParam("WithCounters").split("_");
|
||||
tok.addEtbCounter(CounterType.getType(parse[0]), Integer.parseInt(parse[1]), creator);
|
||||
@@ -60,34 +139,43 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
|
||||
tok.addEtbCounter(cType, cAmount, creator);
|
||||
}
|
||||
|
||||
if (sa.hasParam("AddTriggersFrom")) {
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(host, sa.getParam("AddTriggersFrom"), sa);
|
||||
for (final Card card : cards) {
|
||||
for (final Trigger trig : card.getTriggers()) {
|
||||
tok.addTrigger(trig.copy(tok, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (clone) {
|
||||
tok.setCopiedPermanent(prototype);
|
||||
}
|
||||
|
||||
// Should this be catching the Card that's returned?
|
||||
Card c = game.getAction().moveToPlay(tok, sa);
|
||||
if (c == null || c.getZone() == null) {
|
||||
Card moved = game.getAction().moveToPlay(tok, sa);
|
||||
if (moved == null || moved.getZone() == null) {
|
||||
// in case token can't enter the battlefield, it isn't created
|
||||
triggerList.put(ZoneType.None, ZoneType.None, c);
|
||||
triggerList.put(ZoneType.None, ZoneType.None, moved);
|
||||
continue;
|
||||
}
|
||||
triggerList.put(ZoneType.None, c.getZone().getZoneType(), c);
|
||||
triggerList.put(ZoneType.None, moved.getZone().getZoneType(), moved);
|
||||
|
||||
creator.addTokensCreatedThisTurn();
|
||||
|
||||
if (clone) {
|
||||
c.setCloneOrigin(host);
|
||||
moved.setCloneOrigin(host);
|
||||
}
|
||||
if (!pumpKeywords.isEmpty()) {
|
||||
c.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, false, timestamp);
|
||||
addPumpUntil(sa, c, timestamp);
|
||||
moved.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, false, timestamp);
|
||||
addPumpUntil(sa, moved, timestamp);
|
||||
}
|
||||
|
||||
if (sa.hasParam("AtEOTTrig")) {
|
||||
addSelfTrigger(sa, sa.getParam("AtEOTTrig"), c);
|
||||
addSelfTrigger(sa, sa.getParam("AtEOTTrig"), moved);
|
||||
}
|
||||
|
||||
if (addToCombat(c, tok.getController(), sa, "TokenAttacking", "TokenBlocking")) {
|
||||
if (addToCombat(moved, tok.getController(), sa, "TokenAttacking", "TokenBlocking")) {
|
||||
combatChanged.setTrue();
|
||||
}
|
||||
|
||||
@@ -95,30 +183,31 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
|
||||
attachTokenTo(tok, sa);
|
||||
}
|
||||
|
||||
c.updateStateForView();
|
||||
moved.updateStateForView();
|
||||
|
||||
if (sa.hasParam("RememberTokens")) {
|
||||
host.addRemembered(c);
|
||||
host.addRemembered(moved);
|
||||
}
|
||||
if (sa.hasParam("ImprintTokens")) {
|
||||
host.addImprintedCard(c);
|
||||
host.addImprintedCard(moved);
|
||||
}
|
||||
if (sa.hasParam("RememberSource")) {
|
||||
c.addRemembered(host);
|
||||
moved.addRemembered(host);
|
||||
}
|
||||
if (sa.hasParam("TokenRemembered")) {
|
||||
final String remembered = sa.getParam("TokenRemembered");
|
||||
for (final Object o : AbilityUtils.getDefinedObjects(host, remembered, sa)) {
|
||||
c.addRemembered(o);
|
||||
moved.addRemembered(o);
|
||||
}
|
||||
}
|
||||
allTokens.add(c);
|
||||
allTokens.add(moved);
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("AtEOT")) {
|
||||
registerDelayedTrigger(sa, sa.getParam("AtEOT"), allTokens);
|
||||
}
|
||||
return allTokens;
|
||||
return tokenTable;
|
||||
}
|
||||
|
||||
private boolean attachTokenTo(Card tok, SpellAbility sa) {
|
||||
|
||||
@@ -206,6 +206,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|
||||
// for Vanguard / Manapool / Emblems etc.
|
||||
private boolean isImmutable = false;
|
||||
private boolean isEmblem = false;
|
||||
|
||||
private int exertThisTurn = 0;
|
||||
private PlayerCollection exertedByPlayer = new PlayerCollection();
|
||||
@@ -1629,7 +1630,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
return exiledWith;
|
||||
}
|
||||
public final void setExiledWith(final Card e) {
|
||||
exiledWith = e;
|
||||
exiledWith = view.setCard(exiledWith, e, TrackableProperty.ExiledWith);
|
||||
}
|
||||
|
||||
public final void cleanupExiledWith() {
|
||||
@@ -1738,6 +1739,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
public void setCurrentRoom(String room) {
|
||||
currentRoom = room;
|
||||
view.updateCurrentRoom(this);
|
||||
view.getCurrentState().updateAbilityText(this, getCurrentState());
|
||||
}
|
||||
public boolean isInLastRoom() {
|
||||
for (final Trigger t : getTriggers()) {
|
||||
@@ -2154,6 +2156,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
public String getAbilityText(final CardState state) {
|
||||
final String linebreak = "\r\n\r\n";
|
||||
final String grayTag = "<span style=\"color:gray;\">";
|
||||
final String endTag = "</span>";
|
||||
final CardTypeView type = state.getType();
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
@@ -2199,9 +2204,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
// Get original description since text might be translated
|
||||
if (replacementEffect.hasParam("Description") &&
|
||||
replacementEffect.getParam("Description").contains("enters the battlefield")) {
|
||||
sb.append(text).append("\r\n");
|
||||
sb.append(text).append(linebreak);
|
||||
} else {
|
||||
replacementEffects.append(text).append("\r\n");
|
||||
replacementEffects.append(text).append(linebreak);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2213,7 +2218,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
Cost cost = first.getPayCosts();
|
||||
if (cost != null && !cost.isOnlyManaCost()) {
|
||||
sb.append(cost.toString());
|
||||
sb.append("\r\n");
|
||||
sb.append(linebreak);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2233,15 +2238,22 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
// Give spellText line breaks for easier reading
|
||||
sb.append("\r\n");
|
||||
sb.append(text.replaceAll("\\\\r\\\\n", "\r\n"));
|
||||
sb.append("\r\n");
|
||||
sb.append(linebreak);
|
||||
|
||||
// Triggered abilities
|
||||
for (final Trigger trig : state.getTriggers()) {
|
||||
if (!trig.isSecondary() && !trig.isClassAbility()) {
|
||||
boolean disabled = false;
|
||||
// Disable text of other rooms
|
||||
if (type.isDungeon() && !trig.getOverridingAbility().getParam("RoomName").equals(getCurrentRoom())) {
|
||||
disabled = true;
|
||||
}
|
||||
String trigStr = trig.replaceAbilityText(trig.toString(), state);
|
||||
sb.append(trigStr.replaceAll("\\\\r\\\\n", "\r\n")).append("\r\n");
|
||||
if (disabled) sb.append(grayTag);
|
||||
sb.append(trigStr.replaceAll("\\\\r\\\\n", "\r\n"));
|
||||
if (disabled) sb.append(endTag);
|
||||
sb.append(linebreak);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2253,7 +2265,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
if (!stAb.isSecondary() && !stAb.isClassAbility()) {
|
||||
final String stAbD = stAb.toString();
|
||||
if (!stAbD.equals("")) {
|
||||
sb.append(stAbD).append("\r\n");
|
||||
sb.append(stAbD).append(linebreak);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2326,7 +2338,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", host.getEffectSource().getName());
|
||||
}
|
||||
sb.append(desc);
|
||||
sb.append("\r\n");
|
||||
sb.append(linebreak);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2334,13 +2346,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|
||||
// Class Abilities
|
||||
if (isClassCard()) {
|
||||
String linebreak = "\r\n\r\n";
|
||||
sb.append(linebreak);
|
||||
// Currently the maximum levels of all Class cards are all 3
|
||||
for (int level = 1; level <= 3; ++level) {
|
||||
boolean disabled = level > getClassLevel() && isInZone(ZoneType.Battlefield);
|
||||
final String grayTag = "<span style=\"color:gray;\">";
|
||||
final String endTag = "</span>";
|
||||
for (final Trigger trig : state.getTriggers()) {
|
||||
if (trig.isClassLevelNAbility(level) && !trig.isSecondary()) {
|
||||
if (disabled) sb.append(grayTag);
|
||||
@@ -4532,15 +4541,13 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
public final boolean isPermanent() {
|
||||
return !isImmutable && (isInZone(ZoneType.Battlefield) || getType().isPermanent());
|
||||
return !isImmutable() && (isInZone(ZoneType.Battlefield) || getType().isPermanent());
|
||||
}
|
||||
|
||||
public final boolean isSpell() {
|
||||
return (isInstant() || isSorcery() || (isAura() && !isInZone((ZoneType.Battlefield))));
|
||||
}
|
||||
|
||||
public final boolean isEmblem() { return getType().isEmblem(); }
|
||||
|
||||
public final boolean isLand() { return getType().isLand(); }
|
||||
public final boolean isBasicLand() { return getType().isBasicLand(); }
|
||||
public final boolean isSnow() { return getType().isSnow(); }
|
||||
@@ -4799,11 +4806,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
// Takes one argument like Permanent.Blue+withFlying
|
||||
@Override
|
||||
public final boolean isValid(final String restriction, final Player sourceController, final Card source, CardTraitBase spellAbility) {
|
||||
if (isImmutable() && source != null && !source.isRemembered(this) &&
|
||||
!(restriction.startsWith("Emblem") || restriction.startsWith("Effect"))) { // special case exclusion
|
||||
return false;
|
||||
}
|
||||
|
||||
// Inclusive restrictions are Card types
|
||||
final String[] incR = restriction.split("\\.", 2);
|
||||
|
||||
@@ -4813,14 +4815,27 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
incR[0] = incR[0].substring(1); // consume negation sign
|
||||
}
|
||||
|
||||
if (incR[0].equals("Spell") && !isSpell()) {
|
||||
if (incR[0].equals("Spell")) {
|
||||
if (!isSpell()) {
|
||||
return testFailed;
|
||||
}
|
||||
if (incR[0].equals("Permanent") && !isPermanent()) {
|
||||
} else if (incR[0].equals("Permanent")) {
|
||||
if (!isPermanent()) {
|
||||
return testFailed;
|
||||
}
|
||||
if (!incR[0].equals("card") && !incR[0].equals("Card") && !incR[0].equals("Spell")
|
||||
&& !incR[0].equals("Permanent") && !getType().hasStringType(incR[0])) {
|
||||
} else if (incR[0].equals("Effect")) {
|
||||
if (!isImmutable()) {
|
||||
return testFailed;
|
||||
}
|
||||
} else if (incR[0].equals("Emblem")) {
|
||||
if (!isEmblem()) {
|
||||
return testFailed;
|
||||
}
|
||||
} else if (incR[0].equals("card") || incR[0].equals("Card")) {
|
||||
if (isImmutable()) {
|
||||
return testFailed;
|
||||
}
|
||||
} else if (!getType().hasStringType(incR[0])) {
|
||||
return testFailed; // Check for wrong type
|
||||
}
|
||||
|
||||
@@ -4828,24 +4843,20 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
final String excR = incR[1];
|
||||
final String[] exRs = excR.split("\\+"); // Exclusive Restrictions are ...
|
||||
for (String exR : exRs) {
|
||||
if (exR.startsWith("!")) {
|
||||
exR = exR.substring(1);
|
||||
if (hasProperty(exR, sourceController, source, spellAbility)) {
|
||||
return testFailed;
|
||||
}
|
||||
} else {
|
||||
if (!hasProperty(exR, sourceController, source, spellAbility)) {
|
||||
return testFailed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return !testFailed;
|
||||
}
|
||||
|
||||
// Takes arguments like Blue or withFlying
|
||||
@Override
|
||||
public boolean hasProperty(final String property, final Player sourceController, final Card source, CardTraitBase spellAbility) {
|
||||
if (property.startsWith("!")) {
|
||||
return !CardProperty.cardHasProperty(this, property.substring(1), sourceController, source, spellAbility);
|
||||
}
|
||||
return CardProperty.cardHasProperty(this, property, sourceController, source, spellAbility);
|
||||
}
|
||||
|
||||
@@ -4854,6 +4865,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
public final void setImmutable(final boolean isImmutable0) {
|
||||
isImmutable = isImmutable0;
|
||||
view.updateImmutable(this);
|
||||
}
|
||||
|
||||
public final boolean isEmblem() {
|
||||
return isEmblem;
|
||||
}
|
||||
public final void setEmblem(final boolean isEmblem0) {
|
||||
isEmblem = isEmblem0;
|
||||
view.updateEmblem(this);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -5969,6 +5989,19 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
public void setCastFrom(final ZoneType castFrom0) {
|
||||
castFrom = castFrom0;
|
||||
}
|
||||
public boolean wasCast() {
|
||||
if (hasMergedCard()) {
|
||||
boolean wasCast = false;
|
||||
for (Card c : getMergedCards()) {
|
||||
if (null != c.getCastFrom()) {
|
||||
wasCast = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return wasCast;
|
||||
}
|
||||
return getCastFrom() != null;
|
||||
}
|
||||
|
||||
public SpellAbility getCastSA() {
|
||||
return castSA;
|
||||
|
||||
@@ -350,7 +350,7 @@ public class CardProperty {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("EffectSource")) {
|
||||
if (!source.isEmblem() && !source.getType().hasSubtype("Effect")) {
|
||||
if (!source.isImmutable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -446,6 +446,9 @@ public class CardProperty {
|
||||
}
|
||||
} else if (property.startsWith("CanEnchant")) {
|
||||
final String restriction = property.substring(10);
|
||||
if (restriction.equals("EquippedBy")) {
|
||||
if (!source.getEquipping().canBeAttached(card)) return false;
|
||||
}
|
||||
if (restriction.equals("Remembered")) {
|
||||
for (final Object rem : source.getRemembered()) {
|
||||
if (!(rem instanceof Card) || !((Card) rem).canBeAttached(card))
|
||||
@@ -1658,6 +1661,10 @@ public class CardProperty {
|
||||
if (source.hasImprintedCard(card)) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("IsGoaded")) {
|
||||
if (!card.isGoaded()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("NoAbilities")) {
|
||||
if (!card.hasNoAbilities()) {
|
||||
return false;
|
||||
@@ -1680,16 +1687,15 @@ public class CardProperty {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("wasCast")) {
|
||||
if (null == card.getCastFrom()) {
|
||||
if (!card.wasCast()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("wasNotCast")) {
|
||||
if (null != card.getCastFrom()) {
|
||||
if (card.wasCast()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("wasCastFrom")) {
|
||||
// How are we getting in here with a comma?
|
||||
final String strZone = property.split(",")[0].substring(11);
|
||||
final String strZone = property.substring(11);
|
||||
final ZoneType realZone = ZoneType.smartValueOf(strZone);
|
||||
if (realZone != card.getCastFrom()) {
|
||||
return false;
|
||||
|
||||
@@ -120,7 +120,7 @@ public final class CardUtil {
|
||||
List<Card> res = Lists.newArrayList();
|
||||
final Game game = src.getGame();
|
||||
if (to != ZoneType.Stack) {
|
||||
for (Player p : game.getPlayers()) {
|
||||
for (Player p : game.getRegisteredPlayers()) {
|
||||
res.addAll(p.getZone(to).getCardsAddedThisTurn(from));
|
||||
}
|
||||
}
|
||||
@@ -242,6 +242,7 @@ public final class CardUtil {
|
||||
newCopy.setToken(in.isToken());
|
||||
newCopy.setCopiedSpell(in.isCopiedSpell());
|
||||
newCopy.setImmutable(in.isImmutable());
|
||||
newCopy.setEmblem(in.isEmblem());
|
||||
|
||||
// lock in the current P/T
|
||||
newCopy.setBasePower(in.getCurrentPower());
|
||||
@@ -530,6 +531,7 @@ public final class CardUtil {
|
||||
// however, due to the changes necessary for SA_Requirements this is much
|
||||
// different than the original
|
||||
public static List<Card> getValidCardsToTarget(TargetRestrictions tgt, SpellAbility ability) {
|
||||
Card activatingCard = ability.getHostCard();
|
||||
final Game game = ability.getActivatingPlayer().getGame();
|
||||
final List<ZoneType> zone = tgt.getZone();
|
||||
|
||||
@@ -539,7 +541,6 @@ public final class CardUtil {
|
||||
if (canTgtStack) {
|
||||
// Since getTargetableCards doesn't have additional checks if one of the Zones is stack
|
||||
// Remove the activating card from targeting itself if its on the Stack
|
||||
Card activatingCard = ability.getHostCard();
|
||||
if (activatingCard.isInZone(ZoneType.Stack)) {
|
||||
choices.remove(ability.getHostCard());
|
||||
}
|
||||
@@ -561,7 +562,7 @@ public final class CardUtil {
|
||||
|
||||
final List<Card> choicesCopy = Lists.newArrayList(choices);
|
||||
for (final Card c : choicesCopy) {
|
||||
if (c.getCMC() > tgt.getMaxTotalCMC(c, ability) - totalCMCTargeted) {
|
||||
if (c.getCMC() > tgt.getMaxTotalCMC(activatingCard, ability) - totalCMCTargeted) {
|
||||
choices.remove(c);
|
||||
}
|
||||
}
|
||||
@@ -576,7 +577,7 @@ public final class CardUtil {
|
||||
|
||||
final List<Card> choicesCopy = Lists.newArrayList(choices);
|
||||
for (final Card c : choicesCopy) {
|
||||
if (c.getNetPower() > tgt.getMaxTotalPower(c, ability) - totalPowerTargeted) {
|
||||
if (c.getNetPower() > tgt.getMaxTotalPower(activatingCard, ability) - totalPowerTargeted) {
|
||||
choices.remove(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +233,20 @@ public class CardView extends GameEntityView {
|
||||
set(TrackableProperty.Token, c.isToken());
|
||||
}
|
||||
|
||||
public boolean isImmutable() {
|
||||
return get(TrackableProperty.IsImmutable);
|
||||
}
|
||||
public void updateImmutable(Card c) {
|
||||
set(TrackableProperty.IsImmutable, c.isImmutable());
|
||||
}
|
||||
|
||||
public boolean isEmblem() {
|
||||
return get(TrackableProperty.IsEmblem);
|
||||
}
|
||||
public void updateEmblem(Card c) {
|
||||
set(TrackableProperty.IsEmblem, c.isEmblem());
|
||||
}
|
||||
|
||||
public boolean isTokenCard() { return get(TrackableProperty.TokenCard); }
|
||||
void updateTokenCard(Card c) { set(TrackableProperty.TokenCard, c.isTokenCard()); }
|
||||
|
||||
@@ -585,6 +599,10 @@ public class CardView extends GameEntityView {
|
||||
return get(TrackableProperty.CloneOrigin);
|
||||
}
|
||||
|
||||
public CardView getExiledWith() {
|
||||
return get(TrackableProperty.ExiledWith);
|
||||
}
|
||||
|
||||
public FCollectionView<CardView> getImprintedCards() {
|
||||
return get(TrackableProperty.ImprintedCards);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ public enum CounterEnumType {
|
||||
|
||||
COIN("COIN",255,215,0),
|
||||
|
||||
COMPONENT("COMPN", 224, 160, 48),
|
||||
|
||||
CORPSE("CRPSE", 230, 186, 209),
|
||||
|
||||
CORRUPTION("CRPTN", 210, 121, 210),
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package forge.game.card;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
import com.google.common.collect.ForwardingTable;
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
import forge.game.CardTraitBase;
|
||||
import forge.game.GameObjectPredicates;
|
||||
import forge.game.player.Player;
|
||||
|
||||
public class TokenCreateTable extends ForwardingTable<Player, Card, Integer> {
|
||||
|
||||
Table<Player, Card, Integer> dataMap = HashBasedTable.create();
|
||||
|
||||
public TokenCreateTable() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Table<Player, Card, Integer> delegate() {
|
||||
return dataMap;
|
||||
}
|
||||
|
||||
public int add(Player p, Card c, int i) {
|
||||
int old = ObjectUtils.defaultIfNull(this.get(p, c), 0);
|
||||
int newValue = old + i;
|
||||
this.put(p, c, newValue);
|
||||
return newValue;
|
||||
}
|
||||
|
||||
public int getFilterAmount(String validOwner, String validToken, final CardTraitBase ctb) {
|
||||
final Card host = ctb.getHostCard();
|
||||
int result = 0;
|
||||
List<Card> filteredCards = null;
|
||||
List<Player> filteredPlayer = null;
|
||||
|
||||
if (validOwner == null && validToken == null) {
|
||||
for (Integer i : values()) {
|
||||
result += i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (validOwner != null) {
|
||||
filteredPlayer = Lists.newArrayList(Iterables.filter(rowKeySet(),
|
||||
GameObjectPredicates.restriction(validOwner.split(","), host.getController(), host, ctb)));
|
||||
if (filteredPlayer.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (validToken != null) {
|
||||
filteredCards = CardLists.getValidCardsAsList(columnKeySet(), validToken, host.getController(), host, ctb);
|
||||
if (filteredCards.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (filteredPlayer == null) {
|
||||
for (Map.Entry<Card, Map<Player, Integer>> e : columnMap().entrySet()) {
|
||||
for (Integer i : e.getValue().values()) {
|
||||
result += i;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (filteredCards == null) {
|
||||
for (Map.Entry<Player, Map<Card, Integer>> e : rowMap().entrySet()) {
|
||||
for (Integer i : e.getValue().values()) {
|
||||
result += i;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
for (Table.Cell<Player, Card, Integer> c : this.cellSet()) {
|
||||
if (!filteredPlayer.contains(c.getRowKey())) {
|
||||
continue;
|
||||
}
|
||||
if (!filteredCards.contains(c.getColumnKey())) {
|
||||
continue;
|
||||
}
|
||||
result += c.getValue();
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -15,15 +15,12 @@ import forge.StaticData;
|
||||
import forge.card.CardType;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardFactory;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.item.PaperToken;
|
||||
|
||||
@@ -140,59 +137,6 @@ public class TokenInfo {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static List<Card> makeToken(final Card prototype, final Player owner,
|
||||
final boolean applyMultiplier, final int num) {
|
||||
final List<Card> list = Lists.newArrayList();
|
||||
|
||||
final Game game = owner.getGame();
|
||||
int multiplier = num;
|
||||
Player player = owner;
|
||||
Card proto = prototype;
|
||||
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(player);
|
||||
repParams.put(AbilityKey.Token, prototype);
|
||||
repParams.put(AbilityKey.TokenNum, multiplier);
|
||||
repParams.put(AbilityKey.EffectOnly, applyMultiplier);
|
||||
|
||||
switch (game.getReplacementHandler().run(ReplacementType.CreateToken, repParams)) {
|
||||
case NotReplaced:
|
||||
break;
|
||||
case Updated: {
|
||||
multiplier = (int) repParams.get(AbilityKey.TokenNum);
|
||||
player = (Player) repParams.get(AbilityKey.Affected);
|
||||
proto = (Card) repParams.get(AbilityKey.Token);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
multiplier = 0;
|
||||
break;
|
||||
}
|
||||
if (multiplier <= 0) {
|
||||
return list;
|
||||
}
|
||||
|
||||
long timestamp = game.getNextTimestamp();
|
||||
|
||||
for (int i = 0; i < multiplier; i++) {
|
||||
// need to set owner or copyCard will fail with assign new ID
|
||||
proto.setOwner(owner);
|
||||
Card copy = CardFactory.copyCard(proto, true);
|
||||
// need to assign player after token is copied
|
||||
if (player != owner) {
|
||||
copy.setController(player, timestamp);
|
||||
}
|
||||
copy.setTimestamp(timestamp);
|
||||
copy.setToken(true);
|
||||
list.add(copy);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
static public List<Card> makeTokensFromPrototype(Card prototype, final Player owner, int amount, final boolean applyMultiplier) {
|
||||
return makeToken(prototype, owner, applyMultiplier, amount);
|
||||
}
|
||||
|
||||
public Card makeOneToken(final Player controller) {
|
||||
final Game game = controller.getGame();
|
||||
final Card c = toCard(game);
|
||||
@@ -321,10 +265,10 @@ public class TokenInfo {
|
||||
result.getCurrentState().changeTextIntrinsic(colorMap, typeMap);
|
||||
}
|
||||
|
||||
static public Card getProtoType(final String script, final SpellAbility sa) {
|
||||
return getProtoType(script, sa, true);
|
||||
static public Card getProtoType(final String script, final SpellAbility sa, final Player owner) {
|
||||
return getProtoType(script, sa, owner, true);
|
||||
}
|
||||
static public Card getProtoType(final String script, final SpellAbility sa, boolean applyTextChange) {
|
||||
static public Card getProtoType(final String script, final SpellAbility sa, final Player owner, boolean applyTextChange) {
|
||||
// script might be null, or sa might be null
|
||||
if (script == null || sa == null) {
|
||||
return null;
|
||||
@@ -338,7 +282,7 @@ public class TokenInfo {
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
final Card result = Card.fromPaperCard(token, null, game);
|
||||
final Card result = Card.fromPaperCard(token, owner, game);
|
||||
|
||||
if (sa.hasParam("TokenPower")) {
|
||||
String str = sa.getParam("TokenPower");
|
||||
|
||||
@@ -1080,7 +1080,7 @@ public class CombatUtil {
|
||||
return canAttackerBeBlockedWithAmount(attacker, amount, combat != null ? combat.getDefenderPlayerByAttacker(attacker) : null);
|
||||
}
|
||||
public static boolean canAttackerBeBlockedWithAmount(Card attacker, int amount, Player defender) {
|
||||
if(amount == 0 )
|
||||
if (amount == 0)
|
||||
return false; // no block
|
||||
|
||||
List<String> restrictions = Lists.newArrayList();
|
||||
@@ -1093,7 +1093,7 @@ public class CombatUtil {
|
||||
restrictions.add("LT2");
|
||||
}
|
||||
}
|
||||
for ( String res : restrictions ) {
|
||||
for (String res : restrictions) {
|
||||
int operand = Integer.parseInt(res.substring(2));
|
||||
String operator = res.substring(0,2);
|
||||
if (Expressions.compare(amount, operator, operand) )
|
||||
@@ -1118,7 +1118,7 @@ public class CombatUtil {
|
||||
}
|
||||
}
|
||||
int minBlockers = 1;
|
||||
for ( String res : restrictions ) {
|
||||
for (String res : restrictions) {
|
||||
int operand = Integer.parseInt(res.substring(2));
|
||||
String operator = res.substring(0, 2);
|
||||
if (operator.equals("LT") || operator.equals("GE")) {
|
||||
|
||||
@@ -363,6 +363,13 @@ public class Cost implements Serializable {
|
||||
return new CostFlipCoin(splitStr[0]);
|
||||
}
|
||||
|
||||
if (parse.startsWith("RollDice<")) {
|
||||
// RollDice<NumDice/Sides/ResultSVar>
|
||||
final String[] splitStr = abCostParse(parse, 4);
|
||||
final String description = splitStr.length > 3 ? splitStr[3] : null;
|
||||
return new CostRollDice(splitStr[0], splitStr[1], splitStr[2], description);
|
||||
}
|
||||
|
||||
if (parse.startsWith("Discard<")) {
|
||||
// Discard<NumCards/Type>
|
||||
final String[] splitStr = abCostParse(parse, 3);
|
||||
|
||||
@@ -211,7 +211,7 @@ public class CostPayment extends ManaConversionMatrix {
|
||||
return false;
|
||||
}
|
||||
// abilities care what was used to pay for them
|
||||
if( part instanceof CostPartWithList ) {
|
||||
if (part instanceof CostPartWithList) {
|
||||
((CostPartWithList) part).resetLists();
|
||||
}
|
||||
|
||||
|
||||
68
forge-game/src/main/java/forge/game/cost/CostRollDice.java
Normal file
68
forge-game/src/main/java/forge/game/cost/CostRollDice.java
Normal file
@@ -0,0 +1,68 @@
|
||||
package forge.game.cost;
|
||||
|
||||
import forge.game.ability.effects.RollDiceEffect;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* This is for the "RollDice" Cost
|
||||
*/
|
||||
public class CostRollDice extends CostPart {
|
||||
|
||||
/**
|
||||
* Serializables need a version ID.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String resultSVar;
|
||||
|
||||
/**
|
||||
* Instantiates a new cost RollDice.
|
||||
*
|
||||
* @param amount
|
||||
* the amount
|
||||
*/
|
||||
public CostRollDice(final String amount, final String sides, final String resultSVar, final String description) {
|
||||
super(amount, sides, description);
|
||||
this.resultSVar = resultSVar;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility,
|
||||
* forge.Card, forge.Player, forge.card.cost.Cost)
|
||||
*/
|
||||
@Override
|
||||
public final boolean canPay(final SpellAbility ability, final Player payer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("Roll ").append(getAmount());
|
||||
|
||||
if (this.getTypeDescription() == null) {
|
||||
sb.append("d").append(getType());
|
||||
} else {
|
||||
sb.append(" ").append(this.getTypeDescription());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean payAsDecided(Player payer, PaymentDecision pd, SpellAbility sa) {
|
||||
int sides = Integer.parseInt(getType());
|
||||
int result = RollDiceEffect.rollDiceForPlayer(sa, payer, pd.c, sides);
|
||||
sa.setSVar(resultSVar, Integer.toString(result));
|
||||
return true;
|
||||
}
|
||||
|
||||
public <T> T accept(ICostVisitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ public interface ICostVisitor<T> {
|
||||
T visit(CostExiledMoveToGrave cost);
|
||||
T visit(CostExert cost);
|
||||
T visit(CostFlipCoin cost);
|
||||
T visit(CostRollDice cost);
|
||||
T visit(CostMill cost);
|
||||
T visit(CostAddMana cost);
|
||||
T visit(CostPayLife cost);
|
||||
@@ -84,6 +85,11 @@ public interface ICostVisitor<T> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visit(CostRollDice cost) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visit(CostMill cost) {
|
||||
return null;
|
||||
|
||||
@@ -2217,6 +2217,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
|
||||
@Override
|
||||
public final boolean hasProperty(final String property, final Player sourceController, final Card source, CardTraitBase spellAbility) {
|
||||
if (property.startsWith("!")) {
|
||||
return !PlayerProperty.playerHasProperty(this, property.substring(1), sourceController, source, spellAbility);
|
||||
}
|
||||
return PlayerProperty.playerHasProperty(this, property, sourceController, source, spellAbility);
|
||||
}
|
||||
|
||||
@@ -3173,6 +3176,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
if (monarchEffect == null) {
|
||||
monarchEffect = new Card(game.nextCardId(), null, game);
|
||||
monarchEffect.setOwner(this);
|
||||
monarchEffect.setImmutable(true);
|
||||
if (set != null) {
|
||||
monarchEffect.setImageKey("t:monarch_" + set.toLowerCase());
|
||||
monarchEffect.setSetCode(set);
|
||||
@@ -3180,7 +3184,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
monarchEffect.setImageKey("t:monarch");
|
||||
}
|
||||
monarchEffect.setName("The Monarch");
|
||||
monarchEffect.addType("Effect");
|
||||
|
||||
{
|
||||
final String drawTrig = "Mode$ Phase | Phase$ End of Turn | TriggerZones$ Command | " +
|
||||
@@ -3277,8 +3280,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
blessingEffect.setOwner(this);
|
||||
blessingEffect.setImageKey("t:blessing");
|
||||
blessingEffect.setName("City's Blessing");
|
||||
blessingEffect.addType("Effect");
|
||||
|
||||
blessingEffect.setImmutable(true);
|
||||
|
||||
blessingEffect.updateStateForView();
|
||||
|
||||
@@ -3350,7 +3352,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
keywordEffect.setOwner(this);
|
||||
keywordEffect.setName("Keyword Effects");
|
||||
keywordEffect.setImageKey(ImageKeys.HIDDEN_CARD);
|
||||
keywordEffect.addType("Effect");
|
||||
|
||||
keywordEffect.updateStateForView();
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.Map;
|
||||
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.TokenCreateTable;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
@@ -27,9 +28,11 @@ public class ReplaceToken extends ReplacementEffect {
|
||||
*/
|
||||
@Override
|
||||
public boolean canReplace(Map<AbilityKey, Object> runParams) {
|
||||
/*
|
||||
if (((int) runParams.get(AbilityKey.TokenNum)) <= 0) {
|
||||
return false;
|
||||
}
|
||||
//*/
|
||||
|
||||
if (hasParam("EffectOnly")) {
|
||||
final Boolean effectOnly = (Boolean) runParams.get(AbilityKey.EffectOnly);
|
||||
@@ -41,10 +44,16 @@ public class ReplaceToken extends ReplacementEffect {
|
||||
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*/
|
||||
if (!matchesValidParam("ValidToken", runParams.get(AbilityKey.Token))) {
|
||||
return false;
|
||||
}
|
||||
//*/
|
||||
|
||||
if (filterAmount((TokenCreateTable) runParams.get(AbilityKey.Token)) <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -54,8 +63,13 @@ public class ReplaceToken extends ReplacementEffect {
|
||||
*/
|
||||
@Override
|
||||
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
|
||||
sa.setReplacingObject(AbilityKey.TokenNum, runParams.get(AbilityKey.TokenNum));
|
||||
sa.setReplacingObject(AbilityKey.TokenNum, filterAmount((TokenCreateTable) runParams.get(AbilityKey.Token)));
|
||||
sa.setReplacingObject(AbilityKey.Token, runParams.get(AbilityKey.Token));
|
||||
sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Affected));
|
||||
}
|
||||
|
||||
|
||||
public int filterAmount(final TokenCreateTable table) {
|
||||
return table.getFilterAmount(getParamOrDefault("ValidPlayer", null), getParamOrDefault("ValidToken", null), this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
|
||||
if (hasParam("Description") && !this.isSuppressed()) {
|
||||
String desc = AbilityUtils.applyDescriptionTextChangeEffects(getParam("Description"), this);
|
||||
String currentName;
|
||||
if (this.isIntrinsic() && !this.getHostCard().isMutated() && cardState != null) {
|
||||
if (this.isIntrinsic() && cardState != null && cardState.getCard() == getHostCard()) {
|
||||
currentName = cardState.getName();
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import forge.game.CardTraitBase;
|
||||
@@ -274,20 +275,45 @@ public class ReplacementHandler {
|
||||
chosenRE.setOtherChoices(null);
|
||||
return res;
|
||||
}
|
||||
chosenRE.setHasRun(false);
|
||||
hasRun.remove(chosenRE);
|
||||
chosenRE.setOtherChoices(null);
|
||||
|
||||
// Updated Replacements need to be logged elsewhere because its otherwise in the wrong order
|
||||
if (res != ReplacementResult.Updated) {
|
||||
// Log there
|
||||
String message = chosenRE.getDescription();
|
||||
if (!StringUtils.isEmpty(message))
|
||||
if (!StringUtils.isEmpty(message)) {
|
||||
if (chosenRE.getHostCard() != null) {
|
||||
message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName());
|
||||
}
|
||||
game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
|
||||
}
|
||||
|
||||
// if its updated, try to call event again
|
||||
if (res == ReplacementResult.Updated) {
|
||||
Map<AbilityKey, Object> params = Maps.newHashMap(runParams);
|
||||
|
||||
if (params.containsKey(AbilityKey.EffectOnly)) {
|
||||
params.put(AbilityKey.EffectOnly, true);
|
||||
}
|
||||
ReplacementResult result = run(event, params);
|
||||
switch (result) {
|
||||
case NotReplaced:
|
||||
case Updated: {
|
||||
for (Map.Entry<AbilityKey, Object> e : params.entrySet()) {
|
||||
runParams.put(e.getKey(), e.getValue());
|
||||
}
|
||||
// effect was updated
|
||||
runParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// effect was replaced with something else
|
||||
runParams.put(AbilityKey.ReplacementResult, result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
chosenRE.setHasRun(false);
|
||||
hasRun.remove(chosenRE);
|
||||
chosenRE.setOtherChoices(null);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -300,7 +326,6 @@ public class ReplacementHandler {
|
||||
*/
|
||||
private ReplacementResult executeReplacement(final Map<AbilityKey, Object> runParams,
|
||||
final ReplacementEffect replacementEffect, final Player decider, final Game game) {
|
||||
final Map<String, String> mapParams = replacementEffect.getMapParams();
|
||||
|
||||
SpellAbility effectSA = null;
|
||||
|
||||
@@ -311,10 +336,9 @@ public class ReplacementHandler {
|
||||
host = game.getCardState(host);
|
||||
}
|
||||
|
||||
if (replacementEffect.getOverridingAbility() == null && mapParams.containsKey("ReplaceWith")) {
|
||||
final String effectSVar = mapParams.get("ReplaceWith");
|
||||
if (replacementEffect.getOverridingAbility() == null && replacementEffect.hasParam("ReplaceWith")) {
|
||||
// TODO: the source of replacement effect should be the source of the original effect
|
||||
effectSA = AbilityFactory.getAbility(host, effectSVar, replacementEffect);
|
||||
effectSA = AbilityFactory.getAbility(host, replacementEffect.getParam("ReplaceWith"), replacementEffect);
|
||||
//replacementEffect.setOverridingAbility(effectSA);
|
||||
//effectSA.setTrigger(true);
|
||||
} else if (replacementEffect.getOverridingAbility() != null) {
|
||||
@@ -342,10 +366,10 @@ public class ReplacementHandler {
|
||||
// Decider gets to choose whether or not to apply the replacement.
|
||||
if (replacementEffect.hasParam("Optional")) {
|
||||
Player optDecider = decider;
|
||||
if (mapParams.containsKey("OptionalDecider") && (effectSA != null)) {
|
||||
if (replacementEffect.hasParam("OptionalDecider") && (effectSA != null)) {
|
||||
effectSA.setActivatingPlayer(host.getController());
|
||||
optDecider = AbilityUtils.getDefinedPlayers(host,
|
||||
mapParams.get("OptionalDecider"), effectSA).get(0);
|
||||
replacementEffect.getParam("OptionalDecider"), effectSA).get(0);
|
||||
}
|
||||
|
||||
Card cardForUi = host.getCardForUi();
|
||||
@@ -360,12 +384,12 @@ public class ReplacementHandler {
|
||||
}
|
||||
}
|
||||
|
||||
boolean isPrevent = mapParams.containsKey("Prevent") && mapParams.get("Prevent").equals("True");
|
||||
if (isPrevent || mapParams.containsKey("PreventionEffect")) {
|
||||
boolean isPrevent = "True".equals(replacementEffect.getParam("Prevent"));
|
||||
if (isPrevent || replacementEffect.hasParam("PreventionEffect")) {
|
||||
if (Boolean.TRUE.equals(runParams.get(AbilityKey.NoPreventDamage))) {
|
||||
// If can't prevent damage, result is not replaced
|
||||
// But still put "prevented" amount for buffered SA
|
||||
if (mapParams.containsKey("AlwaysReplace")) {
|
||||
if (replacementEffect.hasParam("AlwaysReplace")) {
|
||||
runParams.put(AbilityKey.PreventedAmount, runParams.get(AbilityKey.DamageAmount));
|
||||
} else {
|
||||
runParams.put(AbilityKey.PreventedAmount, 0);
|
||||
@@ -377,11 +401,9 @@ public class ReplacementHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (mapParams.containsKey("Skip")) {
|
||||
if (mapParams.get("Skip").equals("True")) {
|
||||
if ("True".equals(replacementEffect.getParam("Skip"))) {
|
||||
return ReplacementResult.Skipped; // Event is skipped.
|
||||
}
|
||||
}
|
||||
|
||||
Player player = host.getController();
|
||||
|
||||
@@ -394,6 +416,11 @@ public class ReplacementHandler {
|
||||
// The SA if buffered, but replacement result should be set to Replaced
|
||||
runParams.put(AbilityKey.ReplacementResult, ReplacementResult.Replaced);
|
||||
}
|
||||
|
||||
// these ones are special for updating
|
||||
if (apiType == ApiType.ReplaceToken || apiType == ApiType.ReplaceEffect || apiType == ApiType.ReplaceMana) {
|
||||
runParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
|
||||
}
|
||||
}
|
||||
|
||||
// if the spellability is a replace effect then its some new logic
|
||||
|
||||
@@ -95,8 +95,7 @@ public class LandAbility extends Ability {
|
||||
Card source = sta.getHostCard();
|
||||
if (!source.equals(getHostCard())) {
|
||||
sb.append(" by ");
|
||||
if ((source.isEmblem() || source.getType().hasSubtype("Effect"))
|
||||
&& source.getEffectSource() != null) {
|
||||
if (source.isImmutable() && source.getEffectSource() != null) {
|
||||
sb.append(source.getEffectSource());
|
||||
} else {
|
||||
sb.append(source);
|
||||
|
||||
@@ -854,7 +854,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
if (node.getHostCard() != null) {
|
||||
String currentName;
|
||||
// if alternate state is viewed while card uses original
|
||||
if (node.isIntrinsic() && !node.getHostCard().isMutated() && node.cardState != null) {
|
||||
if (node.isIntrinsic() && node.cardState != null && node.cardState.getCard() == node.getHostCard()) {
|
||||
currentName = node.cardState.getName();
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -209,7 +209,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
|
||||
public final String toString() {
|
||||
if (hasParam("Description") && !this.isSuppressed()) {
|
||||
String currentName;
|
||||
if (this.isIntrinsic() && !this.getHostCard().isMutated() && cardState != null) {
|
||||
if (this.isIntrinsic() && cardState != null && cardState.getCard() == getHostCard()) {
|
||||
currentName = cardState.getName();
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -92,7 +92,7 @@ public class StaticAbilityCantAttackBlock {
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if attacker can be blocked by blocker
|
||||
* returns true if attacker can't be blocked by blocker
|
||||
* @param stAb
|
||||
* @param attacker
|
||||
* @param blocker
|
||||
@@ -104,9 +104,10 @@ public class StaticAbilityCantAttackBlock {
|
||||
return false;
|
||||
}
|
||||
if (stAb.hasParam("ValidBlocker")) {
|
||||
boolean stillblock = true;
|
||||
for (final String v : stAb.getParam("ValidBlocker").split(",")) {
|
||||
if (blocker.isValid(v, host.getController(), host, stAb)) {
|
||||
boolean stillblock = false;
|
||||
stillblock = false;
|
||||
//Dragon Hunter check
|
||||
if (v.contains("withoutReach") && blocker.hasStartOfKeyword("IfReach")) {
|
||||
for (KeywordInterface inst : blocker.getKeywords()) {
|
||||
@@ -120,13 +121,14 @@ public class StaticAbilityCantAttackBlock {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!stillblock) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stillblock) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// relative valid relative to each other
|
||||
if (!stAb.matchesValidParam("ValidAttackerRelative", attacker, blocker)) {
|
||||
|
||||
@@ -132,7 +132,7 @@ public abstract class Trigger extends TriggerReplacementBase {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String currentName;
|
||||
if (this.isIntrinsic() && !this.getHostCard().isMutated() && cardState != null) {
|
||||
if (this.isIntrinsic() && cardState != null && cardState.getCard() == getHostCard()) {
|
||||
currentName = cardState.getName();
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -31,8 +31,6 @@ import forge.game.IHasSVars;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.card.*;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
@@ -346,8 +344,7 @@ public class TriggerHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean runNonStaticTriggersForPlayer(final Player player, final TriggerWaiting wt, final List<Trigger> delayedTriggersWorkingCopy ) {
|
||||
|
||||
private boolean runNonStaticTriggersForPlayer(final Player player, final TriggerWaiting wt, final List<Trigger> delayedTriggersWorkingCopy) {
|
||||
final TriggerType mode = wt.getMode();
|
||||
final Map<AbilityKey, Object> runParams = wt.getParams();
|
||||
final List<Trigger> triggers = wt.getTriggers() != null ? wt.getTriggers() : activeTriggers;
|
||||
@@ -503,7 +500,6 @@ public class TriggerHandler {
|
||||
// runs it if so.
|
||||
// Return true if the trigger went off, false otherwise.
|
||||
private void runSingleTriggerInternal(final Trigger regtrig, final Map<AbilityKey, Object> runParams) {
|
||||
|
||||
// All tests passed, execute ability.
|
||||
if (regtrig instanceof TriggerTapsForMana) {
|
||||
final SpellAbility abMana = (SpellAbility) runParams.get(AbilityKey.AbilityMana);
|
||||
@@ -519,8 +515,7 @@ public class TriggerHandler {
|
||||
if (sa == null) {
|
||||
if (!regtrig.hasParam("Execute")) {
|
||||
sa = new SpellAbility.EmptySa(host);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
String name = regtrig.getParam("Execute");
|
||||
if (!host.getCurrentState().hasSVar(name)) {
|
||||
System.err.println("Warning: tried to run a trigger for card " + host + " referencing a SVar " + name + " not present on the current state " + host.getCurrentState() + ". Aborting trigger execution to prevent a crash.");
|
||||
@@ -577,12 +572,6 @@ public class TriggerHandler {
|
||||
}
|
||||
|
||||
sa.setStackDescription(sa.toString());
|
||||
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
||||
if (!CharmEffect.makeChoices(sa)) {
|
||||
// 603.3c If no mode is chosen, the ability is removed from the stack.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Player decider = null;
|
||||
boolean isMandatory = false;
|
||||
@@ -592,8 +581,7 @@ public class TriggerHandler {
|
||||
}
|
||||
else if (sa instanceof AbilitySub || !sa.hasParam("Cost") || sa.getParam("Cost").equals("0")) {
|
||||
isMandatory = true;
|
||||
}
|
||||
else { // triggers with a cost can't be mandatory
|
||||
} else { // triggers with a cost can't be mandatory
|
||||
sa.setOptionalTrigger(true);
|
||||
decider = sa.getActivatingPlayer();
|
||||
}
|
||||
@@ -605,8 +593,7 @@ public class TriggerHandler {
|
||||
wrapperAbility.setLastStateBattlefield(game.getLastStateBattlefield());
|
||||
if (regtrig.isStatic()) {
|
||||
wrapperAbility.getActivatingPlayer().getController().playTrigger(host, wrapperAbility, isMandatory);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
game.getStack().addSimultaneousStackEntry(wrapperAbility);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,11 @@ public class TriggerRolledDie extends Trigger {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (hasParam("ValidSides")) {
|
||||
final int validSides = Integer.parseInt(getParam("ValidSides"));
|
||||
final int sides = (int) runParams.get(AbilityKey.Sides);
|
||||
if (sides == validSides) return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -232,8 +232,8 @@ public class WrappedAbility extends Ability {
|
||||
if (regtrig == null) return "";
|
||||
final StringBuilder sb = new StringBuilder(regtrig.replaceAbilityText(regtrig.toString(true), this));
|
||||
List<TargetChoices> allTargets = sa.getAllTargetChoices();
|
||||
if (!allTargets.isEmpty()) {
|
||||
sb.append(" (Targeting ");
|
||||
if (!allTargets.isEmpty() && !ApiType.Charm.equals(sa.getApi())) {
|
||||
sb.append(" (Targeting: ");
|
||||
sb.append(allTargets);
|
||||
sb.append(")");
|
||||
}
|
||||
@@ -549,7 +549,6 @@ public class WrappedAbility extends Ability {
|
||||
sa.setXManaCostPaid(n);
|
||||
}
|
||||
|
||||
|
||||
public CardState getCardState() {
|
||||
return sa.getCardState();
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardUtil;
|
||||
@@ -224,8 +225,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
undoStackOwner = activator;
|
||||
}
|
||||
undoStack.push(sp);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
clearUndoStack();
|
||||
}
|
||||
|
||||
@@ -628,8 +628,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
if (sa.usesTargeting()) {
|
||||
if (sa.isZeroTargets()) {
|
||||
// Nothing targeted, and nothing needs to be targeted.
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Some targets were chosen, fizzling for this subability is now possible
|
||||
//fizzle = true;
|
||||
// With multi-targets, as long as one target is still legal,
|
||||
@@ -675,8 +674,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
}
|
||||
else if (sa.getTargetCard() != null) {
|
||||
fizzle = !sa.canTarget(sa.getTargetCard());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Set fizzle to the same as the parent if there's no target info
|
||||
fizzle = parentFizzled;
|
||||
}
|
||||
@@ -799,7 +797,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
result |= chooseOrderOfSimultaneousStackEntry(whoAddsToStack);
|
||||
// 2014-08-10 Fix infinite loop when a player dies during a multiplayer game during their turn
|
||||
whoAddsToStack = game.getNextPlayerAfter(whoAddsToStack);
|
||||
} while( whoAddsToStack != null && whoAddsToStack != playerTurn);
|
||||
} while (whoAddsToStack != null && whoAddsToStack != playerTurn);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -809,9 +807,19 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
}
|
||||
|
||||
final List<SpellAbility> activePlayerSAs = Lists.newArrayList();
|
||||
final List<SpellAbility> failedSAs = Lists.newArrayList();
|
||||
for (int i = 0; i < simultaneousStackEntryList.size(); i++) {
|
||||
SpellAbility sa = simultaneousStackEntryList.get(i);
|
||||
Player activator = sa.getActivatingPlayer();
|
||||
|
||||
if (sa.getApi() == ApiType.Charm) {
|
||||
if (!CharmEffect.makeChoices(sa)) {
|
||||
// 603.3c If no mode is chosen, the ability is removed from the stack.
|
||||
failedSAs.add(sa);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (activator == null) {
|
||||
if (sa.getHostCard().getController().equals(activePlayer)) {
|
||||
activePlayerSAs.add(sa);
|
||||
@@ -823,6 +831,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
}
|
||||
}
|
||||
simultaneousStackEntryList.removeAll(activePlayerSAs);
|
||||
simultaneousStackEntryList.removeAll(failedSAs);
|
||||
|
||||
if (activePlayerSAs.isEmpty()) {
|
||||
return false;
|
||||
@@ -875,8 +884,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
public final void addCastCommand(final String valid, final GameCommand c) {
|
||||
if (commandList.containsKey(valid)) {
|
||||
commandList.get(valid).add(0, c);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
commandList.put(valid, Lists.newArrayList(c));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ public enum TrackableProperty {
|
||||
Controller(TrackableTypes.PlayerViewType),
|
||||
Zone(TrackableTypes.EnumType(ZoneType.class)),
|
||||
|
||||
IsImmutable(TrackableTypes.BooleanType),
|
||||
IsEmblem(TrackableTypes.BooleanType),
|
||||
|
||||
Flipped(TrackableTypes.BooleanType),
|
||||
Facedown(TrackableTypes.BooleanType),
|
||||
Foretold(TrackableTypes.BooleanType),
|
||||
@@ -74,6 +77,7 @@ public enum TrackableProperty {
|
||||
UntilLeavesBattlefield(TrackableTypes.CardViewCollectionType),
|
||||
GainControlTargets(TrackableTypes.CardViewCollectionType),
|
||||
CloneOrigin(TrackableTypes.CardViewType),
|
||||
ExiledWith(TrackableTypes.CardViewType),
|
||||
|
||||
ImprintedCards(TrackableTypes.CardViewCollectionType),
|
||||
HauntedBy(TrackableTypes.CardViewCollectionType),
|
||||
|
||||
@@ -53,6 +53,7 @@ public class MessageUtil {
|
||||
case Protection:
|
||||
return Localizer.getInstance().getMessage("lblPlayerChooseValue", choser, value);
|
||||
case RollDice:
|
||||
case PutCounter:// For Clay Golem cost text
|
||||
return value;
|
||||
case Vote:
|
||||
String chooser = StringUtils.capitalize(mayBeYou(player, target));
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<packaging.type>jar</packaging.type>
|
||||
<build.min.memory>-Xms1024m</build.min.memory>
|
||||
<build.max.memory>-Xmx1536m</build.max.memory>
|
||||
<alpha-version>1.6.42.001</alpha-version>
|
||||
<alpha-version>1.6.43.001</alpha-version>
|
||||
<sign.keystore>keystore</sign.keystore>
|
||||
<sign.alias>alias</sign.alias>
|
||||
<sign.storepass>storepass</sign.storepass>
|
||||
@@ -19,7 +19,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.43-SNAPSHOT</version>
|
||||
<version>1.6.44-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-android</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.43-SNAPSHOT</version>
|
||||
<version>1.6.44-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-desktop</artifactId>
|
||||
|
||||
@@ -414,4 +414,3 @@ public enum FControl implements KeyEventDispatcher {
|
||||
return GamePlayerUtil.getGuiPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -194,7 +194,6 @@ public final class DeckManager extends ItemManager<DeckProxy> implements IHasGam
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
protected void buildAddFilterMenu(final JMenu menu) {
|
||||
GuiUtils.addSeparator(menu); //separate from current search item
|
||||
|
||||
@@ -228,7 +227,6 @@ public final class DeckManager extends ItemManager<DeckProxy> implements IHasGam
|
||||
}
|
||||
menu.add(fmt);
|
||||
|
||||
|
||||
GuiUtils.addMenuItem(menu, localizer.getMessage("lblFormats") + "...", null, new Runnable() {
|
||||
@Override public void run() {
|
||||
final DeckFormatFilter existingFilter = getFilter(DeckFormatFilter.class);
|
||||
|
||||
@@ -340,4 +340,3 @@ public enum CDeckEditorUI implements ICDoc {
|
||||
@Override
|
||||
public void update() { }
|
||||
}
|
||||
|
||||
|
||||
@@ -288,8 +288,7 @@ public class DeckController<T extends DeckBase> {
|
||||
final String name = getModelName();
|
||||
if (name.isEmpty()) {
|
||||
newModel();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
load(name);
|
||||
}
|
||||
}
|
||||
@@ -297,8 +296,7 @@ public class DeckController<T extends DeckBase> {
|
||||
public void load(final String path, final String name) {
|
||||
if (StringUtils.isBlank(path)) {
|
||||
currentFolder = rootFolder;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
currentFolder = rootFolder.tryGetFolder(path);
|
||||
}
|
||||
modelPath = path;
|
||||
@@ -380,8 +378,7 @@ public class DeckController<T extends DeckBase> {
|
||||
public void refreshModel() {
|
||||
if (model == null) {
|
||||
newModel();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
setModel(model, modelInStorage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,7 +501,7 @@ public final class CMatchUI
|
||||
|
||||
@Override
|
||||
public void hideZones(final PlayerView controller, final Iterable<PlayerZoneUpdate> zonesToUpdate) {
|
||||
if ( zonesToUpdate != null ) {
|
||||
if (zonesToUpdate != null) {
|
||||
for (final PlayerZoneUpdate update : zonesToUpdate) {
|
||||
final PlayerView player = update.getPlayer();
|
||||
for (final ZoneType zone : update.getZones()) {
|
||||
@@ -532,7 +532,6 @@ public final class CMatchUI
|
||||
for (final PlayerView p : manaPoolUpdate) {
|
||||
getFieldViewFor(p).updateManaPool();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Player's lives and poison counters
|
||||
@@ -580,10 +579,10 @@ public final class CMatchUI
|
||||
FThreads.invokeInEdtNowOrLater(new Runnable() {
|
||||
@Override public final void run() {
|
||||
for (final PlayerView p : getGameView().getPlayers()) {
|
||||
if ( p.getCards(ZoneType.Battlefield) != null ) {
|
||||
if (p.getCards(ZoneType.Battlefield) != null) {
|
||||
updateCards(p.getCards(ZoneType.Battlefield));
|
||||
}
|
||||
if ( p.getCards(ZoneType.Hand) != null ) {
|
||||
if (p.getCards(ZoneType.Hand) != null) {
|
||||
updateCards(p.getCards(ZoneType.Hand));
|
||||
}
|
||||
}
|
||||
@@ -599,10 +598,10 @@ public final class CMatchUI
|
||||
FThreads.invokeInEdtNowOrLater(new Runnable() {
|
||||
@Override public final void run() {
|
||||
for (final PlayerView p : getGameView().getPlayers()) {
|
||||
if ( p.getCards(ZoneType.Battlefield) != null ) {
|
||||
if (p.getCards(ZoneType.Battlefield) != null) {
|
||||
updateCards(p.getCards(ZoneType.Battlefield));
|
||||
}
|
||||
if ( p.getCards(ZoneType.Hand) != null ) {
|
||||
if (p.getCards(ZoneType.Hand) != null) {
|
||||
updateCards(p.getCards(ZoneType.Hand));
|
||||
}
|
||||
}
|
||||
@@ -617,7 +616,7 @@ public final class CMatchUI
|
||||
FThreads.invokeInEdtNowOrLater(new Runnable() {
|
||||
@Override public final void run() {
|
||||
for (final PlayerView p : getGameView().getPlayers()) {
|
||||
if ( p.getCards(ZoneType.Battlefield) != null ) {
|
||||
if (p.getCards(ZoneType.Battlefield) != null) {
|
||||
updateCards(p.getCards(ZoneType.Battlefield));
|
||||
}
|
||||
}
|
||||
@@ -787,7 +786,7 @@ public final class CMatchUI
|
||||
final PhaseType ph = getGameView().getPhase();
|
||||
// this should never happen, but I've seen it periodically... so, need to get to the bottom of it
|
||||
PhaseLabel lbl = null;
|
||||
if (ph != null ) {
|
||||
if (ph != null) {
|
||||
lbl = p == null ? null : getFieldViewFor(p).getPhaseIndicator().getLabelFor(ph);
|
||||
} else {
|
||||
// not sure what debugging information would help here, log for now
|
||||
@@ -1295,8 +1294,8 @@ public final class CMatchUI
|
||||
boolean isAi = sa.getActivatingPlayer().isAI();
|
||||
boolean isTrigger = sa.isTrigger();
|
||||
int stackIndex = event.stackIndex;
|
||||
if(stackIndex == nextNotifiableStackIndex) {
|
||||
if(ForgeConstants.STACK_EFFECT_NOTIFICATION_ALWAYS.equals(stackNotificationPolicy) || (ForgeConstants.STACK_EFFECT_NOTIFICATION_AI_AND_TRIGGERED.equals(stackNotificationPolicy) && (isAi || isTrigger))) {
|
||||
if (stackIndex == nextNotifiableStackIndex) {
|
||||
if (ForgeConstants.STACK_EFFECT_NOTIFICATION_ALWAYS.equals(stackNotificationPolicy) || (ForgeConstants.STACK_EFFECT_NOTIFICATION_AI_AND_TRIGGERED.equals(stackNotificationPolicy) && (isAi || isTrigger))) {
|
||||
// We can go and show the modal
|
||||
SpellAbilityStackInstance si = event.si;
|
||||
|
||||
@@ -1319,9 +1318,9 @@ public final class CMatchUI
|
||||
// If current effect is a triggered/activated ability of an enchantment card, I want to show the enchanted card
|
||||
GameEntityView enchantedEntityView = null;
|
||||
Card hostCard = sa.getHostCard();
|
||||
if(hostCard.isEnchantment()) {
|
||||
if (hostCard.isEnchantment()) {
|
||||
GameEntity enchantedEntity = hostCard.getEntityAttachedTo();
|
||||
if(enchantedEntity != null) {
|
||||
if (enchantedEntity != null) {
|
||||
enchantedEntityView = enchantedEntity.getView();
|
||||
numSmallImages++;
|
||||
} else if ((sa.getRootAbility() != null)
|
||||
@@ -1329,7 +1328,7 @@ public final class CMatchUI
|
||||
&& !sa.getRootAbility().getPaidList("Sacrificed").isEmpty()) {
|
||||
// If the player activated its ability by sacrificing the enchantment, the enchantment has not anything attached anymore and the ex-enchanted card has to be searched in other ways.. for example, the green enchantment "Carapace"
|
||||
enchantedEntity = sa.getRootAbility().getPaidList("Sacrificed").get(0).getEnchantingCard();
|
||||
if(enchantedEntity != null) {
|
||||
if (enchantedEntity != null) {
|
||||
enchantedEntityView = enchantedEntity.getView();
|
||||
numSmallImages++;
|
||||
}
|
||||
@@ -1339,7 +1338,7 @@ public final class CMatchUI
|
||||
// If current effect is a triggered ability, I want to show the triggering card if present
|
||||
SpellAbility sourceSA = (SpellAbility) si.getTriggeringObject(AbilityKey.SourceSA);
|
||||
CardView sourceCardView = null;
|
||||
if(sourceSA != null) {
|
||||
if (sourceSA != null) {
|
||||
sourceCardView = sourceSA.getHostCard().getView();
|
||||
numSmallImages++;
|
||||
}
|
||||
@@ -1349,13 +1348,13 @@ public final class CMatchUI
|
||||
numSmallImages = numSmallImages + targets.size();
|
||||
|
||||
// Now I know how many small images - on to render them
|
||||
if(enchantedEntityView != null) {
|
||||
if (enchantedEntityView != null) {
|
||||
addSmallImageToStackModalPanel(enchantedEntityView,mainPanel,numSmallImages);
|
||||
}
|
||||
if(sourceCardView != null) {
|
||||
if (sourceCardView != null) {
|
||||
addSmallImageToStackModalPanel(sourceCardView,mainPanel,numSmallImages);
|
||||
}
|
||||
for(GameEntityView gev : targets) {
|
||||
for (GameEntityView gev : targets) {
|
||||
addSmallImageToStackModalPanel(gev, mainPanel, numSmallImages);
|
||||
}
|
||||
|
||||
@@ -1367,7 +1366,6 @@ public final class CMatchUI
|
||||
nextNotifiableStackIndex++;
|
||||
|
||||
} else {
|
||||
|
||||
// Not yet time to show the modal - schedule the method again, and try again later
|
||||
Runnable tryAgainThread = new Runnable() {
|
||||
@Override
|
||||
@@ -1381,21 +1379,21 @@ public final class CMatchUI
|
||||
}
|
||||
|
||||
private List<GameEntityView> getTargets(SpellAbilityStackInstance si, List<GameEntityView> result){
|
||||
if(si == null) {
|
||||
if (si == null) {
|
||||
return result;
|
||||
}
|
||||
FCollectionView<CardView> targetCards = CardView.getCollection(si.getTargetChoices().getTargetCards());
|
||||
for(CardView currCardView: targetCards) {
|
||||
for (CardView currCardView: targetCards) {
|
||||
result.add(currCardView);
|
||||
}
|
||||
|
||||
for(SpellAbility currSA : si.getTargetChoices().getTargetSpells()) {
|
||||
for (SpellAbility currSA : si.getTargetChoices().getTargetSpells()) {
|
||||
CardView currCardView = currSA.getCardView();
|
||||
result.add(currCardView);
|
||||
}
|
||||
|
||||
FCollectionView<PlayerView> targetPlayers = PlayerView.getCollection(si.getTargetChoices().getTargetPlayers());
|
||||
for(PlayerView currPlayerView : targetPlayers) {
|
||||
for (PlayerView currPlayerView : targetPlayers) {
|
||||
result.add(currPlayerView);
|
||||
}
|
||||
|
||||
@@ -1443,7 +1441,7 @@ public final class CMatchUI
|
||||
}
|
||||
|
||||
private void addSmallImageToStackModalPanel(GameEntityView gameEntityView, JPanel mainPanel, int numTarget) {
|
||||
if(gameEntityView instanceof CardView) {
|
||||
if (gameEntityView instanceof CardView) {
|
||||
CardView cardView = (CardView) gameEntityView;
|
||||
int currRotation = getRotation(cardView);
|
||||
FImagePanel targetPanel = new FImagePanel();
|
||||
@@ -1454,7 +1452,7 @@ public final class CMatchUI
|
||||
Dimension targetPanelDimension = new Dimension(imageWidth,imageHeight);
|
||||
targetPanel.setMinimumSize(targetPanelDimension);
|
||||
mainPanel.add(targetPanel, "cell 1 1, split " + numTarget+ ", aligny bottom");
|
||||
} else if(gameEntityView instanceof PlayerView) {
|
||||
} else if (gameEntityView instanceof PlayerView) {
|
||||
PlayerView playerView = (PlayerView) gameEntityView;
|
||||
SkinImage playerAvatar = getPlayerAvatar(playerView, 0);
|
||||
final FLabel lblIcon = new FLabel.Builder().icon(playerAvatar).build();
|
||||
@@ -1499,11 +1497,10 @@ public final class CMatchUI
|
||||
}
|
||||
|
||||
private void createLandPopupPanel(Card land) {
|
||||
|
||||
String landPlayedNotificationPolicy = FModel.getPreferences().getPref(FPref.UI_LAND_PLAYED_NOTIFICATION_POLICY);
|
||||
Player cardController = land.getController();
|
||||
boolean isAi = cardController.isAI();
|
||||
if(ForgeConstants.LAND_PLAYED_NOTIFICATION_ALWAYS.equals(landPlayedNotificationPolicy)
|
||||
if (ForgeConstants.LAND_PLAYED_NOTIFICATION_ALWAYS.equals(landPlayedNotificationPolicy)
|
||||
|| (ForgeConstants.LAND_PLAYED_NOTIFICATION_AI.equals(landPlayedNotificationPolicy) && (isAi))
|
||||
|| (ForgeConstants.LAND_PLAYED_NOTIFICATION_ALWAYS_FOR_NONBASIC_LANDS.equals(landPlayedNotificationPolicy) && !land.isBasicLand())
|
||||
|| (ForgeConstants.LAND_PLAYED_NOTIFICATION_AI_FOR_NONBASIC_LANDS.equals(landPlayedNotificationPolicy) && !land.isBasicLand()) && (isAi)) {
|
||||
|
||||
@@ -121,7 +121,6 @@ public class GameLogPanel extends JPanel {
|
||||
}
|
||||
|
||||
public void addLogEntry(final String text) {
|
||||
|
||||
final boolean useAlternateBackColor = (scrollablePanel.getComponents().length % 2 == 0);
|
||||
final JTextArea tar = createNewLogEntryJTextArea(text, useAlternateBackColor);
|
||||
|
||||
@@ -138,7 +137,6 @@ public class GameLogPanel extends JPanel {
|
||||
}
|
||||
|
||||
forceVerticalScrollbarToMax();
|
||||
|
||||
}
|
||||
|
||||
public void setTextFont(final SkinFont newFont) {
|
||||
|
||||
@@ -107,8 +107,8 @@ public class VAssignCombatDamage {
|
||||
|
||||
private boolean canAssignTo(final CardView card) {
|
||||
for (DamageTarget dt : defenders) {
|
||||
if (dt.card == card ) return true;
|
||||
if (getDamageToKill(dt.card) > dt.damage )
|
||||
if (dt.card == card) return true;
|
||||
if (getDamageToKill(dt.card) > dt.damage)
|
||||
return false;
|
||||
}
|
||||
throw new RuntimeException("Asking to assign damage to object which is not present in defenders list");
|
||||
@@ -145,7 +145,7 @@ public class VAssignCombatDamage {
|
||||
boolean isLMB = SwingUtilities.isLeftMouseButton(evt);
|
||||
boolean isRMB = SwingUtilities.isRightMouseButton(evt);
|
||||
|
||||
if ( isLMB || isRMB)
|
||||
if (isLMB || isRMB)
|
||||
assignDamageTo(source, meta, isLMB);
|
||||
}
|
||||
};
|
||||
@@ -305,7 +305,7 @@ public class VAssignCombatDamage {
|
||||
int leftToAssign = getRemainingDamage();
|
||||
// Left click adds damage, right click substracts damage.
|
||||
// Hold Ctrl to assign lethal damage, Ctrl-click again on a creature with lethal damage to assign all available damage to it
|
||||
if ( meta ) {
|
||||
if (meta) {
|
||||
if (isAdding) {
|
||||
damageToAdd = leftToKill > 0 ? leftToKill : leftToAssign;
|
||||
} else {
|
||||
@@ -313,7 +313,7 @@ public class VAssignCombatDamage {
|
||||
}
|
||||
}
|
||||
|
||||
if ( damageToAdd > leftToAssign )
|
||||
if (damageToAdd > leftToAssign)
|
||||
damageToAdd = leftToAssign;
|
||||
|
||||
// cannot assign first blocker less than lethal damage except when overriding order
|
||||
@@ -321,7 +321,7 @@ public class VAssignCombatDamage {
|
||||
if (!overrideCombatantOrder && isFirstBlocker && damageToAdd + damageItHad < lethalDamage )
|
||||
return;
|
||||
|
||||
if ( 0 == damageToAdd || damageToAdd + damageItHad < 0)
|
||||
if (0 == damageToAdd || damageToAdd + damageItHad < 0)
|
||||
return;
|
||||
|
||||
addDamage(source, damageToAdd);
|
||||
@@ -335,12 +335,12 @@ public class VAssignCombatDamage {
|
||||
}
|
||||
// Clear out any Damage that shouldn't be assigned to other combatants
|
||||
boolean hasAliveEnemy = false;
|
||||
for(DamageTarget dt : defenders) {
|
||||
for (DamageTarget dt : defenders) {
|
||||
int lethal = getDamageToKill(dt.card);
|
||||
int damage = dt.damage;
|
||||
// If overriding combatant order, make sure everything has lethal if defender has damage assigned to it
|
||||
// Otherwise, follow normal combatant order
|
||||
if ( hasAliveEnemy && (!overrideCombatantOrder || dt.card == null || dt.card == defender))
|
||||
if (hasAliveEnemy && (!overrideCombatantOrder || dt.card == null || dt.card == defender))
|
||||
dt.damage = 0;
|
||||
else
|
||||
hasAliveEnemy |= damage < lethal;
|
||||
@@ -357,15 +357,15 @@ public class VAssignCombatDamage {
|
||||
|
||||
int dmgLeft = totalDamageToAssign;
|
||||
DamageTarget dtLast = null;
|
||||
for(DamageTarget dt : defenders) { // MUST NOT RUN WITH EMPTY collection
|
||||
for (DamageTarget dt : defenders) { // MUST NOT RUN WITH EMPTY collection
|
||||
int lethal = getDamageToKill(dt.card);
|
||||
int damage = Math.min(lethal, dmgLeft);
|
||||
addDamage(dt.card, damage);
|
||||
dmgLeft -= damage;
|
||||
dtLast = dt;
|
||||
if ( dmgLeft <= 0 || !toAllBlockers ) break;
|
||||
if (dmgLeft <= 0 || !toAllBlockers) break;
|
||||
}
|
||||
if ( dmgLeft < 0 )
|
||||
if (dmgLeft < 0)
|
||||
throw new RuntimeException("initialAssignDamage managed to assign more damage than it could");
|
||||
if (toAllBlockers && dmgLeft > 0) {
|
||||
// flush the remaining damage into last defender if assigning all damage
|
||||
@@ -376,7 +376,7 @@ public class VAssignCombatDamage {
|
||||
|
||||
/** Reset Assign Damage back to how it was at the beginning. */
|
||||
private void resetAssignedDamage() {
|
||||
for(DamageTarget dt : defenders)
|
||||
for (DamageTarget dt : defenders)
|
||||
dt.damage = 0;
|
||||
}
|
||||
|
||||
@@ -398,7 +398,7 @@ public class VAssignCombatDamage {
|
||||
*/
|
||||
private int getRemainingDamage() {
|
||||
int spent = 0;
|
||||
for(DamageTarget dt : defenders) {
|
||||
for (DamageTarget dt : defenders) {
|
||||
spent += dt.damage;
|
||||
}
|
||||
return totalDamageToAssign - spent;
|
||||
@@ -407,11 +407,10 @@ public class VAssignCombatDamage {
|
||||
/** Updates labels and other UI elements.
|
||||
* @param index index of the last assigned damage*/
|
||||
private void updateLabels() {
|
||||
|
||||
int damageLeft = totalDamageToAssign;
|
||||
boolean allHaveLethal = true;
|
||||
|
||||
for ( DamageTarget dt : defenders )
|
||||
for (DamageTarget dt : defenders)
|
||||
{
|
||||
int dmg = dt.damage;
|
||||
damageLeft -= dmg;
|
||||
@@ -438,7 +437,7 @@ public class VAssignCombatDamage {
|
||||
// assigned dynamically, the cards die off and further damage to them can't
|
||||
// be modified.
|
||||
private void finish() {
|
||||
if ( getRemainingDamage() > 0 )
|
||||
if (getRemainingDamage() > 0)
|
||||
return;
|
||||
|
||||
dlg.dispose();
|
||||
@@ -456,8 +455,7 @@ public class VAssignCombatDamage {
|
||||
final CardView pw = (CardView)defender;
|
||||
lethalDamage = Integer.valueOf(pw.getCurrentState().getLoyalty());
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
lethalDamage = Math.max(0, card.getLethalDamage());
|
||||
if (card.getCurrentState().getType().isPlaneswalker()) {
|
||||
lethalDamage = Integer.valueOf(card.getCurrentState().getLoyalty());
|
||||
|
||||
@@ -78,7 +78,6 @@ public class CDetail implements ICDoc {
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -311,7 +311,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
final CardStateView state = getCard().getCurrentState();
|
||||
final CardEdition ed = FModel.getMagicDb().getEditions().get(state.getSetCode());
|
||||
boolean colorIsSet = false;
|
||||
if (state.getType().isEmblem() || state.getType().hasStringType("Effect")) {
|
||||
if (getCard().isImmutable()) {
|
||||
// Effects are drawn with orange border
|
||||
g2d.setColor(Color.ORANGE);
|
||||
colorIsSet = true;
|
||||
@@ -368,7 +368,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
|
||||
boolean nonselectable = matchUI.isSelecting() && !matchUI.isSelectable(getCard());
|
||||
// if selecting, darken non-selectable cards
|
||||
if ( nonselectable ) {
|
||||
if (nonselectable) {
|
||||
boolean noBorderPref = !isPreferenceEnabled(FPref.UI_RENDER_BLACK_BORDERS);
|
||||
boolean cardImgHasAlpha = imagePanel != null && imagePanel.getSrcImage() != null && imagePanel.getSrcImage().getColorModel().hasAlpha();
|
||||
final int cornerSize = noBorderPref && !cardImgHasAlpha ? 0 : Math.max(4, Math.round(cardWidth * CardPanel.ROUNDED_CORNER_SIZE));
|
||||
@@ -390,7 +390,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
|
||||
@Override
|
||||
public final void doLayout() {
|
||||
|
||||
int borderSize = calculateBorderSize();
|
||||
|
||||
final Point imgPos = new Point(cardXOffset + borderSize, cardYOffset + borderSize);
|
||||
@@ -408,7 +407,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
}
|
||||
|
||||
private int calculateBorderSize() {
|
||||
|
||||
// Determine whether to render border from properties
|
||||
boolean noBorderPref = !isPreferenceEnabled(FPref.UI_RENDER_BLACK_BORDERS);
|
||||
|
||||
@@ -426,7 +424,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
private Dimension calculateImageSize() {
|
||||
@@ -491,7 +488,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (card.getCounters() != null && !card.getCounters().isEmpty()) {
|
||||
|
||||
switch (CounterDisplayType.from(FModel.getPreferences().getPref(FPref.UI_CARD_COUNTER_DISPLAY_TYPE))) {
|
||||
@@ -734,8 +730,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
abiY += abiSpace;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
String keywordKey = card.getCurrentState().getKeywordKey();
|
||||
String abilityText = card.getCurrentState().getAbilityText();
|
||||
if (((keywordKey.indexOf("Flashback") == -1)
|
||||
@@ -752,7 +747,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
}
|
||||
|
||||
private void drawCounterTabs(final Graphics g) {
|
||||
|
||||
final Dimension imgSize = calculateImageSize();
|
||||
final int titleY = Math.round(imgSize.height * (54f / 640)) - 15;
|
||||
|
||||
@@ -767,7 +761,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
FontMetrics largeFontMetrics = g.getFontMetrics(largeCounterFont);
|
||||
|
||||
if (CounterDisplayType.from(FModel.getPreferences().getPref(FPref.UI_CARD_COUNTER_DISPLAY_TYPE)) == CounterDisplayType.OLD_WHEN_SMALL) {
|
||||
|
||||
int maxCounters = 0;
|
||||
for (Integer numberOfCounters : card.getCounters().values()) {
|
||||
maxCounters = Math.max(maxCounters, numberOfCounters);
|
||||
@@ -780,9 +773,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
|
||||
}
|
||||
|
||||
|
||||
for (Map.Entry<CounterType, Integer> counterEntry : card.getCounters().entrySet()) {
|
||||
|
||||
final CounterType counter = counterEntry.getKey();
|
||||
final int numberOfCounters = counterEntry.getValue();
|
||||
final int counterBoxRealWidth = counterBoxBaseWidth + largeFontMetrics.stringWidth(String.valueOf(numberOfCounters));
|
||||
@@ -827,7 +818,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
}
|
||||
|
||||
private void drawCounterImage(final Graphics g) {
|
||||
|
||||
int counters = 0;
|
||||
for (final Integer i : card.getCounters().values()) {
|
||||
counters += i;
|
||||
@@ -848,7 +838,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
}
|
||||
|
||||
private void drawMarkersTabs(final Graphics g, List<String> markers) {
|
||||
|
||||
final Dimension imgSize = calculateImageSize();
|
||||
final int titleY = Math.round(imgSize.height * (54f / 640)) - 15;
|
||||
|
||||
@@ -862,7 +851,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
FontMetrics smallFontMetrics = g.getFontMetrics(smallCounterFont);
|
||||
|
||||
for (String marker : markers) {
|
||||
|
||||
final int markerBoxRealWidth = markerBoxBaseWidth + smallFontMetrics.stringWidth(marker);
|
||||
final int markerYOffset;
|
||||
|
||||
@@ -907,7 +895,6 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
* @param font The font to use to draw the text.
|
||||
*/
|
||||
private void drawVerticallyCenteredString(Graphics g, String text, Rectangle area, Font font, final FontMetrics fontMetrics) {
|
||||
|
||||
Font oldFont = g.getFont();
|
||||
|
||||
int x = area.x;
|
||||
@@ -1114,8 +1101,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
}
|
||||
|
||||
private boolean showCardManaCostOverlay() {
|
||||
return isShowingOverlays() &&
|
||||
isPreferenceEnabled(FPref.UI_OVERLAY_CARD_MANA_COST);
|
||||
return isShowingOverlays() && isPreferenceEnabled(FPref.UI_OVERLAY_CARD_MANA_COST);
|
||||
}
|
||||
|
||||
private boolean showCardIdOverlay() {
|
||||
|
||||
@@ -307,7 +307,7 @@ public abstract class CardPanelContainer extends SkinnedPanel {
|
||||
fromPanel.dispose();
|
||||
getCardPanels().remove(fromPanel);
|
||||
remove(fromPanel);
|
||||
if ( repaint ) {
|
||||
if (repaint) {
|
||||
invalidate();
|
||||
repaint();
|
||||
doingLayout();
|
||||
@@ -347,7 +347,7 @@ public abstract class CardPanelContainer extends SkinnedPanel {
|
||||
}
|
||||
getCardPanels().clear();
|
||||
removeAll();
|
||||
if ( repaint ) {
|
||||
if (repaint) {
|
||||
setPreferredSize(new Dimension(0, 0));
|
||||
invalidate();
|
||||
getParent().validate();
|
||||
|
||||
@@ -624,8 +624,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
|
||||
|
||||
if (toDelete.size() == getCardPanels().size()) {
|
||||
clear(false);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
for (final CardView card : toDelete) {
|
||||
removeCardPanel(getCardPanel(card.getId()),false);
|
||||
}
|
||||
@@ -681,8 +680,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
|
||||
if (card.isTapped()) {
|
||||
toPanel.setTapped(true);
|
||||
toPanel.setTappedAngle(forge.view.arcane.CardPanel.TAPPED_ANGLE);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
toPanel.setTapped(false);
|
||||
toPanel.setTappedAngle(0);
|
||||
}
|
||||
@@ -705,8 +703,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
|
||||
CardPanel attachedToPanel;
|
||||
if (card.getAttachedTo() != null) {
|
||||
attachedToPanel = getCardPanel(card.getAttachedTo().getId());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
attachedToPanel = null;
|
||||
}
|
||||
if (toPanel.getAttachedToPanel() != attachedToPanel) {
|
||||
|
||||
@@ -104,18 +104,13 @@ public class ScaledImagePanel extends JPanel {
|
||||
return;
|
||||
}
|
||||
|
||||
//System.out.println(sz + " -- " + src);
|
||||
|
||||
//ResampleOp resizer = new ResampleOp(DimensionConstrain.createMaxDimension(this.getWidth(), this.getHeight(), !scaleLarger));
|
||||
//resizer.setUnsharpenMask(UnsharpenMask.Soft);
|
||||
BufferedImage img = getSrcImage(); //resizer.filter(getSrcImage(), null);
|
||||
|
||||
boolean needsScale = img.getWidth() < sz.width;
|
||||
float scaleFactor = ((float)img.getWidth()) / sz.width;
|
||||
if ( needsScale && ( scaleFactor < 0.95 || scaleFactor > 1.05 ) ) { // This should very low-quality scaling to draw during animation
|
||||
|
||||
//System.out.println("Painting: " + img.getWidth() + " -> " + sz.width );
|
||||
|
||||
if (needsScale && ( scaleFactor < 0.95 || scaleFactor > 1.05 )) { // This should very low-quality scaling to draw during animation
|
||||
float maxZoomX = ((float)sz.width) / img.getWidth();
|
||||
float maxZoomY = ((float)sz.height) / img.getHeight();
|
||||
float zoom = Math.min(maxZoomX, maxZoomY);
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
<packaging.type>jar</packaging.type>
|
||||
<build.min.memory>-Xms128m</build.min.memory>
|
||||
<build.max.memory>-Xmx2048m</build.max.memory>
|
||||
<alpha-version>1.6.42.001</alpha-version>
|
||||
<alpha-version>1.6.43.001</alpha-version>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.43-SNAPSHOT</version>
|
||||
<version>1.6.44-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-ios</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.43-SNAPSHOT</version>
|
||||
<version>1.6.44-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-mobile-dev</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.43-SNAPSHOT</version>
|
||||
<version>1.6.44-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-mobile</artifactId>
|
||||
|
||||
@@ -46,7 +46,7 @@ import forge.util.Localizer;
|
||||
import forge.util.Utils;
|
||||
|
||||
public class Forge implements ApplicationListener {
|
||||
public static final String CURRENT_VERSION = "1.6.42.001";
|
||||
public static final String CURRENT_VERSION = "1.6.43.001";
|
||||
|
||||
private static final ApplicationListener app = new Forge();
|
||||
private static Clipboard clipboard;
|
||||
|
||||
@@ -291,6 +291,19 @@ public class TextRenderer {
|
||||
case "clr":
|
||||
colorOverride = value != null ? new Color(Integer.parseInt(value)) : null;
|
||||
break;
|
||||
case "span":
|
||||
// <span style="color:gray;">
|
||||
if (value != null && value.contains("color:")) {
|
||||
int startIdx = value.indexOf(':') + 1;
|
||||
int endIdx = value.indexOf(';');
|
||||
String colorName = value.substring(startIdx, endIdx);
|
||||
if (colorName.equals("gray")) {
|
||||
colorOverride = Color.GRAY;
|
||||
}
|
||||
} else {
|
||||
colorOverride = null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
validKeyword = false;
|
||||
break;
|
||||
|
||||
@@ -362,8 +362,7 @@ public class CardImageRenderer {
|
||||
drawDetails(g, card, gameView, altState, x, y, w, h);
|
||||
return;
|
||||
}
|
||||
if(card.isToken() && card.getCurrentState().getType().hasSubtype("Effect")
|
||||
&& FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_DISABLE_IMAGES_EFFECT_CARDS)){
|
||||
if(card.isImmutable() && FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_DISABLE_IMAGES_EFFECT_CARDS)){
|
||||
drawDetails(g, card, gameView, altState, x, y, w, h);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.43-SNAPSHOT</version>
|
||||
<version>1.6.44-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui</artifactId>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
#Add one announcement per line
|
||||
Get in the discord if you aren't yet.
|
||||
Card support has been extended up to "Modern Horizons 2", including the available previews from the upcoming "Dungeons and Dragons: Adventures in the Forgotten Realms".
|
||||
Collector Number has been added to all cards in the Catalog.
|
||||
[Desktop] Card Catalog now adds the Cards' Collector Number as a sortable column.
|
||||
[Desktop] Improved support for Custom Editions: now custom editions appear with their own names within other official editions, and can be selected in Set filters.
|
||||
Net decks have been expanded and there are now tons of new decks in constructed, commander, brawl, oathbreaker and tiny leaders.
|
||||
Complete support to game mechanics for all cards in Modern format.
|
||||
Various other improvements to game mechanics and AI: please check the Git log for details if interested.
|
||||
Improvements to Italian translation.
|
||||
Get in the discord if you aren't yet. https://discord.gg/3v9JCVr
|
||||
Pre-release build for "Dungeons and Dragons: Adventures in the Forgotten Realms", please report any bugs!
|
||||
Commander cards for the D&D set are still being worked on.
|
||||
Custom cards can now be loaded from your user profile. Enable it your preferences.
|
||||
Add support for Collector Boosters (not implemented everywhere yet)
|
||||
*** Android 7 & 8 support is now deprecated. Support will be dropped in an upcoming release. ***
|
||||
|
||||
@@ -97,3 +97,4 @@ Kaldheim, 3/6/KHM, KHM
|
||||
Time Spiral Remastered, 3/6/KHM, TSR
|
||||
Strixhaven: School of Mages, 3/6/STX, STX
|
||||
Modern Horizons 2, 3/6/MH2, MH2
|
||||
Adventures in the Forgotten Realms, 3/6/AFR, AFR
|
||||
|
||||
@@ -2,11 +2,8 @@ Name:Academy Manufactor
|
||||
ManaCost:3
|
||||
Types:Artifact Creature Assembly-Worker
|
||||
PT:1/3
|
||||
R:Event$ CreateToken | ActiveZones$ Battlefield | ValidPlayer$ You | ValidToken$ Clue,Food,Treasure | ReplaceWith$ DBToken | Description$ If you would create a Clue, Food, or Treasure token, instead create one of each.
|
||||
SVar:DBToken:DB$ Token | TokenScript$ c_a_clue_draw | TokenAmount$ X | SubAbility$ DBToken2
|
||||
SVar:DBToken2:DB$ Token | TokenScript$ c_a_food_sac | TokenAmount$ X | SubAbility$ DBToken3
|
||||
SVar:DBToken3:DB$ Token | TokenScript$ c_a_treasure_sac | TokenAmount$ X
|
||||
SVar:X:ReplaceCount$TokenNum
|
||||
R:Event$ CreateToken | ActiveZones$ Battlefield | ValidPlayer$ You | ValidToken$ Clue,Food,Treasure | ReplaceWith$ TokenReplace | Description$ If you would create a Clue, Food, or Treasure token, instead create one of each.
|
||||
SVar:TokenReplace:DB$ ReplaceToken | Type$ ReplaceToken | ValidCard$ Clue,Food,Treasure | TokenScript$ c_a_clue_draw,c_a_food_sac,c_a_treasure_sac
|
||||
DeckHas:Ability$Sacrifice & Ability$Token & Ability$LifeGain
|
||||
DeckHints:Ability$Investigate
|
||||
Oracle:If you would create a Clue, Food, or Treasure token, instead create one of each.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user