From 549045994b10621b260ebccd85f208f19182d82c Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sat, 19 Feb 2022 11:51:19 +0100 Subject: [PATCH 1/3] Fix test --- .../main/java/forge/game/ability/effects/DiscardEffect.java | 4 ++-- .../v/voltaic_visionary_volt_charged_berserker.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java index c0951e8f1cc..f66fe752f83 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java @@ -255,14 +255,14 @@ public class DiscardEffect extends SpellAbilityEffect { Player chooser = p; if (mode.endsWith("YouChoose")) { chooser = source.getController(); - } else if (mode.endsWith("TgtChoose")) { + } else if (mode.equals("RevealTgtChoose")) { chooser = firstTarget; } if (mode.startsWith("Reveal")) { game.getAction().reveal(dPHand, p); } - if (mode.startsWith("Look")) { + if (mode.startsWith("Look") && p != chooser) { game.getAction().revealTo(dPHand, chooser); } diff --git a/forge-gui/res/cardsfolder/v/voltaic_visionary_volt_charged_berserker.txt b/forge-gui/res/cardsfolder/v/voltaic_visionary_volt_charged_berserker.txt index 59bb7222dcc..eeec833f7fe 100644 --- a/forge-gui/res/cardsfolder/v/voltaic_visionary_volt_charged_berserker.txt +++ b/forge-gui/res/cardsfolder/v/voltaic_visionary_volt_charged_berserker.txt @@ -7,15 +7,15 @@ SVar:DBExile:DB$ Dig | Defined$ You | DigNum$ 1 | ChangeNum$ All | DestinationZo SVar:DBEffect:DB$ Effect | RememberObjects$ RememberedCard | StaticAbilities$ STPlay | SubAbility$ DBCleanup | ForgetOnMoved$ Exile | StackDescription$ SpellDescription | SpellDescription$ You may play that card this turn. Activate only as a sorcery. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:STPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play the exiled card this turn. -T:Mode$ SpellCast | ValidCard$ Card.IsImprinted | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigTransform | TriggerDescription$ Whenever you play a card exiled with CARDNAME, transform it. -T:Mode$ LandPlayed | ValidCard$ Land.IsImprinted+YouCtrl | Execute$ TrigTransform | TriggerZones$ Battlefield | Secondary$ True | TriggerDescription$ Whenever you play a card exiled with CARDNAME, transform it. +T:Mode$ SpellCast | ValidCard$ Card.IsImprinted | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigTransform | TriggerDescription$ When you play a card exiled with CARDNAME, transform it. +T:Mode$ LandPlayed | ValidCard$ Land.IsImprinted+YouCtrl | Execute$ TrigTransform | TriggerZones$ Battlefield | Secondary$ True | TriggerDescription$ When you play a card exiled with CARDNAME, transform it. SVar:TrigTransform:DB$ SetState | Defined$ Self | Mode$ Transform T:Mode$ ChangesZone | Origin$ Battlefield | ValidCard$ Card.Self | Destination$ Any | Execute$ DBClear | Static$ True SVar:DBClear:DB$ Cleanup | ClearImprinted$ True T:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsImprinted+ExiledWithSource | Execute$ DBForget SVar:DBForget:DB$ Pump | ForgetImprinted$ TriggeredCard AlternateMode:DoubleFaced -Oracle:{T}: Voltaic Visionary deals 2 damage to you. Exile the top card of your library. You may play that card this turn. Activate only as a sorcery.\nWhenever you play a card exiled with Voltaic Visionary, transform it. +Oracle:{T}: Voltaic Visionary deals 2 damage to you. Exile the top card of your library. You may play that card this turn. Activate only as a sorcery.\nWhen you play a card exiled with Voltaic Visionary, transform it. ALTERNATE From 164da1f77f87c1a072252ba91b67c8fe76a7e10d Mon Sep 17 00:00:00 2001 From: Tim Mocny Date: Sat, 19 Feb 2022 11:37:13 +0000 Subject: [PATCH 2/3] =?UTF-8?q?NEO=20=E2=80=93=2018=20Feb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../res/cardsfolder/upcoming/disruption_protocol.txt | 7 +++++++ forge-gui/res/cardsfolder/upcoming/futurist_sentinel.txt | 6 ++++++ .../res/cardsfolder/upcoming/moonfolk_puzzlemaker.txt | 8 ++++++++ 3 files changed, 21 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/disruption_protocol.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/futurist_sentinel.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/moonfolk_puzzlemaker.txt diff --git a/forge-gui/res/cardsfolder/upcoming/disruption_protocol.txt b/forge-gui/res/cardsfolder/upcoming/disruption_protocol.txt new file mode 100644 index 00000000000..64b18323ef6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/disruption_protocol.txt @@ -0,0 +1,7 @@ +Name:Disruption Protocol +ManaCost:U U +Types:Instant +K:AlternateAdditionalCost:tapXType<1/Artifact>:1 +A:SP$ Counter | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | SpellDescription$ Counter target spell. +DeckHints:Type$Artifact +Oracle:As an additional cost to cast this spell, tap an untapped artifact you control or pay {1}.\nCounter target spell. diff --git a/forge-gui/res/cardsfolder/upcoming/futurist_sentinel.txt b/forge-gui/res/cardsfolder/upcoming/futurist_sentinel.txt new file mode 100644 index 00000000000..f33b409db1e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/futurist_sentinel.txt @@ -0,0 +1,6 @@ +Name:Futurist Sentinel +ManaCost:3 U +Types:Artifact Vehicle +PT:6/6 +K:Crew:3 +Oracle:Crew 3 (Tap any number of creatures you control with total power 3 or more: This Vehicle becomes an artifact creature until end of turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/moonfolk_puzzlemaker.txt b/forge-gui/res/cardsfolder/upcoming/moonfolk_puzzlemaker.txt new file mode 100644 index 00000000000..68a879500ae --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/moonfolk_puzzlemaker.txt @@ -0,0 +1,8 @@ +Name:Moonfolk Puzzlemaker +ManaCost:2 U +Types:Artifact Creature Moonfolk Wizard +PT:1/4 +K:Flying +T:Mode$ Taps | ValidCard$ Card.Self | Execute$ TrigSry | TriggerDescription$ Whenever CARDNAME becomes tapped, scry 1. +SVar:TrigSry:DB$ Scry | ScryNum$ 1 +Oracle:Flying\nWhenever Moonfolk Puzzlemaker becomes tapped, scry 1. From d886ae876d9df6b6853dc58d3ee22a4d65878e79 Mon Sep 17 00:00:00 2001 From: Bug Hunter Date: Sat, 19 Feb 2022 11:38:10 +0000 Subject: [PATCH 3/3] receivedDamageFromThisTurn: use LKI --- .../src/main/java/forge/game/GameAction.java | 20 ++++++++- .../src/main/java/forge/game/card/Card.java | 43 ++++++++++--------- .../java/forge/game/card/CardProperty.java | 14 +++--- .../main/java/forge/game/card/CardUtil.java | 1 + 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 4f8a3b03c99..f9cd5adae81 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -27,6 +27,7 @@ import java.util.Set; import forge.util.*; import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; import com.google.common.base.Predicate; import com.google.common.collect.ArrayListMultimap; @@ -1265,7 +1266,24 @@ public class GameAction { noRegCreats.add(c); checkAgain = true; } else if (c.hasKeyword("CARDNAME can't be destroyed by lethal damage unless lethal damage dealt by a single source is marked on it.")) { - for (final Integer dmg : c.getReceivedDamageFromThisTurn().values()) { + // merge entries with same source + List dmgList = Lists.newArrayList(); + List> remainingDamaged = Lists.newArrayList(c.getReceivedDamageFromThisTurn()); + while (!remainingDamaged.isEmpty()) { + Pair damaged = remainingDamaged.get(0); + int sum = damaged.getRight(); + remainingDamaged.remove(damaged); + for (Pair other : Lists.newArrayList(remainingDamaged)) { + if (other.getLeft().equalsWithTimestamp(damaged.getLeft())) { + sum += other.getRight(); + // once it got counted keep it out + remainingDamaged.remove(other); + } + } + dmgList.add(sum); + } + + for (final Integer dmg : dmgList) { if (c.getLethal() <= dmg.intValue() || c.hasBeenDealtDeathtouchDamage()) { if (desCreats == null) { desCreats = new CardCollection(); 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 35535d84f81..06a2e8b36bd 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -174,8 +174,9 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private final Set rememberedObjects = Sets.newLinkedHashSet(); private Map flipResult; - private Map receivedDamageFromThisTurn = Maps.newHashMap(); - + private List> receivedDamageFromThisTurn = Lists.newArrayList(); + private Map receivedDamageFromPlayerThisTurn = Maps.newHashMap(); + private final Map assignedDamageMap = Maps.newTreeMap(); private boolean isCommander = false; @@ -5151,46 +5152,48 @@ public class Card extends GameEntity implements Comparable, IHasSVars { damageHistory = history; } - public final Map getReceivedDamageFromThisTurn() { + public final List> getReceivedDamageFromThisTurn() { return receivedDamageFromThisTurn; } - public final void setReceivedDamageFromThisTurn(final Map receivedDamageList) { - receivedDamageFromThisTurn = Maps.newHashMap(receivedDamageList); + public final void setReceivedDamageFromThisTurn(final List> receivedDamageList) { + receivedDamageFromThisTurn = Lists.newArrayList(receivedDamageList); + } + public final Map getReceivedDamageFromPlayerThisTurn() { + return receivedDamageFromPlayerThisTurn; + } + public final void setReceivedDamageFromPlayerThisTurn(final Map receivedDamageMap) { + receivedDamageFromPlayerThisTurn = Maps.newHashMap(receivedDamageMap); } public int getReceivedDamageByPlayerThisTurn(final Player p) { - if (receivedDamageFromThisTurn.containsKey(p)) { - return receivedDamageFromThisTurn.get(p); + if (receivedDamageFromPlayerThisTurn.containsKey(p)) { + return receivedDamageFromPlayerThisTurn.get(p); } return 0; } - public final void addReceivedDamageFromThisTurn(final Card c, final int damage) { + public final void addReceivedDamageFromThisTurn(Card c, final int damage) { int currentDamage = 0; - if (receivedDamageFromThisTurn.containsKey(c)) { - currentDamage = receivedDamageFromThisTurn.get(c); - } - receivedDamageFromThisTurn.put(c, damage+currentDamage); + // because Aegar cares about the past state we need to keep all LKI instances + receivedDamageFromThisTurn.add(Pair.of(c.isLKI() ? c : CardUtil.getLKICopy(c), damage)); Player p = c.getController(); if (p != null) { - currentDamage = 0; - if (receivedDamageFromThisTurn.containsKey(p)) { - currentDamage = receivedDamageFromThisTurn.get(p); + if (receivedDamageFromPlayerThisTurn.containsKey(p)) { + currentDamage = receivedDamageFromPlayerThisTurn.get(p); } - receivedDamageFromThisTurn.put(p, damage+currentDamage); + receivedDamageFromPlayerThisTurn.put(p, damage+currentDamage); } } public final void resetReceivedDamageFromThisTurn() { receivedDamageFromThisTurn.clear(); + receivedDamageFromPlayerThisTurn.clear(); } public final int getTotalDamageReceivedThisTurn() { int total = 0; - for (Entry e : receivedDamageFromThisTurn.entrySet()) { - if (e.getKey() instanceof Player) { - total += e.getValue(); - } + for (Integer i : receivedDamageFromPlayerThisTurn.values()) { + total += i; } return total; } diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index 5dbeeb00428..82e2e6e1abd 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -32,6 +32,7 @@ import forge.util.TextUtil; import forge.util.collect.FCollection; import forge.util.collect.FCollectionView; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import java.util.Collections; import java.util.List; @@ -633,18 +634,21 @@ public class CardProperty { return false; } } else if (property.startsWith("DamagedBy")) { + List damaged = Lists.newArrayList(); + for (Pair pair : card.getReceivedDamageFromThisTurn()) { + damaged.add(pair.getLeft()); + } if (property.endsWith("Source") || property.equals("DamagedBy")) { - if (!card.getReceivedDamageFromThisTurn().containsKey(source)) { + if (!damaged.contains(source)) { return false; } } else { String prop = property.substring("DamagedBy".length()); - final Iterable list = Iterables.filter(card.getReceivedDamageFromThisTurn().keySet(), Card.class); - boolean found = Iterables.any(list, CardPredicates.restriction(prop, sourceController, source, spellAbility)); + boolean found = Iterables.any(damaged, CardPredicates.restriction(prop, sourceController, source, spellAbility)); if (!found) { for (Card d : AbilityUtils.getDefinedCards(source, prop, spellAbility)) { - if (card.getReceivedDamageFromThisTurn().containsKey(d)) { + if (damaged.contains(d)) { found = true; break; } @@ -1177,7 +1181,7 @@ public class CardProperty { return false; } } else if (property.startsWith("wasDealtDamageThisTurn")) { - if ((card.getReceivedDamageFromThisTurn().keySet()).isEmpty()) { + if (card.getReceivedDamageFromPlayerThisTurn().isEmpty()) { return false; } } else if (property.startsWith("dealtDamageThisTurn")) { diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java index 80a19e91847..f8f35a2c028 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -250,6 +250,7 @@ public final class CardUtil { newCopy.setPhasedOut(in.isPhasedOut()); newCopy.setReceivedDamageFromThisTurn(in.getReceivedDamageFromThisTurn()); + newCopy.setReceivedDamageFromPlayerThisTurn(in.getReceivedDamageFromPlayerThisTurn()); newCopy.setDamageHistory(in.getDamageHistory()); for (Card c : in.getBlockedThisTurn()) { newCopy.addBlockedThisTurn(c);