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);
|
||||
|
@ -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();
|
||||
|
|
|
@ -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