[J21] Implemented Faceless Agent

This commit is contained in:
Evan Kranzler 2021-08-06 08:52:31 -04:00
parent b10b3da8f0
commit f5687acfad
8 changed files with 190 additions and 36 deletions

View file

@ -26,6 +26,7 @@ public class Historic extends Constructed {
// Additional sets
setCodes.add("JMP"); // Jumpstart (replacements below)
setCodes.add("STA"); // Strixhaven Mystical Archive
setCodes.add("J21"); // Jumpstart: Historic Horizons
// Regular ban list
banned.add("Agent of Treachery");

View file

@ -0,0 +1,122 @@
package mage.cards.f;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.ChangelingAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.SubTypeSet;
import mage.filter.FilterCard;
import mage.filter.common.FilterCreatureCard;
import mage.filter.predicate.Predicate;
import mage.filter.predicate.Predicates;
import mage.game.Game;
import mage.players.Player;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author TheElk801
*/
public final class FacelessAgent extends CardImpl {
public FacelessAgent(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}");
this.subtype.add(SubType.SHAPESHIFTER);
this.power = new MageInt(2);
this.toughness = new MageInt(1);
// Changeling
this.addAbility(new ChangelingAbility());
// When Faceless Agent enters the battlefield, seek a creature card of the most prevalent creature type in your library.
this.addAbility(new EntersBattlefieldTriggeredAbility(new FacelessAgentEffect()));
}
private FacelessAgent(final FacelessAgent card) {
super(card);
}
@Override
public FacelessAgent copy() {
return new FacelessAgent(this);
}
}
class FacelessAgentEffect extends OneShotEffect {
private static final FilterCard filterAnyType = new FilterCreatureCard();
static {
filterAnyType.add(FacelessAgentPredicate.instance);
}
FacelessAgentEffect() {
super(Outcome.Benefit);
staticText = "seek a creature card of the most prevalent creature type in your library";
}
private FacelessAgentEffect(final FacelessAgentEffect effect) {
super(effect);
}
@Override
public FacelessAgentEffect copy() {
return new FacelessAgentEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null || player.getLibrary().count(filterAnyType, game) < 1) {
return false;
}
Map<SubType, Integer> typeMap = player
.getLibrary()
.getCards(game)
.stream()
.filter(card -> !card.isAllCreatureTypes(game))
.map(card -> card.getSubtype(game))
.flatMap(Collection::stream)
.filter(subType -> subType.getSubTypeSet() == SubTypeSet.CreatureType)
.collect(Collectors.toMap(Function.identity(), x -> 1, Integer::sum));
if (typeMap.isEmpty()) {
return player.seekCard(filterAnyType, source, game);
}
int max = typeMap.values().stream().mapToInt(x -> x).max().orElse(0);
FilterCard filter = new FilterCreatureCard();
filter.add(Predicates.or(
typeMap.entrySet()
.stream()
.filter(entry -> entry.getValue() == max)
.map(Map.Entry::getKey)
.map(SubType::getPredicate)
.collect(Collectors.toSet())
));
return player.seekCard(filter, source, game);
}
}
enum FacelessAgentPredicate implements Predicate<Card> {
instance;
@Override
public boolean apply(Card input, Game game) {
return input.isAllCreatureTypes(game)
|| input
.getSubtype(game)
.stream()
.anyMatch(subType -> subType.getSubTypeSet() == SubTypeSet.CreatureType);
}
}

View file

@ -1,6 +1,7 @@
package mage.sets;
import mage.cards.ExpansionSet;
import mage.constants.Rarity;
import mage.constants.SetType;
/**
@ -15,8 +16,10 @@ public final class JumpstartHistoricHorizons extends ExpansionSet {
}
private JumpstartHistoricHorizons() {
super("Historic Anthology 5", "HA5", ExpansionSet.buildDate(2021, 8, 12), SetType.MAGIC_ARENA);
super("Jumpstart: Historic Horizons", "J21", ExpansionSet.buildDate(2021, 8, 12), SetType.MAGIC_ARENA);
this.hasBoosters = false;
this.hasBasicLands = false;
cards.add(new SetCardInfo("Faceless Agent", 31, Rarity.COMMON, mage.cards.f.FacelessAgent.class));
}
}

View file

@ -23,10 +23,7 @@ import mage.counters.CounterType;
import mage.counters.Counters;
import mage.designations.Designation;
import mage.designations.DesignationType;
import mage.filter.Filter;
import mage.filter.FilterMana;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.*;
import mage.filter.common.*;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.NamePredicate;
@ -3447,6 +3444,11 @@ public class TestPlayer implements Player {
return computerPlayer.searchLibrary(target, source, game, targetPlayerId);
}
@Override
public boolean seekCard(FilterCard filter, Ability source, Game game) {
return computerPlayer.seekCard(filter, source, game);
}
@Override
public void lookAtAllLibraries(Ability source, Game game) {
computerPlayer.lookAtAllLibraries(source, game);

View file

@ -20,6 +20,7 @@ import mage.counters.Counter;
import mage.counters.Counters;
import mage.designations.Designation;
import mage.designations.DesignationType;
import mage.filter.FilterCard;
import mage.filter.FilterMana;
import mage.filter.FilterPermanent;
import mage.game.Game;
@ -585,6 +586,11 @@ public class PlayerStub implements Player {
return false;
}
@Override
public boolean seekCard(FilterCard filter, Ability source, Game game) {
return false;
}
@Override
public void lookAtAllLibraries(Ability source, Game game) {
}

View file

@ -21,6 +21,7 @@ import mage.counters.Counter;
import mage.counters.Counters;
import mage.designations.Designation;
import mage.designations.DesignationType;
import mage.filter.FilterCard;
import mage.filter.FilterMana;
import mage.filter.FilterPermanent;
import mage.game.Game;
@ -430,6 +431,12 @@ public interface Player extends MageItem, Copyable<Player> {
boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId);
/**
* Gets a random card which matches the given filter and puts it into its owner's hand
* Doesn't reveal the card
*/
boolean seekCard(FilterCard filter, Ability source, Game game);
/**
* Reveals all players' libraries. Useful for abilities like Jace, Architect
* of Thought's -8 that have effects that require information from all

View file

@ -1,10 +1,6 @@
package mage.players;
import com.google.common.collect.ImmutableMap;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import mage.*;
import mage.abilities.*;
import mage.abilities.ActivatedAbility.ActivationStatus;
@ -12,11 +8,7 @@ import mage.abilities.common.PassAbility;
import mage.abilities.common.PlayLandAsCommanderAbility;
import mage.abilities.common.WhileSearchingPlayFromLibraryAbility;
import mage.abilities.common.delayed.AtTheEndOfTurnStepPostDelayedTriggeredAbility;
import mage.abilities.costs.AlternativeCost2;
import mage.abilities.costs.AlternativeSourceCosts;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.OptionalAdditionalSourceCosts;
import mage.abilities.costs.*;
import mage.abilities.costs.mana.AlternateManaPaymentAbility;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
@ -70,6 +62,11 @@ import mage.util.GameLog;
import mage.util.RandomUtil;
import org.apache.log4j.Logger;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
public abstract class PlayerImpl implements Player, Serializable {
private static final Logger logger = Logger.getLogger(PlayerImpl.class);
@ -2711,6 +2708,22 @@ public abstract class PlayerImpl implements Player, Serializable {
return true;
}
@Override
public boolean seekCard(FilterCard filter, Ability source, Game game) {
Set<Card> cards = this.getLibrary()
.getCards(game)
.stream()
.filter(card -> filter.match(card, source.getSourceId(), getId(), game))
.collect(Collectors.toSet());
Card card = RandomUtil.randomFromSet(cards);
if (card == null) {
return false;
}
game.informPlayers(this.getLogName() + " seeks a card from their library");
this.moveCards(card, Zone.HAND, source, game);
return true;
}
@Override
public void lookAtAllLibraries(Ability source, Game game) {
for (UUID playerId : game.getState().getPlayersInRange(this.getId(), game)) {
@ -2965,7 +2978,7 @@ public abstract class PlayerImpl implements Player, Serializable {
for (Card card : getHand().getCards(game)) {
Abilities<ActivatedManaAbilityImpl> manaAbilities
= card.getAbilities(game).getAvailableActivatedManaAbilities(Zone.HAND, playerId, game);
for (Iterator<ActivatedManaAbilityImpl> it = manaAbilities.iterator(); it.hasNext();) {
for (Iterator<ActivatedManaAbilityImpl> it = manaAbilities.iterator(); it.hasNext(); ) {
ActivatedManaAbilityImpl ability = it.next();
Abilities<ActivatedManaAbilityImpl> noTapAbilities = new AbilitiesImpl<>(ability);
if (ability.getManaCosts().isEmpty() && !ability.isPoolDependant()) {
@ -2982,7 +2995,7 @@ public abstract class PlayerImpl implements Player, Serializable {
boolean useLater = false; // sources with mana costs or mana pool dependency
Abilities<ActivatedManaAbilityImpl> manaAbilities
= permanent.getAbilities(game).getAvailableActivatedManaAbilities(Zone.BATTLEFIELD, playerId, game); // returns ability only if canActivate is true
for (Iterator<ActivatedManaAbilityImpl> it = manaAbilities.iterator(); it.hasNext();) {
for (Iterator<ActivatedManaAbilityImpl> it = manaAbilities.iterator(); it.hasNext(); ) {
ActivatedManaAbilityImpl ability = it.next();
if (canUse == null) {
canUse = permanent.canUseActivatedAbilities(game);
@ -3024,7 +3037,7 @@ public abstract class PlayerImpl implements Player, Serializable {
boolean usePoolDependantAbilities = false; // use such abilities later than other if possible because it can maximize mana production
while (anAbilityWasUsed && !sourceWithCosts.isEmpty()) {
anAbilityWasUsed = false;
for (Iterator<Abilities<ActivatedManaAbilityImpl>> iterator = sourceWithCosts.iterator(); iterator.hasNext();) {
for (Iterator<Abilities<ActivatedManaAbilityImpl>> iterator = sourceWithCosts.iterator(); iterator.hasNext(); ) {
Abilities<ActivatedManaAbilityImpl> manaAbilities = iterator.next();
if (usePoolDependantAbilities || !manaAbilities.hasPoolDependantAbilities()) {
boolean used;
@ -3291,7 +3304,7 @@ public abstract class PlayerImpl implements Player, Serializable {
ManaCostsImpl manaCosts = new ManaCostsImpl();
for (Cost cost : alternateSourceCostsAbility.getCosts()) {
if (cost instanceof AlternativeCost2) {
if(((AlternativeCost2) cost).getCost() instanceof ManaCost) {
if (((AlternativeCost2) cost).getCost() instanceof ManaCost) {
manaCosts.add((ManaCost) ((AlternativeCost2) cost).getCost());
}
} else {
@ -4308,7 +4321,7 @@ public abstract class PlayerImpl implements Player, Serializable {
// identify cards from one owner
Cards cards = new CardsImpl();
UUID ownerId = null;
for (Iterator<? extends Card> it = allCards.iterator(); it.hasNext();) {
for (Iterator<? extends Card> it = allCards.iterator(); it.hasNext(); ) {
Card card = it.next();
if (cards.isEmpty()) {
ownerId = card.getOwnerId();

View file

@ -10,7 +10,7 @@ Bloodthirst|number|
Bushido|number|
Buyback|manaString|
Cascade|new|
Changeling|instance|
Changeling|new|
Convoke|new|
Crew|number|
Cumulative upkeep|cost|