diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 2d0e8ac5a23..b4b3293d108 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -616,7 +616,8 @@ public class AiController { } catch (IllegalArgumentException ex) { 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)) { @@ -1585,7 +1586,8 @@ public class AiController { } catch (IllegalArgumentException ex) { 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)) { @@ -1844,7 +1846,7 @@ public class AiController { // Special case for Bow to My Command which simulates a complex tap cost via ChooseCard // TODO: consider enhancing support for tapXType in UnlessCost to get rid of this hack 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 result.clear(); break; diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 99ff3bcbda6..f874561aaa1 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -2403,7 +2403,7 @@ public class ComputerUtil { } // if source is not on the battlefield anymore, choose +1/+1 // ones - if (!game.getCardState(source).getZone().is(ZoneType.Battlefield)) { + if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) { return opponent ? "Feather" : "Quill"; } // if no hand cards, try to mill opponent @@ -2435,7 +2435,7 @@ public class ComputerUtil { } // 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"; } @@ -2484,7 +2484,7 @@ public class ComputerUtil { } // 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"; } // TODO add Lifegain to +1/+1 counters trigger diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 2dbce19187d..13e558ea7a9 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -1294,7 +1294,7 @@ public class ComputerUtilCard { // cast it during Declare Blockers, thus ruining its attacker if (holdCombatTricks && sa.getApi() == ApiType.Pump && 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 && sa.getHostCard().isInstant() // only do it for instant speed spells in hand && ComputerUtilMana.hasEnoughManaSourcesToCast(sa, ai)) { diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index d7042d89276..f0d1250cb58 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -729,7 +729,7 @@ public class PlayerControllerAi extends PlayerController { return true; } else { Card rem = (Card) source.getFirstRemembered(); - if (!rem.getZone().is(ZoneType.Battlefield)) { + if (!rem.isInZone(ZoneType.Battlefield)) { return true; } } @@ -737,7 +737,7 @@ public class PlayerControllerAi extends PlayerController { case "BetterTgtThanRemembered": if (source.getRememberedCount() > 0) { Card rem = (Card) source.getFirstRemembered(); - if (!rem.getZone().is(ZoneType.Battlefield)) { + if (!rem.isInZone(ZoneType.Battlefield)) { return true; } for (Card c : source.getController().getCreaturesInPlay()) { diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java index 40141ae80a4..bd5371cbb75 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -76,7 +76,7 @@ public class DamageDealAi extends DamageAiBase { // Set PayX here to maximum value. dmg = ComputerUtilMana.determineLeftoverMana(sa, ai); 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 } } @@ -113,7 +113,7 @@ public class DamageDealAi extends DamageAiBase { // Set PayX here to maximum value. It will be adjusted later depending on the target. 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 } 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 diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java index 212f0bbdab6..355361bfda7 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -409,7 +409,7 @@ public class DrawAi extends SpellAbilityAi { if (computerHandSize + numCards > computerMaxHandSize && game.getPhaseHandler().isPlayerTurn(ai)) { if (xPaid) { numCards = computerMaxHandSize - computerHandSize; - if (sa.getHostCard().getZone().is(ZoneType.Hand)) { + if (sa.getHostCard().isInZone(ZoneType.Hand)) { numCards++; // the card will be spent } source.setSVar("PayX", Integer.toString(numCards)); diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java index 31c4c7a8ddd..ce161caee80 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -309,7 +309,7 @@ public class PumpAi extends PumpAiBase { } } else { 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 } } @@ -328,7 +328,7 @@ public class PumpAi extends PumpAiBase { } } else { 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 } } diff --git a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java index 79104d062a6..7e03c1cf0f5 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java @@ -148,7 +148,7 @@ public class SetStateAi extends SpellAbilityAi { if (card.isFaceDown()) { // hidden agenda if (card.getState(CardStateName.Original).hasIntrinsicKeyword("Hidden agenda") - && card.getZone().is(ZoneType.Command)) { + && card.isInZone(ZoneType.Command)) { String chosenName = card.getNamedCard(); for (Card cast : ai.getGame().getStack().getSpellsCastThisTurn()) { if (cast.getController() == ai && cast.getName().equals(chosenName)) { diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java index d2ae1de2379..cd99a5a6d94 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java @@ -228,7 +228,7 @@ public class GameCopier { CardFactory.copyCopiableCharacteristics(c, 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); newCard.setCommander(c.isCommander()); return newCard; diff --git a/forge-core/src/main/java/forge/util/ComparatorUtil.java b/forge-core/src/main/java/forge/util/ComparatorUtil.java index 7e0d6f584c5..7f575825439 100644 --- a/forge-core/src/main/java/forge/util/ComparatorUtil.java +++ b/forge-core/src/main/java/forge/util/ComparatorUtil.java @@ -15,8 +15,9 @@ public final class ComparatorUtil * @param elements the elements to test against * @throws AssertionError if the comparator is not transitive */ - public static void verifyTransitivity(Comparator comparator, Collection elements) + public static String verifyTransitivity(Comparator comparator, Collection elements) { + String exception = ""; for (T first: elements) { for (T second: elements) @@ -27,8 +28,12 @@ public final class ComparatorUtil { // Uncomment the following line to step through the failed case //comparator.compare(first, second); - throw new AssertionError("compare(" + first + ", " + second + ") == " + result1 + - " but swapping the parameters returns " + result2); + /*throw new AssertionError("compare(" + first + ", " + second + ") == " + result1 + + " 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 //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 + ") == " + - firstGreaterThanThird); + firstGreaterThanThird);*/ + exception = "compare(" + first + ", " + second + ") > 0, " + + "compare(" + second + ", " + third + ") > 0, but compare(" + first + ", " + third + ") == " + + firstGreaterThanThird; + System.err.println(exception); + return exception; } } } } + return exception; } /** diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index b086f80912b..8396778b6be 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -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 AtEOT SVar String endofTurn = c.getSVar("EndOfTurnLeavePlay"); @@ -256,7 +251,7 @@ public class GameAction { } if(noLandLKI.isLand()) { // 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; } // 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(); // need to suspend cards own replacement effects diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index b0575c15420..e1f237ecf0d 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -300,12 +300,12 @@ public final class GameActionUtil { costs.add(new OptionalCostValue(type, cost)); } } 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); costs.add(new OptionalCostValue(OptionalCost.Retrace, cost)); } } 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); costs.add(new OptionalCostValue(OptionalCost.Jumpstart, cost)); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java index 742132bac5f..5ca1a765d2f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java @@ -110,7 +110,7 @@ public class CloneEffect extends SpellAbilityEffect { } if (sa.hasParam("CloneZone")) { - if (!tgtCard.getZone().is(ZoneType.smartValueOf(sa.getParam("CloneZone")))) { + if (!tgtCard.isInZone(ZoneType.smartValueOf(sa.getParam("CloneZone")))) { return; } } @@ -134,7 +134,7 @@ public class CloneEffect extends SpellAbilityEffect { tgtCard.clearImprintedCards(); // 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); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java index e43f7876b5d..75d8693b8f0 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java @@ -120,7 +120,10 @@ public class PumpEffect extends SpellAbilityEffect { && !(host.isInPlay() || host.isInZone(ZoneType.Stack))) { return; } - p.addChangedKeywords(keywords, ImmutableList.of(), timestamp); + + if (!keywords.isEmpty()) { + p.addChangedKeywords(keywords, ImmutableList.of(), timestamp); + } if (!sa.hasParam("Permanent")) { // If not Permanent, remove Pumped at EOT @@ -129,12 +132,7 @@ public class PumpEffect extends SpellAbilityEffect { @Override public void run() { - - if (keywords.size() > 0) { - for (int i = 0; i < keywords.size(); i++) { - p.removeKeyword(keywords.get(i)); - } - } + p.removeChangedKeywords(timestamp); } }; addUntilCommand(sa, untilEOT); diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 8e71779676f..e7fac44ec75 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -3155,6 +3155,7 @@ public class Card extends GameEntity implements Comparable { public final void addColor(final String s, final boolean addToColors, final long timestamp) { changedCardColors.put(timestamp, new CardColor(s, addToColors, timestamp)); currentState.getView().updateColors(this); + currentState.getView().updateHasChangeColors(!getChangedCardColors().isEmpty()); } public final void removeColor(final long timestampIn) { @@ -3162,6 +3163,7 @@ public class Card extends GameEntity implements Comparable { if (removeCol != null) { currentState.getView().updateColors(this); + currentState.getView().updateHasChangeColors(!getChangedCardColors().isEmpty()); } } @@ -5616,34 +5618,7 @@ public class Card extends GameEntity implements Comparable { } final Card source = sa.getHostCard(); - final MutableBoolean result = new MutableBoolean(true); - visitKeywords(currentState, new Visitor() { - @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()) { for(KeywordInterface inst : source.getKeywords()) { String kw = inst.getOriginal(); diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index f0317647e70..d5ffd39750c 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -4501,7 +4501,9 @@ public class CardFactoryUtil { effect = "Mode$ CantTarget | Hexproof$ True | ValidCard$ Card.Self | Secondary$ True" + sbValid.toString() + " | Activator$ Opponent | Description$ " + 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")) { final String[] k = keyword.split(":"); final String manacost = k[1]; @@ -4693,19 +4695,30 @@ public class CardFactoryUtil { } 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"; - 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"; AbilitySub saEffect = (AbilitySub)AbilityFactory.getAbility(abEffect, card); - StringBuilder sb = new StringBuilder(); - sb.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure"); - sb.append(" | AffectedZone$ Exile | Description$ You may cast the card."); - saEffect.setSVar("Play", sb.toString()); + StringBuilder sbPlay = new StringBuilder(); + sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure"); + sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card."); + saEffect.setSVar("Play", sbPlay.toString()); saExile.setSubAbility(saEffect); - sa.appendSubAbility(saExile); + + ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, true); + re.setLayer(ReplacementLayer.Other); + + re.setOverridingAbility(saExile); + card.addReplacementEffect(re); } } diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index e14f45f1bb6..bc2c4c9ac2c 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -523,7 +523,6 @@ public class CardView extends GameEntityView { void updateChangedColorWords(Card c) { set(TrackableProperty.ChangedColorWords, c.getChangedTextColorWords()); } - public Map getChangedTypes() { return get(TrackableProperty.ChangedTypes); } @@ -706,11 +705,13 @@ public class CardView extends GameEntityView { // update the color only while in Game if (c.getGame() != null) { currentStateView.updateColors(currentState); + currentStateView.updateHasChangeColors(!c.getChangedCardColors().isEmpty()); } } else { currentStateView.updateLoyalty(currentState); } 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(); @@ -840,13 +841,22 @@ public class CardView extends GameEntityView { public ColorSet getColors() { return get(TrackableProperty.Colors); } + public ColorSet getOriginalColors() { + return get(TrackableProperty.OriginalColors); + } void updateColors(Card c) { set(TrackableProperty.Colors, c.determineColor()); } void updateColors(CardState c) { 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() { return getImageKey(null); } diff --git a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java index 6e178003a76..dc24242bdba 100644 --- a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java +++ b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java @@ -192,7 +192,7 @@ public class CostAdjustment { } 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 cost.decreaseGenericMana(sumGeneric); @@ -360,7 +360,7 @@ public class CostAdjustment { * @param 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 if (manaCost.toString().equals("{0}")) { return 0; @@ -393,7 +393,7 @@ public class CostAdjustment { 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) { return Math.min(value, maxReduction); } diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordInstance.java b/forge-game/src/main/java/forge/game/keyword/KeywordInstance.java index 6e7adb840fb..0ef5b3ffcd7 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordInstance.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordInstance.java @@ -9,6 +9,8 @@ import com.google.common.collect.Lists; import forge.game.card.Card; import forge.game.card.CardFactoryUtil; +import forge.game.player.Player; +import forge.game.player.PlayerFactoryUtil; import forge.game.replacement.ReplacementEffect; import forge.game.spellability.SpellAbility; import forge.game.staticability.StaticAbility; @@ -82,7 +84,7 @@ public abstract class KeywordInstance> implements K public final void createTraits(final Card host, final boolean intrinsic) { createTraits(host, intrinsic, false); } - + /* * (non-Javadoc) * @see forge.game.keyword.KeywordInterface#createTraits(forge.game.card.Card, boolean, boolean) @@ -125,6 +127,53 @@ public abstract class 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) * @see forge.game.keyword.KeywordInterface#addTrigger(forge.game.trigger.Trigger) diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordInterface.java b/forge-game/src/main/java/forge/game/keyword/KeywordInterface.java index d8b5f152605..2832cdd2ee4 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordInterface.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordInterface.java @@ -3,6 +3,7 @@ package forge.game.keyword; import java.util.Collection; 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; @@ -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, final boolean clear); + void createTraits(final Player player); + void createTraits(final Player player, final boolean clear); + void addTrigger(final Trigger trg); void addReplacement(final ReplacementEffect trg); diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordsChange.java b/forge-game/src/main/java/forge/game/keyword/KeywordsChange.java index 1f87bc4762f..73e1c1aa272 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordsChange.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordsChange.java @@ -23,6 +23,11 @@ import java.util.List; import com.google.common.collect.Lists; 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; /** *

@@ -131,7 +136,13 @@ public class KeywordsChange implements Cloneable { 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) { return keywords.remove(keyword); } @@ -201,6 +212,47 @@ public class KeywordsChange implements Cloneable { } } + /** + * @return the triggers + */ + public Collection getTriggers() { + List result = Lists.newArrayList(); + for (KeywordInterface k : this.keywords.getValues()) { + result.addAll(k.getTriggers()); + } + return result; + } + /** + * @return the replacements + */ + public Collection getReplacements() { + List result = Lists.newArrayList(); + for (KeywordInterface k : this.keywords.getValues()) { + result.addAll(k.getReplacements()); + } + return result; + } + /** + * @return the abilities + */ + public Collection getAbilities() { + List result = Lists.newArrayList(); + for (KeywordInterface k : this.keywords.getValues()) { + result.addAll(k.getAbilities()); + } + return result; + } + /** + * @return the staticAbilities + */ + public Collection getStaticAbilities() { + List result = Lists.newArrayList(); + for (KeywordInterface k : this.keywords.getValues()) { + result.addAll(k.getStaticAbilities()); + } + return result; + } + /* (non-Javadoc) * @see java.lang.Object#toString() */ diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 76d0d56c21e..6d5ac51478c 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -20,6 +20,8 @@ package forge.game.player; import com.google.common.base.Function; import com.google.common.base.Predicates; import com.google.common.collect.*; + +import forge.ImageKeys; import forge.LobbyPlayer; import forge.card.MagicColor; import forge.game.*; @@ -41,7 +43,6 @@ import forge.game.phase.PhaseType; import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementType; -import forge.game.spellability.AbilityActivated; import forge.game.spellability.SpellAbility; import forge.game.staticability.StaticAbility; import forge.game.trigger.Trigger; @@ -155,6 +156,7 @@ public class Player extends GameEntity implements Comparable { private Card monarchEffect = null; private Card blessingEffect = null; + private Card keywordEffect = null; private final AchievementTracker achievementTracker = new AchievementTracker(); private final PlayerView view; @@ -1025,23 +1027,25 @@ public class Player extends GameEntity implements Comparable { public final void addChangedKeywords(final List addKeywords, final List removeKeywords, final Long timestamp) { // if the key already exists - merge entries + KeywordsChange cks = null; if (changedKeywords.containsKey(timestamp)) { - final KeywordsChange cks = changedKeywords.get(timestamp); + getKeywordCard().removeChangedCardTraits(timestamp); - changedKeywords.put(timestamp, cks.merge(addKeywords, removeKeywords, - cks.isRemoveAllKeywords(), cks.isRemoveIntrinsicKeywords())); - updateKeywords(); - return; + cks = changedKeywords.get(timestamp).merge(addKeywords, removeKeywords, false, false); + } else { + cks = new KeywordsChange(addKeywords, removeKeywords, false, false); } - - changedKeywords.put(timestamp, new KeywordsChange(addKeywords, removeKeywords, false, false)); + cks.addKeywordsToPlayer(this); + getKeywordCard().addChangedCardTraits(cks.getAbilities(), null, cks.getTriggers(), cks.getReplacements(), cks.getStaticAbilities(), false, false, false, timestamp); + changedKeywords.put(timestamp, cks); updateKeywords(); game.fireEvent(new GameEventPlayerStatsChanged(this)); } public final KeywordsChange removeChangedKeywords(final Long timestamp) { - KeywordsChange change = changedKeywords.remove(Long.valueOf(timestamp)); + KeywordsChange change = changedKeywords.remove(timestamp); if (change != null) { + getKeywordCard().removeChangedCardTraits(timestamp); updateKeywords(); game.fireEvent(new GameEventPlayerStatsChanged(this)); } @@ -1100,6 +1104,7 @@ public class Player extends GameEntity implements Comparable { for (final Entry ck : ImmutableList.copyOf(changedKeywords.entrySet())) { if (ck.getValue().isEmpty() && changedKeywords.remove(ck.getKey()) != null) { keywordRemoved = true; + getKeywordCard().removeChangedCardTraits(ck.getKey()); } } @@ -1178,33 +1183,17 @@ public class Player extends GameEntity implements Comparable { @Override public final boolean canBeTargetedBy(final SpellAbility sa) { - if (hasKeyword("Shroud")) { - return false; - } - if (hasKeyword("Hexproof")) { - final Player a = sa.getActivatingPlayer(); - 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) { + + // CantTarget static abilities + for (final Card ca : getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { + for (final StaticAbility stAb : ca.getStaticAbilities()) { + if (stAb.applyAbility("CantTarget", this, sa)) { return false; } } } - if (hasProtectionFrom(sa.getHostCard())) { - return false; - } - - return (!hasKeyword("You can't be the targets of spells or activated abilities") || (!sa.isSpell() && (!(sa instanceof AbilityActivated)))); + return !hasProtectionFrom(sa.getHostCard()); } @@ -2971,4 +2960,26 @@ public class Player extends GameEntity implements Comparable { || !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; + } } diff --git a/forge-game/src/main/java/forge/game/player/PlayerFactoryUtil.java b/forge-game/src/main/java/forge/game/player/PlayerFactoryUtil.java new file mode 100644 index 00000000000..0eff22b1711 --- /dev/null +++ b/forge-game/src/main/java/forge/game/player/PlayerFactoryUtil.java @@ -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) { + } +} diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java index bad27de11c1..5b8cc7018a3 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java @@ -391,6 +391,23 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone 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) { // don't apply the ability if it hasn't got the right mode diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java index 91ab7dc10bd..e58ede1cd8c 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -22,8 +22,6 @@ import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; -import java.util.Map; - /** * The Class StaticAbilityCantTarget. */ @@ -31,8 +29,8 @@ public class StaticAbilityCantTarget { /** * Apply can't target ability. - * - * @param staticAbility + * + * @param st * the static ability * @param card * the card @@ -40,16 +38,19 @@ public class StaticAbilityCantTarget { * the spell/ability * @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 Map params = staticAbility.getMapParams(); - final Card hostCard = staticAbility.getHostCard(); + final Card hostCard = st.getHostCard(); final Card source = spellAbility.getHostCard(); final Player activator = spellAbility.getActivatingPlayer(); - if (params.containsKey("AffectedZone")) { + if (st.hasParam("ValidPlayer")) { + return false; + } + + if (st.hasParam("AffectedZone")) { 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)) { inZone = true; 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; } - if (params.containsKey("ValidCard") - && !card.isValid(params.get("ValidCard").split(","), hostCard.getController(), hostCard, null)) { - return false; - } - if (params.containsKey("ValidSource") - && !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)) { + if (st.hasParam("Hexproof") && (activator != null)) { for (String k : activator.getKeywords()) { if (k.startsWith("IgnoreHexproof")) { 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; } + 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; + } } diff --git a/forge-game/src/main/java/forge/trackable/TrackableProperty.java b/forge-game/src/main/java/forge/trackable/TrackableProperty.java index a26cfa0951b..b19902edcaa 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableProperty.java +++ b/forge-game/src/main/java/forge/trackable/TrackableProperty.java @@ -69,6 +69,7 @@ public enum TrackableProperty { //Card State Name(TrackableTypes.StringType), Colors(TrackableTypes.ColorSetType), + OriginalColors(TrackableTypes.ColorSetType), ImageKey(TrackableTypes.StringType), Type(TrackableTypes.CardTypeViewType), ManaCost(TrackableTypes.ManaCostType), @@ -80,6 +81,7 @@ public enum TrackableProperty { Toughness(TrackableTypes.IntegerType), Loyalty(TrackableTypes.StringType), ChangedColorWords(TrackableTypes.StringMapType), + HasChangedColors(TrackableTypes.BooleanType), ChangedTypes(TrackableTypes.StringMapType), KeywordKey(TrackableTypes.StringType), diff --git a/forge-gui-android/libs/arm64-v8a/libgdx-freetype.so b/forge-gui-android/libs/arm64-v8a/libgdx-freetype.so new file mode 100644 index 00000000000..7565cedda9c Binary files /dev/null and b/forge-gui-android/libs/arm64-v8a/libgdx-freetype.so differ diff --git a/forge-gui-android/libs/arm64-v8a/libgdx.so b/forge-gui-android/libs/arm64-v8a/libgdx.so new file mode 100644 index 00000000000..258b198378c Binary files /dev/null and b/forge-gui-android/libs/arm64-v8a/libgdx.so differ diff --git a/forge-gui-android/libs/armeabi-v7a/libgdx-freetype.so b/forge-gui-android/libs/armeabi-v7a/libgdx-freetype.so index acdeb53636e..648bf6c4f3e 100644 Binary files a/forge-gui-android/libs/armeabi-v7a/libgdx-freetype.so and b/forge-gui-android/libs/armeabi-v7a/libgdx-freetype.so differ diff --git a/forge-gui-android/libs/armeabi-v7a/libgdx.so b/forge-gui-android/libs/armeabi-v7a/libgdx.so index 1b517c99f7a..89150620111 100644 Binary files a/forge-gui-android/libs/armeabi-v7a/libgdx.so and b/forge-gui-android/libs/armeabi-v7a/libgdx.so differ diff --git a/forge-gui-android/libs/armeabi/libgdx-freetype.so b/forge-gui-android/libs/armeabi/libgdx-freetype.so index 26388012eae..53121395224 100644 Binary files a/forge-gui-android/libs/armeabi/libgdx-freetype.so and b/forge-gui-android/libs/armeabi/libgdx-freetype.so differ diff --git a/forge-gui-android/libs/armeabi/libgdx.so b/forge-gui-android/libs/armeabi/libgdx.so index c54c7338740..18158fb4d69 100644 Binary files a/forge-gui-android/libs/armeabi/libgdx.so and b/forge-gui-android/libs/armeabi/libgdx.so differ diff --git a/forge-gui-android/libs/gdx-backend-android-sources.jar b/forge-gui-android/libs/gdx-backend-android-sources.jar index 5315cab4bdb..414fa961cfd 100644 Binary files a/forge-gui-android/libs/gdx-backend-android-sources.jar and b/forge-gui-android/libs/gdx-backend-android-sources.jar differ diff --git a/forge-gui-android/libs/gdx-backend-android.jar b/forge-gui-android/libs/gdx-backend-android.jar index c847c6e1c74..d780673aa6b 100644 Binary files a/forge-gui-android/libs/gdx-backend-android.jar and b/forge-gui-android/libs/gdx-backend-android.jar differ diff --git a/forge-gui-android/libs/gdx-freetype.jar b/forge-gui-android/libs/gdx-freetype.jar index cff324ecd35..eda8b39b168 100644 Binary files a/forge-gui-android/libs/gdx-freetype.jar and b/forge-gui-android/libs/gdx-freetype.jar differ diff --git a/forge-gui-android/libs/gdx-sources.jar b/forge-gui-android/libs/gdx-sources.jar index ca6c8a53100..798fee80a7b 100644 Binary files a/forge-gui-android/libs/gdx-sources.jar and b/forge-gui-android/libs/gdx-sources.jar differ diff --git a/forge-gui-android/libs/gdx.jar b/forge-gui-android/libs/gdx.jar index 7d93f8ba6b3..0ba4a3f2bb4 100644 Binary files a/forge-gui-android/libs/gdx.jar and b/forge-gui-android/libs/gdx.jar differ diff --git a/forge-gui-android/libs/x86/libgdx-freetype.so b/forge-gui-android/libs/x86/libgdx-freetype.so index 62c08dd6038..93020a63c0d 100644 Binary files a/forge-gui-android/libs/x86/libgdx-freetype.so and b/forge-gui-android/libs/x86/libgdx-freetype.so differ diff --git a/forge-gui-android/libs/x86/libgdx.so b/forge-gui-android/libs/x86/libgdx.so index 1f144b902b5..812a5ce27ca 100644 Binary files a/forge-gui-android/libs/x86/libgdx.so and b/forge-gui-android/libs/x86/libgdx.so differ diff --git a/forge-gui-android/libs/x86_64/libgdx-freetype.so b/forge-gui-android/libs/x86_64/libgdx-freetype.so new file mode 100644 index 00000000000..140daa47306 Binary files /dev/null and b/forge-gui-android/libs/x86_64/libgdx-freetype.so differ diff --git a/forge-gui-android/libs/x86_64/libgdx.so b/forge-gui-android/libs/x86_64/libgdx.so new file mode 100644 index 00000000000..03ca6fdc9b8 Binary files /dev/null and b/forge-gui-android/libs/x86_64/libgdx.so differ diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java index 67927bbf6a3..7b7fc22d6fa 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java @@ -1850,7 +1850,7 @@ public class GameSimulatorTest extends SimulationTestCase { Card simSpark = (Card)sim.getGameCopier().find(sparkDouble); assertNotNull(simSpark); - assertTrue(simSpark.getZone().is(ZoneType.Battlefield)); + assertTrue(simSpark.isInZone(ZoneType.Battlefield)); assertEquals(1, simSpark.getCounters(CounterType.P1P1)); assertEquals(5, simSpark.getCounters(CounterType.LOYALTY)); } diff --git a/forge-gui-ios/libs/gdx-backend-robovm-sources.jar b/forge-gui-ios/libs/gdx-backend-robovm-sources.jar index de757050e91..1ed5c6f5486 100644 Binary files a/forge-gui-ios/libs/gdx-backend-robovm-sources.jar and b/forge-gui-ios/libs/gdx-backend-robovm-sources.jar differ diff --git a/forge-gui-ios/libs/gdx-backend-robovm.jar b/forge-gui-ios/libs/gdx-backend-robovm.jar index 13e11a2c091..cbf789dd7b1 100644 Binary files a/forge-gui-ios/libs/gdx-backend-robovm.jar and b/forge-gui-ios/libs/gdx-backend-robovm.jar differ diff --git a/forge-gui-ios/libs/gdx-sources.jar b/forge-gui-ios/libs/gdx-sources.jar index ca6c8a53100..798fee80a7b 100644 Binary files a/forge-gui-ios/libs/gdx-sources.jar and b/forge-gui-ios/libs/gdx-sources.jar differ diff --git a/forge-gui-ios/libs/gdx.jar b/forge-gui-ios/libs/gdx.jar index 7d93f8ba6b3..0ba4a3f2bb4 100644 Binary files a/forge-gui-ios/libs/gdx.jar and b/forge-gui-ios/libs/gdx.jar differ diff --git a/forge-gui-ios/libs/libObjectAL.a b/forge-gui-ios/libs/libObjectAL.a index 671fe0f4107..6113d44ffc9 100644 Binary files a/forge-gui-ios/libs/libObjectAL.a and b/forge-gui-ios/libs/libObjectAL.a differ diff --git a/forge-gui-ios/libs/libgdx-freetype.a b/forge-gui-ios/libs/libgdx-freetype.a index 10d04063a66..67ed332cb29 100644 Binary files a/forge-gui-ios/libs/libgdx-freetype.a and b/forge-gui-ios/libs/libgdx-freetype.a differ diff --git a/forge-gui-ios/libs/libgdx.a b/forge-gui-ios/libs/libgdx.a index 6e7ffbc168c..038693b6c84 100644 Binary files a/forge-gui-ios/libs/libgdx.a and b/forge-gui-ios/libs/libgdx.a differ diff --git a/forge-gui-mobile-dev/libs/gdx-backend-lwjgl-sources.jar b/forge-gui-mobile-dev/libs/gdx-backend-lwjgl-sources.jar index 5a9f4b59ad6..f736a822dd8 100644 Binary files a/forge-gui-mobile-dev/libs/gdx-backend-lwjgl-sources.jar and b/forge-gui-mobile-dev/libs/gdx-backend-lwjgl-sources.jar differ diff --git a/forge-gui-mobile-dev/libs/gdx-backend-lwjgl.jar b/forge-gui-mobile-dev/libs/gdx-backend-lwjgl.jar index 7b1bc3d1143..d1699805b6d 100644 Binary files a/forge-gui-mobile-dev/libs/gdx-backend-lwjgl.jar and b/forge-gui-mobile-dev/libs/gdx-backend-lwjgl.jar differ diff --git a/forge-gui-mobile-dev/libs/gdx-freetype-natives.jar b/forge-gui-mobile-dev/libs/gdx-freetype-natives.jar index c338f9fa739..025707e2973 100644 Binary files a/forge-gui-mobile-dev/libs/gdx-freetype-natives.jar and b/forge-gui-mobile-dev/libs/gdx-freetype-natives.jar differ diff --git a/forge-gui-mobile-dev/libs/gdx-natives.jar b/forge-gui-mobile-dev/libs/gdx-natives.jar index efba8c4ab2e..18f0fbc3dcf 100644 Binary files a/forge-gui-mobile-dev/libs/gdx-natives.jar and b/forge-gui-mobile-dev/libs/gdx-natives.jar differ diff --git a/forge-gui-mobile/libs/gdx-freetype.jar b/forge-gui-mobile/libs/gdx-freetype.jar index cff324ecd35..eda8b39b168 100644 Binary files a/forge-gui-mobile/libs/gdx-freetype.jar and b/forge-gui-mobile/libs/gdx-freetype.jar differ diff --git a/forge-gui-mobile/libs/gdx-sources.jar b/forge-gui-mobile/libs/gdx-sources.jar index ca6c8a53100..798fee80a7b 100644 Binary files a/forge-gui-mobile/libs/gdx-sources.jar and b/forge-gui-mobile/libs/gdx-sources.jar differ diff --git a/forge-gui-mobile/libs/gdx.jar b/forge-gui-mobile/libs/gdx.jar index 7d93f8ba6b3..0ba4a3f2bb4 100644 Binary files a/forge-gui-mobile/libs/gdx.jar and b/forge-gui-mobile/libs/gdx.jar differ diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java index 65846082ef1..fef11349ffc 100644 --- a/forge-gui-mobile/src/forge/Forge.java +++ b/forge-gui-mobile/src/forge/Forge.java @@ -63,6 +63,9 @@ public class Forge implements ApplicationListener { public static float heigtModifier = 0.0f; private static boolean isloadingaMatch = 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) { if (GuiBase.getInterface() == null) { @@ -105,8 +108,10 @@ public class Forge implements ApplicationListener { FSkin.loadLight(skinName, splashScreen); textureFiltering = prefs.getPrefBoolean(FPref.UI_LIBGDX_TEXTURE_FILTERING); - 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(); @@ -121,15 +126,15 @@ public class Forge implements ApplicationListener { FModel.initialize(splashScreen.getProgressBar(), null); splashScreen.getProgressBar().setDescription(localizer.getMessage("lblLoadingFonts")); - FSkinFont.preloadAll(prefs.getPref(FPref.UI_LANGUAGE)); + FSkinFont.preloadAll(locale); splashScreen.getProgressBar().setDescription(localizer.getMessage("lblLoadingCardTranslations")); - CardTranslation.preloadTranslation(prefs.getPref(FPref.UI_LANGUAGE)); + CardTranslation.preloadTranslation(locale); splashScreen.getProgressBar().setDescription(localizer.getMessage("lblFinishingStartup")); //add reminder to preload - if (prefs.getPrefBoolean(FPref.UI_ENABLE_PRELOAD_EXTENDED_ART)) + if (enablePreloadExtendedArt) splashScreen.getProgressBar().setDescription("Preload Extended Art..."); Gdx.app.postRunnable(new Runnable() { @Override @@ -145,7 +150,7 @@ public class Forge implements ApplicationListener { } private void preloadExtendedArt() { - if (!FModel.getPreferences().getPrefBoolean(FPref.UI_ENABLE_PRELOAD_EXTENDED_ART)) + if (!enablePreloadExtendedArt) return; List keys = new ArrayList<>(); File[] directories = new File(ForgeConstants.CACHE_CARD_PICS_DIR).listFiles(new FileFilter() { diff --git a/forge-gui-mobile/src/forge/Graphics.java b/forge-gui-mobile/src/forge/Graphics.java index d00c86c6802..366eecc54bf 100644 --- a/forge-gui-mobile/src/forge/Graphics.java +++ b/forge-gui-mobile/src/forge/Graphics.java @@ -563,6 +563,18 @@ public class Graphics { } 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) { drawImage(image, x, y, w, h, false); } diff --git a/forge-gui-mobile/src/forge/assets/FSkinFont.java b/forge-gui-mobile/src/forge/assets/FSkinFont.java index 88ccba3806d..c6436bfd3d4 100644 --- a/forge-gui-mobile/src/forge/assets/FSkinFont.java +++ b/forge-gui-mobile/src/forge/assets/FSkinFont.java @@ -16,6 +16,7 @@ import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFont import com.badlogic.gdx.graphics.glutils.PixmapTextureData; import com.badlogic.gdx.utils.Array; import forge.FThreads; +import forge.Forge; import forge.properties.ForgeConstants; import forge.util.FileUtil; import forge.util.TextBounds; @@ -26,8 +27,10 @@ import java.util.Map; public class FSkinFont { private static final int MIN_FONT_SIZE = 8; - private static final int MAX_FONT_SIZE = 72; - private static final int MAX_FONT_SIZE_ZH_CN = 28; + private static int MAX_FONT_SIZE = 72; + + 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 Map fonts = new HashMap<>(); @@ -60,8 +63,9 @@ public class FSkinFont { //pre-load all supported font sizes public static void preloadAll(String language) { - int maxfontSize = (language.equals("zh-CN")) ? MAX_FONT_SIZE_ZH_CN : MAX_FONT_SIZE; - for (int size = MIN_FONT_SIZE; size <= maxfontSize; size++) { + //todo:really check the language glyph is a lot + 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); } } @@ -357,7 +361,8 @@ public class FSkinFont { //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) //maybe using libgdx-hiero generate font cache is better - chars += "●、。「」『』一丁七万三上下不与丑专且世丘业丛东丝两严丧个中" + if (Forge.locale.equals("zh-CN")) + chars += "●、。「」『』一丁七万三上下不与丑专且世丘业丛东丝两严丧个中" + "丰临丸丹为主丽举乃久么义之乌乍乐乔乖乘乙九也乡书乱乳乾了予争" + "事二于云互五井亘亚些亡交亥亦产享京亮亲亵人亿什仁仅仆仇今介仍" + "从仑仓仕他仗付仙代令以仪们仰仲件价任份仿伊伍伏伐休众优伙会伟" diff --git a/forge-gui-mobile/src/forge/assets/ImageCache.java b/forge-gui-mobile/src/forge/assets/ImageCache.java index 3cbf1a10b3d..5c7f63dfd99 100644 --- a/forge-gui-mobile/src/forge/assets/ImageCache.java +++ b/forge-gui-mobile/src/forge/assets/ImageCache.java @@ -231,7 +231,9 @@ public class ImageCache { return true; return false; } - public static FImage getBorderImage(CardView c) { + public static FImage getBorderImage(CardView c, boolean canshow) { + if (!canshow) + return BlackBorder; if (isWhiteBordered(c)) return WhiteBorder; return BlackBorder; @@ -241,4 +243,29 @@ public class ImageCache { return WhiteBorder; 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; + } } diff --git a/forge-gui-mobile/src/forge/assets/ImageLoader.java b/forge-gui-mobile/src/forge/assets/ImageLoader.java index 463fdd783fd..814c9ff84ab 100644 --- a/forge-gui-mobile/src/forge/assets/ImageLoader.java +++ b/forge-gui-mobile/src/forge/assets/ImageLoader.java @@ -8,12 +8,14 @@ import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.TextureData; import com.badlogic.gdx.graphics.glutils.PixmapTextureData; +import forge.FThreads; import org.cache2k.integration.CacheLoader; import forge.Forge; import forge.ImageKeys; final class ImageLoader extends CacheLoader { + Texture n; @Override public Texture load(String key) { boolean extendedArt = false; @@ -44,23 +46,28 @@ final class ImageLoader extends CacheLoader { public Texture generateTexture(FileHandle fh, Texture t, boolean textureFilter) { if (t == null || fh == null) return t; - Pixmap pImage = new Pixmap(fh); - int w = pImage.getWidth(); - int h = pImage.getHeight(); - int radius = (h - w) / 8; - Pixmap pMask = createRoundedRectangle(w, h, radius, Color.RED); - drawPixelstoMask(pImage, pMask); - TextureData textureData = new PixmapTextureData( - pMask, //pixmap to use - Pixmap.Format.RGBA8888, - textureFilter, //use mipmaps - false, true); - t = new Texture(textureData); - if (textureFilter) - t.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear); - pImage.dispose(); - pMask.dispose(); - return t; + FThreads.invokeInEdtNowOrLater(new Runnable() { + @Override + public void run() { + Pixmap pImage = new Pixmap(fh); + int w = pImage.getWidth(); + int h = pImage.getHeight(); + int radius = (h - w) / 8; + Pixmap pMask = createRoundedRectangle(w, h, radius, Color.RED); + drawPixelstoMask(pImage, pMask); + TextureData textureData = new PixmapTextureData( + pMask, //pixmap to use + Pixmap.Format.RGBA8888, + textureFilter, //use mipmaps + false, true); + n = new Texture(textureData); + if (textureFilter) + 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) { Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888); diff --git a/forge-gui-mobile/src/forge/card/CardImage.java b/forge-gui-mobile/src/forge/card/CardImage.java index 2b94a2e6073..ae4d4a5456a 100644 --- a/forge-gui-mobile/src/forge/card/CardImage.java +++ b/forge-gui-mobile/src/forge/card/CardImage.java @@ -2,14 +2,13 @@ package forge.card; import com.badlogic.gdx.graphics.Texture; +import forge.Forge; import forge.Graphics; import forge.assets.FImage; import forge.assets.ImageCache; import forge.card.CardRenderer.CardStackPosition; import forge.game.card.CardView; import forge.item.PaperCard; -import forge.model.FModel; -import forge.properties.ForgePreferences; import forge.toolbox.FCardPanel; public class CardImage implements FImage { @@ -19,9 +18,6 @@ public class CardImage implements FImage { public CardImage(PaperCard card0) { card = card0; } - private static boolean isPreferenceEnabled(ForgePreferences.FPref preferenceName) { - return FModel.getPreferences().getPrefBoolean(preferenceName); - } @Override public float getWidth() { @@ -38,11 +34,10 @@ public class CardImage implements FImage { @Override 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 image = ImageCache.getImage(card); 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); 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); } else { - if (mask) { + if (Forge.enableUIMask) { if (ImageCache.isExtendedArt(card)) g.drawImage(image, x, y, w, h); else { diff --git a/forge-gui-mobile/src/forge/card/CardImageRenderer.java b/forge-gui-mobile/src/forge/card/CardImageRenderer.java index 5fe46790263..732427443eb 100644 --- a/forge-gui-mobile/src/forge/card/CardImageRenderer.java +++ b/forge-gui-mobile/src/forge/card/CardImageRenderer.java @@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.utils.Align; import com.google.common.collect.ImmutableList; +import forge.Forge; import forge.Graphics; import forge.assets.FBufferedImage; 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) { - 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); if (image == null) { //draw details if can't draw zoom 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 rotatePlane = FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ROTATE_PLANE_OR_PHENOMENON); if (rotatePlane && (card.getCurrentState().isPhenomenon() || card.getCurrentState().isPlane())) { - if (mask){ + if (Forge.enableUIMask){ 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); 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); } else if (rotateSplit && isCurrentCard && card.isSplitCard() && canLook) { boolean isAftermath = card.getText().contains("Aftermath") || card.getAlternateState().getOracleText().contains("Aftermath"); - if (mask) { + if (Forge.enableUIMask) { 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); 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); } else { - if (mask) { + if (Forge.enableUIMask) { if (ImageCache.isExtendedArt(card)) g.drawImage(image, x, y, w, h); 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); } } @@ -561,8 +560,4 @@ public class CardImageRenderer { 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); } - - private static boolean isPreferenceEnabled(ForgePreferences.FPref preferenceName) { - return FModel.getPreferences().getPrefBoolean(preferenceName); - } } diff --git a/forge-gui-mobile/src/forge/card/CardRenderer.java b/forge-gui-mobile/src/forge/card/CardRenderer.java index 7a693f153cf..77fae4d5bd1 100644 --- a/forge-gui-mobile/src/forge/card/CardRenderer.java +++ b/forge-gui-mobile/src/forge/card/CardRenderer.java @@ -16,6 +16,7 @@ import com.badlogic.gdx.graphics.glutils.PixmapTextureData; import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Array; import forge.CachedCardImage; +import forge.Forge; import forge.FThreads; import forge.Graphics; import forge.StaticData; @@ -120,6 +121,8 @@ public class CardRenderer { } public static Color getRarityColor(CardRarity rarity) { + if (rarity == null)// NPE from Rarity weird... + return Color.CLEAR; switch(rarity) { case 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) { - boolean mask = isPreferenceEnabled(FPref.UI_ENABLE_BORDER_MASKING); Texture image = new RendererCachedCardImage(pc, false).getImage(); float radius = (h - w)/8; @@ -401,7 +403,7 @@ public class CardRenderer { CardImageRenderer.drawCardImage(g, CardView.getCardForUi(pc), false, x, y, w, h, pos); } else { - if (mask) { + if (Forge.enableUIMask) { if (ImageCache.isExtendedArt(pc)) g.drawImage(image, x, y, w, h); else { @@ -421,15 +423,13 @@ public class CardRenderer { } } 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); else //draw cards without textures as just a black rectangle 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) { - boolean mask = isPreferenceEnabled(FPref.UI_ENABLE_BORDER_MASKING); Texture image = new RendererCachedCardImage(card, false).getImage(); float radius = (h - w)/8; @@ -440,7 +440,7 @@ public class CardRenderer { else { if(FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ROTATE_PLANE_OR_PHENOMENON) && (card.getCurrentState().isPhenomenon() || card.getCurrentState().isPlane()) && rotate){ - if (mask) { + if (Forge.enableUIMask) { if (ImageCache.isExtendedArt(card)) g.drawRotatedImage(image, x, y, w, h, x + w / 2, y + h / 2, -90); else { @@ -452,11 +452,12 @@ public class CardRenderer { g.drawRotatedImage(image, x, y, w, h, x + w / 2, y + h / 2, -90); } else { - if (mask) { + if (Forge.enableUIMask) { if (ImageCache.isExtendedArt(card)) g.drawImage(image, x, y, w, h); 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); } } @@ -467,7 +468,7 @@ public class CardRenderer { drawFoilEffect(g, card, x, y, w, h, false); } 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); else //draw cards without textures as just a black rectangle 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) { + boolean canShow = MatchController.instance.mayView(card); + float oldAlpha = g.getfloatAlphaComposite(); + boolean unselectable = !MatchController.instance.isSelectable(card) && MatchController.instance.isSelecting(); float cx, cy, cw, ch; cx = x; cy = y; cw = w; ch = h; drawCard(g, card, x, y, w, h, pos, false); @@ -485,10 +489,6 @@ public class CardRenderer { w -= 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. final CardStateView details = card.getCurrentState(); final boolean isFaceDown = card.isFaceDown(); diff --git a/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java b/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java index 663be9d606d..6ab403d951f 100644 --- a/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java +++ b/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java @@ -1,5 +1,6 @@ package forge.itemmanager.views; +import forge.Forge; import forge.Forge.KeyInputAdapter; import forge.Graphics; import forge.assets.FImage; @@ -181,7 +182,7 @@ public class ImageView extends ItemView { getPnlOptions().add(cbPileByOptions); Group group = new Group(""); //add default group - groups.add(group); + groups.add(group); getScroller().add(group); } @@ -300,7 +301,7 @@ public class ImageView extends ItemView { updateLayout(false); return; } - + float offsetTop = focalItem0.getTop() - getScrollValue(); updateLayout(false); setScrollValue(focalItem0.getTop() - offsetTop); @@ -891,7 +892,7 @@ public class ImageView extends ItemView { public void draw(Graphics g) { final float visibleTop = getScrollValue(); final float visibleBottom = visibleTop + getScroller().getHeight(); - + ItemInfo skippedItem = null; for (ItemInfo itemInfo : items) { if (itemInfo.getBottom() < visibleTop) { @@ -954,9 +955,18 @@ public class ImageView extends ItemView { final float w = getWidth(); final float h = getHeight(); - if (selected) { - g.fillRect(Color.GREEN, x - SEL_BORDER_SIZE, y - SEL_BORDER_SIZE, - w + 2 * SEL_BORDER_SIZE, h + 2 * SEL_BORDER_SIZE); + if (selected) { //if round border is enabled, the select highlight is also rounded.. + if (Forge.enableUIMask) { + //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) { diff --git a/forge-gui-mobile/src/forge/screens/match/views/VField.java b/forge-gui-mobile/src/forge/screens/match/views/VField.java index ff645ad5e12..18f4e476473 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VField.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VField.java @@ -129,6 +129,7 @@ public class VField extends FContainer { cardName.equals(c.getCurrentState().getName()) && card.hasSameCounters(c) && 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.isToken() == c.isToken()) { //don't stack tokens on top of non-tokens CardAreaPanel cPanel = CardAreaPanel.get(c); diff --git a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java index 63c84137f51..7c2b0cd4711 100644 --- a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java +++ b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java @@ -260,7 +260,7 @@ public class SettingsPage extends TabPage { TabPageScreen.COMPACT_TABS = FModel.getPreferences().getPrefBoolean(FPref.UI_COMPACT_TABS); parentScreen.revalidate(); } - }, 4); + },4); lstSettings.addItem(new BooleanSetting(FPref.UI_COMPACT_LIST_ITEMS, localizer.getMessage("lblCompactListItems"), localizer.getMessage("nlCompactListItems")), @@ -302,32 +302,42 @@ public class SettingsPage extends TabPage { localizer.getMessage("nlDisableCardEffect")), 4); lstSettings.addItem(new BooleanSetting(FPref.UI_ENABLE_BORDER_MASKING, - "Enable Round Border Mask", - "When enabled, the card corners are rounded (Preferably Card with Full Borders)."), - 4); + "Enable Round Border Mask", + "When enabled, the card corners are rounded (Preferably Card with Full Borders)."){ + @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, - "Preload Extended Art Cards", - "When enabled, Preloads Extended Art Cards to Cache on Startup."), - 4); + "Preload Extended Art Cards", + "When enabled, Preloads Extended Art Cards to Cache on Startup."){ + @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, - "Show FPS Display", - "When enabled, show the FPS Display (Experimental)."){ - @Override - public void select() { - super.select(); - //update - Forge.showFPS = FModel.getPreferences().getPrefBoolean(FPref.UI_SHOW_FPS); - } - },4); - + "Show FPS Display", + "When enabled, show the FPS Display (Experimental)."){ + @Override + public void select() { + super.select(); + //update + Forge.showFPS = FModel.getPreferences().getPrefBoolean(FPref.UI_SHOW_FPS); + } + },4); lstSettings.addItem(new CustomSelectSetting(FPref.UI_CARD_COUNTER_DISPLAY_TYPE, - localizer.getMessage("cbpCounterDisplayType"), - localizer.getMessage("nlCounterDisplayType"), - new String[]{ - ForgeConstants.CounterDisplayType.TEXT.getName(), ForgeConstants.CounterDisplayType.IMAGE.getName(), - ForgeConstants.CounterDisplayType.HYBRID.getName(), ForgeConstants.CounterDisplayType.OLD_WHEN_SMALL.getName()}), + localizer.getMessage("cbpCounterDisplayType"), + localizer.getMessage("nlCounterDisplayType"), + new String[]{ + ForgeConstants.CounterDisplayType.TEXT.getName(), ForgeConstants.CounterDisplayType.IMAGE.getName(), + ForgeConstants.CounterDisplayType.HYBRID.getName(), ForgeConstants.CounterDisplayType.OLD_WHEN_SMALL.getName()}), 4); - //Card Overlays lstSettings.addItem(new BooleanSetting(FPref.UI_SHOW_CARD_OVERLAYS, localizer.getMessage("lblShowCardOverlays"), @@ -353,7 +363,6 @@ public class SettingsPage extends TabPage { localizer.getMessage("lblShowAbilityIconsOverlays"), localizer.getMessage("nlShowAbilityIconsOverlays")), 5); - //Vibration Options lstSettings.addItem(new BooleanSetting(FPref.UI_VIBRATE_ON_LIFE_LOSS, localizer.getMessage("lblVibrateWhenLosingLife"), @@ -363,7 +372,6 @@ public class SettingsPage extends TabPage { localizer.getMessage("lblVibrateAfterLongPress"), localizer.getMessage("nlVibrateAfterLongPress")), 6); - //Sound Options lstSettings.addItem(new BooleanSetting(FPref.UI_ENABLE_SOUNDS, localizer.getMessage("cbEnableSounds"), @@ -378,7 +386,7 @@ public class SettingsPage extends TabPage { //update background music when this setting changes SoundSystem.instance.changeBackgroundTrack(); } - }, 7); + },7); /*lstSettings.addItem(new BooleanSetting(FPref.UI_ALT_SOUND_SYSTEM, "Use Alternate Sound System", "Use the alternate sound system (only use if you have issues with sound not playing or disappearing)."), diff --git a/forge-gui/res/cardsfolder/a/autumn_willow.txt b/forge-gui/res/cardsfolder/a/autumn_willow.txt index 4ec308f4e5b..b9df47db469 100644 --- a/forge-gui/res/cardsfolder/a/autumn_willow.txt +++ b/forge-gui/res/cardsfolder/a/autumn_willow.txt @@ -3,7 +3,7 @@ ManaCost:4 G G Types:Legendary Creature Avatar PT:4/4 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 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. diff --git a/forge-gui/res/cardsfolder/p/peace_talks.txt b/forge-gui/res/cardsfolder/p/peace_talks.txt index 37e12faf05b..a3305e52628 100644 --- a/forge-gui/res/cardsfolder/p/peace_talks.txt +++ b/forge-gui/res/cardsfolder/p/peace_talks.txt @@ -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. 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: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 Oracle:This turn and next turn, creatures can't attack, and players and permanents can't be the targets of spells or activated abilities. diff --git a/forge-gui/res/cardsfolder/s/spectral_shield.txt b/forge-gui/res/cardsfolder/s/spectral_shield.txt index f37807ffe07..ee34f2530c8 100644 --- a/forge-gui/res/cardsfolder/s/spectral_shield.txt +++ b/forge-gui/res/cardsfolder/s/spectral_shield.txt @@ -3,6 +3,7 @@ ManaCost:1 W U Types:Enchantment Aura K:Enchant creature 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 Oracle:Enchant creature\nEnchanted creature gets +0/+2 and can't be the target of spells. diff --git a/forge-gui/res/cardsfolder/v/veil_of_summer.txt b/forge-gui/res/cardsfolder/v/veil_of_summer.txt index 54941da97e1..d0124692772 100644 --- a/forge-gui/res/cardsfolder/v/veil_of_summer.txt +++ b/forge-gui/res/cardsfolder/v/veil_of_summer.txt @@ -1,8 +1,8 @@ Name:Veil of Summer ManaCost:G 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.) -SVar:DBEffect:DB$ Effect | Name$ CARDNAME Effect | StaticAbilities$ AntiMagic | SubAbility$ DBPump +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 | 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: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 diff --git a/forge-gui/res/deckgendecks/Standard.lda.dat b/forge-gui/res/deckgendecks/Standard.lda.dat index e5487bcf6bd..b95d983a1f6 100644 Binary files a/forge-gui/res/deckgendecks/Standard.lda.dat and b/forge-gui/res/deckgendecks/Standard.lda.dat differ diff --git a/forge-gui/res/deckgendecks/Standard.raw.dat b/forge-gui/res/deckgendecks/Standard.raw.dat index cf78609fe0a..69a2206d8b1 100644 Binary files a/forge-gui/res/deckgendecks/Standard.raw.dat and b/forge-gui/res/deckgendecks/Standard.raw.dat differ diff --git a/forge-gui/res/puzzle/PS_ELD1.pzl b/forge-gui/res/puzzle/PS_ELD1.pzl new file mode 100644 index 00000000000..caea4cc1864 --- /dev/null +++ b/forge-gui/res/puzzle/PS_ELD1.pzl @@ -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 diff --git a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java index c906151501c..bc91fede027 100644 --- a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java +++ b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java @@ -58,7 +58,7 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards { public final void setCurrentPlayer(PlayerView 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(); }