Merge remote-tracking branch 'upstream/master' into collector-number-in-card-list-and-card-db-refactoring

This commit is contained in:
leriomaggio
2021-07-21 17:32:02 +01:00
488 changed files with 2679 additions and 1017 deletions

View File

@@ -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>

View File

@@ -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;
@@ -2013,7 +2013,7 @@ public class AiController {
final CardCollection library = new CardCollection(in);
CardLists.shuffle(library);
// remove all land, keep non-basicland in there, shuffled
CardCollection land = CardLists.filter(library, CardPredicates.Presets.LANDS);
for (Card c : land) {
@@ -2021,7 +2021,7 @@ public class AiController {
library.remove(c);
}
}
try {
// mana weave, total of 7 land
// The Following have all been reduced by 1, to account for the
@@ -2038,19 +2038,14 @@ public class AiController {
System.err.println("Error: cannot smooth mana curve, not enough land");
return in;
}
// add the rest of land to the end of the deck
for (int i = 0; i < land.size(); i++) {
if (!library.contains(land.get(i))) {
library.add(land.get(i));
}
}
// check
for (int i = 0; i < library.size(); i++) {
System.out.println(library.get(i));
}
return library;
} // smoothComputerManaCurve()
@@ -2224,7 +2219,7 @@ public class AiController {
}
return ComputerUtil.chooseSacrificeType(player, type, ability, ability.getTargetCard(), amount);
}
private boolean checkAiSpecificRestrictions(final SpellAbility sa) {
// AI-specific restrictions specified as activation parameters in spell abilities
@@ -2276,5 +2271,5 @@ public class AiController {
// AI logic for choosing which replacement effect to apply happens here.
return Iterables.getFirst(list, null);
}
}

View File

@@ -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;
}
}

View File

@@ -947,7 +947,7 @@ public class ComputerUtil {
canRegen = true;
}
} catch (final Exception ex) {
} catch (final Exception ex) {
throw new RuntimeException(TextUtil.concatNoSpace("There is an error in the card code for ", c.getName(), ":", ex.getMessage()), ex);
}
}
@@ -2832,7 +2832,6 @@ public class ComputerUtil {
}
public static boolean lifegainPositive(final Player player, final Card source) {
if (!player.canGainLife()) {
return false;
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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)

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -1,48 +1,33 @@
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
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
final String logic = sa.hasParam("AILogic") ? sa.getParam("AILogic") : "";
final Game game = ai.getGame();
final Card source = sa.getHostCard();
// don't use this as a response (ReplaySpell logic is an exception, might be called from a subability
@@ -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();
}
}
@@ -112,7 +96,7 @@ public class PlayAi extends SpellAbilityAi {
}
if ("ReplaySpell".equals(logic)) {
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"));
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"));
} else if (logic.startsWith("NeedsChosenCard")) {
int minCMC = 0;
if (sa.getPayCosts().getCostMana() != null) {
@@ -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()) {
@@ -134,7 +135,7 @@ public class PlayAi extends SpellAbilityAi {
return true;
}
/**
* <p>
* doTriggerAINoCost
@@ -152,7 +153,7 @@ public class PlayAi extends SpellAbilityAi {
if (!sa.hasParam("AILogic")) {
return false;
}
return checkApiLogic(ai, sa);
}
@@ -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)) {

View File

@@ -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"));

View File

@@ -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;

View File

@@ -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>

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -87,7 +87,6 @@ public class DeckGeneratorMonoColor extends DeckGeneratorBase {
}
}
@Override
public final CardPool getDeck(final int size, final boolean forAi) {
addCreaturesAndSpells(size, cmcLevels, forAi);

View File

@@ -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) )

View File

@@ -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;

View File

@@ -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>

View File

@@ -705,17 +705,20 @@ public class GameAction {
}
}
if (zoneFrom == null) {
c.setCastFrom(null);
c.setCastSA(null);
} else if (zoneTo.is(ZoneType.Stack)) {
c.setCastFrom(zoneFrom.getZoneType());
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);
} 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);
}

View File

@@ -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());

View File

@@ -1,5 +1,6 @@
package forge.game;
import java.util.Arrays;
import java.util.Map;
import com.google.common.collect.ImmutableList;
@@ -8,7 +9,7 @@ import forge.game.ability.AbilityKey;
import forge.game.player.Player;
import forge.game.trigger.TriggerType;
/**
/**
* Represents the planar dice for Planechase games.
*
*/
@@ -16,7 +17,7 @@ public enum PlanarDice {
Planeswalk,
Chaos,
Blank;
public static PlanarDice roll(Player roller, PlanarDice riggedResult)
{
PlanarDice res = Blank;
@@ -27,25 +28,37 @@ public enum PlanarDice {
res = Planeswalk;
else if (i == 1)
res = Chaos;
PlanarDice trigRes = res;
if(roller.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.blankIsChaos)
&& res == Blank)
{
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;
}
/**
* Parses a string into an enum member.
* @param string to parse

View File

@@ -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()) {

View File

@@ -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),

View File

@@ -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);

View File

@@ -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);
triggerList.triggerChangesZoneAll(game, sa);
triggerList.clear();
if (!useZoneTable) {
triggerList.triggerChangesZoneAll(game, sa);
triggerList.clear();
}
game.fireEvent(new GameEventTokenCreated());
if (combatChanged.isTrue()) {
game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged());
}
}
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);

View File

@@ -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));
}

View File

@@ -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();

View File

@@ -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));
}
}

View File

@@ -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");
}
}

View File

@@ -67,7 +67,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
if (tapOnLose) {
c.tap();
}
} // if
}
host.removeGainControlTargets(c);
}
@@ -91,7 +91,12 @@ public class ControlGainEffect extends SpellAbilityEffect {
if (sa.hasParam("ControlledByTarget")) {
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)) {
@@ -103,7 +108,6 @@ public class ControlGainEffect extends SpellAbilityEffect {
boolean combatChanged = false;
for (Card tgtC : tgtCards) {
if (!tgtC.isInPlay() || !tgtC.canBeControlledBy(newController)) {
continue;
}

View File

@@ -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);

View File

@@ -12,7 +12,6 @@ public class DetachedCardEffect extends Card {
card = card0;
setName(name0);
addType("Effect");
setOwner(card0.getOwner());
setImmutable(true);

View File

@@ -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

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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

View File

@@ -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(" ");
}

View File

@@ -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);
}

View File

@@ -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. ");

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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;
}
// effect was updated
params.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -2,123 +2,212 @@ 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)) {
if (sa.hasParam("TokenTapped")) {
tok.setTapped(true);
}
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);
}
if (sa.hasParam("WithCountersType")) {
CounterType cType = CounterType.getType(sa.getParam("WithCountersType"));
int cAmount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("WithCountersAmount", "1"), sa);
tok.addEtbCounter(cType, cAmount, creator);
}
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) {
// in case token can't enter the battlefield, it isn't created
triggerList.put(ZoneType.None, ZoneType.None, c);
continue;
}
triggerList.put(ZoneType.None, c.getZone().getZoneType(), c);
creator.addTokensCreatedThisTurn();
if (clone) {
c.setCloneOrigin(host);
}
if (!pumpKeywords.isEmpty()) {
c.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, false, timestamp);
addPumpUntil(sa, c, timestamp);
}
if (sa.hasParam("AtEOTTrig")) {
addSelfTrigger(sa, sa.getParam("AtEOTTrig"), c);
}
if (addToCombat(c, tok.getController(), sa, "TokenAttacking", "TokenBlocking")) {
combatChanged.setTrue();
}
if (sa.hasParam("AttachAfter") && sa.hasParam("AttachedTo")) {
attachTokenTo(tok, sa);
}
c.updateStateForView();
if (sa.hasParam("RememberTokens")) {
host.addRemembered(c);
}
if (sa.hasParam("ImprintTokens")) {
host.addImprintedCard(c);
}
if (sa.hasParam("RememberSource")) {
c.addRemembered(host);
}
if (sa.hasParam("TokenRemembered")) {
final String remembered = sa.getParam("TokenRemembered");
for (final Object o : AbilityUtils.getDefinedObjects(host, remembered, sa)) {
c.addRemembered(o);
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);
}
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);
}
if (sa.hasParam("WithCountersType")) {
CounterType cType = CounterType.getType(sa.getParam("WithCountersType"));
int cAmount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("WithCountersAmount", "1"), sa);
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 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, moved);
continue;
}
triggerList.put(ZoneType.None, moved.getZone().getZoneType(), moved);
creator.addTokensCreatedThisTurn();
if (clone) {
moved.setCloneOrigin(host);
}
if (!pumpKeywords.isEmpty()) {
moved.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, false, timestamp);
addPumpUntil(sa, moved, timestamp);
}
if (sa.hasParam("AtEOTTrig")) {
addSelfTrigger(sa, sa.getParam("AtEOTTrig"), moved);
}
if (addToCombat(moved, tok.getController(), sa, "TokenAttacking", "TokenBlocking")) {
combatChanged.setTrue();
}
if (sa.hasParam("AttachAfter") && sa.hasParam("AttachedTo")) {
attachTokenTo(tok, sa);
}
moved.updateStateForView();
if (sa.hasParam("RememberTokens")) {
host.addRemembered(moved);
}
if (sa.hasParam("ImprintTokens")) {
host.addImprintedCard(moved);
}
if (sa.hasParam("RememberSource")) {
moved.addRemembered(host);
}
if (sa.hasParam("TokenRemembered")) {
final String remembered = sa.getParam("TokenRemembered");
for (final Object o : AbilityUtils.getDefinedObjects(host, remembered, sa)) {
moved.addRemembered(o);
}
}
allTokens.add(moved);
}
allTokens.add(c);
}
if (sa.hasParam("AtEOT")) {
registerDelayedTrigger(sa, sa.getParam("AtEOT"), allTokens);
}
return allTokens;
return tokenTable;
}
private boolean attachTokenTo(Card tok, SpellAbility sa) {

View File

@@ -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()) {
return testFailed;
}
if (incR[0].equals("Permanent") && !isPermanent()) {
return testFailed;
}
if (!incR[0].equals("card") && !incR[0].equals("Card") && !incR[0].equals("Spell")
&& !incR[0].equals("Permanent") && !getType().hasStringType(incR[0])) {
if (incR[0].equals("Spell")) {
if (!isSpell()) {
return testFailed;
}
} else if (incR[0].equals("Permanent")) {
if (!isPermanent()) {
return testFailed;
}
} 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,15 +4843,8 @@ 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;
}
if (!hasProperty(exR, sourceController, source, spellAbility)) {
return testFailed;
}
}
}
@@ -4846,6 +4854,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
// 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;

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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),

View File

@@ -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;
}
}

View File

@@ -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");

View File

@@ -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")) {

View File

@@ -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);

View File

@@ -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();
}

View 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);
}
}

View File

@@ -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;
@@ -173,7 +179,7 @@ public interface ICostVisitor<T> {
public T visit(CostUnattach cost) {
return null;
}
@Override
public T visit(CostTapType cost) {
return null;

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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 {

View File

@@ -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;
}
// Log there
String message = chosenRE.getDescription();
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);
// Updated Replacements need to be logged elsewhere because its otherwise in the wrong order
if (res != ReplacementResult.Updated) {
String message = chosenRE.getDescription();
if (!StringUtils.isEmpty(message))
if (chosenRE.getHostCard() != null) {
message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName());
}
game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
}
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,10 +401,8 @@ public class ReplacementHandler {
}
}
if (mapParams.containsKey("Skip")) {
if (mapParams.get("Skip").equals("True")) {
return ReplacementResult.Skipped; // Event is skipped.
}
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

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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) {
return false;
if (!stillblock) {
break;
}
} else {
return false;
}
}
if (stillblock) {
return false;
}
}
// relative valid relative to each other
if (!stAb.matchesValidParam("ValidAttackerRelative", attacker, blocker)) {

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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));
}
}

View File

@@ -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),

View File

@@ -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));

View File

@@ -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>

View File

@@ -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>

View File

@@ -414,4 +414,3 @@ public enum FControl implements KeyEventDispatcher {
return GamePlayerUtil.getGuiPlayer();
}
}

View File

@@ -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);

View File

@@ -340,4 +340,3 @@ public enum CDeckEditorUI implements ICDoc {
@Override
public void update() { }
}

View File

@@ -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);
}
}

View File

@@ -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
@@ -575,15 +574,15 @@ public final class CMatchUI
@Override
public void setSelectables(final Iterable<CardView> cards) {
super.setSelectables(cards);
// update zones on tabletop and floating zones - non-selectable cards may be rendered differently
FThreads.invokeInEdtNowOrLater(new Runnable() {
super.setSelectables(cards);
// update zones on tabletop and floating zones - non-selectable cards may be rendered differently
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));
}
}
@@ -594,15 +593,15 @@ public final class CMatchUI
@Override
public void clearSelectables() {
super.clearSelectables();
// update zones on tabletop and floating zones - non-selectable cards may be rendered differently
FThreads.invokeInEdtNowOrLater(new Runnable() {
super.clearSelectables();
// update zones on tabletop and floating zones - non-selectable cards may be rendered differently
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,79 +1294,78 @@ 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))) {
// We can go and show the modal
SpellAbilityStackInstance si = event.si;
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;
MigLayout migLayout = new MigLayout("insets 15, left, gap 30, fill");
JPanel mainPanel = new JPanel(migLayout);
final Dimension parentSize = JOptionPane.getRootFrame().getSize();
Dimension maxSize = new Dimension(1400, parentSize.height - 100);
mainPanel.setMaximumSize(maxSize);
mainPanel.setOpaque(false);
MigLayout migLayout = new MigLayout("insets 15, left, gap 30, fill");
JPanel mainPanel = new JPanel(migLayout);
final Dimension parentSize = JOptionPane.getRootFrame().getSize();
Dimension maxSize = new Dimension(1400, parentSize.height - 100);
mainPanel.setMaximumSize(maxSize);
mainPanel.setOpaque(false);
// Big Image
addBigImageToStackModalPanel(mainPanel, si);
// Big Image
addBigImageToStackModalPanel(mainPanel, si);
// Text
addTextToStackModalPanel(mainPanel,sa,si);
// Text
addTextToStackModalPanel(mainPanel,sa,si);
// Small images
int numSmallImages = 0;
// Small images
int numSmallImages = 0;
// 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()) {
GameEntity enchantedEntity = hostCard.getEntityAttachedTo();
if(enchantedEntity != null) {
enchantedEntityView = enchantedEntity.getView();
numSmallImages++;
} else if ((sa.getRootAbility() != null)
&& (sa.getRootAbility().getPaidList("Sacrificed") != null)
&& !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 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()) {
GameEntity enchantedEntity = hostCard.getEntityAttachedTo();
if (enchantedEntity != null) {
enchantedEntityView = enchantedEntity.getView();
numSmallImages++;
} else if ((sa.getRootAbility() != null)
&& (sa.getRootAbility().getPaidList("Sacrificed") != null)
&& !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) {
enchantedEntityView = enchantedEntity.getView();
numSmallImages++;
}
}
}
}
// 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) {
sourceCardView = sourceSA.getHostCard().getView();
numSmallImages++;
}
// 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) {
sourceCardView = sourceSA.getHostCard().getView();
numSmallImages++;
}
// I also want to show each type of targets (both cards and players)
List<GameEntityView> targets = getTargets(si,new ArrayList<GameEntityView>());
numSmallImages = numSmallImages + targets.size();
// I also want to show each type of targets (both cards and players)
List<GameEntityView> targets = getTargets(si,new ArrayList<GameEntityView>());
numSmallImages = numSmallImages + targets.size();
// Now I know how many small images - on to render them
if(enchantedEntityView != null) {
addSmallImageToStackModalPanel(enchantedEntityView,mainPanel,numSmallImages);
}
if(sourceCardView != null) {
addSmallImageToStackModalPanel(sourceCardView,mainPanel,numSmallImages);
}
for(GameEntityView gev : targets) {
addSmallImageToStackModalPanel(gev, mainPanel, numSmallImages);
}
// Now I know how many small images - on to render them
if (enchantedEntityView != null) {
addSmallImageToStackModalPanel(enchantedEntityView,mainPanel,numSmallImages);
}
if (sourceCardView != null) {
addSmallImageToStackModalPanel(sourceCardView,mainPanel,numSmallImages);
}
for (GameEntityView gev : targets) {
addSmallImageToStackModalPanel(gev, mainPanel, numSmallImages);
}
FOptionPane.showOptionDialog(null, "Forge", null, mainPanel, ImmutableList.of(Localizer.getInstance().getMessage("lblOK")));
// here the user closed the modal - time to update the next notifiable stack index
FOptionPane.showOptionDialog(null, "Forge", null, mainPanel, ImmutableList.of(Localizer.getInstance().getMessage("lblOK")));
// here the user closed the modal - time to update the next notifiable stack index
}
// In any case, I have to increase the counter
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)) {

View File

@@ -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) {

View File

@@ -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());

View File

@@ -156,7 +156,7 @@ public class CCombat implements ICDoc {
sb.append("( ").append(state.getPower()).append(" / ").append(state.getToughness()).append(" ) ... ");
if (c.isFaceDown()) {
sb.append("Morph");
} else {
} else {
sb.append(name);
}
sb.append(" [").append(state.getDisplayId()).append("] ");

View File

@@ -78,7 +78,6 @@ public class CDetail implements ICDoc {
@Override
public void initialize() {
}
@Override

View File

@@ -269,7 +269,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
g2d.rotate(getTappedAngle(), cardXOffset + edgeOffset, (cardYOffset + cardHeight)
- edgeOffset);
}
super.paint(g2d);
super.paint(g2d);
}
@Override
@@ -284,12 +284,12 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
final int cornerSize = noBorderPref && !cardImgHasAlpha ? 0 : Math.max(4, Math.round(cardWidth * CardPanel.ROUNDED_CORNER_SIZE));
final int offset = isTapped() && (!noBorderPref || cardImgHasAlpha) ? 1 : 0;
// Magenta outline for when card is chosen
// Magenta outline for when card is chosen
if (matchUI.isUsedToPay(getCard())) {
g2d.setColor(Color.magenta);
final int n2 = Math.max(1, Math.round(2 * cardWidth * CardPanel.SELECTED_BORDER_SIZE));
g2d.fillRoundRect(cardXOffset - n2, (cardYOffset - n2) + offset, cardWidth + (n2 * 2), cardHeight + (n2 * 2), cornerSize + n2, cornerSize + n2);
}
}
// Green outline for hover
if (isSelected) {
@@ -311,24 +311,24 @@ 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;
} else if (ed != null && state.getFoilIndex() == 0) {
// Non-foil cards from white-bordered sets are drawn with white border
switch (ed.getBorderColor()) {
case WHITE:
g2d.setColor(Color.WHITE);
colorIsSet = true;
break;
case GOLD:
g2d.setColor(Color.ORANGE);
colorIsSet = true;
break;
case SILVER:
g2d.setColor(Color.GRAY);
colorIsSet = true;
case WHITE:
g2d.setColor(Color.WHITE);
colorIsSet = true;
break;
case GOLD:
g2d.setColor(Color.ORANGE);
colorIsSet = true;
break;
case SILVER:
g2d.setColor(Color.GRAY);
colorIsSet = true;
}
}
if (colorIsSet) {
@@ -337,11 +337,11 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
}
}
if (matchUI.isSelectable(getCard())) { // White border for selectable cards to further highlight them
g2d.setColor(Color.WHITE);
final int ins = 1;
g2d.fillRoundRect(cardXOffset+ins, cardYOffset+ins, cardWidth-ins*2, cardHeight-ins*2, cornerSize-ins, cornerSize-ins);
}
if (matchUI.isSelectable(getCard())) { // White border for selectable cards to further highlight them
g2d.setColor(Color.WHITE);
final int ins = 1;
g2d.fillRoundRect(cardXOffset+ins, cardYOffset+ins, cardWidth-ins*2, cardHeight-ins*2, cornerSize-ins, cornerSize-ins);
}
}
private void drawManaCost(final Graphics g, final ManaCost cost, final int deltaY) {
@@ -366,16 +366,16 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
cardWidth, cardHeight, Math.round(cardWidth * BLACK_BORDER_SIZE));
}
boolean nonselectable = matchUI.isSelecting() && !matchUI.isSelectable(getCard());
// if selecting, darken non-selectable cards
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));
final int offset = isTapped() && (!noBorderPref || cardImgHasAlpha) ? 1 : 0;
g.setColor(new Color(0.0f,0.0f,0.0f,0.6f));
g.fillRoundRect(cardXOffset, cardYOffset + offset, cardWidth, cardHeight, cornerSize, cornerSize);
}
boolean nonselectable = matchUI.isSelecting() && !matchUI.isSelectable(getCard());
// if selecting, darken non-selectable cards
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));
final int offset = isTapped() && (!noBorderPref || cardImgHasAlpha) ? 1 : 0;
g.setColor(new Color(0.0f,0.0f,0.0f,0.6f));
g.fillRoundRect(cardXOffset, cardYOffset + offset, cardWidth, cardHeight, cornerSize, cornerSize);
}
}
public static void drawFoilEffect(final Graphics g, final CardView card2, final int x, final int y, final int width, final int height, final int borderSize) {
@@ -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() {

View File

@@ -183,7 +183,7 @@ public abstract class CardPanelContainer extends SkinnedPanel {
}
protected boolean cardPanelDraggable(final CardPanel panel) {
return true;
return true;
}
private MouseMotionListener setupMotionMouseListener() {
@@ -212,22 +212,22 @@ public abstract class CardPanelContainer extends SkinnedPanel {
return;
}
if (cardPanelDraggable(panel)) { // allow for non-draggable cards
if (intialMouseDragX == -1) {
intialMouseDragX = x;
intialMouseDragY = y;
return;
}
if ((Math.abs(x - intialMouseDragX) < CardPanelContainer.DRAG_SMUDGE)
&& (Math.abs(y - intialMouseDragY) < CardPanelContainer.DRAG_SMUDGE)) {
return;
}
mouseDownPanel = null;
setMouseDragPanel(panel);
mouseDragOffsetX = panel.getX() - intialMouseDragX;
mouseDragOffsetY = panel.getY() - intialMouseDragY;
mouseDragStart(getMouseDragPanel(), evt);
}
if (cardPanelDraggable(panel)) { // allow for non-draggable cards
if (intialMouseDragX == -1) {
intialMouseDragX = x;
intialMouseDragY = y;
return;
}
if ((Math.abs(x - intialMouseDragX) < CardPanelContainer.DRAG_SMUDGE)
&& (Math.abs(y - intialMouseDragY) < CardPanelContainer.DRAG_SMUDGE)) {
return;
}
mouseDownPanel = null;
setMouseDragPanel(panel);
mouseDragOffsetX = panel.getX() - intialMouseDragX;
mouseDragOffsetY = panel.getY() - intialMouseDragY;
mouseDragStart(getMouseDragPanel(), evt);
}
}
@Override
@@ -291,7 +291,7 @@ public abstract class CardPanelContainer extends SkinnedPanel {
}
public final void removeCardPanel(final CardPanel fromPanel) {
removeCardPanel(fromPanel,true);
removeCardPanel(fromPanel,true);
}
public final void removeCardPanel(final CardPanel fromPanel, final boolean repaint) {
@@ -307,11 +307,11 @@ public abstract class CardPanelContainer extends SkinnedPanel {
fromPanel.dispose();
getCardPanels().remove(fromPanel);
remove(fromPanel);
if ( repaint ) {
invalidate();
repaint();
doingLayout();
}
if (repaint) {
invalidate();
repaint();
doingLayout();
}
}
public final void setCardPanels(final List<CardPanel> cardPanels) {
@@ -331,14 +331,14 @@ public abstract class CardPanelContainer extends SkinnedPanel {
for (final CardPanel cardPanel : cardPanels) {
this.add(cardPanel);
}
//pfps the validate just below will do the layout, so don't do it here this.doLayout();
//pfps the validate just below will do the layout, so don't do it here this.doLayout();
this.invalidate();
this.getParent().validate();
this.repaint();
}
public final void clear() {
clear(true);
clear(true);
}
public final void clear(final boolean repaint) {
FThreads.assertExecutedByEdt(true);
@@ -347,12 +347,12 @@ public abstract class CardPanelContainer extends SkinnedPanel {
}
getCardPanels().clear();
removeAll();
if ( repaint ) {
setPreferredSize(new Dimension(0, 0));
invalidate();
getParent().validate();
repaint();
}
if (repaint) {
setPreferredSize(new Dimension(0, 0));
invalidate();
getParent().validate();
repaint();
}
}
public final FScrollPane getScrollPane() {

View File

@@ -57,10 +57,10 @@ public abstract class FloatingCardArea extends CardArea {
protected abstract Iterable<CardView> getCards();
protected FloatingCardArea(final CMatchUI matchUI) {
this(matchUI, new FScrollPane(false, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER));
this(matchUI, new FScrollPane(false, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER));
}
protected FloatingCardArea(final CMatchUI matchUI, final FScrollPane scrollPane) {
super(matchUI, scrollPane);
super(matchUI, scrollPane);
}
protected void showWindow() {
@@ -127,7 +127,7 @@ public abstract class FloatingCardArea extends CardArea {
}
protected FDialog getWindow() {
return window;
return window;
}
protected void loadLocation() {

View File

@@ -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);
}
@@ -649,9 +648,9 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
needLayoutRefresh = true;
}
}
if (needLayoutRefresh) {
doLayout();
}
if (needLayoutRefresh) {
doLayout();
}
invalidate(); //pfps do the extra invalidate before any scrolling
if (!newPanels.isEmpty()) {
@@ -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) {

View File

@@ -103,8 +103,6 @@ public class ScaledImagePanel extends JPanel {
if (src == null) {
return;
}
//System.out.println(sz + " -- " + src);
//ResampleOp resizer = new ResampleOp(DimensionConstrain.createMaxDimension(this.getWidth(), this.getHeight(), !scaleLarger));
//resizer.setUnsharpenMask(UnsharpenMask.Soft);
@@ -112,10 +110,7 @@ public class ScaledImagePanel extends JPanel {
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);

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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. ***

View File

@@ -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

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