mirror of
https://github.com/correl/mage.git
synced 2024-12-24 11:50:45 +00:00
[J21] Implemented Faceless Agent
This commit is contained in:
parent
b10b3da8f0
commit
f5687acfad
8 changed files with 190 additions and 36 deletions
|
@ -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");
|
||||
|
|
122
Mage.Sets/src/mage/cards/f/FacelessAgent.java
Normal file
122
Mage.Sets/src/mage/cards/f/FacelessAgent.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
@ -637,9 +634,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
&& this.hasOpponent(sourceControllerId, game)
|
||||
&& game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) == null
|
||||
&& abilities.stream()
|
||||
.filter(HexproofBaseAbility.class::isInstance)
|
||||
.map(HexproofBaseAbility.class::cast)
|
||||
.anyMatch(ability -> ability.checkObject(source, game))) {
|
||||
.filter(HexproofBaseAbility.class::isInstance)
|
||||
.map(HexproofBaseAbility.class::cast)
|
||||
.anyMatch(ability -> ability.checkObject(source, game))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -679,7 +676,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
game.informPlayers(getLogName() + " discards down to "
|
||||
+ this.maxHandSize
|
||||
+ (this.maxHandSize == 1
|
||||
? " hand card" : " hand cards"));
|
||||
? " hand card" : " hand cards"));
|
||||
}
|
||||
discard(hand.size() - this.maxHandSize, false, false, null, game);
|
||||
}
|
||||
|
@ -1147,7 +1144,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
/**
|
||||
* @param originalAbility
|
||||
* @param game
|
||||
* @param noMana cast it without paying mana costs
|
||||
* @param noMana cast it without paying mana costs
|
||||
* @param approvingObject which object approved the cast
|
||||
* @return
|
||||
*/
|
||||
|
@ -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)) {
|
||||
|
@ -2848,7 +2861,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
* @param source
|
||||
* @param game
|
||||
* @param appliedEffects
|
||||
* @param numSides Number of sides the dice has
|
||||
* @param numSides Number of sides the dice has
|
||||
* @return the number that the player rolled
|
||||
*/
|
||||
@Override
|
||||
|
@ -2883,10 +2896,10 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
/**
|
||||
* @param game
|
||||
* @param appliedEffects
|
||||
* @param numberChaosSides The number of chaos sides the planar die
|
||||
* currently has (normally 1 but can be 5)
|
||||
* @param numberChaosSides The number of chaos sides the planar die
|
||||
* currently has (normally 1 but can be 5)
|
||||
* @param numberPlanarSides The number of chaos sides the planar die
|
||||
* currently has (normally 1)
|
||||
* currently has (normally 1)
|
||||
* @return the outcome that the player rolled. Either ChaosRoll, PlanarRoll
|
||||
* or NilRoll
|
||||
*/
|
||||
|
@ -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;
|
||||
|
@ -3060,7 +3073,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
* and cleared thereafter
|
||||
*
|
||||
* @param netManaAvailable the net mana produced by the triggered mana
|
||||
* abaility
|
||||
* abaility
|
||||
*/
|
||||
@Override
|
||||
public void addAvailableTriggeredMana(List<Mana> netManaAvailable
|
||||
|
@ -3142,7 +3155,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
/**
|
||||
* @param ability
|
||||
* @param availableMana if null, it won't be checked if enough mana is
|
||||
* available
|
||||
* available
|
||||
* @param sourceObject
|
||||
* @param game
|
||||
* @return
|
||||
|
@ -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 {
|
||||
|
@ -3570,10 +3583,10 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
* currently cast/activate with his available resources
|
||||
*
|
||||
* @param game
|
||||
* @param hidden also from hidden objects (e.g. turned face down cards ?)
|
||||
* @param fromZone of objects from which zone (ALL = from all zones)
|
||||
* @param hidden also from hidden objects (e.g. turned face down cards ?)
|
||||
* @param fromZone of objects from which zone (ALL = from all zones)
|
||||
* @param hideDuplicatedAbilities if equal abilities exist return only the
|
||||
* first instance
|
||||
* first instance
|
||||
* @return
|
||||
*/
|
||||
public List<ActivatedAbility> getPlayable(Game game, boolean hidden, Zone fromZone, boolean hideDuplicatedAbilities) {
|
||||
|
@ -4152,7 +4165,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCards(Set<? extends Card> cards, Zone toZone,
|
||||
Ability source, Game game
|
||||
Ability source, Game game
|
||||
) {
|
||||
return moveCards(cards, toZone, source, game, false, false, false, null);
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -4486,7 +4499,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName()
|
||||
+ (card.isCopy() ? " (Copy)" : "") : "a card face down") + ' '
|
||||
+ (fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH)
|
||||
+ ' ' : "") + "to the exile zone" + CardUtil.getSourceLogName(game, source, card.getId()));
|
||||
+ ' ' : "") + "to the exile zone" + CardUtil.getSourceLogName(game, source, card.getId()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ Bloodthirst|number|
|
|||
Bushido|number|
|
||||
Buyback|manaString|
|
||||
Cascade|new|
|
||||
Changeling|instance|
|
||||
Changeling|new|
|
||||
Convoke|new|
|
||||
Crew|number|
|
||||
Cumulative upkeep|cost|
|
||||
|
|
Loading…
Reference in a new issue