[BRO] Implemented Gix, Yawgmoth Praetor (#9765)

* [BRO] Implemented Gix, Yawgmoth Praetor

* Add missing TestPlayer, PlayerStub implementations
This commit is contained in:
Daniel Bomar 2023-04-23 10:49:52 -05:00 committed by GitHub
parent 89687b305a
commit 92b6d7a531
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 280 additions and 15 deletions

View file

@ -2280,6 +2280,53 @@ public class HumanPlayer extends PlayerImpl {
return card.getSpellAbility();
}
@Override
public ActivatedAbility chooseLandOrSpellAbility(Card card, Game game, boolean noMana) {
if (gameInCheckPlayableState(game)) {
return null;
}
// TODO: add canRespond cycle?
if (!canRespond()) {
return null;
}
MageObject object = game.getObject(card.getId()); // must be object to find real abilities (example: commander)
if (object != null) {
LinkedHashMap<UUID, ActivatedAbility> useableAbilities = new LinkedHashMap<>(PlayerImpl.getCastableSpellAbilities(game, playerId, object, game.getState().getZone(object.getId()), noMana));
if (canPlayLand() && isActivePlayer(game)) {
for (Ability ability : card.getAbilities(game)) {
if (ability instanceof PlayLandAbility) {
useableAbilities.put(ability.getId(), (PlayLandAbility) ability);
}
}
}
switch (useableAbilities.size()) {
case 0:
return card.getSpellAbility();
case 1:
return useableAbilities.values().iterator().next();
default:
updateGameStatePriority("chooseLandOrSpellAbility", game);
prepareForResponse(game);
if (!isExecutingMacro()) {
String message = "Choose spell or ability to play" + (noMana ? " for FREE" : "") + "<br>" + object.getLogName();
game.fireGetChoiceEvent(playerId, message, object, new ArrayList<>(useableAbilities.values()));
}
waitForResponse(game);
ActivatedAbility response = useableAbilities.get(getFixedResponseUUID(game));
if (response != null) {
return response;
}
}
}
// default ability (example: on disconnect or cancel)
return card.getSpellAbility();
}
@Override
public Mode chooseMode(Modes modes, Ability source, Game game) {
// choose mode to activate

View file

@ -0,0 +1,152 @@
package mage.cards.g;
import java.util.Set;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.DiscardXTargetCost;
import mage.abilities.costs.common.PayLifeCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.common.GetXValue;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.DrawCardTargetEffect;
import mage.cards.*;
import mage.constants.*;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.DamagedEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetOpponent;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
/**
*
* @author weirddan455
*/
public final class GixYawgmothPraetor extends CardImpl {
public GixYawgmothPraetor(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{B}");
this.addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.PHYREXIAN);
this.subtype.add(SubType.PRAETOR);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Whenever a creature deals combat damage to one of your opponents, its controller may pay 1 life. If they do, they draw a card.
this.addAbility(new GixYawgmothPraetorTriggeredAbility());
// {4}{B}{B}{B}, Discard X cards: Exile the top X cards of target opponent's library. You may play land cards and cast spells from among cards exiled this way without paying their mana costs.
Ability ability = new SimpleActivatedAbility(new GixYawgmothPraetorExileEffect(), new ManaCostsImpl<>("{4}{B}{B}{B}"));
ability.addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS));
ability.addTarget(new TargetOpponent());
this.addAbility(ability);
}
private GixYawgmothPraetor(final GixYawgmothPraetor card) {
super(card);
}
@Override
public GixYawgmothPraetor copy() {
return new GixYawgmothPraetor(this);
}
}
class GixYawgmothPraetorTriggeredAbility extends TriggeredAbilityImpl {
public GixYawgmothPraetorTriggeredAbility() {
super(Zone.BATTLEFIELD, new GixYawgmothPraetorDrawEffect());
setTriggerPhrase("Whenever a creature deals combat damage to one of your opponents, ");
}
private GixYawgmothPraetorTriggeredAbility(final GixYawgmothPraetorTriggeredAbility ability) {
super(ability);
}
@Override
public GixYawgmothPraetorTriggeredAbility copy() {
return new GixYawgmothPraetorTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DAMAGED_PLAYER;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
DamagedEvent damagedEvent = (DamagedEvent) event;
if (damagedEvent.isCombatDamage() && game.getOpponents(controllerId).contains(damagedEvent.getTargetId())) {
Permanent permanent = game.getPermanentOrLKIBattlefield(damagedEvent.getSourceId());
if (permanent != null && permanent.isCreature(game)) {
getEffects().setTargetPointer(new FixedTarget(permanent.getControllerId()));
return true;
}
}
return false;
}
}
class GixYawgmothPraetorDrawEffect extends DoIfCostPaid {
public GixYawgmothPraetorDrawEffect() {
super(new DrawCardTargetEffect(1), new PayLifeCost(1), "Pay 1 life and draw a card?");
this.staticText = "its controller may pay 1 life. If they do, they draw a card";
}
private GixYawgmothPraetorDrawEffect(final GixYawgmothPraetorDrawEffect effect) {
super(effect);
}
@Override
public GixYawgmothPraetorDrawEffect copy() {
return new GixYawgmothPraetorDrawEffect(this);
}
@Override
protected Player getPayingPlayer(Game game, Ability source) {
return game.getPlayer(targetPointer.getFirst(game, source));
}
}
class GixYawgmothPraetorExileEffect extends OneShotEffect {
public GixYawgmothPraetorExileEffect() {
super(Outcome.PlayForFree);
this.staticText = "Exile the top X cards of target opponent's library. You may play land cards and cast spells from among cards exiled this way without paying their mana costs.";
}
private GixYawgmothPraetorExileEffect(final GixYawgmothPraetorExileEffect effect) {
super(effect);
}
@Override
public GixYawgmothPraetorExileEffect copy() {
return new GixYawgmothPraetorExileEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Player opponent = game.getPlayer(source.getFirstTarget());
if (controller == null || opponent == null) {
return false;
}
int xValue = GetXValue.instance.calculate(game, source, this);
Set<Card> toExile = opponent.getLibrary().getTopCards(game, xValue);
controller.moveCards(toExile, Zone.EXILED, source, game);
Cards cards = new CardsImpl(toExile);
cards.retainZone(Zone.EXILED, game);
CardUtil.castMultipleWithAttributeForFree(controller, source, game, cards, StaticFilters.FILTER_CARD, Integer.MAX_VALUE, null, true);
return true;
}
}

View file

@ -126,6 +126,7 @@ public final class TheBrothersWar extends ExpansionSet {
cards.add(new SetCardInfo("Giant Growth", 183, Rarity.COMMON, mage.cards.g.GiantGrowth.class));
cards.add(new SetCardInfo("Gix's Caress", 96, Rarity.COMMON, mage.cards.g.GixsCaress.class));
cards.add(new SetCardInfo("Gix's Command", 97, Rarity.RARE, mage.cards.g.GixsCommand.class));
cards.add(new SetCardInfo("Gix, Yawgmoth Praetor", 95, Rarity.MYTHIC, mage.cards.g.GixYawgmothPraetor.class));
cards.add(new SetCardInfo("Gixian Infiltrator", 98, Rarity.COMMON, mage.cards.g.GixianInfiltrator.class));
cards.add(new SetCardInfo("Gixian Puppeteer", 99, Rarity.RARE, mage.cards.g.GixianPuppeteer.class));
cards.add(new SetCardInfo("Gixian Skullflayer", 100, Rarity.COMMON, mage.cards.g.GixianSkullflayer.class));

View file

@ -4387,6 +4387,40 @@ public class TestPlayer implements Player {
return computerPlayer.chooseAbilityForCast(card, game, noMana);
}
@Override
public ActivatedAbility chooseLandOrSpellAbility(Card card, Game game, boolean noMana) {
assertAliasSupportInChoices(false);
MageObject object = game.getObject(card.getId()); // must be object to find real abilities (example: commander)
Map<UUID, ActivatedAbility> useable = new LinkedHashMap<>(PlayerImpl.getCastableSpellAbilities(game, this.getId(), object, game.getState().getZone(object.getId()), noMana));
if (canPlayLand()) {
for (Ability ability : card.getAbilities(game)) {
if (ability instanceof PlayLandAbility) {
useable.put(ability.getId(), (PlayLandAbility) ability);
}
}
}
if (useable.size() == 1) {
return useable.values().iterator().next();
}
if (!choices.isEmpty()) {
for (ActivatedAbility ability : useable.values()) {
if (ability.toString().startsWith(choices.get(0))) {
choices.remove(0);
return ability;
}
}
// TODO: enable fail checks and fix tests
//Assert.fail("Wrong choice");
LOGGER.warn("Wrong choice");
}
String allInfo = useable.values().stream().map(Object::toString).collect(Collectors.joining("\n"));
this.chooseStrictModeFailed("choice", game, getInfo(card) + " - can't select ability to cast.\n" + "Card's abilities:\n" + allInfo);
return computerPlayer.chooseAbilityForCast(card, game, noMana);
}
public ComputerPlayer getComputerPlayer() {
return computerPlayer;
}

View file

@ -1429,4 +1429,8 @@ public class PlayerStub implements Player {
return card.getSpellAbility();
}
@Override
public ActivatedAbility chooseLandOrSpellAbility(Card card, Game game, boolean noMana) {
return card.getSpellAbility();
}
}

View file

@ -425,6 +425,8 @@ public interface Player extends MageItem, Copyable<Player> {
*/
SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana);
ActivatedAbility chooseLandOrSpellAbility(Card card, Game game, boolean noMana);
boolean removeFromHand(Card card, Game game);
boolean removeFromBattlefield(Permanent permanent, Ability source, Game game);

View file

@ -5127,6 +5127,11 @@ public abstract class PlayerImpl implements Player, Serializable {
return this.phyrexianColors;
}
@Override
public ActivatedAbility chooseLandOrSpellAbility(Card card, Game game, boolean noMana) {
return card.getSpellAbility();
}
@Override
public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) {
return card.getSpellAbility();

View file

@ -4,10 +4,7 @@ import com.google.common.collect.ImmutableList;
import mage.ApprovingObject;
import mage.MageObject;
import mage.Mana;
import mage.abilities.Abilities;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.SpellAbility;
import mage.abilities.*;
import mage.abilities.condition.Condition;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
@ -1235,7 +1232,8 @@ public final class CardUtil {
void addCard(Card card, Ability source, Game game);
}
private static List<Card> getCastableComponents(Card cardToCast, FilterCard filter, Ability source, UUID playerId, Game game, SpellCastTracker spellCastTracker) {
private static List<Card> getCastableComponents(Card cardToCast, FilterCard filter, Ability source, Player player, Game game, SpellCastTracker spellCastTracker, boolean playLand) {
UUID playerId = player.getId();
List<Card> cards = new ArrayList<>();
if (cardToCast instanceof CardWithHalves) {
cards.add(((CardWithHalves) cardToCast).getLeftHalfCard());
@ -1247,7 +1245,9 @@ public final class CardUtil {
cards.add(cardToCast);
}
cards.removeIf(Objects::isNull);
cards.removeIf(card -> card.isLand(game));
if (!playLand || !player.canPlayLand() || !game.isActivePlayer(playerId)) {
cards.removeIf(card -> card.isLand(game));
}
cards.removeIf(card -> !filter.match(card, playerId, source, game));
if (spellCastTracker != null) {
cards.removeIf(card -> !spellCastTracker.checkCard(card, game));
@ -1266,9 +1266,13 @@ public final class CardUtil {
}
public static boolean castSpellWithAttributesForFree(Player player, Ability source, Game game, Cards cards, FilterCard filter, SpellCastTracker spellCastTracker) {
return castSpellWithAttributesForFree(player, source, game, cards, filter, spellCastTracker, false);
}
public static boolean castSpellWithAttributesForFree(Player player, Ability source, Game game, Cards cards, FilterCard filter, SpellCastTracker spellCastTracker, boolean playLand) {
Map<UUID, List<Card>> cardMap = new HashMap<>();
for (Card card : cards.getCards(game)) {
List<Card> castableComponents = getCastableComponents(card, filter, source, player.getId(), game, spellCastTracker);
List<Card> castableComponents = getCastableComponents(card, filter, source, player, game, spellCastTracker, playLand);
if (!castableComponents.isEmpty()) {
cardMap.put(card.getId(), castableComponents);
}
@ -1303,10 +1307,22 @@ public final class CardUtil {
return false;
}
partsToCast.forEach(card -> game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE));
boolean result = player.cast(
player.chooseAbilityForCast(cardToCast, game, true),
game, true, new ApprovingObject(source, game)
);
ActivatedAbility chosenAbility;
if (playLand) {
chosenAbility = player.chooseLandOrSpellAbility(cardToCast, game, true);
} else {
chosenAbility = player.chooseAbilityForCast(cardToCast, game, true);
}
boolean result = false;
if (chosenAbility instanceof SpellAbility) {
result = player.cast(
(SpellAbility) chosenAbility,
game, true, new ApprovingObject(source, game)
);
} else if (playLand && chosenAbility instanceof PlayLandAbility) {
Card land = game.getCard(chosenAbility.getSourceId());
result = player.playLand(land, game, true);
}
partsToCast.forEach(card -> game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null));
if (result && spellCastTracker != null) {
spellCastTracker.addCard(cardToCast, source, game);
@ -1317,11 +1333,11 @@ public final class CardUtil {
return result;
}
private static boolean checkForPlayable(Cards cards, FilterCard filter, Ability source, UUID playerId, Game game, SpellCastTracker spellCastTracker) {
private static boolean checkForPlayable(Cards cards, FilterCard filter, Ability source, Player player, Game game, SpellCastTracker spellCastTracker, boolean playLand) {
return cards
.getCards(game)
.stream()
.anyMatch(card -> !getCastableComponents(card, filter, source, playerId, game, spellCastTracker).isEmpty());
.anyMatch(card -> !getCastableComponents(card, filter, source, player, game, spellCastTracker, playLand).isEmpty());
}
public static void castMultipleWithAttributeForFree(Player player, Ability source, Game game, Cards cards, FilterCard filter) {
@ -1333,6 +1349,10 @@ public final class CardUtil {
}
public static void castMultipleWithAttributeForFree(Player player, Ability source, Game game, Cards cards, FilterCard filter, int maxSpells, SpellCastTracker spellCastTracker) {
castMultipleWithAttributeForFree(player, source, game, cards, filter, maxSpells, spellCastTracker, false);
}
public static void castMultipleWithAttributeForFree(Player player, Ability source, Game game, Cards cards, FilterCard filter, int maxSpells, SpellCastTracker spellCastTracker, boolean playLand) {
if (maxSpells == 1) {
CardUtil.castSpellWithAttributesForFree(player, source, game, cards, filter);
return;
@ -1340,11 +1360,11 @@ public final class CardUtil {
int spellsCast = 0;
cards.removeZone(Zone.STACK, game);
while (player.canRespond() && spellsCast < maxSpells && !cards.isEmpty()) {
if (CardUtil.castSpellWithAttributesForFree(player, source, game, cards, filter, spellCastTracker)) {
if (CardUtil.castSpellWithAttributesForFree(player, source, game, cards, filter, spellCastTracker, playLand)) {
spellsCast++;
cards.removeZone(Zone.STACK, game);
} else if (!checkForPlayable(
cards, filter, source, player.getId(), game, spellCastTracker
cards, filter, source, player, game, spellCastTracker, playLand
) || !player.chooseUse(
Outcome.PlayForFree, "Continue casting spells?", source, game
)) {