refactor methods to find cards in exile (#13967)

* refactor exile method names, add comments

* fix card effects checking exile with filter to process ObjectSourcePlayerPredicates

* fix card effects checking exile to respect range of influence
This commit is contained in:
xenohedron 2025-09-12 17:06:53 -04:00 committed by GitHub
parent 32af4a0671
commit 34c26f09c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 183 additions and 195 deletions

View file

@ -74,8 +74,8 @@ public enum CardsInExileCount implements DynamicValue {
return playerIds.stream()
.map(game::getPlayer)
.filter(Objects::nonNull)
.map(player -> game.getExile().getAllCards(game, player.getId()))
.map(player -> game.getExile().getCardsOwned(game, player.getId()))
.flatMap(Collection::stream)
.filter(Objects::nonNull);
}
}
}

View file

@ -3,7 +3,6 @@ package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.cards.Card;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
@ -17,18 +16,11 @@ public enum InstantSorceryExileGraveyardCount implements DynamicValue {
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
Player player = game.getPlayer(sourceAbility.getControllerId());
if (player != null) {
int exileCount = 0;
for (Card exiledCard : game.getExile().getAllCards(game)) {
if (exiledCard.getOwnerId().equals(player.getId()) && exiledCard.isInstantOrSorcery(game)) {
exileCount++;
}
}
return player.getGraveyard().count(
StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY, game
) + exileCount;
if (player == null) {
return 0;
}
return 0;
return game.getExile().getCardsOwned(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY, player.getId(), sourceAbility, game).size()
+ player.getGraveyard().count(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY, player.getId(), sourceAbility, game);
}
@Override

View file

@ -26,7 +26,7 @@ public enum TotalCardsExiledOwnedManaValue implements DynamicValue {
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
int totalCMC = 0;
List<Card> cards = game.getExile().getAllCards(
List<Card> cards = game.getExile().getCardsOwned(
game,
sourceAbility.getControllerId()
);

View file

@ -99,7 +99,7 @@ public class WishEffect extends OneShotEffect {
return false;
}
Cards cards = controller.getSideboard();
List<Card> exile = game.getExile().getAllCards(game);
List<Card> exile = game.getExile().getCardsOwned(game, controller.getId());
boolean noTargets = cards.isEmpty() && (!alsoFromExile || exile.isEmpty());
if (noTargets) {
game.informPlayer(controller, "You have no cards outside the game" + (alsoFromExile ? " or in exile" : "") + '.');

View file

@ -98,7 +98,7 @@ public class AddCreatureSubTypeAllMultiZoneEffect extends ContinuousEffectImpl {
}
}
// in Exile
for (Card card : game.getState().getExile().getAllCards(game, controllerId)) {
for (Card card : game.getState().getExile().getCardsOwned(game, controllerId)) {
if (filterCard.match(card, controllerId, source, game) && !card.hasSubtype(subType, game)) {
game.getState().getCreateMageObjectAttribute(card, game).getSubtype().add(subType);
}

View file

@ -44,7 +44,7 @@ public class GainAbilityControlledSpellsEffect extends ContinuousEffectImpl {
return false;
}
for (Card card : game.getExile().getAllCardsByRange(game, source.getControllerId())) {
for (Card card : game.getExile().getCardsInRange(game, source.getControllerId())) {
if (filter.match(card, player.getId(), source, game)) {
game.getState().addOtherAbility(card, ability);
}

View file

@ -93,7 +93,7 @@ public class NextSpellCastHasAbilityEffect extends ContinuousEffectImpl {
discard(); // only one use
return false;
}
for (Card card : game.getExile().getAllCardsByRange(game, playerId)) {
for (Card card : game.getExile().getCardsInRange(game, playerId)) {
if (filter.match(card, playerId, source, game)) {
game.getState().addOtherAbility(card, ability);
}

View file

@ -79,7 +79,7 @@ public class ChoiceCreatureType extends ChoiceImpl {
});
// exile
game.getExile().getAllCards(game, playerId).forEach(card -> {
game.getExile().getCardsOwned(game, playerId).forEach(card -> {
list.addAll(card.getSubtype(game).stream().map(SubType::toString).collect(Collectors.toList()));
});
});

View file

@ -1,5 +1,6 @@
package mage.game;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.filter.FilterCard;
import mage.util.Copyable;
@ -61,24 +62,33 @@ public class Exile implements Serializable, Copyable<Exile> {
return null;
}
/**
* Returns all cards in exile matching the filter. Use only for test framework.
* For card effects, instead use a method that checks owner or range of influence.
*/
@Deprecated
public List<Card> getCards(FilterCard filter, Game game) {
List<Card> allCards = getAllCards(game);
return allCards.stream().filter(card -> filter.match(card, game)).collect(Collectors.toList());
}
@Deprecated // TODO: must use related request due game range like getAllCardsByRange
/**
* Returns all cards in exile. Use only for test framework.
* For card effects, instead use a method that checks owner or range of influence.
*/
@Deprecated
public List<Card> getAllCards(Game game) {
return getAllCards(game, null);
return getCardsOwned(game, null);
}
/**
* Return exiled cards owned by a specific player. Use it in effects to find all cards in range.
* Returns all cards in exile owned by the specified player
*/
public List<Card> getAllCards(Game game, UUID fromPlayerId) {
public List<Card> getCardsOwned(Game game, UUID ownerId) {
List<Card> res = new ArrayList<>();
for (ExileZone exile : exileZones.values()) {
for (Card card : exile.getCards(game)) {
if (fromPlayerId == null || card.isOwnedBy(fromPlayerId)) {
if (ownerId == null || card.isOwnedBy(ownerId)) {
res.add(card);
}
}
@ -86,14 +96,37 @@ public class Exile implements Serializable, Copyable<Exile> {
return res;
}
public List<Card> getAllCardsByRange(Game game, UUID controllerId) {
/**
* Returns all cards in exile matching the filter, owned by the specified player
*/
public Set<Card> getCardsOwned(FilterCard filter, UUID playerId, Ability source, Game game) {
return getCardsOwned(game, playerId)
.stream()
.filter(card -> filter.match(card, playerId, source, game))
.collect(Collectors.toCollection(LinkedHashSet::new));
}
/**
* Returns all cards in exile in range of the specified player
*/
public List<Card> getCardsInRange(Game game, UUID controllerId) {
List<Card> res = new ArrayList<>();
for (UUID playerId : game.getState().getPlayersInRange(controllerId, game)) {
res.addAll(getAllCards(game, playerId));
res.addAll(getCardsOwned(game, playerId));
}
return res;
}
/**
* Returns all cards in exile matching the filter, in range of the specified player
*/
public Set<Card> getCardsInRange(FilterCard filter, UUID playerId, Ability source, Game game) {
return getCardsInRange(game, playerId)
.stream()
.filter(card -> filter.match(card, playerId, source, game))
.collect(Collectors.toCollection(LinkedHashSet::new));
}
public boolean removeCard(Card card) {
for (ExileZone exile : exileZones.values()) {
if (exile.contains(card.getId())) {

View file

@ -48,7 +48,6 @@ public class KayaTheInexorableEmblem extends Emblem {
class KayaTheInexorableEmblemEffect extends OneShotEffect {
private static final FilterCard filter = new FilterOwnedCard();
private static final FilterCard filter2 = new FilterCard();
private static final Set<String> choices = new LinkedHashSet<>();
@ -94,7 +93,7 @@ class KayaTheInexorableEmblemEffect extends OneShotEffect {
cards.addAll(player.getGraveyard());
break;
case "Exile":
cards.addAllCards(game.getExile().getCards(filter, game));
cards.addAllCards(game.getExile().getCardsOwned(game, player.getId()));
break;
}
return CardUtil.castSpellWithAttributesForFree(player, source, game, cards, filter2);

View file

@ -134,7 +134,7 @@ public class TargetCard extends TargetObject {
protected static Set<UUID> getAllPossibleTargetInExile(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget) {
Set<UUID> possibleTargets = new HashSet<>();
UUID sourceId = source != null ? source.getSourceId() : null;
for (Card card : game.getExile().getAllCardsByRange(game, sourceControllerId)) {
for (Card card : game.getExile().getCardsInRange(game, sourceControllerId)) {
if (filter.match(card, sourceControllerId, source, game)) {
possibleTargets.add(card.getId());
}

View file

@ -115,7 +115,7 @@ public class TargetSource extends TargetObject {
}
}
}
for (Card card : game.getExile().getAllCards(game)) {
for (Card card : game.getExile().getCardsInRange(game, sourceControllerId)) {
if (filter.match(card, sourceControllerId, source, game)) {
possibleTargets.add(card.getId());
}

View file

@ -52,7 +52,7 @@ public class TargetCardInExile extends TargetCard {
Set<UUID> possibleTargets = new HashSet<>();
if (zoneId == null) { // no specific exile zone
for (Card card : game.getExile().getAllCardsByRange(game, sourceControllerId)) {
for (Card card : game.getExile().getCardsInRange(game, sourceControllerId)) {
if (filter.match(card, sourceControllerId, source, game)) {
possibleTargets.add(card.getId());
}

View file

@ -66,7 +66,7 @@ public class TargetPermanentOrSuspendedCard extends TargetImpl {
possibleTargets.add(permanent.getId());
}
}
for (Card card : game.getExile().getAllCards(game)) {
for (Card card : game.getExile().getCardsInRange(game, sourceControllerId)) {
if (filter.match(card, sourceControllerId, source, game)) {
possibleTargets.add(card.getId());
}