Simplify implementation and fix bugs of Kaito, Dancing Shadow (#10639)

Implement PermanentReferenceInCollectionPredicate used by that
convert existing cards to use new Predicate as needed
Replaces PermanentInListPredicate for longer term effects
This commit is contained in:
ssk97 2023-07-17 20:47:37 -07:00 committed by GitHub
parent 8128e9935d
commit c3f7a9ab21
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 84 additions and 166 deletions

View file

@ -1,9 +1,10 @@
package mage.cards.b;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.Set;
import java.util.HashSet;
import mage.MageInt;
import mage.MageObjectReference;
import mage.abilities.Ability;
@ -16,9 +17,8 @@ import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.permanent.PermanentInListPredicate;
import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
@ -77,22 +77,10 @@ class BrineHagEffect extends OneShotEffect {
return false;
}
List<Permanent> list = new ArrayList<>();
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player == null) {
continue;
}
for (Permanent creature : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game)) {
if (sourcePermanent.getDealtDamageByThisTurn().contains(new MageObjectReference(creature.getId(), game))) {
list.add(creature);
}
}
}
if (!list.isEmpty()) {
Set<MageObjectReference> set = new HashSet<>(sourcePermanent.getDealtDamageByThisTurn());
if (!set.isEmpty()) {
FilterCreaturePermanent filter = new FilterCreaturePermanent();
filter.add(new PermanentInListPredicate(list));
filter.add(new PermanentReferenceInCollectionPredicate(set));
game.addEffect(new SetBasePowerToughnessAllEffect(0, 2, Duration.Custom, filter, true), source);
}
return true;

View file

@ -1,8 +1,8 @@
package mage.cards.k;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.Filter;
import mage.MageObjectReference;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.cards.Card;
import mage.cards.CardImpl;
@ -10,31 +10,29 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.DealCombatDamageControlledTriggeredAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.combat.CantAttackTargetEffect;
import mage.abilities.effects.common.combat.CantBlockTargetEffect;
import mage.constants.*;
import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate;
import mage.game.Game;
import mage.game.events.DamagedEvent;
import mage.game.events.DamagedPlayerBatchEvent;
import mage.game.events.DamagedPlayerEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.DroneToken;
import mage.players.Player;
import mage.target.TargetObject;
import mage.target.TargetPermanent;
import mage.watchers.Watcher;
import java.util.*;
import java.util.stream.Collectors;
/**
*
* @author @stwalsh4118
* @author notgreat
*/
public final class KaitoDancingShadow extends CardImpl {
@ -46,7 +44,8 @@ public final class KaitoDancingShadow extends CardImpl {
this.setStartingLoyalty(3);
// Whenever one or more creatures you control deal combat damage to a player, you may return one of them to its owner's hand. If you do, you may activate loyalty abilities of Kaito twice this turn rather than only once.
Ability ability = new KaitoDancingShadowTriggeredAbility();
Ability ability = new DealCombatDamageControlledTriggeredAbility(Zone.BATTLEFIELD, new KaitoDancingShadowEffect(), true);
ability.addWatcher(new KaitoDancingShadowWatcher());
this.addAbility(ability);
// +1: Up to one target creature can't attack or block until your next turn.
@ -72,45 +71,6 @@ public final class KaitoDancingShadow extends CardImpl {
}
}
class KaitoDancingShadowTriggeredAbility extends TriggeredAbilityImpl {
KaitoDancingShadowTriggeredAbility() {
super(Zone.BATTLEFIELD, new KaitoDancingShadowEffect());
this.setTriggerPhrase("Whenever one or more creatures you control deal combat damage to a player, ");
this.addWatcher(new KaitoDancingShadowWatcher());
}
private KaitoDancingShadowTriggeredAbility(final KaitoDancingShadowTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DAMAGED_PLAYER_BATCH;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
DamagedPlayerBatchEvent dEvent = (DamagedPlayerBatchEvent) event;
for (DamagedEvent damagedEvent : dEvent.getEvents()) {
if (!damagedEvent.isCombatDamage()) {
continue;
}
Permanent permanent = game.getPermanent(damagedEvent.getSourceId());
if (permanent != null && permanent.isControlledBy(getControllerId())) {
return true;
}
}
return false;
}
@Override
public KaitoDancingShadowTriggeredAbility copy() {
return new KaitoDancingShadowTriggeredAbility(this);
}
}
class KaitoDancingShadowEffect extends OneShotEffect {
KaitoDancingShadowEffect() {
@ -133,13 +93,18 @@ class KaitoDancingShadowEffect extends OneShotEffect {
if (watcher == null) {
return false;
}
TargetCreatureThatDealtCombatDamage target = new TargetCreatureThatDealtCombatDamage(0, 1, watcher.getPermanents());
Player damagedPlayer = game.getPlayer(targetPointer.getFirst(game, source));
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
if (controller == null || damagedPlayer == null) {
return false;
}
if (controller.chooseUse(outcome, "Return a creature card that dealt damage to hand?", source, game) && target.chooseTarget(Outcome.ReturnToHand, source.getControllerId(), source, game)) {
Card card = game.getCard(target.getFirstTarget());
FilterPermanent filter = new FilterPermanent();
filter.add(new PermanentReferenceInCollectionPredicate(
watcher.getPermanents(controller.getId(),damagedPlayer.getId())));
TargetPermanent target = new TargetPermanent(0, 1, filter, true);
target.setTargetName("creature to return to hand?");
if (target.chooseTarget(Outcome.ReturnToHand, source.getControllerId(), source, game)) {
Card card = game.getPermanent(target.getFirstTarget());
if (card != null) {
controller.moveCards(card, Zone.HAND, source, game);
@ -147,13 +112,18 @@ class KaitoDancingShadowEffect extends OneShotEffect {
game.addEffect(effect, source);
}
}
return true;
}
}
class KaitoDancingShadowWatcher extends Watcher {
//A creature you control that dealt damage to a player - does not apply across multiple combat steps
private final List<Permanent> permanents = new ArrayList<>();
//Player ID -> List of permanents they controlled that dealt damage
private final Map<UUID, List<MageObjectReference>> permanents = new HashMap<>();
//MOR -> Player they dealt damage to
private final Map<MageObjectReference, UUID> damageTarget = new HashMap<>();
KaitoDancingShadowWatcher() {
super(WatcherScope.GAME);
@ -163,6 +133,7 @@ class KaitoDancingShadowWatcher extends Watcher {
public void watch(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_POST) {
permanents.clear();
damageTarget.clear();
return;
}
if (event.getType() != GameEvent.EventType.DAMAGED_PLAYER
@ -173,91 +144,18 @@ class KaitoDancingShadowWatcher extends Watcher {
if (creature == null) {
return;
}
permanents.add(creature);
MageObjectReference mor = new MageObjectReference(creature, game);
damageTarget.put(mor, event.getPlayerId());
List<MageObjectReference> list = permanents.computeIfAbsent(creature.getControllerId(), (key) -> new ArrayList<>());
list.add(mor);
}
public List<Permanent> getPermanents() {
return permanents;
}
}
class TargetCreatureThatDealtCombatDamage extends TargetObject {
protected List<Permanent> permanents;
private Permanent firstTarget = null;
public TargetCreatureThatDealtCombatDamage() {
super();
}
public TargetCreatureThatDealtCombatDamage(final TargetCreatureThatDealtCombatDamage target) {
super(target);
this.firstTarget = target.firstTarget;
}
public TargetCreatureThatDealtCombatDamage(int minNumTargets, int maxNumTargets, List<Permanent> permanents) {
super(minNumTargets, maxNumTargets, Zone.BATTLEFIELD, true);
this.permanents = permanents;
}
@Override
public boolean canTarget(UUID id, Game game) {
Card card = game.getCard(id);
if (card != null && game.getState().getZone(card.getId()) == Zone.BATTLEFIELD) {
for (Permanent permanent : permanents) {
if (permanent.getId().equals(id)) {
return true;
}
}
}
return false;
}
@Override
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) {
firstTarget = game.getPermanent(source.getFirstTarget());
return super.chooseTarget(Outcome.Benefit, playerId, source, game);
}
@Override
public TargetCreatureThatDealtCombatDamage copy() {
return new TargetCreatureThatDealtCombatDamage(this);
}
@Override
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
return permanents.size() > 0;
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>();
for (Permanent permanent : permanents) {
if (permanent != null && permanent.isControlledBy(sourceControllerId)) {
possibleTargets.add(permanent.getId());
}
}
return possibleTargets;
}
@Override
public Filter getFilter() {
return null;
}
@Override
public boolean canChoose(UUID sourceControllerId, Game game) {
// TODO Auto-generated method stub
return false;
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Game game) {
// TODO Auto-generated method stub
return null;
//Return the set of permanents that the controller controlled which dealt combat damage to the player
public Set<MageObjectReference> getPermanents(UUID controllerID, UUID damagedPlayerID) {
return permanents.get(controllerID).stream()
.filter((mor) -> damagedPlayerID.equals(damageTarget.get(mor)))
.collect(Collectors.toSet());
}
}
@ -278,13 +176,12 @@ class KaitoDancingShadowIncreaseLoyaltyUseEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
for (Permanent permanent : game.getBattlefield().getActivePermanents(
StaticFilters.FILTER_CONTROLLED_PERMANENT_PLANESWALKER,
source.getControllerId(), source, game
)) {
permanent.setLoyaltyActivationsAvailable(2);
Permanent kaito = source.getSourcePermanentIfItStillExists(game);
if (kaito == null) {
discard();
return false;
}
kaito.setLoyaltyActivationsAvailable(2);
return true;
}

View file

@ -16,7 +16,7 @@ import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.AbilityPredicate;
import mage.filter.predicate.permanent.PermanentInListPredicate;
import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate;
import mage.game.Game;
import mage.game.combat.CombatGroup;
import mage.game.permanent.Permanent;
@ -136,10 +136,10 @@ class RagingRiverEffect extends OneShotEffect {
if (controller.choosePile(outcome, attacker.getName() + ": attacking " + defender.getName(), leftLog, rightLog, game)) {
filter.add(Predicates.not(Predicates.or(new AbilityPredicate(FlyingAbility.class), new PermanentInListPredicate(left))));
filter.add(Predicates.not(Predicates.or(new AbilityPredicate(FlyingAbility.class), new PermanentReferenceInCollectionPredicate(left, game))));
game.informPlayers(attacker.getLogName() + ": attacks left (" + defender.getLogName() + ")");
} else {
filter.add(Predicates.not(Predicates.or(new AbilityPredicate(FlyingAbility.class), new PermanentInListPredicate(right))));
filter.add(Predicates.not(Predicates.or(new AbilityPredicate(FlyingAbility.class), new PermanentReferenceInCollectionPredicate(right, game))));
game.informPlayers(attacker.getLogName() + ": attacks right (" + defender.getLogName() + ")");
}
}

View file

@ -20,6 +20,6 @@ public class PermanentInListPredicate implements Predicate<Permanent> {
@Override
public boolean apply(Permanent input, Game game) {
return permanents.contains(input);
return (permanents != null && permanents.contains(input));
}
}

View file

@ -0,0 +1,33 @@
package mage.filter.predicate.permanent;
import mage.MageObjectReference;
import mage.filter.predicate.Predicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.Collection;
import java.util.stream.Collectors;
/**
*
* @author notgreat
*/
public class PermanentReferenceInCollectionPredicate implements Predicate<Permanent> {
private final Collection<MageObjectReference> references;
public PermanentReferenceInCollectionPredicate(Collection<MageObjectReference> references) {
//Note: it is assumed that the collection passed in isn't ever mutated afterwards
this.references = references;
}
public PermanentReferenceInCollectionPredicate(Collection<Permanent> permanents, Game game) {
this.references = permanents.stream().map((p) -> new MageObjectReference(p, game))
.collect(Collectors.toSet());
}
@Override
public boolean apply(Permanent input, Game game) {
return (references != null &&
references.contains(new MageObjectReference(input, game)));
}
}