Implement The Ring Tempts You mechanic (#10320)

* remove skip

* initial implementation of the ring mechanic

* some changes

* rework ring-bearer choosing

* [LTR] Implement Call of the Ring

* update ring-bearer condition
This commit is contained in:
Evan Kranzler 2023-05-07 14:32:28 -04:00 committed by GitHub
parent d56c148118
commit 3503513c4e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 508 additions and 48 deletions

View file

@ -0,0 +1,71 @@
package mage.cards.c;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.costs.common.PayLifeCost;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.keyword.TheRingTemptsYouEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class CallOfTheRing extends CardImpl {
public CallOfTheRing(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}");
// At the beginning of your upkeep, the Ring tempts you.
this.addAbility(new BeginningOfUpkeepTriggeredAbility(
new TheRingTemptsYouEffect(), TargetController.YOU, false
));
// Whenever you choose a creature as your Ring-bearer, you may pay 2 life. If you do, draw a card.
this.addAbility(new CallOfTheRingTriggeredAbility());
}
private CallOfTheRing(final CallOfTheRing card) {
super(card);
}
@Override
public CallOfTheRing copy() {
return new CallOfTheRing(this);
}
}
class CallOfTheRingTriggeredAbility extends TriggeredAbilityImpl {
CallOfTheRingTriggeredAbility() {
super(Zone.BATTLEFIELD, new DoIfCostPaid(new DrawCardSourceControllerEffect(1), new PayLifeCost(2)));
setTriggerPhrase("Whenever you choose a creature as your Ring-bearer, ");
}
private CallOfTheRingTriggeredAbility(final CallOfTheRingTriggeredAbility ability) {
super(ability);
}
@Override
public CallOfTheRingTriggeredAbility copy() {
return new CallOfTheRingTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.RING_BEARER_CHOSEN;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return isControlledBy(event.getPlayerId());
}
}

View file

@ -1,10 +1,9 @@
package mage.cards.f;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldThisOrAnotherTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.Condition;
import mage.abilities.condition.common.SourceIsRingBearerCondition;
import mage.abilities.decorator.ConditionalRequirementEffect;
import mage.abilities.effects.common.combat.MustBeBlockedByAtLeastOneSourceEffect;
import mage.abilities.effects.keyword.TheRingTemptsYouEffect;
@ -15,7 +14,6 @@ import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import java.util.UUID;
@ -46,7 +44,7 @@ public final class FrodoBaggins extends CardImpl {
// As long as Frodo is your Ring-bearer, it must be blocked if able.
this.addAbility(new SimpleStaticAbility(new ConditionalRequirementEffect(
new MustBeBlockedByAtLeastOneSourceEffect(), FrodoBagginsCondition.instance,
new MustBeBlockedByAtLeastOneSourceEffect(), SourceIsRingBearerCondition.instance,
"as long as {this} is your Ring-bearer, it must be blocked if able"
)));
}
@ -60,13 +58,3 @@ public final class FrodoBaggins extends CardImpl {
return new FrodoBaggins(this);
}
}
enum FrodoBagginsCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
// TODO: Implement this
return false;
}
}

View file

@ -19,6 +19,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.watchers.common.TemptedByTheRingWatcher;
import java.util.UUID;
@ -80,7 +81,6 @@ enum FrodoSauronsBaneCondition implements Condition {
@Override
public boolean apply(Game game, Ability source) {
// TODO: Implement when mechanic is known
return false;
return TemptedByTheRingWatcher.getCount(source.getControllerId(), game) >= 4;
}
}

View file

@ -6,6 +6,8 @@ import mage.abilities.Ability;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.condition.Condition;
import mage.abilities.condition.InvertCondition;
import mage.abilities.condition.common.SourceIsRingBearerCondition;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenCopyTargetEffect;
@ -38,7 +40,7 @@ public final class SauronTheNecromancer extends CardImpl {
this.toughness = new MageInt(4);
// Menace
this.addAbility(new MenaceAbility());
this.addAbility(new MenaceAbility(false));
// Whenever Sauron, the Necromancer attacks, exile target creature card from your graveyard. Create a tapped and attacking token that's a copy of that card, except it's a 3/3 black Wraith with menace. At the beginning of the next end step, exile that token unless Sauron is your Ring-bearer.
Ability ability = new AttacksTriggeredAbility(new SauronTheNecromancerEffect());
@ -56,18 +58,10 @@ public final class SauronTheNecromancer extends CardImpl {
}
}
enum SauronTheNecromancerCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
// TODO: Implement this
return true;
}
}
class SauronTheNecromancerEffect extends OneShotEffect {
private static final Condition condition = new InvertCondition(SourceIsRingBearerCondition.instance);
SauronTheNecromancerEffect() {
super(Outcome.Benefit);
staticText = "exile target creature card from your graveyard. Create a tapped and attacking " +
@ -105,7 +99,7 @@ class SauronTheNecromancerEffect extends OneShotEffect {
effect.apply(game, source);
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(
new ConditionalOneShotEffect(
new ExileTargetEffect(), SauronTheNecromancerCondition.instance,
new ExileTargetEffect(), condition,
"exile that token unless {this} is your Ring-bearer"
).setTargetPointer(new FixedTargets(effect.getAddedPermanents(), game))
), source);

View file

@ -4,12 +4,8 @@ import mage.cards.ExpansionSet;
import mage.constants.Rarity;
import mage.constants.SetType;
import java.util.Arrays;
import java.util.List;
public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet {
private static final List<String> unfinished = Arrays.asList("Bilbo, Retired Burglar", "Call of the Ring", "Frodo Baggins", "Frodo, Sauron's Bane", "Gollum, Patient Plotter", "Samwise the Stouthearted");
private static final TheLordOfTheRingsTalesOfMiddleEarth instance = new TheLordOfTheRingsTalesOfMiddleEarth();
public static TheLordOfTheRingsTalesOfMiddleEarth getInstance() {
@ -24,6 +20,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet {
cards.add(new SetCardInfo("Aragorn and Arwen, Wed", 287, Rarity.MYTHIC, mage.cards.a.AragornAndArwenWed.class));
cards.add(new SetCardInfo("Bilbo, Retired Burglar", 403, Rarity.UNCOMMON, mage.cards.b.BilboRetiredBurglar.class));
cards.add(new SetCardInfo("Call of the Ring", 79, Rarity.RARE, mage.cards.c.CallOfTheRing.class));
cards.add(new SetCardInfo("Forest", 280, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS));
cards.add(new SetCardInfo("Frodo Baggins", 404, Rarity.UNCOMMON, mage.cards.f.FrodoBaggins.class));
cards.add(new SetCardInfo("Frodo, Sauron's Bane", 18, Rarity.RARE, mage.cards.f.FrodoSauronsBane.class));
@ -45,7 +42,5 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet {
cards.add(new SetCardInfo("Trailblazer's Boots", 398, Rarity.RARE, mage.cards.t.TrailblazersBoots.class));
cards.add(new SetCardInfo("Wizard's Rockets", 400, Rarity.COMMON, mage.cards.w.WizardsRockets.class));
cards.add(new SetCardInfo("You Cannot Pass!", 38, Rarity.UNCOMMON, mage.cards.y.YouCannotPass.class));
cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); // remove when mechanic is implemented
}
}

View file

@ -4361,6 +4361,21 @@ public class TestPlayer implements Player {
return computerPlayer.getPhyrexianColors();
}
@Override
public UUID getRingBearerId() {
return computerPlayer.getRingBearerId();
}
@Override
public Permanent getRingBearer(Game game) {
return computerPlayer.getRingBearer(game);
}
@Override
public void chooseRingBearer(Game game) {
computerPlayer.chooseRingBearer(game);
}
@Override
public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) {
assertAliasSupportInChoices(false);

View file

@ -1419,6 +1419,20 @@ public class PlayerStub implements Player {
return (new FilterMana());
}
@Override
public UUID getRingBearerId() {
return null;
}
@Override
public Permanent getRingBearer(Game game) {
return null;
}
@Override
public void chooseRingBearer(Game game) {
}
@Override
public UserData getControllingPlayersUserData(Game game) {
return null;

View file

@ -4,7 +4,8 @@ import mage.MageObjectReference;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
@ -16,7 +17,7 @@ import mage.util.CardUtil;
*/
public class AttacksCreatureYouControlTriggeredAbility extends TriggeredAbilityImpl {
protected final FilterControlledCreaturePermanent filter;
protected final FilterPermanent filter;
protected final boolean setTargetPointer;
protected boolean once = false;
@ -25,25 +26,29 @@ public class AttacksCreatureYouControlTriggeredAbility extends TriggeredAbilityI
}
public AttacksCreatureYouControlTriggeredAbility(Effect effect, boolean optional) {
this(effect, optional, new FilterControlledCreaturePermanent());
this(effect, optional, StaticFilters.FILTER_CONTROLLED_CREATURE);
}
public AttacksCreatureYouControlTriggeredAbility(Effect effect, boolean optional, boolean setTargetPointer) {
this(effect, optional, new FilterControlledCreaturePermanent(), setTargetPointer);
this(effect, optional, StaticFilters.FILTER_CONTROLLED_CREATURE, setTargetPointer);
}
public AttacksCreatureYouControlTriggeredAbility(Effect effect, boolean optional, FilterControlledCreaturePermanent filter) {
public AttacksCreatureYouControlTriggeredAbility(Effect effect, boolean optional, FilterPermanent filter) {
this(effect, optional, filter, false);
}
public AttacksCreatureYouControlTriggeredAbility(Effect effect, boolean optional, FilterControlledCreaturePermanent filter, boolean setTargetPointer) {
super(Zone.BATTLEFIELD, effect, optional);
public AttacksCreatureYouControlTriggeredAbility(Effect effect, boolean optional, FilterPermanent filter, boolean setTargetPointer) {
this(Zone.BATTLEFIELD, effect, optional, filter, setTargetPointer);
}
public AttacksCreatureYouControlTriggeredAbility(Zone zone, Effect effect, boolean optional, FilterPermanent filter, boolean setTargetPointer) {
super(zone, effect, optional);
this.filter = filter;
this.setTargetPointer = setTargetPointer;
setTriggerPhrase("Whenever " + CardUtil.addArticle(filter.getMessage()) + " attacks, ");
}
public AttacksCreatureYouControlTriggeredAbility(AttacksCreatureYouControlTriggeredAbility ability) {
private AttacksCreatureYouControlTriggeredAbility(final AttacksCreatureYouControlTriggeredAbility ability) {
super(ability);
this.filter = ability.filter;
this.setTargetPointer = ability.setTargetPointer;

View file

@ -0,0 +1,30 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import java.util.Objects;
import java.util.Optional;
/**
* @author TheElk801
*/
public enum SourceIsRingBearerCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
return Optional
.ofNullable(source.getSourcePermanentIfItStillExists(game))
.filter(Objects::nonNull)
.filter(permanent -> permanent.isControlledBy(source.getControllerId()))
.map(permanent -> permanent.isRingBearer(game))
.orElse(false);
}
@Override
public String toString() {
return "{this} is your Ring-bearer";
}
}

View file

@ -26,7 +26,7 @@ public class TheRingTemptsYouEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
// TODO: Implement when we know what the mechanic does
return false;
game.temptWithTheRing(source.getControllerId());
return true;
}
}

View file

@ -406,6 +406,8 @@ public interface Game extends MageItem, Serializable, Copyable<Game> {
void ventureIntoDungeon(UUID playerId, boolean undercity);
void temptWithTheRing(UUID playerId);
/**
* Tells whether the current game has day or night, defaults to false
*/
@ -552,8 +554,8 @@ public interface Game extends MageItem, Serializable, Copyable<Game> {
/**
* Function to call for a player to take the initiative.
*
* @param source The ability granting initiative.
* @param initiativeId UUID of the player taking the initiative
* @param source The ability granting initiative.
* @param initiativeId UUID of the player taking the initiative
*/
void takeInitiative(Ability source, UUID initiativeId);
@ -681,6 +683,6 @@ public interface Game extends MageItem, Serializable, Copyable<Game> {
void setGameStopped(boolean gameStopped);
boolean isGameStopped();
boolean isTurnOrderReversed();
}

View file

@ -41,6 +41,7 @@ import mage.game.combat.Combat;
import mage.game.combat.CombatGroup;
import mage.game.command.*;
import mage.game.command.dungeons.UndercityDungeon;
import mage.game.command.emblems.TheRingEmblem;
import mage.game.events.*;
import mage.game.events.TableEvent.EventType;
import mage.game.mulligan.Mulligan;
@ -563,6 +564,34 @@ public abstract class GameImpl implements Game {
fireEvent(GameEvent.getEvent(GameEvent.EventType.VENTURED, playerId, null, playerId));
}
private TheRingEmblem getOrCreateTheRing(UUID playerId) {
TheRingEmblem emblem = state
.getCommand()
.stream()
.filter(TheRingEmblem.class::isInstance)
.map(TheRingEmblem.class::cast)
.filter(commandObject -> commandObject.isControlledBy(playerId))
.findFirst()
.orElse(null);
if (emblem != null) {
return emblem;
}
TheRingEmblem newEmblem = new TheRingEmblem(playerId);
state.addCommandObject(newEmblem);
return newEmblem;
}
@Override
public void temptWithTheRing(UUID playerId) {
Player player = getPlayer(playerId);
if (player == null) {
return;
}
player.chooseRingBearer(this);
getOrCreateTheRing(playerId).addNextAbility(this);
fireEvent(GameEvent.getEvent(GameEvent.EventType.TEMPTED_BY_RING, playerId, null, playerId));
}
@Override
public boolean hasDayNight() {
return state.isHasDayNight();
@ -1302,6 +1331,7 @@ public abstract class GameImpl implements Game {
newWatchers.add(new EndStepCountWatcher());
newWatchers.add(new CommanderPlaysCountWatcher()); // commander plays count uses in non commander games by some cards
newWatchers.add(new CreaturesDiedWatcher());
newWatchers.add(new TemptedByTheRingWatcher());
// runtime check - allows only GAME scope (one watcher per game)
newWatchers.forEach(watcher -> {

View file

@ -0,0 +1,191 @@
package mage.game.command.emblems;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.AttacksCreatureYouControlTriggeredAbility;
import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.RestrictionEffect;
import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect;
import mage.abilities.effects.common.DrawDiscardControllerEffect;
import mage.abilities.effects.common.LoseLifeOpponentsEffect;
import mage.abilities.effects.common.SacrificeTargetEffect;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.Predicate;
import mage.game.Game;
import mage.game.command.Emblem;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
import mage.watchers.common.TemptedByTheRingWatcher;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class TheRingEmblem extends Emblem {
private static final FilterPermanent filter = new FilterControlledPermanent("your Ring-bearer");
static {
filter.add(TheRingEmblemPredicate.instance);
}
public TheRingEmblem(UUID controllerId) {
super();
this.setName("The Ring");
this.setExpansionSetCodeForImage("LTR");
this.setControllerId(controllerId);
}
public void addNextAbility(Game game) {
Ability ability;
switch (TemptedByTheRingWatcher.getCount(this.getControllerId(), game)) {
case 0:
// Your Ring-bearer is legendary and can't be blocked by creatures with greater power.
ability = new SimpleStaticAbility(Zone.COMMAND, new TheRingEmblemLegendaryEffect());
ability.addEffect(new TheRingEmblemEvasionEffect());
break;
case 1:
// Whenever your Ring-bearer attacks, draw a card, then discard a card.
ability = new AttacksCreatureYouControlTriggeredAbility(
Zone.COMMAND,
new DrawDiscardControllerEffect(1, 1),
false, filter, false
).setTriggerPhrase("Whenever your Ring-bearer attacks, ");
break;
case 2:
// Whenever your Ring-bearer becomes blocked by a creature, that creature's controller sacrifices it at end of combat.
ability = new TheRingEmblemTriggeredAbility();
break;
case 3:
// Whenever your Ring-bearer deals combat damage to a player, each opponent loses 3 life.
ability = new DealsDamageToAPlayerAllTriggeredAbility(
Zone.COMMAND, new LoseLifeOpponentsEffect(3), filter, false,
SetTargetPointer.NONE, true, false
);
break;
default:
return;
}
this.getAbilities().add(ability);
ability.setSourceId(this.getId());
ability.setControllerId(this.getControllerId());
game.getState().addAbility(ability, this);
}
}
enum TheRingEmblemPredicate implements Predicate<Permanent> {
instance;
@Override
public boolean apply(Permanent input, Game game) {
return input.isRingBearer(game);
}
}
class TheRingEmblemLegendaryEffect extends ContinuousEffectImpl {
TheRingEmblemLegendaryEffect() {
super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit);
staticText = "your Ring-bearer is legendary";
}
private TheRingEmblemLegendaryEffect(final TheRingEmblemLegendaryEffect effect) {
super(effect);
}
@Override
public TheRingEmblemLegendaryEffect copy() {
return new TheRingEmblemLegendaryEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = Optional
.ofNullable(game.getPlayer(source.getControllerId()))
.filter(Objects::nonNull)
.map(player -> player.getRingBearer(game))
.orElse(null);
if (permanent == null) {
return false;
}
permanent.addSuperType(SuperType.LEGENDARY);
return true;
}
}
class TheRingEmblemEvasionEffect extends RestrictionEffect {
TheRingEmblemEvasionEffect() {
super(Duration.WhileOnBattlefield);
staticText = "and can't be blocked by creatures with greater power";
}
private TheRingEmblemEvasionEffect(final TheRingEmblemEvasionEffect effect) {
super(effect);
}
@Override
public TheRingEmblemEvasionEffect copy() {
return new TheRingEmblemEvasionEffect(this);
}
@Override
public boolean applies(Permanent permanent, Ability source, Game game) {
return permanent.isControlledBy(source.getControllerId())
&& permanent.isRingBearer(game);
}
@Override
public boolean canBeBlocked(Permanent attacker, Permanent blocker, Ability source, Game game, boolean canUseChooseDialogs) {
return blocker.getPower().getValue() <= attacker.getPower().getValue();
}
}
class TheRingEmblemTriggeredAbility extends TriggeredAbilityImpl {
TheRingEmblemTriggeredAbility() {
super(Zone.COMMAND, new CreateDelayedTriggeredAbilityEffect(new AtTheEndOfCombatDelayedTriggeredAbility(new SacrificeTargetEffect())));
}
private TheRingEmblemTriggeredAbility(final TheRingEmblemTriggeredAbility ability) {
super(ability);
}
@Override
public TheRingEmblemTriggeredAbility copy() {
return new TheRingEmblemTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.CREATURE_BLOCKED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent attacker = game.getPermanent(event.getTargetId());
Permanent blocker = game.getPermanent(event.getSourceId());
if (attacker == null
|| blocker == null
|| attacker.isControlledBy(getControllerId())
|| !attacker.isRingBearer(game)) {
return false;
}
this.getEffects().setTargetPointer(new FixedTarget(blocker, game));
return true;
}
@Override
public String getRule() {
return "Whenever your Ring-bearer becomes blocked by a creature, " +
"that creature's controller sacrifices it at end of combat.";
}
}

View file

@ -493,6 +493,7 @@ public class GameEvent implements Serializable {
ROOM_ENTERED,
VENTURE, VENTURED,
DUNGEON_COMPLETED,
TEMPTED_BY_RING, RING_BEARER_CHOSEN,
REMOVED_FROM_COMBAT, // targetId id of permanent removed from combat
FORETOLD, // targetId id of card foretold
FORETELL, // targetId id of card foretell playerId id of the controller

View file

@ -419,6 +419,8 @@ public interface Permanent extends Card, Controllable {
boolean isManifested();
boolean isRingBearer(Game game);
@Override
Permanent copy();

View file

@ -37,7 +37,6 @@ import mage.players.Player;
import mage.target.TargetPlayer;
import mage.util.CardUtil;
import mage.util.GameLog;
import mage.util.RandomUtil;
import mage.util.ThreadLocalStringBuilder;
import org.apache.log4j.Logger;
@ -1813,6 +1812,12 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
this.secondSideCard = card;
}
@Override
public boolean isRingBearer(Game game) {
Player player = game.getPlayer(getControllerId());
return player != null && this.equals(player.getRingBearer(game));
}
@Override
public boolean fight(Permanent fightTarget, Ability source, Game game) {
return this.fight(fightTarget, source, game, true);

View file

@ -1080,6 +1080,12 @@ public interface Player extends MageItem, Copyable<Player> {
*/
FilterMana getPhyrexianColors();
UUID getRingBearerId();
Permanent getRingBearer(Game game);
void chooseRingBearer(Game game);
/**
* Function to query if the player has strictChooseMode enabled. Only the test player can have it.
* Function is added here so that the test suite project does not have to be imported into the client/server project.

View file

@ -58,6 +58,7 @@ import mage.target.TargetAmount;
import mage.target.TargetCard;
import mage.target.TargetPermanent;
import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.target.common.TargetDiscard;
import mage.util.CardUtil;
import mage.util.GameLog;
@ -176,6 +177,8 @@ public abstract class PlayerImpl implements Player, Serializable {
// mana colors the player can handle like Phyrexian mana
protected FilterMana phyrexianColors;
protected UUID ringBearerId = null;
// Used during available mana calculation to give back possible available net mana from triggered mana abilities (No need to copy)
protected final List<List<Mana>> availableTriggeredManaList = new ArrayList<>();
@ -285,6 +288,7 @@ public abstract class PlayerImpl implements Player, Serializable {
}
this.payManaMode = player.payManaMode;
this.phyrexianColors = player.getPhyrexianColors() != null ? player.phyrexianColors.copy() : null;
this.ringBearerId = player.ringBearerId;
for (Designation object : player.designations) {
this.designations.add(object.copy());
}
@ -372,6 +376,8 @@ public abstract class PlayerImpl implements Player, Serializable {
this.phyrexianColors = player.getPhyrexianColors() != null ? player.getPhyrexianColors().copy() : null;
this.ringBearerId = player.getRingBearerId();
this.designations.clear();
for (Designation object : player.getDesignations()) {
this.designations.add(object.copy());
@ -5127,6 +5133,62 @@ public abstract class PlayerImpl implements Player, Serializable {
return this.phyrexianColors;
}
@Override
public UUID getRingBearerId() {
return ringBearerId;
}
@Override
public Permanent getRingBearer(Game game) {
if (ringBearerId == null) {
return null;
}
Permanent bearer = game.getPermanent(ringBearerId);
if (bearer != null && bearer.isControlledBy(getId())) {
return bearer;
}
ringBearerId = null;
return null;
}
@Override
public void chooseRingBearer(Game game) {
Permanent currentBearer = getRingBearer(game);
int creatureCount = game.getBattlefield().count(
StaticFilters.FILTER_CONTROLLED_CREATURE, getId(), null, game
);
boolean mustChoose;
if (currentBearer == null) {
if (creatureCount > 0) {
mustChoose = true;
} else {
return;
}
} else if (currentBearer.isCreature(game)) {
if (creatureCount > 1) {
mustChoose = false;
} else {
return;
}
} else if (creatureCount > 0) {
mustChoose = false;
} else {
return;
}
if (!mustChoose && !chooseUse(Outcome.Neutral, "Choose a new Ring-bearer?", null, game)) {
return;
}
TargetPermanent target = new TargetControlledCreaturePermanent();
target.setNotTarget(true);
target.withChooseHint("to be your Ring-bearer");
choose(Outcome.Neutral, target, null, game);
UUID newBearerId = target.getFirstTarget();
if (game.getPermanent(newBearerId) != null) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.RING_BEARER_CHOSEN, newBearerId, null, getId()));
this.ringBearerId = newBearerId;
}
}
@Override
public ActivatedAbility chooseLandOrSpellAbility(Card card, Game game, boolean noMana) {
return card.getSpellAbility();

View file

@ -0,0 +1,49 @@
package mage.watchers.common;
import mage.constants.WatcherScope;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.util.CardUtil;
import mage.watchers.Watcher;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author TheElk801
*/
public class TemptedByTheRingWatcher extends Watcher {
private final Map<UUID, Integer> map = new HashMap<>();
public TemptedByTheRingWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
switch (event.getType()) {
case TEMPTED_BY_RING:
map.compute(event.getPlayerId(), CardUtil::setOrIncrementValue);
return;
case BEGINNING_PHASE_PRE:
if (game.getTurnNum() == 1) {
map.clear();
}
}
}
@Override
public void reset() {
super.reset();
}
public static int getCount(UUID playerId, Game game) {
return game
.getState()
.getWatcher(TemptedByTheRingWatcher.class)
.map
.getOrDefault(playerId, 0);
}
}