From f562ae6fdb070facb0162d233ccb18f211115d95 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Mon, 3 Feb 2025 22:12:03 +0100 Subject: [PATCH] More view cleanup (#6967) --- .../main/java/forge/ai/ComputerUtilMana.java | 4 +- .../src/main/java/forge/game/GameAction.java | 2 +- .../main/java/forge/game/StaticEffect.java | 6 +- .../game/ability/effects/AnimateEffect.java | 2 +- .../game/ability/effects/PumpAllEffect.java | 4 +- .../game/ability/effects/PumpEffect.java | 4 +- .../src/main/java/forge/game/card/Card.java | 1342 ++++++++--------- .../main/java/forge/game/player/Player.java | 26 +- .../StaticAbilityContinuous.java | 9 +- .../res/cardsfolder/p/pias_revolution.txt | 2 +- 10 files changed, 696 insertions(+), 705 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index b537238aaad..7bebb6c549e 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -1326,7 +1326,9 @@ public static ManaCostBeingPaid calculateManaCost(final Cost cost, final SpellAb } } - CostAdjustment.adjust(manaCost, sa, null, test); + if (!effect) { + CostAdjustment.adjust(manaCost, sa, null, test); + } if ("NumTimes".equals(sa.getParam("Announce"))) { // e.g. the Adversary cycle ManaCost mkCost = sa.getPayCosts().getTotalMana(); diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 1e41bbad16e..ae42e2138bb 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -1276,7 +1276,7 @@ public boolean visit(final Card c) { // Update P/T and type in the view only once after all the cards have been processed, to avoid flickering for (Card c : affectedCards) { c.updateNameforView(); - c.updatePowerToughnessForView(); + c.updatePTforView(); c.updateTypesForView(); c.updateKeywords(); } diff --git a/forge-game/src/main/java/forge/game/StaticEffect.java b/forge-game/src/main/java/forge/game/StaticEffect.java index 0809f4cdf90..c1f8eeaf6c4 100644 --- a/forge-game/src/main/java/forge/game/StaticEffect.java +++ b/forge-game/src/main/java/forge/game/StaticEffect.java @@ -267,7 +267,7 @@ final CardCollectionView remove(List layers) { if (hasParam("AddAbility") || hasParam("GainsAbilitiesOf") || hasParam("GainsAbilitiesOfDefined") || hasParam("GainsTriggerAbsOf") || hasParam("AddTrigger") || hasParam("AddStaticAbility") - || hasParam("AddReplacementEffects") || hasParam("RemoveAllAbilities") + || hasParam("AddReplacementEffect") || hasParam("RemoveAllAbilities") || hasParam("RemoveLandTypes")) { affectedCard.removeChangedCardTraits(getTimestamp(), ability.getId()); } @@ -282,9 +282,9 @@ final CardCollectionView remove(List layers) { affectedCard.updateKeywordsCache(affectedCard.getCurrentState()); } - if (layers.contains(StaticAbilityLayer.SETPT)) { + if (layers.contains(StaticAbilityLayer.CHARACTERISTIC) || layers.contains(StaticAbilityLayer.SETPT)) { if (hasParam("SetPower") || hasParam("SetToughness")) { - affectedCard.removeNewPT(getTimestamp(), ability.getId()); + affectedCard.removeNewPT(getTimestamp(), ability.getId(), false); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffect.java index 07d83ec9087..a01de5d1d9b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffect.java @@ -202,7 +202,7 @@ public void resolve(final SpellAbility sa) { if (sa.isCrew()) { gameCard.becomesCrewed(sa); - gameCard.updatePowerToughnessForView(); + gameCard.updatePTforView(); } game.fireEvent(new GameEventCardStatsChanged(gameCard)); diff --git a/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java index 28df9b4a6cb..2d54ce2bd79 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java @@ -71,7 +71,7 @@ private static void applyPumpAll(final SpellAbility sa, tgtC.addChangedCardKeywords(kws, null, false, timestamp, null); } if (redrawPT) { - tgtC.updatePowerToughnessForView(); + tgtC.updatePTforView(); } if (!hiddenkws.isEmpty()) { @@ -93,7 +93,7 @@ public void run() { tgtC.removeChangedCardKeywords(timestamp, 0); tgtC.removeHiddenExtrinsicKeywords(timestamp, 0); - tgtC.updatePowerToughnessForView(); + tgtC.updatePTforView(); game.fireEvent(new GameEventCardStatsChanged(tgtC)); } 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 facd34c31e6..5a3f5cb3012 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 @@ -84,7 +84,7 @@ private static void applyPump(final SpellAbility sa, final Card applyTo, gameCard.addHiddenExtrinsicKeywords(timestamp, 0, hiddenKws); } if (redrawPT) { - gameCard.updatePowerToughnessForView(); + gameCard.updatePTforView(); } if (sa.hasParam("CanBlockAny")) { @@ -120,7 +120,7 @@ public void run() { gameCard.removeHiddenExtrinsicKeywords(timestamp, 0); gameCard.removeChangedCardKeywords(timestamp, 0); } - gameCard.updatePowerToughnessForView(); + gameCard.updatePTforView(); if (updateText) { gameCard.updateAbilityTextForView(); } 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 960235e8ab2..50174123d2f 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -408,11 +408,8 @@ public Card(final int id0, final IPaperCard paperCard0, final Game game0, final setChosenColorID(paperCard.getColorID()); } - public boolean changeToState(final CardStateName state) { - if (hasState(state)) { - return setState(state, true); - } - return false; + public int getHiddenId() { + return view.getHiddenId(); } public long getPrototypeTimestamp() { return prototypeTimestamp; } @@ -421,6 +418,45 @@ public boolean changeToState(final CardStateName state) { public void incrementTransformedTimestamp() { this.transformedTimestamp++; } public void undoIncrementTransformedTimestamp() { this.transformedTimestamp--; } + // The following methods are used to selectively update certain view components (text, + // P/T, card types) in order to avoid card flickering due to aggressive full update + public void updateAbilityTextForView() { + view.getCurrentState().updateAbilityText(this, getCurrentState()); + } + + public void updateManaCostForView() { + currentState.getView().updateManaCost(this); + } + + public void updatePTforView() { + getView().updateLethalDamage(this); + currentState.getView().updatePower(this); + currentState.getView().updateToughness(this); + } + + public final void updateTypesForView() { + currentState.getView().updateType(currentState); + } + + public final void updateColorForView() { + currentState.getView().updateColors(this); + currentState.getView().updateHasChangeColors(!Iterables.isEmpty(getChangedCardColors())); + } + + public void updateAttackingForView() { + view.updateAttacking(this); + getGame().updateCombatForView(); + } + public void updateBlockingForView() { + view.updateBlocking(this); + //ensure blocking arrow shown/hidden as needed + getGame().updateCombatForView(); + } + + public void updateStateForView() { + view.updateState(this); + } + public CardState getCurrentState() { return currentState; } @@ -498,6 +534,13 @@ public void setOriginalStateAsFaceDown() { states.put(CardStateName.Original, currentState); } + public boolean changeToState(final CardStateName state) { + if (hasState(state)) { + return setState(state, true); + } + return false; + } + public boolean setState(final CardStateName state, boolean updateView) { return setState(state, updateView, false); } @@ -537,7 +580,7 @@ public boolean setState(final CardStateName state, boolean updateView, boolean f currentState = getState(state); if (updateView) { - view.updateState(this); + updateStateForView(); view.updateNeedsTransformAnimation(needsTransformAnimation); if (game != null) { @@ -577,7 +620,7 @@ public void setStates(Map map) { public final void addAlternateState(final CardStateName state, final boolean updateView) { states.put(state, new CardState(this, state)); if (updateView) { - view.updateState(this); + updateStateForView(); } } @@ -589,38 +632,17 @@ public void clearStates(final CardStateName state, boolean updateView) { currentStateName = CardStateName.Original; } if (updateView) { - view.updateState(this); + updateStateForView(); } } - public void updateStateForView() { - view.updateState(this); - } - - // The following methods are used to selectively update certain view components (text, - // P/T, card types) in order to avoid card flickering due to aggressive full update - public void updateAbilityTextForView() { - view.getCurrentState().updateAbilityText(this, getCurrentState()); - } - - public void updateManaCostForView() { - currentState.getView().updateManaCost(this); - } - - public final void updatePowerToughnessForView() { - view.updateCounters(this); - } - - public final void updateTypesForView() { - currentState.getView().updateType(currentState); - } - public boolean changeCardState(final String mode, final String customState, final SpellAbility cause) { if (isPhasedOut()) { return false; } - if (mode == null) + if (mode == null) { return changeToState(CardStateName.smartValueOf(customState)); + } // flip and face-down don't overlap. That is there is no chance to turn face down a flipped permanent // and then any effect have it turn upface again and demand its former flip state to be restored @@ -916,19 +938,6 @@ public boolean canTransform(SpellAbility cause) { return !StaticAbilityCantTransform.cantTransform(this, cause); } - public int getHiddenId() { - return view.getHiddenId(); - } - - public void updateAttackingForView() { - view.updateAttacking(this); - getGame().updateCombatForView(); - } - public void updateBlockingForView() { - view.updateBlocking(this); - getGame().updateCombatForView(); //ensure blocking arrow shown/hidden as needed - } - @Override public final String getName() { return getName(currentState, false); @@ -1080,6 +1089,31 @@ public boolean isCloned() { && clonedStates.lastEntry().getKey() != prototypeTimestamp; } + public final boolean isFaceDown() { + if (hasMergedCard()) { + return getTopMergedCard().facedown; + } + return facedown; + } + + public final boolean isRealFaceDown() { + return facedown; + } + public final void setFaceDown(boolean value) { + facedown = value; + } + + public final boolean isTransformed() { + return getTransformedTimestamp() != 0; + } + + public final boolean isFlipped() { + return flipped; + } + public final void setFlipped(boolean value) { + flipped = value; + } + public final CardCollectionView getDevouredCards() { return CardCollection.getView(devouredCards); } @@ -1222,6 +1256,41 @@ public final Map getChosenMap() { return chosenMap; } + public final CardCollectionView getGainControlTargets() { //used primarily with AbilityFactory_GainControl + return CardCollection.getView(gainControlTargets); + } + public final void addGainControlTarget(final Card c) { + gainControlTargets = view.addCard(gainControlTargets, c, TrackableProperty.GainControlTargets); + } + public final void removeGainControlTargets(final Card c) { + gainControlTargets = view.removeCard(gainControlTargets, c, TrackableProperty.GainControlTargets); + } + public final boolean hasGainControlTarget() { + return FCollection.hasElements(gainControlTargets); + } + public final boolean hasGainControlTarget(Card c) { + return FCollection.hasElement(gainControlTargets, c); + } + + public final CardCollectionView getUntilLeavesBattlefield() { + return CardCollection.getView(untilLeavesBattlefield); + } + public final void addUntilLeavesBattlefield(final Card c) { + untilLeavesBattlefield = view.addCard(untilLeavesBattlefield, c, TrackableProperty.UntilLeavesBattlefield); + } + public final void addUntilLeavesBattlefield(final Iterable cards) { + untilLeavesBattlefield = view.addCards(untilLeavesBattlefield, cards, TrackableProperty.UntilLeavesBattlefield); + } + public final void removeUntilLeavesBattlefield(final Card c) { + untilLeavesBattlefield = view.removeCard(untilLeavesBattlefield, c, TrackableProperty.UntilLeavesBattlefield); + } + public final void removeUntilLeavesBattlefield(final Iterable cards) { + untilLeavesBattlefield = view.removeCards(untilLeavesBattlefield, cards, TrackableProperty.UntilLeavesBattlefield); + } + public final void clearUntilLeavesBattlefield() { + untilLeavesBattlefield = view.clearCards(untilLeavesBattlefield, TrackableProperty.UntilLeavesBattlefield); + } + public final CardCollectionView getExiledCards() { return CardCollection.getView(exiledCards); } @@ -1247,6 +1316,48 @@ public final void clearExiledCards() { exiledCards = view.clearCards(exiledCards, TrackableProperty.ExiledCards); } + public final CardCollectionView getHauntedBy() { + return CardCollection.getView(hauntedBy); + } + public final boolean isHaunted() { + return FCollection.hasElements(hauntedBy); + } + public final boolean isHauntedBy(Card c) { + return FCollection.hasElement(hauntedBy, c); + } + public final void addHauntedBy(Card c, final boolean update) { + hauntedBy = view.addCard(hauntedBy, c, TrackableProperty.HauntedBy); + if (c != null && update) { + c.setHaunting(this); + } + } + public final void addHauntedBy(Card c) { + addHauntedBy(c, true); + } + public final void removeHauntedBy(Card c) { + hauntedBy = view.removeCard(hauntedBy, c, TrackableProperty.HauntedBy); + } + + public final Card getHaunting() { + return haunting; + } + public final void setHaunting(final Card c) { + haunting = view.setCard(haunting, c, TrackableProperty.Haunting); + } + + public final Card getPairedWith() { + return pairedWith; + } + public final void setPairedWith(final Card c) { + pairedWith = view.setCard(pairedWith, c, TrackableProperty.PairedWith); + } + public final boolean isPaired() { + return pairedWith != null; + } + + public Card getMeldedWith() { return meldedWith; } + public void setMeldedWith(Card meldedWith) { this.meldedWith = meldedWith; } + public final CardCollectionView getEncodedCards() { return CardCollection.getView(encodedCards); } @@ -1461,35 +1572,6 @@ public final void clearFlipResult() { flipResult = null; } - public final FCollectionView getTriggers() { - return currentState.getTriggers(); - } - public final Trigger addTrigger(final Trigger t) { - currentState.addTrigger(t); - return t; - } - - public final boolean hasTrigger(final Trigger t) { - return currentState.hasTrigger(t); - } - public final boolean hasTrigger(final int id) { - return currentState.hasTrigger(id); - } - - public void updateTriggers(List list, CardState state) { - for (final CardTraitChanges ck : getChangedCardTraitsList(state)) { - if (ck.isRemoveAll()) { - list.clear(); - } - list.addAll(ck.getTriggers()); - } - - // Keywords are already sorted by Layer - for (KeywordInterface kw : getUnhiddenKeywords(state)) { - list.addAll(kw.getTriggers()); - } - } - public final int getXManaCostPaid() { if (getCastSA() != null) { Integer paid = getCastSA().getXManaCostPaid(); @@ -1569,19 +1651,18 @@ public final void setCloneOrigin(final Card cloneOrigin0) { public final boolean hasFirstStrike() { return hasKeyword(Keyword.FIRST_STRIKE); } - public final boolean hasDoubleStrike() { return hasKeyword(Keyword.DOUBLE_STRIKE); } - - public final boolean hasDoubleTeam() { - return hasKeyword(Keyword.DOUBLE_TEAM); - } - public final boolean hasSecondStrike() { return hasDoubleStrike() || !hasFirstStrike(); } + public final boolean hasSuspend() { + return hasKeyword(Keyword.SUSPEND) && getLastKnownZone().is(ZoneType.Exile) + && getCounters(CounterEnumType.TIME) >= 1; + } + public final boolean hasConverge() { return "Count$Converge".equals(getSVar("X")) || "Count$Converge".equals(getSVar("Y")) || hasKeyword(Keyword.SUNBURST) || hasKeyword("Modular:Sunburst"); @@ -1865,6 +1946,21 @@ public final int sumAllCounters() { return count; } + public final void putEtbCounters(Map, Map> etbCounters) { + if (etbCounters == null) { + return; + } + // used for LKI + for (Map m : etbCounters.values()) { + for (Map.Entry e : m.entrySet()) { + CounterType ct = e.getKey(); + if (canReceiveCounters(ct)) { + setCounters(ct, getCounters(ct) + e.getValue()); + } + } + } + } + public final String getSVar(final String var) { for (Map map : changedSVars.values()) { if (map.containsKey(var)) { @@ -1969,6 +2065,13 @@ public final void setChosenPlayer(final Player p) { view.updateChosenPlayer(this); } + public final void setSecretChosenPlayer(final Player p) { + chosenPlayer = p; + } + public final void revealChosenPlayer() { + view.updateChosenPlayer(this); + } + public final boolean hasPromisedGift() { return promisedGift != null; } @@ -1990,13 +2093,6 @@ public final void setProtectingPlayer(final Player p) { view.updateProtectingPlayer(this); } - public final void setSecretChosenPlayer(final Player p) { - chosenPlayer = p; - } - public final void revealChosenPlayer() { - view.updateChosenPlayer(this); - } - public final boolean hasChosenNumber() { return chosenNumber != null; } @@ -2245,43 +2341,6 @@ public void setChosenEvenOdd(EvenOdd chosenEvenOdd0) { view.updateChosenEvenOdd(this); } - public final boolean getDrawnThisTurn() { - return drawnThisTurn; - } - public final void setDrawnThisTurn(final boolean b) { - drawnThisTurn = b; - } - - public final boolean getFoughtThisTurn() { - return foughtThisTurn; - } - public final void setFoughtThisTurn(final boolean b) { - foughtThisTurn = b; - } - - public final boolean getEnlistedThisCombat() { - return enlistedThisCombat; - } - public final void setEnlistedThisCombat(final boolean b) { - enlistedThisCombat = b; - } - - public final CardCollectionView getGainControlTargets() { //used primarily with AbilityFactory_GainControl - return CardCollection.getView(gainControlTargets); - } - public final void addGainControlTarget(final Card c) { - gainControlTargets = view.addCard(gainControlTargets, c, TrackableProperty.GainControlTargets); - } - public final void removeGainControlTargets(final Card c) { - gainControlTargets = view.removeCard(gainControlTargets, c, TrackableProperty.GainControlTargets); - } - public final boolean hasGainControlTarget() { - return FCollection.hasElements(gainControlTargets); - } - public final boolean hasGainControlTarget(Card c) { - return FCollection.hasElement(gainControlTargets, c); - } - public final String getSpellText() { return text; } @@ -3606,7 +3665,6 @@ public final void resetShieldCount() { public final void addRegeneratedThisTurn() { regeneratedThisTurn++; } - public final int getRegeneratedThisTurn() { return regeneratedThisTurn; } @@ -3669,35 +3727,6 @@ public final void setCopiedPermanent(final Card c) { //just stash the original oracle text if this comes up. } - public final boolean isFaceDown() { - if (hasMergedCard()) { - return getTopMergedCard().facedown; - } - return facedown; - } - - public final boolean isRealFaceDown() { - return facedown; - } - - public final void setFaceDown(boolean value) { - facedown = value; - } - - public final boolean isTransformed() { - return getTransformedTimestamp() != 0; - } - - public final boolean isFlipped() { - return flipped; - } - public final void setFlipped(boolean value) { - flipped = value; - } - - public final void addLeavesPlayCommand(final GameCommand c) { - leavePlayCommandList.add(c); - } public final void addUntapCommand(final GameCommand c) { untapCommandList.add(c); } @@ -3716,6 +3745,16 @@ public final void addChangeControllerCommand(final GameCommand c) { public final void addPhaseOutCommand(final GameCommand c) { phaseOutCommandList.add(c); } + public final void addLeavesPlayCommand(final GameCommand c) { + leavePlayCommandList.add(c); + } + + public void addStaticCommandList(Object[] objects) { + staticCommandList.add(objects); + } + public List getStaticCommandList() { + return staticCommandList; + } public final List getLeavesPlayCommands() { return leavePlayCommandList; @@ -4248,8 +4287,11 @@ public Table getChangedCardKeywords() { return changedCardKeywords; } - public Iterable getChangedCardColors() { - return Iterables.concat(changedCardColorsByText.values(), changedCardColorsCharacterDefining.values(), changedCardColors.values()); + public void setChangedCardKeywords(Table changedCardKeywords) { + this.changedCardKeywords.clear(); + for (Table.Cell entry : changedCardKeywords.cellSet()) { + this.changedCardKeywords.put(entry.getRowKey(), entry.getColumnKey(), entry.getValue().copy(this, true)); + } } public final void addChangedCardTypesByText(final CardType addType, final long timestamp, final long staticId) { @@ -4308,6 +4350,10 @@ public final void removeChangedCardTypes(final long timestamp, final long static } } + public Iterable getChangedCardColors() { + return Iterables.concat(changedCardColorsByText.values(), changedCardColorsCharacterDefining.values(), changedCardColors.values()); + } + public void addColorByText(final ColorSet color, final long timestamp, final long staticId) { changedCardColorsByText.put(timestamp, staticId, new CardColor(color, false)); updateColorForView(); @@ -4329,11 +4375,6 @@ public final void removeColor(final long timestampIn, final long staticId) { } } - public final void updateColorForView() { - currentState.getView().updateColors(this); - currentState.getView().updateHasChangeColors(!Iterables.isEmpty(getChangedCardColors())); - } - public final void setColor(final String... color) { setColor(ColorSet.fromNames(color).getColor()); } @@ -4519,31 +4560,30 @@ public final void addNewPTByText(final Integer power, final Integer toughness, f } public final void addNewPT(final Integer power, final Integer toughness, final long timestamp, final long staticId) { - addNewPT(power, toughness, timestamp, staticId, false); + addNewPT(power, toughness, timestamp, staticId, false, true); } - public final void addNewPT(final Integer power, final Integer toughness, final long timestamp, final long staticId, final boolean cda) { + public final void addNewPT(final Integer power, final Integer toughness, final long timestamp, final long staticId, final boolean cda, final boolean updateView) { (cda ? newPTCharacterDefining : newPT).put(timestamp, staticId, Pair.of(power, toughness)); - updatePTforView(); + if (updateView) { + updatePTforView(); + } } public final void removeNewPT(final long timestamp, final long staticId) { + removeNewPT(timestamp, staticId, true); + } + public final void removeNewPT(final long timestamp, final long staticId, final boolean updateView) { boolean removed = false; removed |= newPTText.remove(timestamp, staticId) != null; removed |= newPT.remove(timestamp, staticId) != null; removed |= newPTCharacterDefining.remove(timestamp, staticId) != null; - if (removed) { + if (removed && updateView) { updatePTforView(); } } - public void updatePTforView() { - getView().updateLethalDamage(this); - currentState.getView().updatePower(this); - currentState.getView().updateToughness(this); - } - public Iterable> getPTIterable() { return Iterables.concat(this.newPTText.values(), this.newPTCharacterDefining.values(), this.newPT.values()); } @@ -4679,6 +4719,52 @@ public final int getNetCombatDamage() { return assignNoCombatDamage() ? 0 : (toughnessAssignsDamage() ? getNetToughnessBreakdown() : getNetPowerBreakdown()).getTotal(); } + // for cards like Giant Growth, etc. + public final int getTempPowerBoost() { + int result = 0; + for (Pair pair : boostPT.values()) { + if (pair.getLeft() != null) { + result += pair.getLeft(); + } + } + return result; + } + + public final int getTempToughnessBoost() { + int result = 0; + for (Pair pair : boostPT.values()) { + if (pair.getRight() != null) { + result += pair.getRight(); + } + } + return result; + } + + public void addPTBoost(final Integer power, final Integer toughness, final long timestamp, final long staticId) { + boostPT.put(timestamp, staticId, Pair.of(power, toughness)); + } + + public void removePTBoost(final long timestamp, final long staticId) { + boostPT.remove(timestamp, staticId); + } + + public Table> getPTBoostTable() { + return ImmutableTable.copyOf(boostPT); + } + + public void setPTBoost(Table> table) { + this.boostPT.clear(); + boostPT.putAll(table); + } + + public List getDraftActions() { + return draftActions; + } + + public void addDraftAction(String s) { + draftActions.add(s); + } + private int intensity = 0; public final void addIntensity(final int n) { intensity += n; @@ -4762,52 +4848,6 @@ public final void setPerpetual(final Card oldCard) { } } - public final int getKickerMagnitude() { - if (this.getCastSA() != null && getCastSA().hasOptionalKeywordAmount(Keyword.MULTIKICKER)) { - return getCastSA().getOptionalKeywordAmount(Keyword.MULTIKICKER); - } - boolean hasK1 = isOptionalCostPaid(OptionalCost.Kicker1); - return hasK1 == isOptionalCostPaid(OptionalCost.Kicker2) ? (hasK1 ? 2 : 0) : 1; - } - - // for cards like Giant Growth, etc. - public final int getTempPowerBoost() { - int result = 0; - for (Pair pair : boostPT.values()) { - if (pair.getLeft() != null) { - result += pair.getLeft(); - } - } - return result; - } - - public final int getTempToughnessBoost() { - int result = 0; - for (Pair pair : boostPT.values()) { - if (pair.getRight() != null) { - result += pair.getRight(); - } - } - return result; - } - - public void addPTBoost(final Integer power, final Integer toughness, final long timestamp, final long staticId) { - boostPT.put(timestamp, staticId, Pair.of(power, toughness)); - } - - public void removePTBoost(final long timestamp, final long staticId) { - boostPT.remove(timestamp, staticId); - } - - public Table> getPTBoostTable() { - return ImmutableTable.copyOf(boostPT); - } - - public void setPTBoost(Table> table) { - this.boostPT.clear(); - boostPT.putAll(table); - } - public final boolean isUntapped() { return !tapped; } @@ -4879,23 +4919,6 @@ public final boolean untap(boolean untapAnimation) { return true; } - public final Table getChangedCardTraitsByText() { - return changedCardTraitsByText; - } - public final void setChangedCardTraitsByText(Table changes) { - changedCardTraitsByText.clear(); - for (Table.Cell e : changes.cellSet()) { - changedCardTraitsByText.put(e.getRowKey(), e.getColumnKey(), e.getValue().copy(this, true)); - } - } - public final void addChangedCardTraitsByText(Collection spells, - Collection trigger, Collection replacements, Collection statics, long timestamp, long staticId) { - changedCardTraitsByText.put(timestamp, staticId, new CardTraitChanges( - spells, null, trigger, replacements, statics, true, false - )); - updateAbilityTextForView(); - } - public final SpellAbility getSpellAbilityForStaticAbility(final String str, final StaticAbility stAb) { SpellAbility result = storedSpellAbility.get(stAb, str); if (!canUseCachedTrait(result, stAb)) { @@ -5036,13 +5059,37 @@ private boolean canUseCachedTrait(CardTraitBase cached, CardTraitBase stAb) { return cached.getChangedTextColors().equals(stAb.getChangedTextColors()) && cached.getChangedTextTypes().equals(stAb.getChangedTextTypes()); } + public final Table getChangedCardTraitsByText() { + return changedCardTraitsByText; + } + public final void setChangedCardTraitsByText(Table changes) { + changedCardTraitsByText.clear(); + for (Table.Cell e : changes.cellSet()) { + changedCardTraitsByText.put(e.getRowKey(), e.getColumnKey(), e.getValue().copy(this, true)); + } + } + public final void addChangedCardTraitsByText(Collection spells, + Collection trigger, Collection replacements, Collection statics, long timestamp, long staticId) { + changedCardTraitsByText.put(timestamp, staticId, new CardTraitChanges( + spells, null, trigger, replacements, statics, true, false + )); + updateAbilityTextForView(); + } + public final void addChangedCardTraits(Collection spells, Collection removedAbilities, Collection trigger, Collection replacements, Collection statics, boolean removeAll, boolean removeNonMana, long timestamp, long staticId) { + addChangedCardTraits(spells, removedAbilities, trigger, replacements, statics, removeAll, removeNonMana, timestamp, staticId, true); + } + public final void addChangedCardTraits(Collection spells, Collection removedAbilities, + Collection trigger, Collection replacements, Collection statics, + boolean removeAll, boolean removeNonMana, long timestamp, long staticId, boolean updateView) { changedCardTraits.put(timestamp, staticId, new CardTraitChanges( spells, removedAbilities, trigger, replacements, statics, removeAll, removeNonMana )); - updateAbilityTextForView(); + if (updateView) { + updateAbilityTextForView(); + } } public final void addChangedCardTraits(CardTraitChanges ctc, long timestamp, long staticId) { @@ -5322,17 +5369,204 @@ private void visitUnhiddenKeywords(CardState state, Visitor vi } } - public List getDraftActions() { - return draftActions; + public final KeywordInterface addIntrinsicKeyword(final String s) { + KeywordInterface inst = currentState.addIntrinsicKeyword(s, true); + if (inst != null) { + updateKeywords(); + } + return inst; } - public void addDraftAction(String s) { - draftActions.add(s); + public final void addIntrinsicKeywords(final Iterable s) { + addIntrinsicKeywords(s, true); + } + public final void addIntrinsicKeywords(final Iterable s, boolean initTraits) { + if (currentState.addIntrinsicKeywords(s, initTraits)) { + updateKeywords(); + } } - /** - * Replace all instances of one color word in this card's text by another. - * @param originalWord the original color word. + public final void removeIntrinsicKeyword(final String s) { + if (currentState.removeIntrinsicKeyword(s)) { + updateKeywords(); + } + } + + public final void removeIntrinsicKeyword(final KeywordInterface s) { + if (currentState.removeIntrinsicKeyword(s)) { + updateKeywords(); + } + } + + // Hidden Keywords will be returned without the indicator HIDDEN + public final Iterable getHiddenExtrinsicKeywords() { + return Iterables.concat(this.hiddenExtrinsicKeywords.values()); + } + public final Table> getHiddenExtrinsicKeywordsTable() { + return hiddenExtrinsicKeywords; + } + + public final void addHiddenExtrinsicKeywords(long timestamp, long staticId, Iterable keywords) { + // TODO if some keywords aren't removed anymore, then no need for extra Array List + hiddenExtrinsicKeywords.put(timestamp, staticId, Lists.newArrayList(keywords)); + + view.updateNonAbilityText(this); + updateKeywords(); + } + + public final void removeHiddenExtrinsicKeywords(long timestamp, long staticId) { + if (hiddenExtrinsicKeywords.remove(timestamp, staticId) != null) { + view.updateNonAbilityText(this); + updateKeywords(); + } + } + + public final void removeHiddenExtrinsicKeyword(String s) { + boolean updated = false; + for (List list : hiddenExtrinsicKeywords.values()) { + if (list.remove(s)) { + updated = true; + } + } + if (updated) { + view.updateNonAbilityText(this); + updateKeywords(); + } + } + + public final boolean hasStartOfKeyword(final String keyword) { + return hasStartOfKeyword(keyword, currentState); + } + public final boolean hasStartOfKeyword(String keyword, CardState state) { + for (String s : this.getHiddenExtrinsicKeywords()) { + if (s.startsWith(keyword)) { + return true; + } + } + + HasKeywordVisitor visitor = new HasKeywordVisitor(keyword, true); + visitKeywords(state, visitor); + return visitor.getResult(); + } + + public final boolean hasStartOfUnHiddenKeyword(String keyword) { + return hasStartOfUnHiddenKeyword(keyword, currentState); + } + public final boolean hasStartOfUnHiddenKeyword(String keyword, CardState state) { + HasKeywordVisitor visitor = new HasKeywordVisitor(keyword, true); + visitUnhiddenKeywords(state, visitor); + return visitor.getResult(); + } + + public final boolean hasAnyKeyword(final Iterable keywords) { + return hasAnyKeyword(keywords, currentState); + } + public final boolean hasAnyKeyword(final Iterable keywords, CardState state) { + for (final String keyword : keywords) { + if (hasKeyword(keyword, state)) { + return true; + } + } + return false; + } + + // This counts the number of instances of a keyword a card has + public final int getAmountOfKeyword(final String k) { + return getAmountOfKeyword(k, currentState); + } + public final int getAmountOfKeyword(final String k, CardState state) { + int count = Iterables.frequency(this.getHiddenExtrinsicKeywords(), k); + CountKeywordVisitor visitor = new CountKeywordVisitor(k); + visitKeywords(state, visitor); + return count + visitor.getCount(); + } + + public final int getAmountOfKeyword(final Keyword k) { + return getAmountOfKeyword(k, currentState); + } + public final int getAmountOfKeyword(final Keyword k, CardState state) { + return getKeywords(k, state).size(); + } + + public final Collection getKeywords(final Keyword k) { + return getKeywords(k, currentState); + } + public final Collection getKeywords(final Keyword k, CardState state) { + return state.getCachedKeyword(k); + } + + // This is for keywords with a number like Bushido, Annihilator and Rampage. + // It returns the total. + public final int getKeywordMagnitude(final Keyword k) { + return getKeywordMagnitude(k, currentState); + } + + /** + * use it only for real keywords and not with hidden ones + * + * @return Int + */ + public final int getKeywordMagnitude(final Keyword k, CardState state) { + int count = 0; + for (final KeywordInterface inst : getKeywords(k, state)) { + String kw = inst.getOriginal(); + // this can't be used yet for everything because of X values in Bushido X + // KeywordInterface#getAmount + // KeywordCollection#getAmount + + final String[] parse = kw.contains(":") ? kw.split(":") : kw.split(" "); + if (parse.length < 2) { + count++; + continue; + } + final String s = parse[1]; + if (StringUtils.isNumeric(s)) { + count += Integer.parseInt(s); + } else { + StaticAbility st = inst.getStatic(); + // TODO make keywordinterface inherit from CardTrait somehow, or invent new interface + if (st != null && st.hasSVar(s)) { + count += AbilityUtils.calculateAmount(this, st.getSVar(s), null); + } else { + String svar = StringUtils.join(parse); + if (state.hasSVar(svar)) { + count += AbilityUtils.calculateAmount(this, state.getSVar(svar), null); + } + } + } + } + return count; + } + + public void addCantHaveKeyword(Keyword keyword, Long timestamp) { + cantHaveKeywords.put(timestamp, keyword); + getView().updateCantHaveKeyword(this); + } + + public void addCantHaveKeyword(Long timestamp, Iterable keywords) { + cantHaveKeywords.putAll(timestamp, keywords); + getView().updateCantHaveKeyword(this); + } + + public boolean removeCantHaveKeyword(Long timestamp) { + return removeCantHaveKeyword(timestamp, true); + } + public boolean removeCantHaveKeyword(Long timestamp, boolean updateView) { + boolean change = !cantHaveKeywords.removeAll(timestamp).isEmpty(); + if (change && updateView) { + getView().updateCantHaveKeyword(this); + updateKeywords(); + } + return change; + } + + public Collection getCantHaveKeyword() { + return cantHaveKeywords.values(); + } + + /** + * Replace all instances of one color word in this card's text by another. + * @param originalWord the original color word. * @param newWord the new color word. * @throws RuntimeException if either of the strings is not a valid Magic * color. @@ -5433,151 +5667,21 @@ public void updateChangedText() { view.updateNonAbilityText(this); } - public final ImmutableMap getChangedTextColorWords() { - return ImmutableMap.copyOf(changedTextColors); - } - - public final ImmutableMap getChangedTextTypeWords() { - return ImmutableMap.copyOf(changedTextTypes); - } - - /** - * Copy the color and type text changes from another {@link Card} to this - * one. The original changes of this Card are removed. - */ - public final void copyChangedTextFrom(final Card other) { - changedTextColors.copyFrom(other.changedTextColors); - changedTextTypes.copyFrom(other.changedTextTypes); - } - - public final KeywordInterface addIntrinsicKeyword(final String s) { - KeywordInterface inst = currentState.addIntrinsicKeyword(s, true); - if (inst != null) { - updateKeywords(); - } - return inst; - } - - public final void addIntrinsicKeywords(final Iterable s) { - addIntrinsicKeywords(s, true); - } - public final void addIntrinsicKeywords(final Iterable s, boolean initTraits) { - if (currentState.addIntrinsicKeywords(s, initTraits)) { - updateKeywords(); - } - } - - public final void removeIntrinsicKeyword(final String s) { - if (currentState.removeIntrinsicKeyword(s)) { - updateKeywords(); - } - } - - public final void removeIntrinsicKeyword(final KeywordInterface s) { - if (currentState.removeIntrinsicKeyword(s)) { - updateKeywords(); - } - } - - // Hidden Keywords will be returned without the indicator HIDDEN - public final Iterable getHiddenExtrinsicKeywords() { - return Iterables.concat(this.hiddenExtrinsicKeywords.values()); - } - public final Table> getHiddenExtrinsicKeywordsTable() { - return hiddenExtrinsicKeywords; - } - - public final void addHiddenExtrinsicKeywords(long timestamp, long staticId, Iterable keywords) { - // TODO if some keywords aren't removed anymore, then no need for extra Array List - hiddenExtrinsicKeywords.put(timestamp, staticId, Lists.newArrayList(keywords)); - - view.updateNonAbilityText(this); - updateKeywords(); - } - - public final void removeHiddenExtrinsicKeywords(long timestamp, long staticId) { - if (hiddenExtrinsicKeywords.remove(timestamp, staticId) != null) { - view.updateNonAbilityText(this); - updateKeywords(); - } - } - - public final void removeHiddenExtrinsicKeyword(String s) { - boolean updated = false; - for (List list : hiddenExtrinsicKeywords.values()) { - if (list.remove(s)) { - updated = true; - } - } - if (updated) { - view.updateNonAbilityText(this); - updateKeywords(); - } - } - - public void addCantHaveKeyword(Keyword keyword, Long timestamp) { - cantHaveKeywords.put(timestamp, keyword); - getView().updateCantHaveKeyword(this); - } - - public void addCantHaveKeyword(Long timestamp, Iterable keywords) { - cantHaveKeywords.putAll(timestamp, keywords); - getView().updateCantHaveKeyword(this); - } - - public boolean removeCantHaveKeyword(Long timestamp) { - return removeCantHaveKeyword(timestamp, true); - } - public boolean removeCantHaveKeyword(Long timestamp, boolean updateView) { - boolean change = !cantHaveKeywords.removeAll(timestamp).isEmpty(); - if (change && updateView) { - getView().updateCantHaveKeyword(this); - updateKeywords(); - } - return change; - } - - public Collection getCantHaveKeyword() { - return cantHaveKeywords.values(); - } - - public final void setStaticAbilities(final List a) { - currentState.setStaticAbilities(a); - } - - public final FCollectionView getStaticAbilities() { - return currentState.getStaticAbilities(); - } - public final StaticAbility addStaticAbility(final String s) { - if (!s.trim().isEmpty()) { - final StaticAbility stAb = StaticAbility.create(s, this, currentState, true); - currentState.addStaticAbility(stAb); - return stAb; - } - return null; - } - public final StaticAbility addStaticAbility(final StaticAbility stAb) { - currentState.addStaticAbility(stAb); - return stAb; + public final ImmutableMap getChangedTextColorWords() { + return ImmutableMap.copyOf(changedTextColors); } - @Deprecated - public final void removeStaticAbility(StaticAbility stAb) { - currentState.removeStaticAbility(stAb); + public final ImmutableMap getChangedTextTypeWords() { + return ImmutableMap.copyOf(changedTextTypes); } - public void updateStaticAbilities(List list, CardState state) { - for (final CardTraitChanges ck : getChangedCardTraitsList(state)) { - if (ck.isRemoveAll()) { - list.clear(); - } - list.addAll(ck.getStaticAbilities()); - } - - // keywords are already sorted by Layer - for (KeywordInterface kw : getUnhiddenKeywords(state)) { - list.addAll(kw.getStaticAbilities()); - } + /** + * Copy the color and type text changes from another {@link Card} to this + * one. The original changes of this Card are removed. + */ + public final void copyChangedTextFrom(final Card other) { + changedTextColors.copyFrom(other.changedTextColors); + changedTextTypes.copyFrom(other.changedTextTypes); } public final boolean isPermanent() { @@ -5656,11 +5760,6 @@ public final void setUnearthed(final boolean b) { unearthed = b; } - public final boolean hasSuspend() { - return hasKeyword(Keyword.SUSPEND) && getLastKnownZone().is(ZoneType.Exile) - && getCounters(CounterEnumType.TIME) >= 1; - } - public final boolean isPhasedOut() { return phasedOut != null; } @@ -5789,110 +5888,6 @@ public final boolean isReflectedLand() { return false; } - public final boolean hasStartOfKeyword(final String keyword) { - return hasStartOfKeyword(keyword, currentState); - } - public final boolean hasStartOfKeyword(String keyword, CardState state) { - for (String s : this.getHiddenExtrinsicKeywords()) { - if (s.startsWith(keyword)) { - return true; - } - } - - HasKeywordVisitor visitor = new HasKeywordVisitor(keyword, true); - visitKeywords(state, visitor); - return visitor.getResult(); - } - - public final boolean hasStartOfUnHiddenKeyword(String keyword) { - return hasStartOfUnHiddenKeyword(keyword, currentState); - } - public final boolean hasStartOfUnHiddenKeyword(String keyword, CardState state) { - HasKeywordVisitor visitor = new HasKeywordVisitor(keyword, true); - visitUnhiddenKeywords(state, visitor); - return visitor.getResult(); - } - - public final boolean hasAnyKeyword(final Iterable keywords) { - return hasAnyKeyword(keywords, currentState); - } - public final boolean hasAnyKeyword(final Iterable keywords, CardState state) { - for (final String keyword : keywords) { - if (hasKeyword(keyword, state)) { - return true; - } - } - return false; - } - - // This counts the number of instances of a keyword a card has - public final int getAmountOfKeyword(final String k) { - return getAmountOfKeyword(k, currentState); - } - public final int getAmountOfKeyword(final String k, CardState state) { - int count = Iterables.frequency(this.getHiddenExtrinsicKeywords(), k); - CountKeywordVisitor visitor = new CountKeywordVisitor(k); - visitKeywords(state, visitor); - return count + visitor.getCount(); - } - - public final int getAmountOfKeyword(final Keyword k) { - return getAmountOfKeyword(k, currentState); - } - public final int getAmountOfKeyword(final Keyword k, CardState state) { - return getKeywords(k, state).size(); - } - - public final Collection getKeywords(final Keyword k) { - return getKeywords(k, currentState); - } - public final Collection getKeywords(final Keyword k, CardState state) { - return state.getCachedKeyword(k); - } - - // This is for keywords with a number like Bushido, Annihilator and Rampage. - // It returns the total. - public final int getKeywordMagnitude(final Keyword k) { - return getKeywordMagnitude(k, currentState); - } - - /** - * use it only for real keywords and not with hidden ones - * - * @return Int - */ - public final int getKeywordMagnitude(final Keyword k, CardState state) { - int count = 0; - for (final KeywordInterface inst : getKeywords(k, state)) { - String kw = inst.getOriginal(); - // this can't be used yet for everything because of X values in Bushido X - // KeywordInterface#getAmount - // KeywordCollection#getAmount - - final String[] parse = kw.contains(":") ? kw.split(":") : kw.split(" "); - if (parse.length < 2) { - count++; - continue; - } - final String s = parse[1]; - if (StringUtils.isNumeric(s)) { - count += Integer.parseInt(s); - } else { - StaticAbility st = inst.getStatic(); - // TODO make keywordinterface inherit from CardTrait somehow, or invent new interface - if (st != null && st.hasSVar(s)) { - count += AbilityUtils.calculateAmount(this, st.getSVar(s), null); - } else { - String svar = StringUtils.join(parse); - if (state.hasSVar(svar)) { - count += AbilityUtils.calculateAmount(this, state.getSVar(svar), null); - } - } - } - } - return count; - } - // Takes one argument like Permanent.Blue+withFlying @Override public final boolean isValid(final String restriction, final Player sourceController, final Card source, CardTraitBase spellAbility) { @@ -6428,6 +6423,28 @@ public final int addDamageAfterPrevention(final int damageIn, final Card source, return damageIn; } + /** + * Assign a random foil finish depending on the card edition. + */ + public final void setRandomFoil() { + setFoil(CardEdition.getRandomFoil(getSetCode())); + } + public final void setFoil(final int f) { + currentState.setSVar("Foil", Integer.toString(f)); + } + + public CardEdition.BorderColor borderColor() { + CardEdition edition = StaticData.instance().getEditions().get(getSetCode()); + if (edition == null || isBasicLand()) { + return CardEdition.BorderColor.BLACK; + } + return edition.getBorderColor(); + } + + public final String getMostRecentSet() { + return StaticData.instance().getCommonCards().getCard(getPaperCard().getName()).getEdition(); + } + public final String getSetCode() { return currentState.getSetCode(); } @@ -6442,10 +6459,6 @@ public final void setRarity(CardRarity r) { currentState.setRarity(r); } - public final String getMostRecentSet() { - return StaticData.instance().getCommonCards().getCard(getPaperCard().getName()).getEdition(); - } - public final String getImageKey() { if (!getRenderForUI()) { return ""; @@ -6557,6 +6570,27 @@ public boolean isMadness() { return getCastSA().isMadness(); } + public final boolean getDrawnThisTurn() { + return drawnThisTurn; + } + public final void setDrawnThisTurn(final boolean b) { + drawnThisTurn = b; + } + + public final boolean getFoughtThisTurn() { + return foughtThisTurn; + } + public final void setFoughtThisTurn(final boolean b) { + foughtThisTurn = b; + } + + public final boolean getEnlistedThisCombat() { + return enlistedThisCombat; + } + public final void setEnlistedThisCombat(final boolean b) { + enlistedThisCombat = b; + } + public boolean wasDiscarded() { return discarded; } public void setDiscarded(boolean state) { discarded = state; } public boolean wasSurveilled() { @@ -6657,7 +6691,7 @@ public final boolean setSuspected(final boolean suspected) { suspectedTimestamp = getGame().getNextTimestamp(); // use this for CantHaveKeyword - addChangedCardKeywords(ImmutableList.of("Menace"), ImmutableList.of(), false, suspectedTimestamp, null, true); + addChangedCardKeywords(ImmutableList.of("Menace"), ImmutableList.of(), false, suspectedTimestamp, null, false); if (suspectedStatic == null) { String effect = "Mode$ CantBlockBy | ValidBlocker$ Creature.Self | Description$ CARDNAME can't block."; @@ -6834,80 +6868,28 @@ public final long getBestowTimestamp() { return bestowTimestamp; } public final void setBestowTimestamp(final long t) { - bestowTimestamp = t; - } - - public final long getGameTimestamp() { - return gameTimestamp; - } - public final void setGameTimestamp(final long t) { - gameTimestamp = t; - // 613.7d An object receives a timestamp at the time it enters a zone. - layerTimestamp = t; - } - - public final long getLayerTimestamp() { - return layerTimestamp; - } - public final void setLayerTimestamp(final long t) { - layerTimestamp = t; - } - - public boolean equalsWithGameTimestamp(Card c) { - return equals(c) && c.getGameTimestamp() == gameTimestamp; - } - - /** - * Assign a random foil finish depending on the card edition. - */ - public final void setRandomFoil() { - setFoil(CardEdition.getRandomFoil(getSetCode())); - } - public final void setFoil(final int f) { - currentState.setSVar("Foil", Integer.toString(f)); - } - - public final CardCollectionView getHauntedBy() { - return CardCollection.getView(hauntedBy); - } - public final boolean isHaunted() { - return FCollection.hasElements(hauntedBy); - } - public final boolean isHauntedBy(Card c) { - return FCollection.hasElement(hauntedBy, c); - } - public final void addHauntedBy(Card c, final boolean update) { - hauntedBy = view.addCard(hauntedBy, c, TrackableProperty.HauntedBy); - if (c != null && update) { - c.setHaunting(this); - } - } - public final void addHauntedBy(Card c) { - addHauntedBy(c, true); - } - public final void removeHauntedBy(Card c) { - hauntedBy = view.removeCard(hauntedBy, c, TrackableProperty.HauntedBy); - } - - public final Card getHaunting() { - return haunting; - } - public final void setHaunting(final Card c) { - haunting = view.setCard(haunting, c, TrackableProperty.Haunting); + bestowTimestamp = t; } - public final Card getPairedWith() { - return pairedWith; + public final long getGameTimestamp() { + return gameTimestamp; } - public final void setPairedWith(final Card c) { - pairedWith = view.setCard(pairedWith, c, TrackableProperty.PairedWith); + public final void setGameTimestamp(final long t) { + gameTimestamp = t; + // 613.7d An object receives a timestamp at the time it enters a zone. + layerTimestamp = t; } - public final boolean isPaired() { - return pairedWith != null; + + public final long getLayerTimestamp() { + return layerTimestamp; + } + public final void setLayerTimestamp(final long t) { + layerTimestamp = t; } - public Card getMeldedWith() { return meldedWith; } - public void setMeldedWith(Card meldedWith) { this.meldedWith = meldedWith; } + public boolean equalsWithGameTimestamp(Card c) { + return equals(c) && c.getGameTimestamp() == gameTimestamp; + } public String getProtectionKey() { String protectKey = ""; @@ -7028,6 +7010,14 @@ public boolean isInZones(final List zones) { return z != null && inZones; } + public boolean canBeDiscardedBy(SpellAbility sa, final boolean effect) { + if (!isInZone(ZoneType.Hand)) { + return false; + } + + return getOwner().canDiscardBy(sa, effect); + } + public final boolean canBeDestroyed() { return isInPlay() && !isPhasedOut() && (!hasKeyword(Keyword.INDESTRUCTIBLE) || (isCreature() && getNetToughness() <= 0)); } @@ -7111,6 +7101,104 @@ public boolean canBeAttached(Card attach, SpellAbility sa, boolean checkSBA) { return super.canBeAttached(attach, sa, checkSBA); } + public final boolean canBeSacrificedBy(final SpellAbility source, final boolean effect) { + if (isImmutable()) { + System.out.println("Trying to sacrifice immutables: " + this); + return false; + } + + if (!isInPlay() || isPhasedOut()) { + return false; + } + + // can't sacrifice it for mana ability if it is already marked as sacrifice + if (source != null && source.isManaAbility() && isUsedToPay()) { + return false; + } + + final Card gameCard = game.getCardState(this, null); + // gameCard is LKI in that case, the card is not in game anymore + // or the timestamp did change + // this should check Self too + if (gameCard == null || !this.equalsWithGameTimestamp(gameCard)) { + return false; + } + + return !StaticAbilityCantSacrifice.cantSacrifice(this, source, effect); + } + + public final boolean canExiledBy(final SpellAbility source, final boolean effect) { + return !StaticAbilityCantExile.cantExile(this, source, effect); + } + + public final void setStaticAbilities(final List a) { + currentState.setStaticAbilities(a); + } + + public final FCollectionView getStaticAbilities() { + return currentState.getStaticAbilities(); + } + public final StaticAbility addStaticAbility(final String s) { + if (!s.trim().isEmpty()) { + final StaticAbility stAb = StaticAbility.create(s, this, currentState, true); + currentState.addStaticAbility(stAb); + return stAb; + } + return null; + } + public final StaticAbility addStaticAbility(final StaticAbility stAb) { + currentState.addStaticAbility(stAb); + return stAb; + } + + @Deprecated + public final void removeStaticAbility(StaticAbility stAb) { + currentState.removeStaticAbility(stAb); + } + + public void updateStaticAbilities(List list, CardState state) { + for (final CardTraitChanges ck : getChangedCardTraitsList(state)) { + if (ck.isRemoveAll()) { + list.clear(); + } + list.addAll(ck.getStaticAbilities()); + } + + // keywords are already sorted by Layer + for (KeywordInterface kw : getUnhiddenKeywords(state)) { + list.addAll(kw.getStaticAbilities()); + } + } + + public final FCollectionView getTriggers() { + return currentState.getTriggers(); + } + public final Trigger addTrigger(final Trigger t) { + currentState.addTrigger(t); + return t; + } + + public final boolean hasTrigger(final Trigger t) { + return currentState.hasTrigger(t); + } + public final boolean hasTrigger(final int id) { + return currentState.hasTrigger(id); + } + + public void updateTriggers(List list, CardState state) { + for (final CardTraitChanges ck : getChangedCardTraitsList(state)) { + if (ck.isRemoveAll()) { + list.clear(); + } + list.addAll(ck.getTriggers()); + } + + // Keywords are already sorted by Layer + for (KeywordInterface kw : getUnhiddenKeywords(state)) { + list.addAll(kw.getTriggers()); + } + } + public FCollectionView getReplacementEffects() { return currentState.getReplacementEffects(); } @@ -7228,15 +7316,12 @@ public Card getEffectSource() { } return effectSource; } - public SpellAbility getEffectSourceAbility() { return effectSourceAbility; } - public void setEffectSource(Card src) { effectSource = src; } - public void setEffectSource(SpellAbility sa) { effectSourceAbility = sa; } @@ -7390,36 +7475,6 @@ public final boolean isLKI() { return this.lkiCMC >= 0; } - public final boolean canBeSacrificedBy(final SpellAbility source, final boolean effect) { - if (isImmutable()) { - System.out.println("Trying to sacrifice immutables: " + this); - return false; - } - - if (!isInPlay() || isPhasedOut()) { - return false; - } - - // can't sacrifice it for mana ability if it is already marked as sacrifice - if (source != null && source.isManaAbility() && isUsedToPay()) { - return false; - } - - final Card gameCard = game.getCardState(this, null); - // gameCard is LKI in that case, the card is not in game anymore - // or the timestamp did change - // this should check Self too - if (gameCard == null || !this.equalsWithGameTimestamp(gameCard)) { - return false; - } - - return !StaticAbilityCantSacrifice.cantSacrifice(this, source, effect); - } - - public final boolean canExiledBy(final SpellAbility source, final boolean effect) { - return !StaticAbilityCantExile.cantExile(this, source, effect); - } - public CardRules getRules() { return cardRules; } @@ -7428,6 +7483,15 @@ public void setRules(CardRules r) { currentState.getView().updateRulesText(r, getType()); } + @Override + public Game getGame() { + return game; + } + + public void dangerouslySetGame(Game newGame) { + game = newGame; + } + public boolean isCommander() { if (this.getMeldedWith() != null && this.getMeldedWith().isCommander()) return true; @@ -7499,13 +7563,12 @@ public void setSplitStateToPlayAbility(final SpellAbility sa) { public boolean isOptionalCostPaid(OptionalCost cost) { return getCastSA() == null ? false : getCastSA().isOptionalCostPaid(cost); } - @Override - public Game getGame() { - return game; - } - - public void dangerouslySetGame(Game newGame) { - game = newGame; + public final int getKickerMagnitude() { + if (this.getCastSA() != null && getCastSA().hasOptionalKeywordAmount(Keyword.MULTIKICKER)) { + return getCastSA().getOptionalKeywordAmount(Keyword.MULTIKICKER); + } + boolean hasK1 = isOptionalCostPaid(OptionalCost.Kicker1); + return hasK1 == isOptionalCostPaid(OptionalCost.Kicker2) ? (hasK1 ? 2 : 0) : 1; } public List getAllPossibleAbilities(final Player player, final boolean removeUnplayable) { @@ -7611,7 +7674,6 @@ public Card getCardForUi() { public boolean getRenderForUI() { return this.renderForUi; } - public void setRenderForUI(boolean value) { renderForUi = value; } @@ -7662,14 +7724,6 @@ public static void updateCard(PaperCard pc) { } } - public List getStaticCommandList() { - return staticCommandList; - } - - public void addStaticCommandList(Object[] objects) { - staticCommandList.add(objects); - } - public String getOracleText() { return currentState.getOracleText(); } @@ -7761,13 +7815,6 @@ public List getKeywords() { } } - public void setChangedCardKeywords(Table changedCardKeywords) { - this.changedCardKeywords.clear(); - for (Table.Cell entry : changedCardKeywords.cellSet()) { - this.changedCardKeywords.put(entry.getRowKey(), entry.getColumnKey(), entry.getValue().copy(this, true)); - } - } - public void cleanupCopiedChangesFrom(Card c) { for (StaticAbility stAb : c.getStaticAbilities()) { this.removeChangedCardTypes(c.getLayerTimestamp(), stAb.getId(), false); @@ -7830,33 +7877,10 @@ public final void setLastKnownZone(Zone zone) { this.savedLastKnownZone = zone; } - public final void putEtbCounters(Map, Map> etbCounters) { - if (etbCounters == null) { - return; - } - // used for LKI - for (Map m : etbCounters.values()) { - for (Map.Entry e : m.entrySet()) { - CounterType ct = e.getKey(); - if (canReceiveCounters(ct)) { - setCounters(ct, getCounters(ct) + e.getValue()); - } - } - } - } - public final int getFinalChapterNr() { return getCurrentState().getFinalChapterNr(); } - public boolean canBeDiscardedBy(SpellAbility sa, final boolean effect) { - if (!isInZone(ZoneType.Hand)) { - return false; - } - - return getOwner().canDiscardBy(sa, effect); - } - public boolean activatedThisTurn() { return !numberTurnActivations.isEmpty(); } @@ -8019,7 +8043,6 @@ public void addCanBlockAdditional(int n, long timestamp) { canBlockAdditional.put(timestamp, n); getView().updateBlockAdditional(this); } - public boolean removeCanBlockAdditional(long timestamp) { boolean result = canBlockAdditional.remove(timestamp) != null; if (result) { @@ -8027,7 +8050,6 @@ public boolean removeCanBlockAdditional(long timestamp) { } return result; } - public int canBlockAdditional() { int result = 0; for (Integer v : canBlockAdditional.values()) { @@ -8040,7 +8062,6 @@ public void addCanBlockAny(long timestamp) { canBlockAny.add(timestamp); getView().updateBlockAdditional(this); } - public boolean removeCanBlockAny(long timestamp) { boolean result = canBlockAny.remove(timestamp); if (result) { @@ -8048,7 +8069,6 @@ public boolean removeCanBlockAny(long timestamp) { } return result; } - public boolean canBlockAny() { return !canBlockAny.isEmpty(); } @@ -8068,34 +8088,6 @@ public boolean removeChangedState() { return updateState; } - public CardEdition.BorderColor borderColor() { - CardEdition edition = StaticData.instance().getEditions().get(getSetCode()); - if (edition == null || isBasicLand()) { - return CardEdition.BorderColor.BLACK; - } - return edition.getBorderColor(); - } - - public final CardCollectionView getUntilLeavesBattlefield() { - return CardCollection.getView(untilLeavesBattlefield); - } - - public final void addUntilLeavesBattlefield(final Card c) { - untilLeavesBattlefield = view.addCard(untilLeavesBattlefield, c, TrackableProperty.UntilLeavesBattlefield); - } - public final void addUntilLeavesBattlefield(final Iterable cards) { - untilLeavesBattlefield = view.addCards(untilLeavesBattlefield, cards, TrackableProperty.UntilLeavesBattlefield); - } - public final void removeUntilLeavesBattlefield(final Card c) { - untilLeavesBattlefield = view.removeCard(untilLeavesBattlefield, c, TrackableProperty.UntilLeavesBattlefield); - } - public final void removeUntilLeavesBattlefield(final Iterable cards) { - untilLeavesBattlefield = view.removeCards(untilLeavesBattlefield, cards, TrackableProperty.UntilLeavesBattlefield); - } - public final void clearUntilLeavesBattlefield() { - untilLeavesBattlefield = view.clearCards(untilLeavesBattlefield, TrackableProperty.UntilLeavesBattlefield); - } - public CombatLki getCombatLKI() { return combatLKI; } 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 5d4f6731502..39e230a7eaa 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -1878,6 +1878,10 @@ public final void incrementTurn() { stats.nextTurn(); } + public final int getLastTurnNr() { + return this.lastTurnNr; + } + public boolean hasTappedLandForManaThisTurn() { return tappedLandForManaThisTurn; } @@ -2285,6 +2289,13 @@ public void setUnlimitedHandSize(boolean unlimited) { view.updateUnlimitedHandSize(this); } + public int getStartingHandSize() { + return startingHandSize; + } + public void setStartingHandSize(int shs) { + startingHandSize = shs; + } + public final int getLandsPlayedThisTurn() { return landsPlayedThisTurn; } @@ -2519,10 +2530,6 @@ public final int getAmountOfKeyword(final String k) { return keywords.getAmount(k); } - public final int getLastTurnNr() { - return this.lastTurnNr; - } - public void onCleanupPhase() { for (Card c : getCardsIn(ZoneType.Hand)) { c.setDrawnThisTurn(false); @@ -2693,13 +2700,6 @@ public boolean isSkippingCombat() { return !isInGame(); } - public int getStartingHandSize() { - return startingHandSize; - } - public void setStartingHandSize(int shs) { - startingHandSize = shs; - } - /** * Takes the top plane of the planar deck and put it face up in the command zone. * Then runs triggers. @@ -4027,11 +4027,9 @@ public void createContraptionSprockets() { public void addDeclaresAttackers(long ts, Player p) { this.declaresAttackers.put(ts, p); } - public void removeDeclaresAttackers(long ts) { this.declaresAttackers.remove(ts); } - public Player getDeclaresAttackers() { Map.Entry e = declaresAttackers.lastEntry(); return e == null ? null : e.getValue(); @@ -4040,11 +4038,9 @@ public Player getDeclaresAttackers() { public void addDeclaresBlockers(long ts, Player p) { this.declaresBlockers.put(ts, p); } - public void removeDeclaresBlockers(long ts) { this.declaresBlockers.remove(ts); } - public Player getDeclaresBlockers() { Map.Entry e = declaresBlockers.lastEntry(); return e == null ? null : e.getValue(); diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java index ff555538b18..7a265ded445 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -704,7 +704,7 @@ public static CardCollectionView applyContinuousAbility(final StaticAbility stAb setToughness = AbilityUtils.calculateAmount(affectedCard, setT, stAb, true); } affectedCard.addNewPT(setPower, setToughness, - se.getTimestamp(), stAb.getId(), layer == StaticAbilityLayer.CHARACTERISTIC); + se.getTimestamp(), stAb.getId(), layer == StaticAbilityLayer.CHARACTERISTIC, false); } } @@ -752,7 +752,8 @@ public static CardCollectionView applyContinuousAbility(final StaticAbility stAb } affectedCard.addChangedCardKeywords(newKeywords, removeKeywords, - removeAllAbilities, se.getTimestamp(), stAb, true); + removeAllAbilities, se.getTimestamp(), stAb, false); + affectedCard.updateKeywordsCache(affectedCard.getCurrentState()); } // add HIDDEN keywords @@ -862,7 +863,7 @@ public static CardCollectionView applyContinuousAbility(final StaticAbility stAb || removeAllAbilities) { affectedCard.addChangedCardTraits( addedAbilities, null, addedTrigger, addedReplacementEffects, addedStaticAbility, removeAllAbilities, removeNonMana, - se.getTimestamp(), stAb.getId() + se.getTimestamp(), stAb.getId(), false ); } @@ -874,7 +875,7 @@ public static CardCollectionView applyContinuousAbility(final StaticAbility stAb // add Types if ((addTypes != null && !addTypes.isEmpty()) || (removeTypes != null && !removeTypes.isEmpty()) || addAllCreatureTypes || !remove.isEmpty()) { affectedCard.addChangedCardTypes(addTypes, removeTypes, addAllCreatureTypes, remove, - se.getTimestamp(), stAb.getId(), true, stAb.isCharacteristicDefining()); + se.getTimestamp(), stAb.getId(), false, stAb.isCharacteristicDefining()); } // add colors diff --git a/forge-gui/res/cardsfolder/p/pias_revolution.txt b/forge-gui/res/cardsfolder/p/pias_revolution.txt index 0d44d999a32..ad5d6dcb716 100644 --- a/forge-gui/res/cardsfolder/p/pias_revolution.txt +++ b/forge-gui/res/cardsfolder/p/pias_revolution.txt @@ -2,6 +2,6 @@ Name:Pia's Revolution ManaCost:2 R Types:Enchantment T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Artifact.YouOwn+nonToken | TriggerZones$ Battlefield | Execute$ TrigReturn | TriggerDescription$ Whenever a nontoken artifact is put into your graveyard from the battlefield, return that card to your hand unless target opponent has CARDNAME deal 3 damage to them. -SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Hand | UnlessCost$ DamageYou<3> | UnlessPayer$ Targeted | ValidTgts$ Opponent | IsCurse$ True +SVar:TrigReturn:DB$ ChangeZone | ThisDefinedAndTgts$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Hand | UnlessCost$ DamageYou<3> | UnlessPayer$ Targeted | ValidTgts$ Opponent | IsCurse$ True SVar:BuffedBy:Permanent.White,Permanent.Black Oracle:Whenever a nontoken artifact is put into your graveyard from the battlefield, return that card to your hand unless target opponent has Pia's Revolution deal 3 damage to them.