This commit is contained in:
Alessandro Coli
2019-10-16 14:10:58 +02:00
75 changed files with 573 additions and 248 deletions

View File

@@ -616,7 +616,8 @@ public class AiController {
} }
catch (IllegalArgumentException ex) { catch (IllegalArgumentException ex) {
System.err.println(ex.getMessage()); System.err.println(ex.getMessage());
ComparatorUtil.verifyTransitivity(saComparator, all); String assertex = ComparatorUtil.verifyTransitivity(saComparator, all);
Sentry.capture(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
} }
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) { for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
@@ -1585,7 +1586,8 @@ public class AiController {
} }
catch (IllegalArgumentException ex) { catch (IllegalArgumentException ex) {
System.err.println(ex.getMessage()); System.err.println(ex.getMessage());
ComparatorUtil.verifyTransitivity(saComparator, all); String assertex = ComparatorUtil.verifyTransitivity(saComparator, all);
Sentry.capture(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
} }
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) { for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
@@ -1844,7 +1846,7 @@ public class AiController {
// Special case for Bow to My Command which simulates a complex tap cost via ChooseCard // Special case for Bow to My Command which simulates a complex tap cost via ChooseCard
// TODO: consider enhancing support for tapXType<Any/...> in UnlessCost to get rid of this hack // TODO: consider enhancing support for tapXType<Any/...> in UnlessCost to get rid of this hack
if ("BowToMyCommand".equals(sa.getParam("AILogic"))) { if ("BowToMyCommand".equals(sa.getParam("AILogic"))) {
if (!sa.getHostCard().getZone().is(ZoneType.Command)) { if (!sa.getHostCard().isInZone(ZoneType.Command)) {
// Make sure that other opponents do not tap for an already abandoned scheme // Make sure that other opponents do not tap for an already abandoned scheme
result.clear(); result.clear();
break; break;

View File

@@ -2403,7 +2403,7 @@ public class ComputerUtil {
} }
// if source is not on the battlefield anymore, choose +1/+1 // if source is not on the battlefield anymore, choose +1/+1
// ones // ones
if (!game.getCardState(source).getZone().is(ZoneType.Battlefield)) { if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
return opponent ? "Feather" : "Quill"; return opponent ? "Feather" : "Quill";
} }
// if no hand cards, try to mill opponent // if no hand cards, try to mill opponent
@@ -2435,7 +2435,7 @@ public class ComputerUtil {
} }
// if source is not on the battlefield anymore // if source is not on the battlefield anymore
if (!game.getCardState(source).getZone().is(ZoneType.Battlefield)) { if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
return opponent ? "Strength" : "Numbers"; return opponent ? "Strength" : "Numbers";
} }
@@ -2484,7 +2484,7 @@ public class ComputerUtil {
} }
// if source is not on the battlefield anymore // if source is not on the battlefield anymore
if (!game.getCardState(source).getZone().is(ZoneType.Battlefield)) { if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
return opponent ? "Sprout" : "Harvest"; return opponent ? "Sprout" : "Harvest";
} }
// TODO add Lifegain to +1/+1 counters trigger // TODO add Lifegain to +1/+1 counters trigger

View File

@@ -1294,7 +1294,7 @@ public class ComputerUtilCard {
// cast it during Declare Blockers, thus ruining its attacker // cast it during Declare Blockers, thus ruining its attacker
if (holdCombatTricks && sa.getApi() == ApiType.Pump if (holdCombatTricks && sa.getApi() == ApiType.Pump
&& sa.hasParam("NumAtt") && sa.getHostCard() != null && sa.hasParam("NumAtt") && sa.getHostCard() != null
&& sa.getHostCard().getZone() != null && sa.getHostCard().getZone().is(ZoneType.Hand) && sa.getHostCard().isInZone(ZoneType.Hand)
&& c.getNetPower() > 0 // too obvious if attacking with a 0-power creature && c.getNetPower() > 0 // too obvious if attacking with a 0-power creature
&& sa.getHostCard().isInstant() // only do it for instant speed spells in hand && sa.getHostCard().isInstant() // only do it for instant speed spells in hand
&& ComputerUtilMana.hasEnoughManaSourcesToCast(sa, ai)) { && ComputerUtilMana.hasEnoughManaSourcesToCast(sa, ai)) {

View File

@@ -729,7 +729,7 @@ public class PlayerControllerAi extends PlayerController {
return true; return true;
} else { } else {
Card rem = (Card) source.getFirstRemembered(); Card rem = (Card) source.getFirstRemembered();
if (!rem.getZone().is(ZoneType.Battlefield)) { if (!rem.isInZone(ZoneType.Battlefield)) {
return true; return true;
} }
} }
@@ -737,7 +737,7 @@ public class PlayerControllerAi extends PlayerController {
case "BetterTgtThanRemembered": case "BetterTgtThanRemembered":
if (source.getRememberedCount() > 0) { if (source.getRememberedCount() > 0) {
Card rem = (Card) source.getFirstRemembered(); Card rem = (Card) source.getFirstRemembered();
if (!rem.getZone().is(ZoneType.Battlefield)) { if (!rem.isInZone(ZoneType.Battlefield)) {
return true; return true;
} }
for (Card c : source.getController().getCreaturesInPlay()) { for (Card c : source.getController().getCreaturesInPlay()) {

View File

@@ -76,7 +76,7 @@ public class DamageDealAi extends DamageAiBase {
// Set PayX here to maximum value. // Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai); dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg)); source.setSVar("PayX", Integer.toString(dmg));
} else if (sa.getSVar(damage).equals("Count$CardsInYourHand") && source.getZone().is(ZoneType.Hand)) { } else if (sa.getSVar(damage).equals("Count$CardsInYourHand") && source.isInZone(ZoneType.Hand)) {
dmg--; // the card will be spent casting the spell, so actual damage is 1 less dmg--; // the card will be spent casting the spell, so actual damage is 1 less
} }
} }
@@ -113,7 +113,7 @@ public class DamageDealAi extends DamageAiBase {
// Set PayX here to maximum value. It will be adjusted later depending on the target. // Set PayX here to maximum value. It will be adjusted later depending on the target.
source.setSVar("PayX", Integer.toString(dmg)); source.setSVar("PayX", Integer.toString(dmg));
} else if (sa.getSVar(damage).contains("InYourHand") && source.getZone().is(ZoneType.Hand)) { } else if (sa.getSVar(damage).contains("InYourHand") && source.isInZone(ZoneType.Hand)) {
dmg = CardFactoryUtil.xCount(source, sa.getSVar(damage)) - 1; // the card will be spent casting the spell, so actual damage is 1 less dmg = CardFactoryUtil.xCount(source, sa.getSVar(damage)) - 1; // the card will be spent casting the spell, so actual damage is 1 less
} else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) { } else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) {
// cards that deal damage by the number of cards in target player's hand, e.g. Sudden Impact // cards that deal damage by the number of cards in target player's hand, e.g. Sudden Impact

View File

@@ -409,7 +409,7 @@ public class DrawAi extends SpellAbilityAi {
if (computerHandSize + numCards > computerMaxHandSize && game.getPhaseHandler().isPlayerTurn(ai)) { if (computerHandSize + numCards > computerMaxHandSize && game.getPhaseHandler().isPlayerTurn(ai)) {
if (xPaid) { if (xPaid) {
numCards = computerMaxHandSize - computerHandSize; numCards = computerMaxHandSize - computerHandSize;
if (sa.getHostCard().getZone().is(ZoneType.Hand)) { if (sa.getHostCard().isInZone(ZoneType.Hand)) {
numCards++; // the card will be spent numCards++; // the card will be spent
} }
source.setSVar("PayX", Integer.toString(numCards)); source.setSVar("PayX", Integer.toString(numCards));

View File

@@ -309,7 +309,7 @@ public class PumpAi extends PumpAiBase {
} }
} else { } else {
defense = AbilityUtils.calculateAmount(sa.getHostCard(), numDefense, sa); defense = AbilityUtils.calculateAmount(sa.getHostCard(), numDefense, sa);
if (numDefense.contains("X") && sa.getSVar("X").equals("Count$CardsInYourHand") && source.getZone().is(ZoneType.Hand)) { if (numDefense.contains("X") && sa.getSVar("X").equals("Count$CardsInYourHand") && source.isInZone(ZoneType.Hand)) {
defense--; // the card will be spent casting the spell, so actual toughness is 1 less defense--; // the card will be spent casting the spell, so actual toughness is 1 less
} }
} }
@@ -328,7 +328,7 @@ public class PumpAi extends PumpAiBase {
} }
} else { } else {
attack = AbilityUtils.calculateAmount(sa.getHostCard(), numAttack, sa); attack = AbilityUtils.calculateAmount(sa.getHostCard(), numAttack, sa);
if (numAttack.contains("X") && sa.getSVar("X").equals("Count$CardsInYourHand") && source.getZone().is(ZoneType.Hand)) { if (numAttack.contains("X") && sa.getSVar("X").equals("Count$CardsInYourHand") && source.isInZone(ZoneType.Hand)) {
attack--; // the card will be spent casting the spell, so actual power is 1 less attack--; // the card will be spent casting the spell, so actual power is 1 less
} }
} }

View File

@@ -148,7 +148,7 @@ public class SetStateAi extends SpellAbilityAi {
if (card.isFaceDown()) { if (card.isFaceDown()) {
// hidden agenda // hidden agenda
if (card.getState(CardStateName.Original).hasIntrinsicKeyword("Hidden agenda") if (card.getState(CardStateName.Original).hasIntrinsicKeyword("Hidden agenda")
&& card.getZone().is(ZoneType.Command)) { && card.isInZone(ZoneType.Command)) {
String chosenName = card.getNamedCard(); String chosenName = card.getNamedCard();
for (Card cast : ai.getGame().getStack().getSpellsCastThisTurn()) { for (Card cast : ai.getGame().getStack().getSpellsCastThisTurn()) {
if (cast.getController() == ai && cast.getName().equals(chosenName)) { if (cast.getController() == ai && cast.getName().equals(chosenName)) {

View File

@@ -228,7 +228,7 @@ public class GameCopier {
CardFactory.copyCopiableCharacteristics(c, result); CardFactory.copyCopiableCharacteristics(c, result);
return result; return result;
} }
if (USE_FROM_PAPER_CARD && !c.isEmblem()) { if (USE_FROM_PAPER_CARD && !c.isEmblem() && c.getPaperCard() != null) {
Card newCard = Card.fromPaperCard(c.getPaperCard(), newOwner); Card newCard = Card.fromPaperCard(c.getPaperCard(), newOwner);
newCard.setCommander(c.isCommander()); newCard.setCommander(c.isCommander());
return newCard; return newCard;

View File

@@ -15,8 +15,9 @@ public final class ComparatorUtil
* @param elements the elements to test against * @param elements the elements to test against
* @throws AssertionError if the comparator is not transitive * @throws AssertionError if the comparator is not transitive
*/ */
public static <T> void verifyTransitivity(Comparator<T> comparator, Collection<T> elements) public static <T> String verifyTransitivity(Comparator<T> comparator, Collection<T> elements)
{ {
String exception = "";
for (T first: elements) for (T first: elements)
{ {
for (T second: elements) for (T second: elements)
@@ -27,8 +28,12 @@ public final class ComparatorUtil
{ {
// Uncomment the following line to step through the failed case // Uncomment the following line to step through the failed case
//comparator.compare(first, second); //comparator.compare(first, second);
throw new AssertionError("compare(" + first + ", " + second + ") == " + result1 + /*throw new AssertionError("compare(" + first + ", " + second + ") == " + result1 +
" but swapping the parameters returns " + result2); " but swapping the parameters returns " + result2);*/
exception = "compare(" + first + ", " + second + ") == " + result1 +
" but swapping the parameters returns " + result2;
System.err.println(exception);
return exception;
} }
} }
} }
@@ -49,13 +54,19 @@ public final class ComparatorUtil
{ {
// Uncomment the following line to step through the failed case // Uncomment the following line to step through the failed case
//comparator.compare(first, third); //comparator.compare(first, third);
throw new AssertionError("compare(" + first + ", " + second + ") > 0, " + /*throw new AssertionError("compare(" + first + ", " + second + ") > 0, " +
"compare(" + second + ", " + third + ") > 0, but compare(" + first + ", " + third + ") == " + "compare(" + second + ", " + third + ") > 0, but compare(" + first + ", " + third + ") == " +
firstGreaterThanThird); firstGreaterThanThird);*/
exception = "compare(" + first + ", " + second + ") > 0, " +
"compare(" + second + ", " + third + ") > 0, but compare(" + first + ", " + third + ") == " +
firstGreaterThanThird;
System.err.println(exception);
return exception;
} }
} }
} }
} }
return exception;
} }
/** /**

View File

@@ -145,11 +145,6 @@ public class GameAction {
} }
} }
// if an adventureCard is put from Stack somewhere else, need to reset to Original State
if (c.isAdventureCard() && ((zoneFrom != null && zoneFrom.is(ZoneType.Stack)) || !zoneTo.is(ZoneType.Stack))) {
c.setState(CardStateName.Original, true);
}
// Clean up the temporary Dash SVar when the Dashed card leaves the battlefield // Clean up the temporary Dash SVar when the Dashed card leaves the battlefield
// Clean up the temporary AtEOT SVar // Clean up the temporary AtEOT SVar
String endofTurn = c.getSVar("EndOfTurnLeavePlay"); String endofTurn = c.getSVar("EndOfTurnLeavePlay");
@@ -256,7 +251,7 @@ public class GameAction {
} }
if(noLandLKI.isLand()) { if(noLandLKI.isLand()) {
// if it isn't on the Stack, it stays in that Zone // if it isn't on the Stack, it stays in that Zone
if (!c.getZone().is(ZoneType.Stack)) { if (!c.isInZone(ZoneType.Stack)) {
return c; return c;
} }
// if something would only be a land when entering the battlefield and not before // if something would only be a land when entering the battlefield and not before
@@ -352,6 +347,11 @@ public class GameAction {
} }
} }
// if an adventureCard is put from Stack somewhere else, need to reset to Original State
if (copied.isAdventureCard() && ((zoneFrom != null && zoneFrom.is(ZoneType.Stack)) || !zoneTo.is(ZoneType.Stack))) {
copied.setState(CardStateName.Original, false);
}
GameEntityCounterTable table = new GameEntityCounterTable(); GameEntityCounterTable table = new GameEntityCounterTable();
// need to suspend cards own replacement effects // need to suspend cards own replacement effects

View File

@@ -300,12 +300,12 @@ public final class GameActionUtil {
costs.add(new OptionalCostValue(type, cost)); costs.add(new OptionalCostValue(type, cost));
} }
} else if (keyword.equals("Retrace")) { } else if (keyword.equals("Retrace")) {
if (source.getZone().is(ZoneType.Graveyard)) { if (source.isInZone(ZoneType.Graveyard)) {
final Cost cost = new Cost("Discard<1/Land>", false); final Cost cost = new Cost("Discard<1/Land>", false);
costs.add(new OptionalCostValue(OptionalCost.Retrace, cost)); costs.add(new OptionalCostValue(OptionalCost.Retrace, cost));
} }
} else if (keyword.equals("Jump-start")) { } else if (keyword.equals("Jump-start")) {
if (source.getZone().is(ZoneType.Graveyard)) { if (source.isInZone(ZoneType.Graveyard)) {
final Cost cost = new Cost("Discard<1/Card>", false); final Cost cost = new Cost("Discard<1/Card>", false);
costs.add(new OptionalCostValue(OptionalCost.Jumpstart, cost)); costs.add(new OptionalCostValue(OptionalCost.Jumpstart, cost));
} }

View File

@@ -110,7 +110,7 @@ public class CloneEffect extends SpellAbilityEffect {
} }
if (sa.hasParam("CloneZone")) { if (sa.hasParam("CloneZone")) {
if (!tgtCard.getZone().is(ZoneType.smartValueOf(sa.getParam("CloneZone")))) { if (!tgtCard.isInZone(ZoneType.smartValueOf(sa.getParam("CloneZone")))) {
return; return;
} }
} }
@@ -134,7 +134,7 @@ public class CloneEffect extends SpellAbilityEffect {
tgtCard.clearImprintedCards(); tgtCard.clearImprintedCards();
// check if clone is now an Aura that needs to be attached // check if clone is now an Aura that needs to be attached
if (tgtCard.isAura() && !tgtCard.getZone().is(ZoneType.Battlefield)) { if (tgtCard.isAura() && !tgtCard.isInZone(ZoneType.Battlefield)) {
AttachEffect.attachAuraOnIndirectEnterBattlefield(tgtCard); AttachEffect.attachAuraOnIndirectEnterBattlefield(tgtCard);
} }

View File

@@ -120,7 +120,10 @@ public class PumpEffect extends SpellAbilityEffect {
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) { && !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
return; return;
} }
p.addChangedKeywords(keywords, ImmutableList.of(), timestamp);
if (!keywords.isEmpty()) {
p.addChangedKeywords(keywords, ImmutableList.of(), timestamp);
}
if (!sa.hasParam("Permanent")) { if (!sa.hasParam("Permanent")) {
// If not Permanent, remove Pumped at EOT // If not Permanent, remove Pumped at EOT
@@ -129,12 +132,7 @@ public class PumpEffect extends SpellAbilityEffect {
@Override @Override
public void run() { public void run() {
p.removeChangedKeywords(timestamp);
if (keywords.size() > 0) {
for (int i = 0; i < keywords.size(); i++) {
p.removeKeyword(keywords.get(i));
}
}
} }
}; };
addUntilCommand(sa, untilEOT); addUntilCommand(sa, untilEOT);

View File

@@ -3155,6 +3155,7 @@ public class Card extends GameEntity implements Comparable<Card> {
public final void addColor(final String s, final boolean addToColors, final long timestamp) { public final void addColor(final String s, final boolean addToColors, final long timestamp) {
changedCardColors.put(timestamp, new CardColor(s, addToColors, timestamp)); changedCardColors.put(timestamp, new CardColor(s, addToColors, timestamp));
currentState.getView().updateColors(this); currentState.getView().updateColors(this);
currentState.getView().updateHasChangeColors(!getChangedCardColors().isEmpty());
} }
public final void removeColor(final long timestampIn) { public final void removeColor(final long timestampIn) {
@@ -3162,6 +3163,7 @@ public class Card extends GameEntity implements Comparable<Card> {
if (removeCol != null) { if (removeCol != null) {
currentState.getView().updateColors(this); currentState.getView().updateColors(this);
currentState.getView().updateHasChangeColors(!getChangedCardColors().isEmpty());
} }
} }
@@ -5616,34 +5618,7 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final MutableBoolean result = new MutableBoolean(true);
visitKeywords(currentState, new Visitor<KeywordInterface>() {
@Override
public boolean visit(KeywordInterface kw) {
switch (kw.getOriginal()) {
case "Shroud":
StringBuilder sb = new StringBuilder();
sb.append("Can target CardUID_").append(getId());
sb.append(" with spells and abilities as though it didn't have shroud.");
if (sa.getActivatingPlayer() == null) {
System.err.println("Unexpected behavior: SA activator was null when trying to determine if the activating player could target a card with Shroud. SA host card = " + source + ", SA = " + sa);
result.setFalse(); // FIXME: maybe this should check by SA host card controller at this point instead?
} else if (!sa.getActivatingPlayer().hasKeyword(sb.toString())) {
result.setFalse();
}
break;
case "CARDNAME can't be the target of spells.":
if (sa.isSpell()) {
result.setFalse();
}
break;
}
return result.isTrue();
}
});
if (result.isFalse()) {
return false;
}
if (sa.isSpell()) { if (sa.isSpell()) {
for(KeywordInterface inst : source.getKeywords()) { for(KeywordInterface inst : source.getKeywords()) {
String kw = inst.getOriginal(); String kw = inst.getOriginal();

View File

@@ -4501,7 +4501,9 @@ public class CardFactoryUtil {
effect = "Mode$ CantTarget | Hexproof$ True | ValidCard$ Card.Self | Secondary$ True" effect = "Mode$ CantTarget | Hexproof$ True | ValidCard$ Card.Self | Secondary$ True"
+ sbValid.toString() + " | Activator$ Opponent | Description$ " + sbValid.toString() + " | Activator$ Opponent | Description$ "
+ sbDesc.toString() + " (" + inst.getReminderText() + ")"; + sbDesc.toString() + " (" + inst.getReminderText() + ")";
} else if (keyword.equals("Shroud")) {
effect = "Mode$ CantTarget | Shroud$ True | ValidCard$ Card.Self | Secondary$ True"
+ " | Description$ Shroud (" + inst.getReminderText() + ")";
} else if (keyword.startsWith("Strive")) { } else if (keyword.startsWith("Strive")) {
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
final String manacost = k[1]; final String manacost = k[1];
@@ -4693,19 +4695,30 @@ public class CardFactoryUtil {
} }
sa.setAdventure(true); sa.setAdventure(true);
StringBuilder sb = new StringBuilder();
sb.append("Event$ Moved | ValidCard$ Card.Self | Origin$ Stack | ExcludeDestination$ Exile ");
sb.append("| ValidStackSa$ Spell.Adventure | Fizzle$ False | Secondary$ True | Description$ Adventure");
String repeffstr = sb.toString();
String abExile = "DB$ ChangeZone | Defined$ Self | Origin$ Stack | Destination$ Exile | StackDescription$ None"; String abExile = "DB$ ChangeZone | Defined$ Self | Origin$ Stack | Destination$ Exile | StackDescription$ None";
AbilitySub saExile = (AbilitySub)AbilityFactory.getAbility(abExile, card); SpellAbility saExile = AbilityFactory.getAbility(abExile, card);
String abEffect = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell"; String abEffect = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell";
AbilitySub saEffect = (AbilitySub)AbilityFactory.getAbility(abEffect, card); AbilitySub saEffect = (AbilitySub)AbilityFactory.getAbility(abEffect, card);
StringBuilder sb = new StringBuilder(); StringBuilder sbPlay = new StringBuilder();
sb.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure"); sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure");
sb.append(" | AffectedZone$ Exile | Description$ You may cast the card."); sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");
saEffect.setSVar("Play", sb.toString()); saEffect.setSVar("Play", sbPlay.toString());
saExile.setSubAbility(saEffect); saExile.setSubAbility(saEffect);
sa.appendSubAbility(saExile);
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, true);
re.setLayer(ReplacementLayer.Other);
re.setOverridingAbility(saExile);
card.addReplacementEffect(re);
} }
} }

View File

@@ -523,7 +523,6 @@ public class CardView extends GameEntityView {
void updateChangedColorWords(Card c) { void updateChangedColorWords(Card c) {
set(TrackableProperty.ChangedColorWords, c.getChangedTextColorWords()); set(TrackableProperty.ChangedColorWords, c.getChangedTextColorWords());
} }
public Map<String, String> getChangedTypes() { public Map<String, String> getChangedTypes() {
return get(TrackableProperty.ChangedTypes); return get(TrackableProperty.ChangedTypes);
} }
@@ -706,11 +705,13 @@ public class CardView extends GameEntityView {
// update the color only while in Game // update the color only while in Game
if (c.getGame() != null) { if (c.getGame() != null) {
currentStateView.updateColors(currentState); currentStateView.updateColors(currentState);
currentStateView.updateHasChangeColors(!c.getChangedCardColors().isEmpty());
} }
} else { } else {
currentStateView.updateLoyalty(currentState); currentStateView.updateLoyalty(currentState);
} }
currentState.getView().updateKeywords(c, currentState); //update keywords even if state doesn't change currentState.getView().updateKeywords(c, currentState); //update keywords even if state doesn't change
currentState.getView().setOriginalColors(c); //set original Colors
CardState alternateState = isSplitCard && isFaceDown() ? c.getState(CardStateName.RightSplit) : c.getAlternateState(); CardState alternateState = isSplitCard && isFaceDown() ? c.getState(CardStateName.RightSplit) : c.getAlternateState();
@@ -840,13 +841,22 @@ public class CardView extends GameEntityView {
public ColorSet getColors() { public ColorSet getColors() {
return get(TrackableProperty.Colors); return get(TrackableProperty.Colors);
} }
public ColorSet getOriginalColors() {
return get(TrackableProperty.OriginalColors);
}
void updateColors(Card c) { void updateColors(Card c) {
set(TrackableProperty.Colors, c.determineColor()); set(TrackableProperty.Colors, c.determineColor());
} }
void updateColors(CardState c) { void updateColors(CardState c) {
set(TrackableProperty.Colors, ColorSet.fromMask(c.getColor())); set(TrackableProperty.Colors, ColorSet.fromMask(c.getColor()));
} }
void setOriginalColors(Card c) {
set(TrackableProperty.OriginalColors, c.determineColor());
}
void updateHasChangeColors(boolean hasChangeColor) {
set(TrackableProperty.HasChangedColors, hasChangeColor);
}
public boolean hasChangeColors() { return get(TrackableProperty.HasChangedColors); }
public String getImageKey() { public String getImageKey() {
return getImageKey(null); return getImageKey(null);
} }

View File

@@ -192,7 +192,7 @@ public class CostAdjustment {
} }
for (final StaticAbility stAb : reduceAbilities) { for (final StaticAbility stAb : reduceAbilities) {
sumGeneric += applyReduceCostAbility(stAb, sa, cost); sumGeneric += applyReduceCostAbility(stAb, sa, cost, sumGeneric);
} }
// need to reduce generic extra because of 2 hybrid mana // need to reduce generic extra because of 2 hybrid mana
cost.decreaseGenericMana(sumGeneric); cost.decreaseGenericMana(sumGeneric);
@@ -360,7 +360,7 @@ public class CostAdjustment {
* @param manaCost * @param manaCost
* a ManaCost * a ManaCost
*/ */
private static int applyReduceCostAbility(final StaticAbility staticAbility, final SpellAbility sa, final ManaCostBeingPaid manaCost) { private static int applyReduceCostAbility(final StaticAbility staticAbility, final SpellAbility sa, final ManaCostBeingPaid manaCost, int sumReduced) {
//Can't reduce zero cost //Can't reduce zero cost
if (manaCost.toString().equals("{0}")) { if (manaCost.toString().equals("{0}")) {
return 0; return 0;
@@ -393,7 +393,7 @@ public class CostAdjustment {
minMana = Integer.valueOf(staticAbility.getParam("MinMana")); minMana = Integer.valueOf(staticAbility.getParam("MinMana"));
} }
final int maxReduction = Math.max(0, manaCost.getConvertedManaCost() - minMana); final int maxReduction = Math.max(0, manaCost.getConvertedManaCost() - minMana - sumReduced);
if (maxReduction > 0) { if (maxReduction > 0) {
return Math.min(value, maxReduction); return Math.min(value, maxReduction);
} }

View File

@@ -9,6 +9,8 @@ import com.google.common.collect.Lists;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardFactoryUtil; import forge.game.card.CardFactoryUtil;
import forge.game.player.Player;
import forge.game.player.PlayerFactoryUtil;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
@@ -82,7 +84,7 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
public final void createTraits(final Card host, final boolean intrinsic) { public final void createTraits(final Card host, final boolean intrinsic) {
createTraits(host, intrinsic, false); createTraits(host, intrinsic, false);
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see forge.game.keyword.KeywordInterface#createTraits(forge.game.card.Card, boolean, boolean) * @see forge.game.keyword.KeywordInterface#createTraits(forge.game.card.Card, boolean, boolean)
@@ -125,6 +127,53 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
} }
} }
/* (non-Javadoc)
* @see forge.game.keyword.KeywordInterface#createTraits(forge.game.player.Player)
*/
@Override
public void createTraits(Player player) {
createTraits(player, false);
}
/* (non-Javadoc)
* @see forge.game.keyword.KeywordInterface#createTraits(forge.game.player.Player, boolean)
*/
@Override
public void createTraits(Player player, boolean clear) {
if (clear) {
triggers.clear();
replacements.clear();
abilities.clear();
staticAbilities.clear();
}
try {
String msg = "KeywordInstance:createTraits: make Traits for Keyword";
Sentry.getContext().recordBreadcrumb(
new BreadcrumbBuilder().setMessage(msg)
.withData("Player", player.getName()).withData("Keyword", this.original).build()
);
// add Extra for debugging
Sentry.getContext().addExtra("Player", player);
Sentry.getContext().addExtra("Keyword", this.original);
PlayerFactoryUtil.addTriggerAbility(this, player);
PlayerFactoryUtil.addReplacementEffect(this, player);
PlayerFactoryUtil.addSpellAbility(this, player);
PlayerFactoryUtil.addStaticAbility(this, player);
} catch (Exception e) {
String msg = "KeywordInstance:createTraits: failed Traits for Keyword";
Sentry.getContext().recordBreadcrumb(
new BreadcrumbBuilder().setMessage(msg)
.withData("Player", player.getName()).withData("Keyword", this.original).build()
);
//rethrow
throw new RuntimeException("Error in Keyword " + this.original + " for player " + player.getName(), e);
} finally {
// remove added extra
Sentry.getContext().removeExtra("Player");
Sentry.getContext().removeExtra("Keyword");
}
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see forge.game.keyword.KeywordInterface#addTrigger(forge.game.trigger.Trigger) * @see forge.game.keyword.KeywordInterface#addTrigger(forge.game.trigger.Trigger)

View File

@@ -3,6 +3,7 @@ package forge.game.keyword;
import java.util.Collection; import java.util.Collection;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
@@ -24,6 +25,9 @@ public interface KeywordInterface extends Cloneable {
void createTraits(final Card host, final boolean intrinsic); void createTraits(final Card host, final boolean intrinsic);
void createTraits(final Card host, final boolean intrinsic, final boolean clear); void createTraits(final Card host, final boolean intrinsic, final boolean clear);
void createTraits(final Player player);
void createTraits(final Player player, final boolean clear);
void addTrigger(final Trigger trg); void addTrigger(final Trigger trg);
void addReplacement(final ReplacementEffect trg); void addReplacement(final ReplacementEffect trg);

View File

@@ -23,6 +23,11 @@ import java.util.List;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
/** /**
* <p> * <p>
@@ -131,7 +136,13 @@ public class KeywordsChange implements Cloneable {
inst.createTraits(host, false, true); inst.createTraits(host, false, true);
} }
} }
public final void addKeywordsToPlayer(final Player player) {
for (KeywordInterface inst : keywords.getValues()) {
inst.createTraits(player, true);
}
}
public final boolean removeKeywordfromAdd(final String keyword) { public final boolean removeKeywordfromAdd(final String keyword) {
return keywords.remove(keyword); return keywords.remove(keyword);
} }
@@ -201,6 +212,47 @@ public class KeywordsChange implements Cloneable {
} }
} }
/**
* @return the triggers
*/
public Collection<Trigger> getTriggers() {
List<Trigger> result = Lists.newArrayList();
for (KeywordInterface k : this.keywords.getValues()) {
result.addAll(k.getTriggers());
}
return result;
}
/**
* @return the replacements
*/
public Collection<ReplacementEffect> getReplacements() {
List<ReplacementEffect> result = Lists.newArrayList();
for (KeywordInterface k : this.keywords.getValues()) {
result.addAll(k.getReplacements());
}
return result;
}
/**
* @return the abilities
*/
public Collection<SpellAbility> getAbilities() {
List<SpellAbility> result = Lists.newArrayList();
for (KeywordInterface k : this.keywords.getValues()) {
result.addAll(k.getAbilities());
}
return result;
}
/**
* @return the staticAbilities
*/
public Collection<StaticAbility> getStaticAbilities() {
List<StaticAbility> result = Lists.newArrayList();
for (KeywordInterface k : this.keywords.getValues()) {
result.addAll(k.getStaticAbilities());
}
return result;
}
/* (non-Javadoc) /* (non-Javadoc)
* @see java.lang.Object#toString() * @see java.lang.Object#toString()
*/ */

View File

@@ -20,6 +20,8 @@ package forge.game.player;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.*; import com.google.common.collect.*;
import forge.ImageKeys;
import forge.LobbyPlayer; import forge.LobbyPlayer;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.*; import forge.game.*;
@@ -41,7 +43,6 @@ import forge.game.phase.PhaseType;
import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType; import forge.game.replacement.ReplacementType;
import forge.game.spellability.AbilityActivated;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
@@ -155,6 +156,7 @@ public class Player extends GameEntity implements Comparable<Player> {
private Card monarchEffect = null; private Card monarchEffect = null;
private Card blessingEffect = null; private Card blessingEffect = null;
private Card keywordEffect = null;
private final AchievementTracker achievementTracker = new AchievementTracker(); private final AchievementTracker achievementTracker = new AchievementTracker();
private final PlayerView view; private final PlayerView view;
@@ -1025,23 +1027,25 @@ public class Player extends GameEntity implements Comparable<Player> {
public final void addChangedKeywords(final List<String> addKeywords, final List<String> removeKeywords, final Long timestamp) { public final void addChangedKeywords(final List<String> addKeywords, final List<String> removeKeywords, final Long timestamp) {
// if the key already exists - merge entries // if the key already exists - merge entries
KeywordsChange cks = null;
if (changedKeywords.containsKey(timestamp)) { if (changedKeywords.containsKey(timestamp)) {
final KeywordsChange cks = changedKeywords.get(timestamp); getKeywordCard().removeChangedCardTraits(timestamp);
changedKeywords.put(timestamp, cks.merge(addKeywords, removeKeywords, cks = changedKeywords.get(timestamp).merge(addKeywords, removeKeywords, false, false);
cks.isRemoveAllKeywords(), cks.isRemoveIntrinsicKeywords())); } else {
updateKeywords(); cks = new KeywordsChange(addKeywords, removeKeywords, false, false);
return;
} }
cks.addKeywordsToPlayer(this);
changedKeywords.put(timestamp, new KeywordsChange(addKeywords, removeKeywords, false, false)); getKeywordCard().addChangedCardTraits(cks.getAbilities(), null, cks.getTriggers(), cks.getReplacements(), cks.getStaticAbilities(), false, false, false, timestamp);
changedKeywords.put(timestamp, cks);
updateKeywords(); updateKeywords();
game.fireEvent(new GameEventPlayerStatsChanged(this)); game.fireEvent(new GameEventPlayerStatsChanged(this));
} }
public final KeywordsChange removeChangedKeywords(final Long timestamp) { public final KeywordsChange removeChangedKeywords(final Long timestamp) {
KeywordsChange change = changedKeywords.remove(Long.valueOf(timestamp)); KeywordsChange change = changedKeywords.remove(timestamp);
if (change != null) { if (change != null) {
getKeywordCard().removeChangedCardTraits(timestamp);
updateKeywords(); updateKeywords();
game.fireEvent(new GameEventPlayerStatsChanged(this)); game.fireEvent(new GameEventPlayerStatsChanged(this));
} }
@@ -1100,6 +1104,7 @@ public class Player extends GameEntity implements Comparable<Player> {
for (final Entry<Long, KeywordsChange> ck : ImmutableList.copyOf(changedKeywords.entrySet())) { for (final Entry<Long, KeywordsChange> ck : ImmutableList.copyOf(changedKeywords.entrySet())) {
if (ck.getValue().isEmpty() && changedKeywords.remove(ck.getKey()) != null) { if (ck.getValue().isEmpty() && changedKeywords.remove(ck.getKey()) != null) {
keywordRemoved = true; keywordRemoved = true;
getKeywordCard().removeChangedCardTraits(ck.getKey());
} }
} }
@@ -1178,33 +1183,17 @@ public class Player extends GameEntity implements Comparable<Player> {
@Override @Override
public final boolean canBeTargetedBy(final SpellAbility sa) { public final boolean canBeTargetedBy(final SpellAbility sa) {
if (hasKeyword("Shroud")) {
return false; // CantTarget static abilities
} for (final Card ca : getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
if (hasKeyword("Hexproof")) { for (final StaticAbility stAb : ca.getStaticAbilities()) {
final Player a = sa.getActivatingPlayer(); if (stAb.applyAbility("CantTarget", this, sa)) {
if (isOpponentOf(a)) {
boolean cancelHexproof = false;
for (String k : a.getKeywords()) {
if (k.startsWith("IgnoreHexproof")) {
String[] m = k.split(":");
if (isValid(m[1].split(","), a, sa.getHostCard(), sa)) {
cancelHexproof = true;
break;
}
}
}
if (!cancelHexproof) {
return false; return false;
} }
} }
} }
if (hasProtectionFrom(sa.getHostCard())) { return !hasProtectionFrom(sa.getHostCard());
return false;
}
return (!hasKeyword("You can't be the targets of spells or activated abilities") || (!sa.isSpell() && (!(sa instanceof AbilityActivated))));
} }
@@ -2971,4 +2960,26 @@ public class Player extends GameEntity implements Comparable<Player> {
|| !hasKeyword("Spells and abilities you control can't cause you to search your library."); || !hasKeyword("Spells and abilities you control can't cause you to search your library.");
} }
public Card getKeywordCard() {
if (keywordEffect != null) {
return keywordEffect;
}
final PlayerZone com = getZone(ZoneType.Command);
keywordEffect = new Card(game.nextCardId(), null, false, game);
keywordEffect.setImmutable(true);
keywordEffect.setOwner(this);
keywordEffect.setName("Keyword Effects");
keywordEffect.setImageKey(ImageKeys.HIDDEN_CARD);
keywordEffect.addType("Effect");
keywordEffect.updateStateForView();
com.add(keywordEffect);
this.updateZoneForView(com);
return keywordEffect;
}
} }

View File

@@ -0,0 +1,46 @@
package forge.game.player;
import forge.game.card.Card;
import forge.game.keyword.KeywordInterface;
import forge.game.staticability.StaticAbility;
public class PlayerFactoryUtil {
public static void addStaticAbility(final KeywordInterface inst, final Player player) {
final Card card = player.getKeywordCard();
String keyword = inst.getOriginal();
String effect = null;
if (keyword.startsWith("Hexproof")) {
final StringBuilder sbDesc = new StringBuilder("Hexproof");
final StringBuilder sbValid = new StringBuilder();
if (!keyword.equals("Hexproof")) {
final String[] k = keyword.split(":");
sbDesc.append(" from ").append(k[2]);
sbValid.append("| ValidSource$ ").append(k[1]);
}
effect = "Mode$ CantTarget | Hexproof$ True | ValidPlayer$ Player.You | Secondary$ True "
+ sbValid.toString() + " | Activator$ Opponent | EffectZone$ Command | Description$ "
+ sbDesc.toString() + " (" + inst.getReminderText() + ")";
} else if (keyword.equals("Shroud")) {
effect = "Mode$ CantTarget | Shroud$ True | ValidPlayer$ Player.You | Secondary$ True "
+ "| EffectZone$ Command | Description$ Shroud (" + inst.getReminderText() + ")";
}
if (effect != null) {
StaticAbility st = new StaticAbility(effect, card);
st.setIntrinsic(false);
inst.addStaticAbility(st);
}
}
public static void addTriggerAbility(final KeywordInterface inst, Player player) {
}
public static void addReplacementEffect(final KeywordInterface inst, Player player) {
}
public static void addSpellAbility(final KeywordInterface inst, Player player) {
}
}

View File

@@ -391,6 +391,23 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
return false; return false;
} }
public final boolean applyAbility(final String mode, final Player player, final SpellAbility spellAbility) {
// don't apply the ability if it hasn't got the right mode
if (!getParam("Mode").equals(mode)) {
return false;
}
if (this.isSuppressed() || !this.checkConditions()) {
return false;
}
if (mode.equals("CantTarget")) {
return StaticAbilityCantTarget.applyCantTargetAbility(this, player, spellAbility);
}
return false;
}
public final boolean applyAbility(String mode, Card card, CounterType type) { public final boolean applyAbility(String mode, Card card, CounterType type) {
// don't apply the ability if it hasn't got the right mode // don't apply the ability if it hasn't got the right mode

View File

@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
@@ -22,8 +22,6 @@ import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import java.util.Map;
/** /**
* The Class StaticAbilityCantTarget. * The Class StaticAbilityCantTarget.
*/ */
@@ -31,8 +29,8 @@ public class StaticAbilityCantTarget {
/** /**
* Apply can't target ability. * Apply can't target ability.
* *
* @param staticAbility * @param st
* the static ability * the static ability
* @param card * @param card
* the card * the card
@@ -40,16 +38,19 @@ public class StaticAbilityCantTarget {
* the spell/ability * the spell/ability
* @return true, if successful * @return true, if successful
*/ */
public static boolean applyCantTargetAbility(final StaticAbility staticAbility, final Card card, public static boolean applyCantTargetAbility(final StaticAbility st, final Card card,
final SpellAbility spellAbility) { final SpellAbility spellAbility) {
final Map<String, String> params = staticAbility.getMapParams(); final Card hostCard = st.getHostCard();
final Card hostCard = staticAbility.getHostCard();
final Card source = spellAbility.getHostCard(); final Card source = spellAbility.getHostCard();
final Player activator = spellAbility.getActivatingPlayer(); final Player activator = spellAbility.getActivatingPlayer();
if (params.containsKey("AffectedZone")) { if (st.hasParam("ValidPlayer")) {
return false;
}
if (st.hasParam("AffectedZone")) {
boolean inZone = false; boolean inZone = false;
for (final ZoneType zt : ZoneType.listValueOf(params.get("AffectedZone"))) { for (final ZoneType zt : ZoneType.listValueOf(st.getParam("AffectedZone"))) {
if (card.isInZone(zt)) { if (card.isInZone(zt)) {
inZone = true; inZone = true;
break; break;
@@ -65,38 +66,14 @@ public class StaticAbilityCantTarget {
} }
} }
if (params.containsKey("ValidSA")
&& !spellAbility.isValid(params.get("ValidSA").split(","), hostCard.getController(), hostCard, spellAbility)) { if (st.hasParam("ValidCard")
&& !card.isValid(st.getParam("ValidCard").split(","), hostCard.getController(), hostCard, null)) {
return false; return false;
} }
if (params.containsKey("ValidCard")
&& !card.isValid(params.get("ValidCard").split(","), hostCard.getController(), hostCard, null)) {
return false;
}
if (params.containsKey("ValidSource") if (st.hasParam("Hexproof") && (activator != null)) {
&& !source.isValid(params.get("ValidSource").split(","), hostCard.getController(), hostCard, null)) {
return false;
}
if (params.containsKey("Activator") && (activator != null)
&& !activator.isValid(params.get("Activator"), hostCard.getController(), hostCard, spellAbility)) {
return false;
}
if (spellAbility.getParam("ValidTgts")!=null &&
(params.containsKey("SourceCanOnlyTarget")
&& (!spellAbility.getParam("ValidTgts").contains(params.get("SourceCanOnlyTarget"))
|| spellAbility.getParam("ValidTgts").contains(","))
|| spellAbility.getParam("ValidTgts").contains("non" + params.get("SourceCanOnlyTarget")
)
)
){
return false;
}
if (params.containsKey("Hexproof") && (activator != null)) {
for (String k : activator.getKeywords()) { for (String k : activator.getKeywords()) {
if (k.startsWith("IgnoreHexproof")) { if (k.startsWith("IgnoreHexproof")) {
String[] m = k.split(":"); String[] m = k.split(":");
@@ -106,8 +83,81 @@ public class StaticAbilityCantTarget {
} }
} }
} }
if (st.hasParam("Shroud") && (activator != null)) {
for (String k : activator.getKeywords()) {
if (k.startsWith("IgnoreShroud")) {
String[] m = k.split(":");
if (card.isValid(m[1].split(","), activator, source, spellAbility)) {
return false;
}
}
}
}
return common(st, spellAbility);
}
public static boolean applyCantTargetAbility(final StaticAbility st, final Player player,
final SpellAbility spellAbility) {
final Card hostCard = st.getHostCard();
final Card source = spellAbility.getHostCard();
final Player activator = spellAbility.getActivatingPlayer();
if (st.hasParam("ValidCard") || st.hasParam("AffectedZone")) {
return false;
}
if (st.hasParam("ValidPlayer")
&& !player.isValid(st.getParam("ValidPlayer").split(","), hostCard.getController(), hostCard, null)) {
return false;
}
if (st.hasParam("Hexproof") && (activator != null)) {
for (String k : activator.getKeywords()) {
if (k.startsWith("IgnoreHexproof")) {
String[] m = k.split(":");
if (player.isValid(m[1].split(","), activator, source, spellAbility)) {
return false;
}
}
}
}
return true; return true;
} }
protected static boolean common(final StaticAbility st, final SpellAbility spellAbility) {
final Card hostCard = st.getHostCard();
final Card source = spellAbility.getHostCard();
final Player activator = spellAbility.getActivatingPlayer();
if (st.hasParam("ValidSA")
&& !spellAbility.isValid(st.getParam("ValidSA").split(","), hostCard.getController(), hostCard, spellAbility)) {
return false;
}
if (st.hasParam("ValidSource")
&& !source.isValid(st.getParam("ValidSource").split(","), hostCard.getController(), hostCard, null)) {
return false;
}
if (st.hasParam("Activator") && (activator != null)
&& !activator.isValid(st.getParam("Activator"), hostCard.getController(), hostCard, spellAbility)) {
return false;
}
if (spellAbility.hasParam("ValidTgts") &&
(st.hasParam("SourceCanOnlyTarget")
&& (!spellAbility.getParam("ValidTgts").contains(st.getParam("SourceCanOnlyTarget"))
|| spellAbility.getParam("ValidTgts").contains(","))
|| spellAbility.getParam("ValidTgts").contains("non" + st.getParam("SourceCanOnlyTarget")
)
)
){
return false;
}
return true;
}
} }

View File

@@ -69,6 +69,7 @@ public enum TrackableProperty {
//Card State //Card State
Name(TrackableTypes.StringType), Name(TrackableTypes.StringType),
Colors(TrackableTypes.ColorSetType), Colors(TrackableTypes.ColorSetType),
OriginalColors(TrackableTypes.ColorSetType),
ImageKey(TrackableTypes.StringType), ImageKey(TrackableTypes.StringType),
Type(TrackableTypes.CardTypeViewType), Type(TrackableTypes.CardTypeViewType),
ManaCost(TrackableTypes.ManaCostType), ManaCost(TrackableTypes.ManaCostType),
@@ -80,6 +81,7 @@ public enum TrackableProperty {
Toughness(TrackableTypes.IntegerType), Toughness(TrackableTypes.IntegerType),
Loyalty(TrackableTypes.StringType), Loyalty(TrackableTypes.StringType),
ChangedColorWords(TrackableTypes.StringMapType), ChangedColorWords(TrackableTypes.StringMapType),
HasChangedColors(TrackableTypes.BooleanType),
ChangedTypes(TrackableTypes.StringMapType), ChangedTypes(TrackableTypes.StringMapType),
KeywordKey(TrackableTypes.StringType), KeywordKey(TrackableTypes.StringType),

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1850,7 +1850,7 @@ public class GameSimulatorTest extends SimulationTestCase {
Card simSpark = (Card)sim.getGameCopier().find(sparkDouble); Card simSpark = (Card)sim.getGameCopier().find(sparkDouble);
assertNotNull(simSpark); assertNotNull(simSpark);
assertTrue(simSpark.getZone().is(ZoneType.Battlefield)); assertTrue(simSpark.isInZone(ZoneType.Battlefield));
assertEquals(1, simSpark.getCounters(CounterType.P1P1)); assertEquals(1, simSpark.getCounters(CounterType.P1P1));
assertEquals(5, simSpark.getCounters(CounterType.LOYALTY)); assertEquals(5, simSpark.getCounters(CounterType.LOYALTY));
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -63,6 +63,9 @@ public class Forge implements ApplicationListener {
public static float heigtModifier = 0.0f; public static float heigtModifier = 0.0f;
private static boolean isloadingaMatch = false; private static boolean isloadingaMatch = false;
public static boolean showFPS = false; public static boolean showFPS = false;
public static boolean enableUIMask = false;
public static boolean enablePreloadExtendedArt = false;
public static String locale = "en-US";
public static ApplicationListener getApp(Clipboard clipboard0, IDeviceAdapter deviceAdapter0, String assetDir0) { public static ApplicationListener getApp(Clipboard clipboard0, IDeviceAdapter deviceAdapter0, String assetDir0) {
if (GuiBase.getInterface() == null) { if (GuiBase.getInterface() == null) {
@@ -105,8 +108,10 @@ public class Forge implements ApplicationListener {
FSkin.loadLight(skinName, splashScreen); FSkin.loadLight(skinName, splashScreen);
textureFiltering = prefs.getPrefBoolean(FPref.UI_LIBGDX_TEXTURE_FILTERING); textureFiltering = prefs.getPrefBoolean(FPref.UI_LIBGDX_TEXTURE_FILTERING);
showFPS = prefs.getPrefBoolean(FPref.UI_SHOW_FPS); showFPS = prefs.getPrefBoolean(FPref.UI_SHOW_FPS);
enableUIMask = prefs.getPrefBoolean(FPref.UI_ENABLE_BORDER_MASKING);
enablePreloadExtendedArt = prefs.getPrefBoolean(FPref.UI_ENABLE_PRELOAD_EXTENDED_ART);
locale = prefs.getPref(FPref.UI_LANGUAGE);
final Localizer localizer = Localizer.getInstance(); final Localizer localizer = Localizer.getInstance();
@@ -121,15 +126,15 @@ public class Forge implements ApplicationListener {
FModel.initialize(splashScreen.getProgressBar(), null); FModel.initialize(splashScreen.getProgressBar(), null);
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblLoadingFonts")); splashScreen.getProgressBar().setDescription(localizer.getMessage("lblLoadingFonts"));
FSkinFont.preloadAll(prefs.getPref(FPref.UI_LANGUAGE)); FSkinFont.preloadAll(locale);
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblLoadingCardTranslations")); splashScreen.getProgressBar().setDescription(localizer.getMessage("lblLoadingCardTranslations"));
CardTranslation.preloadTranslation(prefs.getPref(FPref.UI_LANGUAGE)); CardTranslation.preloadTranslation(locale);
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblFinishingStartup")); splashScreen.getProgressBar().setDescription(localizer.getMessage("lblFinishingStartup"));
//add reminder to preload //add reminder to preload
if (prefs.getPrefBoolean(FPref.UI_ENABLE_PRELOAD_EXTENDED_ART)) if (enablePreloadExtendedArt)
splashScreen.getProgressBar().setDescription("Preload Extended Art..."); splashScreen.getProgressBar().setDescription("Preload Extended Art...");
Gdx.app.postRunnable(new Runnable() { Gdx.app.postRunnable(new Runnable() {
@Override @Override
@@ -145,7 +150,7 @@ public class Forge implements ApplicationListener {
} }
private void preloadExtendedArt() { private void preloadExtendedArt() {
if (!FModel.getPreferences().getPrefBoolean(FPref.UI_ENABLE_PRELOAD_EXTENDED_ART)) if (!enablePreloadExtendedArt)
return; return;
List<String> keys = new ArrayList<>(); List<String> keys = new ArrayList<>();
File[] directories = new File(ForgeConstants.CACHE_CARD_PICS_DIR).listFiles(new FileFilter() { File[] directories = new File(ForgeConstants.CACHE_CARD_PICS_DIR).listFiles(new FileFilter() {

View File

@@ -563,6 +563,18 @@ public class Graphics {
} }
public float getfloatAlphaComposite() { return alphaComposite; } public float getfloatAlphaComposite() { return alphaComposite; }
public void drawBorderImage(FImage image, Color color, float x, float y, float w, float h, boolean tint) {
image.draw(this, x, y, w, h);
if(tint){
float oldalpha = alphaComposite;
setAlphaComposite(0.8f);
drawRoundRect(2f, Color.WHITE, x, y, w, h, (h-w)/12);
setAlphaComposite(1f);
fillRoundRect(color, x, y, w, h, (h-w)/12);
setAlphaComposite(oldalpha);
}
}
public void drawImage(FImage image, float x, float y, float w, float h) { public void drawImage(FImage image, float x, float y, float w, float h) {
drawImage(image, x, y, w, h, false); drawImage(image, x, y, w, h, false);
} }

View File

@@ -16,6 +16,7 @@ import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFont
import com.badlogic.gdx.graphics.glutils.PixmapTextureData; import com.badlogic.gdx.graphics.glutils.PixmapTextureData;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import forge.FThreads; import forge.FThreads;
import forge.Forge;
import forge.properties.ForgeConstants; import forge.properties.ForgeConstants;
import forge.util.FileUtil; import forge.util.FileUtil;
import forge.util.TextBounds; import forge.util.TextBounds;
@@ -26,8 +27,10 @@ import java.util.Map;
public class FSkinFont { public class FSkinFont {
private static final int MIN_FONT_SIZE = 8; private static final int MIN_FONT_SIZE = 8;
private static final int MAX_FONT_SIZE = 72; private static int MAX_FONT_SIZE = 72;
private static final int MAX_FONT_SIZE_ZH_CN = 28;
private static final int MAX_FONT_SIZE_LESS_GLYPHS = 72;
private static final int MAX_FONT_SIZE_MANY_GLYPHS = 36;
private static final String TTF_FILE = "font1.ttf"; private static final String TTF_FILE = "font1.ttf";
private static final Map<Integer, FSkinFont> fonts = new HashMap<>(); private static final Map<Integer, FSkinFont> fonts = new HashMap<>();
@@ -60,8 +63,9 @@ public class FSkinFont {
//pre-load all supported font sizes //pre-load all supported font sizes
public static void preloadAll(String language) { public static void preloadAll(String language) {
int maxfontSize = (language.equals("zh-CN")) ? MAX_FONT_SIZE_ZH_CN : MAX_FONT_SIZE; //todo:really check the language glyph is a lot
for (int size = MIN_FONT_SIZE; size <= maxfontSize; size++) { MAX_FONT_SIZE = (language.equals("zh-CN")) ? MAX_FONT_SIZE_MANY_GLYPHS : MAX_FONT_SIZE_LESS_GLYPHS;
for (int size = MIN_FONT_SIZE; size <= MAX_FONT_SIZE; size++) {
_get(size); _get(size);
} }
} }
@@ -357,7 +361,8 @@ public class FSkinFont {
//generate from zh-CN.properties,and cardnames-zh-CN.txt //generate from zh-CN.properties,and cardnames-zh-CN.txt
//forge generate 3000+ characters cache need Take some time(MIN_FONT_SIZE - MAX_FONT_SIZE all size) //forge generate 3000+ characters cache need Take some time(MIN_FONT_SIZE - MAX_FONT_SIZE all size)
//maybe using libgdx-hiero generate font cache is better //maybe using libgdx-hiero generate font cache is better
chars += "●、。「」『』一丁七万三上下不与丑专且世丘业丛东丝两严丧个中" if (Forge.locale.equals("zh-CN"))
chars += "●、。「」『』一丁七万三上下不与丑专且世丘业丛东丝两严丧个中"
+ "丰临丸丹为主丽举乃久么义之乌乍乐乔乖乘乙九也乡书乱乳乾了予争" + "丰临丸丹为主丽举乃久么义之乌乍乐乔乖乘乙九也乡书乱乳乾了予争"
+ "事二于云互五井亘亚些亡交亥亦产享京亮亲亵人亿什仁仅仆仇今介仍" + "事二于云互五井亘亚些亡交亥亦产享京亮亲亵人亿什仁仅仆仇今介仍"
+ "从仑仓仕他仗付仙代令以仪们仰仲件价任份仿伊伍伏伐休众优伙会伟" + "从仑仓仕他仗付仙代令以仪们仰仲件价任份仿伊伍伏伐休众优伙会伟"

View File

@@ -231,7 +231,9 @@ public class ImageCache {
return true; return true;
return false; return false;
} }
public static FImage getBorderImage(CardView c) { public static FImage getBorderImage(CardView c, boolean canshow) {
if (!canshow)
return BlackBorder;
if (isWhiteBordered(c)) if (isWhiteBordered(c))
return WhiteBorder; return WhiteBorder;
return BlackBorder; return BlackBorder;
@@ -241,4 +243,29 @@ public class ImageCache {
return WhiteBorder; return WhiteBorder;
return BlackBorder; return BlackBorder;
} }
public static Color getTint(CardView c) {
if (c == null)
return Color.CLEAR;
if (c.isFaceDown())
return Color.CLEAR;
CardView.CardStateView state = c.getCurrentState();
if (state.getColors().isColorless()) //Moonlace -> target spell or permanent becomes colorless.
return Color.valueOf("#A0A6A4");
else if (state.getColors().isMonoColor()) {
if (state.getColors().hasBlack())
return Color.valueOf("#48494a");
else if (state.getColors().hasBlue())
return Color.valueOf("#62b5f8");
else if (state.getColors().hasRed())
return Color.valueOf("#f6532d");
else if (state.getColors().hasGreen())
return Color.valueOf("#66cb35");
else if (state.getColors().hasWhite())
return Color.valueOf("#EEEBE1");
}
else if (state.getColors().isMulticolor())
return Color.valueOf("#F9E084");
return Color.CLEAR;
}
} }

View File

@@ -8,12 +8,14 @@ import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.TextureData; import com.badlogic.gdx.graphics.TextureData;
import com.badlogic.gdx.graphics.glutils.PixmapTextureData; import com.badlogic.gdx.graphics.glutils.PixmapTextureData;
import forge.FThreads;
import org.cache2k.integration.CacheLoader; import org.cache2k.integration.CacheLoader;
import forge.Forge; import forge.Forge;
import forge.ImageKeys; import forge.ImageKeys;
final class ImageLoader extends CacheLoader<String, Texture> { final class ImageLoader extends CacheLoader<String, Texture> {
Texture n;
@Override @Override
public Texture load(String key) { public Texture load(String key) {
boolean extendedArt = false; boolean extendedArt = false;
@@ -44,23 +46,28 @@ final class ImageLoader extends CacheLoader<String, Texture> {
public Texture generateTexture(FileHandle fh, Texture t, boolean textureFilter) { public Texture generateTexture(FileHandle fh, Texture t, boolean textureFilter) {
if (t == null || fh == null) if (t == null || fh == null)
return t; return t;
Pixmap pImage = new Pixmap(fh); FThreads.invokeInEdtNowOrLater(new Runnable() {
int w = pImage.getWidth(); @Override
int h = pImage.getHeight(); public void run() {
int radius = (h - w) / 8; Pixmap pImage = new Pixmap(fh);
Pixmap pMask = createRoundedRectangle(w, h, radius, Color.RED); int w = pImage.getWidth();
drawPixelstoMask(pImage, pMask); int h = pImage.getHeight();
TextureData textureData = new PixmapTextureData( int radius = (h - w) / 8;
pMask, //pixmap to use Pixmap pMask = createRoundedRectangle(w, h, radius, Color.RED);
Pixmap.Format.RGBA8888, drawPixelstoMask(pImage, pMask);
textureFilter, //use mipmaps TextureData textureData = new PixmapTextureData(
false, true); pMask, //pixmap to use
t = new Texture(textureData); Pixmap.Format.RGBA8888,
if (textureFilter) textureFilter, //use mipmaps
t.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear); false, true);
pImage.dispose(); n = new Texture(textureData);
pMask.dispose(); if (textureFilter)
return t; n.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
pImage.dispose();
pMask.dispose();
}
});
return n;
} }
public Pixmap createRoundedRectangle(int width, int height, int cornerRadius, Color color) { public Pixmap createRoundedRectangle(int width, int height, int cornerRadius, Color color) {
Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888); Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888);

View File

@@ -2,14 +2,13 @@ package forge.card;
import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture;
import forge.Forge;
import forge.Graphics; import forge.Graphics;
import forge.assets.FImage; import forge.assets.FImage;
import forge.assets.ImageCache; import forge.assets.ImageCache;
import forge.card.CardRenderer.CardStackPosition; import forge.card.CardRenderer.CardStackPosition;
import forge.game.card.CardView; import forge.game.card.CardView;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.model.FModel;
import forge.properties.ForgePreferences;
import forge.toolbox.FCardPanel; import forge.toolbox.FCardPanel;
public class CardImage implements FImage { public class CardImage implements FImage {
@@ -19,9 +18,6 @@ public class CardImage implements FImage {
public CardImage(PaperCard card0) { public CardImage(PaperCard card0) {
card = card0; card = card0;
} }
private static boolean isPreferenceEnabled(ForgePreferences.FPref preferenceName) {
return FModel.getPreferences().getPrefBoolean(preferenceName);
}
@Override @Override
public float getWidth() { public float getWidth() {
@@ -38,11 +34,10 @@ public class CardImage implements FImage {
@Override @Override
public void draw(Graphics g, float x, float y, float w, float h) { public void draw(Graphics g, float x, float y, float w, float h) {
boolean mask = isPreferenceEnabled(ForgePreferences.FPref.UI_ENABLE_BORDER_MASKING);
if (image == null) { //attempt to retrieve card image if needed if (image == null) { //attempt to retrieve card image if needed
image = ImageCache.getImage(card); image = ImageCache.getImage(card);
if (image == null) { if (image == null) {
if (mask) //render this if mask is still loading if (Forge.enableUIMask) //render this if mask is still loading
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(card), false, x, y, w, h, CardStackPosition.Top); CardImageRenderer.drawCardImage(g, CardView.getCardForUi(card), false, x, y, w, h, CardStackPosition.Top);
return; //can't draw anything if can't be loaded yet return; //can't draw anything if can't be loaded yet
@@ -53,7 +48,7 @@ public class CardImage implements FImage {
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(card), false, x, y, w, h, CardStackPosition.Top); CardImageRenderer.drawCardImage(g, CardView.getCardForUi(card), false, x, y, w, h, CardStackPosition.Top);
} }
else { else {
if (mask) { if (Forge.enableUIMask) {
if (ImageCache.isExtendedArt(card)) if (ImageCache.isExtendedArt(card))
g.drawImage(image, x, y, w, h); g.drawImage(image, x, y, w, h);
else { else {

View File

@@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Align;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import forge.Forge;
import forge.Graphics; import forge.Graphics;
import forge.assets.FBufferedImage; import forge.assets.FBufferedImage;
import forge.assets.FSkinColor; import forge.assets.FSkinColor;
@@ -325,8 +326,6 @@ public class CardImageRenderer {
} }
public static void drawZoom(Graphics g, CardView card, GameView gameView, boolean altState, float x, float y, float w, float h, float dispW, float dispH, boolean isCurrentCard) { public static void drawZoom(Graphics g, CardView card, GameView gameView, boolean altState, float x, float y, float w, float h, float dispW, float dispH, boolean isCurrentCard) {
boolean mask = isPreferenceEnabled(ForgePreferences.FPref.UI_ENABLE_BORDER_MASKING);
//this one is currently using the mask, others are cropped and use generated borders from shaperenderer ...
final Texture image = ImageCache.getImage(card.getState(altState).getImageKey(MatchController.instance.getLocalPlayers()), true); final Texture image = ImageCache.getImage(card.getState(altState).getImageKey(MatchController.instance.getLocalPlayers()), true);
if (image == null) { //draw details if can't draw zoom if (image == null) { //draw details if can't draw zoom
drawDetails(g, card, gameView, altState, x, y, w, h); drawDetails(g, card, gameView, altState, x, y, w, h);
@@ -355,7 +354,7 @@ public class CardImageRenderer {
boolean rotateSplit = FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ROTATE_SPLIT_CARDS); boolean rotateSplit = FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ROTATE_SPLIT_CARDS);
boolean rotatePlane = FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ROTATE_PLANE_OR_PHENOMENON); boolean rotatePlane = FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ROTATE_PLANE_OR_PHENOMENON);
if (rotatePlane && (card.getCurrentState().isPhenomenon() || card.getCurrentState().isPlane())) { if (rotatePlane && (card.getCurrentState().isPhenomenon() || card.getCurrentState().isPlane())) {
if (mask){ if (Forge.enableUIMask){
if (ImageCache.isExtendedArt(card)) if (ImageCache.isExtendedArt(card))
g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, -90); g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, -90);
else { else {
@@ -371,7 +370,7 @@ public class CardImageRenderer {
g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, -90); g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, -90);
} else if (rotateSplit && isCurrentCard && card.isSplitCard() && canLook) { } else if (rotateSplit && isCurrentCard && card.isSplitCard() && canLook) {
boolean isAftermath = card.getText().contains("Aftermath") || card.getAlternateState().getOracleText().contains("Aftermath"); boolean isAftermath = card.getText().contains("Aftermath") || card.getAlternateState().getOracleText().contains("Aftermath");
if (mask) { if (Forge.enableUIMask) {
if (ImageCache.isExtendedArt(card)) if (ImageCache.isExtendedArt(card))
g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, isAftermath ? 90 : -90); g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, isAftermath ? 90 : -90);
else { else {
@@ -387,11 +386,11 @@ public class CardImageRenderer {
g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, isAftermath ? 90 : -90); g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, isAftermath ? 90 : -90);
} }
else { else {
if (mask) { if (Forge.enableUIMask) {
if (ImageCache.isExtendedArt(card)) if (ImageCache.isExtendedArt(card))
g.drawImage(image, x, y, w, h); g.drawImage(image, x, y, w, h);
else { else {
g.drawImage(ImageCache.getBorderImage(card), x, y, w, h); g.drawImage(ImageCache.getBorderImage(card, canLook), x, y, w, h);
g.drawImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f, y + radius / 2, w * 0.96f, h * 0.96f); g.drawImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f, y + radius / 2, w * 0.96f, h * 0.96f);
} }
} }
@@ -561,8 +560,4 @@ public class CardImageRenderer {
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h); g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
g.drawText(ptText, PT_FONT, Color.BLACK, x, y, w, h, false, Align.center, true); g.drawText(ptText, PT_FONT, Color.BLACK, x, y, w, h, false, Align.center, true);
} }
private static boolean isPreferenceEnabled(ForgePreferences.FPref preferenceName) {
return FModel.getPreferences().getPrefBoolean(preferenceName);
}
} }

View File

@@ -16,6 +16,7 @@ import com.badlogic.gdx.graphics.glutils.PixmapTextureData;
import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import forge.CachedCardImage; import forge.CachedCardImage;
import forge.Forge;
import forge.FThreads; import forge.FThreads;
import forge.Graphics; import forge.Graphics;
import forge.StaticData; import forge.StaticData;
@@ -120,6 +121,8 @@ public class CardRenderer {
} }
public static Color getRarityColor(CardRarity rarity) { public static Color getRarityColor(CardRarity rarity) {
if (rarity == null)// NPE from Rarity weird...
return Color.CLEAR;
switch(rarity) { switch(rarity) {
case Uncommon: case Uncommon:
return fromDetailColor(DetailColors.UNCOMMON); return fromDetailColor(DetailColors.UNCOMMON);
@@ -392,7 +395,6 @@ public class CardRenderer {
} }
public static void drawCard(Graphics g, IPaperCard pc, float x, float y, float w, float h, CardStackPosition pos) { public static void drawCard(Graphics g, IPaperCard pc, float x, float y, float w, float h, CardStackPosition pos) {
boolean mask = isPreferenceEnabled(FPref.UI_ENABLE_BORDER_MASKING);
Texture image = new RendererCachedCardImage(pc, false).getImage(); Texture image = new RendererCachedCardImage(pc, false).getImage();
float radius = (h - w)/8; float radius = (h - w)/8;
@@ -401,7 +403,7 @@ public class CardRenderer {
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(pc), false, x, y, w, h, pos); CardImageRenderer.drawCardImage(g, CardView.getCardForUi(pc), false, x, y, w, h, pos);
} }
else { else {
if (mask) { if (Forge.enableUIMask) {
if (ImageCache.isExtendedArt(pc)) if (ImageCache.isExtendedArt(pc))
g.drawImage(image, x, y, w, h); g.drawImage(image, x, y, w, h);
else { else {
@@ -421,15 +423,13 @@ public class CardRenderer {
} }
} }
else { else {
if (mask) //render this if mask is still loading if (Forge.enableUIMask) //render this if mask is still loading
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(pc), false, x, y, w, h, pos); CardImageRenderer.drawCardImage(g, CardView.getCardForUi(pc), false, x, y, w, h, pos);
else //draw cards without textures as just a black rectangle else //draw cards without textures as just a black rectangle
g.fillRect(Color.BLACK, x, y, w, h); g.fillRect(Color.BLACK, x, y, w, h);
} }
} }
public static void drawCard(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, boolean rotate) { public static void drawCard(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, boolean rotate) {
boolean mask = isPreferenceEnabled(FPref.UI_ENABLE_BORDER_MASKING);
Texture image = new RendererCachedCardImage(card, false).getImage(); Texture image = new RendererCachedCardImage(card, false).getImage();
float radius = (h - w)/8; float radius = (h - w)/8;
@@ -440,7 +440,7 @@ public class CardRenderer {
else { else {
if(FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ROTATE_PLANE_OR_PHENOMENON) if(FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ROTATE_PLANE_OR_PHENOMENON)
&& (card.getCurrentState().isPhenomenon() || card.getCurrentState().isPlane()) && rotate){ && (card.getCurrentState().isPhenomenon() || card.getCurrentState().isPlane()) && rotate){
if (mask) { if (Forge.enableUIMask) {
if (ImageCache.isExtendedArt(card)) if (ImageCache.isExtendedArt(card))
g.drawRotatedImage(image, x, y, w, h, x + w / 2, y + h / 2, -90); g.drawRotatedImage(image, x, y, w, h, x + w / 2, y + h / 2, -90);
else { else {
@@ -452,11 +452,12 @@ public class CardRenderer {
g.drawRotatedImage(image, x, y, w, h, x + w / 2, y + h / 2, -90); g.drawRotatedImage(image, x, y, w, h, x + w / 2, y + h / 2, -90);
} }
else { else {
if (mask) { if (Forge.enableUIMask) {
if (ImageCache.isExtendedArt(card)) if (ImageCache.isExtendedArt(card))
g.drawImage(image, x, y, w, h); g.drawImage(image, x, y, w, h);
else { else {
g.drawImage(ImageCache.getBorderImage(card), x, y, w, h); boolean t = (card.getCurrentState().getOriginalColors() != card.getCurrentState().getColors()) || card.getCurrentState().hasChangeColors();
g.drawBorderImage(ImageCache.getBorderImage(card, MatchController.instance.mayView(card)), ImageCache.getTint(card), x, y, w, h, t); //tint check for changed colors
g.drawImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f, y + radius / 2, w * 0.96f, h * 0.96f); g.drawImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f, y + radius / 2, w * 0.96f, h * 0.96f);
} }
} }
@@ -467,7 +468,7 @@ public class CardRenderer {
drawFoilEffect(g, card, x, y, w, h, false); drawFoilEffect(g, card, x, y, w, h, false);
} }
else { else {
if (mask) //render this if mask is still loading if (Forge.enableUIMask) //render this if mask is still loading
CardImageRenderer.drawCardImage(g, card, false, x, y, w, h, pos); CardImageRenderer.drawCardImage(g, card, false, x, y, w, h, pos);
else //draw cards without textures as just a black rectangle else //draw cards without textures as just a black rectangle
g.fillRect(Color.BLACK, x, y, w, h); g.fillRect(Color.BLACK, x, y, w, h);
@@ -475,6 +476,9 @@ public class CardRenderer {
} }
public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos) { public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos) {
boolean canShow = MatchController.instance.mayView(card);
float oldAlpha = g.getfloatAlphaComposite();
boolean unselectable = !MatchController.instance.isSelectable(card) && MatchController.instance.isSelecting();
float cx, cy, cw, ch; float cx, cy, cw, ch;
cx = x; cy = y; cw = w; ch = h; cx = x; cy = y; cw = w; ch = h;
drawCard(g, card, x, y, w, h, pos, false); drawCard(g, card, x, y, w, h, pos, false);
@@ -485,10 +489,6 @@ public class CardRenderer {
w -= 2 * padding; w -= 2 * padding;
h -= 2 * padding; h -= 2 * padding;
boolean canShow = MatchController.instance.mayView(card);
float oldAlpha = g.getfloatAlphaComposite();
boolean unselectable = !MatchController.instance.isSelectable(card) && MatchController.instance.isSelecting();
// TODO: A hacky workaround is currently used to make the game not leak the color information for Morph cards. // TODO: A hacky workaround is currently used to make the game not leak the color information for Morph cards.
final CardStateView details = card.getCurrentState(); final CardStateView details = card.getCurrentState();
final boolean isFaceDown = card.isFaceDown(); final boolean isFaceDown = card.isFaceDown();

View File

@@ -1,5 +1,6 @@
package forge.itemmanager.views; package forge.itemmanager.views;
import forge.Forge;
import forge.Forge.KeyInputAdapter; import forge.Forge.KeyInputAdapter;
import forge.Graphics; import forge.Graphics;
import forge.assets.FImage; import forge.assets.FImage;
@@ -181,7 +182,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
getPnlOptions().add(cbPileByOptions); getPnlOptions().add(cbPileByOptions);
Group group = new Group(""); //add default group Group group = new Group(""); //add default group
groups.add(group); groups.add(group);
getScroller().add(group); getScroller().add(group);
} }
@@ -300,7 +301,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
updateLayout(false); updateLayout(false);
return; return;
} }
float offsetTop = focalItem0.getTop() - getScrollValue(); float offsetTop = focalItem0.getTop() - getScrollValue();
updateLayout(false); updateLayout(false);
setScrollValue(focalItem0.getTop() - offsetTop); setScrollValue(focalItem0.getTop() - offsetTop);
@@ -891,7 +892,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
public void draw(Graphics g) { public void draw(Graphics g) {
final float visibleTop = getScrollValue(); final float visibleTop = getScrollValue();
final float visibleBottom = visibleTop + getScroller().getHeight(); final float visibleBottom = visibleTop + getScroller().getHeight();
ItemInfo skippedItem = null; ItemInfo skippedItem = null;
for (ItemInfo itemInfo : items) { for (ItemInfo itemInfo : items) {
if (itemInfo.getBottom() < visibleTop) { if (itemInfo.getBottom() < visibleTop) {
@@ -954,9 +955,18 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
final float w = getWidth(); final float w = getWidth();
final float h = getHeight(); final float h = getHeight();
if (selected) { if (selected) { //if round border is enabled, the select highlight is also rounded..
g.fillRect(Color.GREEN, x - SEL_BORDER_SIZE, y - SEL_BORDER_SIZE, if (Forge.enableUIMask) {
w + 2 * SEL_BORDER_SIZE, h + 2 * SEL_BORDER_SIZE); //fillroundrect has rough/aliased corner
g.fillRoundRect(Color.GREEN, x - SEL_BORDER_SIZE, y - SEL_BORDER_SIZE,
w + 2 * SEL_BORDER_SIZE, h + 2 * SEL_BORDER_SIZE, (h - w) / 10);
//drawroundrect has GL_SMOOTH to `smoothen/faux` the aliased corner
g.drawRoundRect(1f, Color.GREEN, x - SEL_BORDER_SIZE, y - SEL_BORDER_SIZE,
w + 1.5f * SEL_BORDER_SIZE, h + 1.5f * SEL_BORDER_SIZE, (h - w) / 10);
}
else //default rectangle highlight
g.fillRect(Color.GREEN, x - SEL_BORDER_SIZE, y - SEL_BORDER_SIZE,
w + 2 * SEL_BORDER_SIZE, h + 2 * SEL_BORDER_SIZE);
} }
if (item instanceof PaperCard) { if (item instanceof PaperCard) {

View File

@@ -129,6 +129,7 @@ public class VField extends FContainer {
cardName.equals(c.getCurrentState().getName()) && cardName.equals(c.getCurrentState().getName()) &&
card.hasSameCounters(c) && card.hasSameCounters(c) &&
card.getCurrentState().getKeywordKey().equals(c.getCurrentState().getKeywordKey()) && card.getCurrentState().getKeywordKey().equals(c.getCurrentState().getKeywordKey()) &&
card.getCurrentState().getColors() == c.getCurrentState().getColors() &&
card.isSick() == c.isSick() && //don't stack sick tokens on non sick card.isSick() == c.isSick() && //don't stack sick tokens on non sick
card.isToken() == c.isToken()) { //don't stack tokens on top of non-tokens card.isToken() == c.isToken()) { //don't stack tokens on top of non-tokens
CardAreaPanel cPanel = CardAreaPanel.get(c); CardAreaPanel cPanel = CardAreaPanel.get(c);

View File

@@ -260,7 +260,7 @@ public class SettingsPage extends TabPage<SettingsScreen> {
TabPageScreen.COMPACT_TABS = FModel.getPreferences().getPrefBoolean(FPref.UI_COMPACT_TABS); TabPageScreen.COMPACT_TABS = FModel.getPreferences().getPrefBoolean(FPref.UI_COMPACT_TABS);
parentScreen.revalidate(); parentScreen.revalidate();
} }
}, 4); },4);
lstSettings.addItem(new BooleanSetting(FPref.UI_COMPACT_LIST_ITEMS, lstSettings.addItem(new BooleanSetting(FPref.UI_COMPACT_LIST_ITEMS,
localizer.getMessage("lblCompactListItems"), localizer.getMessage("lblCompactListItems"),
localizer.getMessage("nlCompactListItems")), localizer.getMessage("nlCompactListItems")),
@@ -302,32 +302,42 @@ public class SettingsPage extends TabPage<SettingsScreen> {
localizer.getMessage("nlDisableCardEffect")), localizer.getMessage("nlDisableCardEffect")),
4); 4);
lstSettings.addItem(new BooleanSetting(FPref.UI_ENABLE_BORDER_MASKING, lstSettings.addItem(new BooleanSetting(FPref.UI_ENABLE_BORDER_MASKING,
"Enable Round Border Mask", "Enable Round Border Mask",
"When enabled, the card corners are rounded (Preferably Card with Full Borders)."), "When enabled, the card corners are rounded (Preferably Card with Full Borders)."){
4); @Override
public void select() {
super.select();
//update
Forge.enableUIMask = FModel.getPreferences().getPrefBoolean(FPref.UI_ENABLE_BORDER_MASKING);
}
},4);
lstSettings.addItem(new BooleanSetting(FPref.UI_ENABLE_PRELOAD_EXTENDED_ART, lstSettings.addItem(new BooleanSetting(FPref.UI_ENABLE_PRELOAD_EXTENDED_ART,
"Preload Extended Art Cards", "Preload Extended Art Cards",
"When enabled, Preloads Extended Art Cards to Cache on Startup."), "When enabled, Preloads Extended Art Cards to Cache on Startup."){
4); @Override
public void select() {
super.select();
//update
Forge.enablePreloadExtendedArt = FModel.getPreferences().getPrefBoolean(FPref.UI_ENABLE_PRELOAD_EXTENDED_ART);
}
},4);
lstSettings.addItem(new BooleanSetting(FPref.UI_SHOW_FPS, lstSettings.addItem(new BooleanSetting(FPref.UI_SHOW_FPS,
"Show FPS Display", "Show FPS Display",
"When enabled, show the FPS Display (Experimental)."){ "When enabled, show the FPS Display (Experimental)."){
@Override @Override
public void select() { public void select() {
super.select(); super.select();
//update //update
Forge.showFPS = FModel.getPreferences().getPrefBoolean(FPref.UI_SHOW_FPS); Forge.showFPS = FModel.getPreferences().getPrefBoolean(FPref.UI_SHOW_FPS);
} }
},4); },4);
lstSettings.addItem(new CustomSelectSetting(FPref.UI_CARD_COUNTER_DISPLAY_TYPE, lstSettings.addItem(new CustomSelectSetting(FPref.UI_CARD_COUNTER_DISPLAY_TYPE,
localizer.getMessage("cbpCounterDisplayType"), localizer.getMessage("cbpCounterDisplayType"),
localizer.getMessage("nlCounterDisplayType"), localizer.getMessage("nlCounterDisplayType"),
new String[]{ new String[]{
ForgeConstants.CounterDisplayType.TEXT.getName(), ForgeConstants.CounterDisplayType.IMAGE.getName(), ForgeConstants.CounterDisplayType.TEXT.getName(), ForgeConstants.CounterDisplayType.IMAGE.getName(),
ForgeConstants.CounterDisplayType.HYBRID.getName(), ForgeConstants.CounterDisplayType.OLD_WHEN_SMALL.getName()}), ForgeConstants.CounterDisplayType.HYBRID.getName(), ForgeConstants.CounterDisplayType.OLD_WHEN_SMALL.getName()}),
4); 4);
//Card Overlays //Card Overlays
lstSettings.addItem(new BooleanSetting(FPref.UI_SHOW_CARD_OVERLAYS, lstSettings.addItem(new BooleanSetting(FPref.UI_SHOW_CARD_OVERLAYS,
localizer.getMessage("lblShowCardOverlays"), localizer.getMessage("lblShowCardOverlays"),
@@ -353,7 +363,6 @@ public class SettingsPage extends TabPage<SettingsScreen> {
localizer.getMessage("lblShowAbilityIconsOverlays"), localizer.getMessage("lblShowAbilityIconsOverlays"),
localizer.getMessage("nlShowAbilityIconsOverlays")), localizer.getMessage("nlShowAbilityIconsOverlays")),
5); 5);
//Vibration Options //Vibration Options
lstSettings.addItem(new BooleanSetting(FPref.UI_VIBRATE_ON_LIFE_LOSS, lstSettings.addItem(new BooleanSetting(FPref.UI_VIBRATE_ON_LIFE_LOSS,
localizer.getMessage("lblVibrateWhenLosingLife"), localizer.getMessage("lblVibrateWhenLosingLife"),
@@ -363,7 +372,6 @@ public class SettingsPage extends TabPage<SettingsScreen> {
localizer.getMessage("lblVibrateAfterLongPress"), localizer.getMessage("lblVibrateAfterLongPress"),
localizer.getMessage("nlVibrateAfterLongPress")), localizer.getMessage("nlVibrateAfterLongPress")),
6); 6);
//Sound Options //Sound Options
lstSettings.addItem(new BooleanSetting(FPref.UI_ENABLE_SOUNDS, lstSettings.addItem(new BooleanSetting(FPref.UI_ENABLE_SOUNDS,
localizer.getMessage("cbEnableSounds"), localizer.getMessage("cbEnableSounds"),
@@ -378,7 +386,7 @@ public class SettingsPage extends TabPage<SettingsScreen> {
//update background music when this setting changes //update background music when this setting changes
SoundSystem.instance.changeBackgroundTrack(); SoundSystem.instance.changeBackgroundTrack();
} }
}, 7); },7);
/*lstSettings.addItem(new BooleanSetting(FPref.UI_ALT_SOUND_SYSTEM, /*lstSettings.addItem(new BooleanSetting(FPref.UI_ALT_SOUND_SYSTEM,
"Use Alternate Sound System", "Use Alternate Sound System",
"Use the alternate sound system (only use if you have issues with sound not playing or disappearing)."), "Use the alternate sound system (only use if you have issues with sound not playing or disappearing)."),

View File

@@ -3,7 +3,7 @@ ManaCost:4 G G
Types:Legendary Creature Avatar Types:Legendary Creature Avatar
PT:4/4 PT:4/4
K:Shroud K:Shroud
A:AB$ Pump | Cost$ G | ValidTgts$ Player | Defined$ Targeted | TgtPrompt$ Select target player to be able to target Autumn Willow | KW$ Can target CardUIDSource with spells and abilities as though it didn't have shroud. | DefinedKW$ CardUIDSource | StackDescription$ Until end of turn, {p:Targeted} can target CARDNAME as though it didn't have shroud. | SpellDescription$ Until end of turn, Autumn Willow can be the target of spells and abilities controlled by target player as though it didn't have shroud. A:AB$ Pump | Cost$ G | ValidTgts$ Player | Defined$ Targeted | TgtPrompt$ Select target player to be able to target Autumn Willow | KW$ IgnoreShroud:Card.CardUIDSource | DefinedKW$ CardUIDSource | UntilHostLeavesPlayOrEOT$ True | StackDescription$ Until end of turn, {p:Targeted} can target CARDNAME as though it didn't have shroud. | SpellDescription$ Until end of turn, Autumn Willow can be the target of spells and abilities controlled by target player as though it didn't have shroud.
AI:RemoveDeck:All AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/autumn_willow.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/autumn_willow.jpg
Oracle:Shroud (This creature can't be the target of spells or abilities.)\n{G}: Until end of turn, Autumn Willow can be the target of spells and abilities controlled by target player as though it didn't have shroud. Oracle:Shroud (This creature can't be the target of spells or abilities.)\n{G}: Until end of turn, Autumn Willow can be the target of spells and abilities controlled by target player as though it didn't have shroud.

View File

@@ -4,6 +4,6 @@ Types:Sorcery
A:SP$ Effect | Cost$ 1 W | Name$ Peace Talks Effect | StaticAbilities$ STCantAttack,STCantTarget,STCantTargetPlayer | Duration$ ThisTurnAndNextTurn | SpellDescription$ This turn and next turn, creatures can't attack, and players and permanents can't be the targets of spells or activated abilities. A:SP$ Effect | Cost$ 1 W | Name$ Peace Talks Effect | StaticAbilities$ STCantAttack,STCantTarget,STCantTargetPlayer | Duration$ ThisTurnAndNextTurn | SpellDescription$ This turn and next turn, creatures can't attack, and players and permanents can't be the targets of spells or activated abilities.
SVar:STCantAttack:Mode$ CantAttack | EffectZone$ Command | ValidCard$ Creature | Description$ Creatures can't attack. SVar:STCantAttack:Mode$ CantAttack | EffectZone$ Command | ValidCard$ Creature | Description$ Creatures can't attack.
SVar:STCantTarget:Mode$ CantTarget | ValidCard$ Permanent | EffectZone$ Command | ValidSA$ Spell,Activated | Description$ Permanents can't be the targets of spells or activated abilities. SVar:STCantTarget:Mode$ CantTarget | ValidCard$ Permanent | EffectZone$ Command | ValidSA$ Spell,Activated | Description$ Permanents can't be the targets of spells or activated abilities.
SVar:STCantTargetPlayer:Mode$ Continuous | Affected$ Player | EffectZone$ Command | AddKeyword$ You can't be the targets of spells or activated abilities | Description$ Players can't be the targets of spells or activated abilities. SVar:STCantTargetPlayer:Mode$ CantTarget | ValidPlayer$ Player | EffectZone$ Command | ValidSA$ Spell,Activated | Description$Players can't be the targets of spells or activated abilities.
SVar:Picture:http://www.wizards.com/global/images/magic/general/peace_talks.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/peace_talks.jpg
Oracle:This turn and next turn, creatures can't attack, and players and permanents can't be the targets of spells or activated abilities. Oracle:This turn and next turn, creatures can't attack, and players and permanents can't be the targets of spells or activated abilities.

View File

@@ -3,6 +3,7 @@ ManaCost:1 W U
Types:Enchantment Aura Types:Enchantment Aura
K:Enchant creature K:Enchant creature
A:SP$ Attach | Cost$ 1 W U | ValidTgts$ Creature | AILogic$ Pump A:SP$ Attach | Cost$ 1 W U | ValidTgts$ Creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddToughness$ 2 | AddHiddenKeyword$ CARDNAME can't be the target of spells. | Description$ Enchanted creature gets +0/+2 and can't be the target of spells. S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddToughness$ 2 | Description$ Enchanted creature gets +0/+2.
S:Mode$ CantTarget | ValidCard$ Creature.EnchantedBy | ValidSA$ Spell | Description$ Enchanted creature can't be the target of spells.
SVar:Picture:http://www.wizards.com/global/images/magic/general/spectral_shield.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/spectral_shield.jpg
Oracle:Enchant creature\nEnchanted creature gets +0/+2 and can't be the target of spells. Oracle:Enchant creature\nEnchanted creature gets +0/+2 and can't be the target of spells.

View File

@@ -1,8 +1,8 @@
Name:Veil of Summer Name:Veil of Summer
ManaCost:G ManaCost:G
Types:Instant Types:Instant
A:SP$ Draw | Cost$ G | Defined$ You | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | SubAbility$ DBEffect | SpellDescription$ Draw a card if an opponent has cast a blue or black spell this turn. Spells you control can't be countered this turn. You and permanents you control gain hexproof from blue and from black until end of turn. (You and they can't be the targets of blue or black spells or abilities your opponents control.) A:SP$ Draw | Cost$ G | Defined$ You | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 |References$ X | SubAbility$ DBEffect | StackDescription$ SpellDescription | SpellDescription$ Draw a card if an opponent has cast a blue or black spell this turn. Spells you control can't be countered this turn. You and permanents you control gain hexproof from blue and from black until end of turn. (You and they can't be the targets of blue or black spells or abilities your opponents control.)
SVar:DBEffect:DB$ Effect | Name$ CARDNAME Effect | StaticAbilities$ AntiMagic | SubAbility$ DBPump SVar:DBEffect:DB$ Effect | StaticAbilities$ AntiMagic | SubAbility$ DBPump
SVar:AntiMagic:Mode$ Continuous | Affected$ Card.YouCtrl | AffectedZone$ Stack | EffectZone$ Command | AddHiddenKeyword$ CARDNAME can't be countered. | Description$ Spells you control can't be countered this turn. SVar:AntiMagic:Mode$ Continuous | Affected$ Card.YouCtrl | AffectedZone$ Stack | EffectZone$ Command | AddHiddenKeyword$ CARDNAME can't be countered. | Description$ Spells you control can't be countered this turn.
SVar:DBPump:DB$ Pump | Defined$ You | KW$ Hexproof:Card.Black:black & Hexproof:Card.Blue:blue | SubAbility$ DBPumpAll SVar:DBPump:DB$ Pump | Defined$ You | KW$ Hexproof:Card.Black:black & Hexproof:Card.Blue:blue | SubAbility$ DBPumpAll
SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Permanent.YouCtrl | KW$ Hexproof:Card.Black:black & Hexproof:Card.Blue:blue SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Permanent.YouCtrl | KW$ Hexproof:Card.Black:black & Hexproof:Card.Blue:blue

View File

@@ -0,0 +1,19 @@
[metadata]
Name:Possibility Storm - Throne of Eldraine #01
URL:http://www.possibilitystorm.com/wp-content/uploads/2019/10/132.-ELD1.jpg
Goal:Win
Turns:1
Difficulty:Rare
Description:Win this turn. You have three cards in your graveyard.
[state]
humanlife=20
ailife=8
turn=1
activeplayer=human
activephase=MAIN1
humanhand=Irencrag Feat;Charnel Troll;Steelbane Hydra;Festive Funeral;Trollbred Guardian
humangraveyard=Molderhulk;Stomping Ground;Deathless Knight
humanbattlefield=Fires of Invention;Goblin Smuggler;Yorvo, Lord of Garenbrig|Counters:P1P1=8;Vraska, Golgari Queen|Counters:LOYALTY=9;Blood Crypt|NoETBTrigs;Blood Crypt|NoETBTrigs;Blood Crypt|NoETBTrigs;Forest|Set:ELD;Forest|Set:ELD
aibattlefield=Inspiring Veteran
aiprecast=The Circle of Loyalty:TrigToken;The Circle of Loyalty:TrigToken;The Circle of Loyalty:TrigToken;The Circle of Loyalty:TrigToken;The Circle of Loyalty:TrigToken;The Circle of Loyalty:TrigToken
removesummoningsickness=true

View File

@@ -58,7 +58,7 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
public final void setCurrentPlayer(PlayerView player) { public final void setCurrentPlayer(PlayerView player) {
player = TrackableTypes.PlayerViewType.lookup(player); //ensure we use the correct player player = TrackableTypes.PlayerViewType.lookup(player); //ensure we use the correct player
if (!gameControllers.containsKey(player)) { if (hasLocalPlayers() && !isLocalPlayer(player)) { //add check if gameControllers is not empty
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }