From a315171ca4c751bea05ac0e1e8c93633fdc79974 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Tue, 9 May 2023 15:36:33 +0400 Subject: [PATCH] Connive ability - fixed game error on usage (NPE), fixed game freeze on disconnect, fixed miss LKI related code; --- .../src/mage/cards/k/KamizObscuraOculus.java | 4 +- Mage.Sets/src/mage/cards/l/LethalScheme.java | 16 +++++-- .../src/mage/cards/m/MaskOfTheSchemer.java | 4 +- .../src/mage/cards/o/ObscuraConfluence.java | 4 +- .../src/mage/cards/r/RaffineSchemingSeer.java | 2 +- .../effects/keyword/ConniveSourceEffect.java | 45 ++++++++++++++----- 6 files changed, 54 insertions(+), 21 deletions(-) diff --git a/Mage.Sets/src/mage/cards/k/KamizObscuraOculus.java b/Mage.Sets/src/mage/cards/k/KamizObscuraOculus.java index 04dd13b150..f7f2f1ecdd 100644 --- a/Mage.Sets/src/mage/cards/k/KamizObscuraOculus.java +++ b/Mage.Sets/src/mage/cards/k/KamizObscuraOculus.java @@ -73,8 +73,8 @@ class KamizConniveEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); - return permanent != null && ConniveSourceEffect.connive(permanent, 1, source, game); + Permanent permanent = game.getPermanentOrLKIBattlefield(getTargetPointer().getFirst(game, source)); + return ConniveSourceEffect.connive(permanent, 1, source, game); } } diff --git a/Mage.Sets/src/mage/cards/l/LethalScheme.java b/Mage.Sets/src/mage/cards/l/LethalScheme.java index 429fe2b695..12e0a5c61c 100644 --- a/Mage.Sets/src/mage/cards/l/LethalScheme.java +++ b/Mage.Sets/src/mage/cards/l/LethalScheme.java @@ -116,10 +116,18 @@ class LethalSchemeEffect extends OneShotEffect { player.choose(Outcome.Neutral, choiceForThisLoop, game); String choice = choiceForThisLoop.getChoice(); - Permanent choicePermanent = permanents.stream().filter(permanent -> permanent.getIdName().equals(choice)).findFirst().get(); - - ConniveSourceEffect.connive(choicePermanent, 1, source, game); - permanents.remove(choicePermanent); + Permanent choicePermanent = permanents + .stream() + .filter(permanent -> permanent.getIdName().equals(choice)) + .findFirst() + .orElse(null); + if (choicePermanent != null) { + ConniveSourceEffect.connive(choicePermanent, 1, source, game); + permanents.remove(choicePermanent); + } else { + // no choices, e.g. disconnection + break; + } } } return true; diff --git a/Mage.Sets/src/mage/cards/m/MaskOfTheSchemer.java b/Mage.Sets/src/mage/cards/m/MaskOfTheSchemer.java index 03778dbd43..c0f39d4910 100644 --- a/Mage.Sets/src/mage/cards/m/MaskOfTheSchemer.java +++ b/Mage.Sets/src/mage/cards/m/MaskOfTheSchemer.java @@ -71,10 +71,10 @@ class MaskOfTheSchemerEffect extends OneShotEffect { if (equipment == null || damage < 1) { return false; } - Permanent permanent = game.getPermanent(equipment.getAttachedTo()); + Permanent permanent = game.getPermanentOrLKIBattlefield(equipment.getAttachedTo()); if (permanent == null) { return false; } - return permanent != null && ConniveSourceEffect.connive(permanent, damage, source, game); + return ConniveSourceEffect.connive(permanent, damage, source, game); } } diff --git a/Mage.Sets/src/mage/cards/o/ObscuraConfluence.java b/Mage.Sets/src/mage/cards/o/ObscuraConfluence.java index 36f4ea1f5c..4c833e9d15 100644 --- a/Mage.Sets/src/mage/cards/o/ObscuraConfluence.java +++ b/Mage.Sets/src/mage/cards/o/ObscuraConfluence.java @@ -79,8 +79,8 @@ class ObscuraConfluenceConniveEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); - return permanent != null && ConniveSourceEffect.connive(permanent, 1, source, game); + Permanent permanent = game.getPermanentOrLKIBattlefield(getTargetPointer().getFirst(game, source)); + return ConniveSourceEffect.connive(permanent, 1, source, game); } } diff --git a/Mage.Sets/src/mage/cards/r/RaffineSchemingSeer.java b/Mage.Sets/src/mage/cards/r/RaffineSchemingSeer.java index cb847fa65b..e21f4f340e 100644 --- a/Mage.Sets/src/mage/cards/r/RaffineSchemingSeer.java +++ b/Mage.Sets/src/mage/cards/r/RaffineSchemingSeer.java @@ -85,7 +85,7 @@ class RaffineSchemingSeerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + Permanent permanent = game.getPermanentOrLKIBattlefield(getTargetPointer().getFirst(game, source)); if (permanent == null) { return false; } diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/ConniveSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/ConniveSourceEffect.java index 20842bd8b3..f7641bf46b 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/ConniveSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/ConniveSourceEffect.java @@ -14,12 +14,31 @@ import mage.players.Player; import mage.util.CardUtil; /** + * 701.47. Connive + *

+ * 701.47a Certain abilities instruct a permanent to connive. To do so, that permanent’s controller draws a card, + * then discards a card. If a nonland card is discarded this way, that player puts a +1/+1 counter on the + * conniving permanent. + *

+ * 701.47b A permanent “connives” after the process described in rule 701.47a is complete, even if some or + * all of those actions were impossible. + *

+ * 701.47c If a permanent changes zones before an effect causes it to connive, its last known information is + * used to determine which object connived and who controlled it. + *

+ * 701.47d If multiple permanents are instructed to connive at the same time, the first player in APNAP order + * who controls one or more of those permanents chooses one of them and it connives. Then if any permanents + * remain on the battlefield which have been instructed to connive and have not done so, this process is repeated. + *

+ * 701.47e Connive N is a variant of connive. The permanent’s controller draws N cards, discards N cards, then + * puts a number of +1/+1 counters on the permanent equal to the number of nonland cards discarded this way. + * * @author TheElk801 */ public class ConniveSourceEffect extends OneShotEffect { private final String selfName; - private final ReflexiveTriggeredAbility ability; + private final ReflexiveTriggeredAbility ability; // apply ability after connived public ConniveSourceEffect() { this("it"); @@ -48,31 +67,37 @@ public class ConniveSourceEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = source.getSourcePermanentIfItStillExists(game); + // 701.47c If a permanent changes zones before an effect causes it to connive, + // its last known information is used to determine which object connived and who controlled it. + Permanent permanent = source.getSourcePermanentOrLKI(game); boolean connived = connive(permanent, 1, source, game); - if (ability != null) { + if (ability != null && connived) { game.fireReflexiveTriggeredAbility(ability, source); } - return connived || ability != null; + return connived; } + /** + * @param permanent must use game.getPermanentOrLKIBattlefield in parent method due rules + * @param amount + * @param source + * @param game + * @return + */ public static boolean connive(Permanent permanent, int amount, Ability source, Game game) { if (amount < 1) { return false; } - boolean permanentStillOnBattlefield; if (permanent == null) { - // If the permanent was killed, get last known information - permanent = (Permanent) game.getLastKnownInformation(source.getSourceId(), Zone.BATTLEFIELD); - permanentStillOnBattlefield = false; - } else { - permanentStillOnBattlefield = true; + return false; } + boolean permanentStillOnBattlefield = game.getState().getZone(permanent.getId()) == Zone.BATTLEFIELD; Player player = game.getPlayer(permanent.getControllerId()); if (player == null) { return false; } + player.drawCards(amount, source, game); int counters = player .discard(amount, false, false, source, game)