mirror of
https://github.com/magefree/mage.git
synced 2025-12-27 14:02:05 -08:00
[NCC] Implement several cards (#9328)
Many associated refactors too. See full PR for detail.
This commit is contained in:
parent
b7151cfa58
commit
fd16f2a16b
104 changed files with 6091 additions and 1069 deletions
|
|
@ -8,6 +8,9 @@ import mage.game.events.GameEvent;
|
|||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author LevelX2
|
||||
*/
|
||||
|
|
@ -31,10 +34,22 @@ public class EnchantedPlayerAttackedTriggeredAbility extends TriggeredAbilityImp
|
|||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
Permanent enchantment = game.getPermanentOrLKIBattlefield(getSourceId());
|
||||
Player controller = game.getPlayer(getControllerId());
|
||||
if (controller != null && enchantment != null) {
|
||||
return game.getCombat().getPlayerDefenders(game, false).contains(enchantment.getAttachedTo());
|
||||
Player attacker = game.getPlayer(game.getCombat().getAttackingPlayerId());
|
||||
if (controller == null || attacker == null || enchantment == null) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
Player enchantedPlayer = game.getPlayer(enchantment.getAttachedTo());
|
||||
if (enchantedPlayer == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<UUID> opponentIds = game.getOpponents(controller.getId());
|
||||
if (!opponentIds.contains(attacker.getId()) || !opponentIds.contains(enchantedPlayer.getId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return game.getCombat().getPlayerDefenders(game, false).contains(enchantment.getAttachedTo());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.MageItem;
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.constants.TargetController;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.events.ZoneChangeGroupEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* "Whenever one or more {filter} enter the battlefield under {target controller} control,
|
||||
*
|
||||
* @author Alex-Vasile
|
||||
*/
|
||||
public class EntersBattlefieldOneOrMoreTriggeredAbility extends TriggeredAbilityImpl {
|
||||
|
||||
private final FilterPermanent filterPermanent;
|
||||
private final TargetController targetController;
|
||||
|
||||
public EntersBattlefieldOneOrMoreTriggeredAbility(Effect effect, FilterPermanent filter, TargetController targetController) {
|
||||
super(Zone.BATTLEFIELD, effect);
|
||||
this.filterPermanent = filter;
|
||||
this.targetController = targetController;
|
||||
setTriggerPhrase(generateTriggerPhrase());
|
||||
}
|
||||
|
||||
private EntersBattlefieldOneOrMoreTriggeredAbility(final EntersBattlefieldOneOrMoreTriggeredAbility ability) {
|
||||
super(ability);
|
||||
this.filterPermanent = ability.filterPermanent;
|
||||
this.targetController = ability.targetController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.ZONE_CHANGE_GROUP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
ZoneChangeGroupEvent zEvent = (ZoneChangeGroupEvent) event;
|
||||
Player controller = game.getPlayer(this.controllerId);
|
||||
if (zEvent.getToZone() != Zone.BATTLEFIELD || controller == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (this.targetController) {
|
||||
case YOU:
|
||||
if (!controller.getId().equals(zEvent.getPlayerId())) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case OPPONENT:
|
||||
if (!controller.hasOpponent(zEvent.getPlayerId(), game)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return Stream.concat(
|
||||
zEvent.getTokens().stream(),
|
||||
zEvent.getCards().stream()
|
||||
.map(MageItem::getId)
|
||||
.map(game::getPermanent)
|
||||
.filter(Objects::nonNull)
|
||||
).anyMatch(permanent -> filterPermanent.match(permanent, this.controllerId, this, game));
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntersBattlefieldOneOrMoreTriggeredAbility copy() {
|
||||
return new EntersBattlefieldOneOrMoreTriggeredAbility(this);
|
||||
}
|
||||
|
||||
private String generateTriggerPhrase() {
|
||||
StringBuilder sb = new StringBuilder("Whenever one or more " + this.filterPermanent.getMessage() + " enter the battlefield under ");
|
||||
switch (targetController) {
|
||||
case YOU:
|
||||
sb.append("your control, ");
|
||||
break;
|
||||
case OPPONENT:
|
||||
sb.append("an opponent's control, ");
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,11 @@ package mage.abilities.common;
|
|||
import mage.abilities.Ability;
|
||||
import mage.abilities.DelayedTriggeredAbility;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.TriggeredAbility;
|
||||
import mage.abilities.effects.ContinuousEffect;
|
||||
import mage.abilities.effects.EntersBattlefieldEffect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
|
||||
import mage.abilities.keyword.EscapeAbility;
|
||||
import mage.constants.AbilityType;
|
||||
import mage.constants.Outcome;
|
||||
|
|
@ -23,22 +26,22 @@ import java.util.UUID;
|
|||
public class EscapesWithAbility extends EntersBattlefieldAbility {
|
||||
|
||||
private final int counters;
|
||||
private final DelayedTriggeredAbility delayedTriggeredAbility;
|
||||
private final TriggeredAbility triggeredAbility;
|
||||
|
||||
public EscapesWithAbility(int counters) {
|
||||
this(counters, null);
|
||||
}
|
||||
|
||||
public EscapesWithAbility(int counters, DelayedTriggeredAbility delayedTriggeredAbility) {
|
||||
super(new EscapesWithEffect(counters, delayedTriggeredAbility), false);
|
||||
public EscapesWithAbility(int counters, TriggeredAbility triggeredAbility) {
|
||||
super(new EscapesWithEffect(counters, triggeredAbility), false);
|
||||
this.counters = counters;
|
||||
this.delayedTriggeredAbility = delayedTriggeredAbility;
|
||||
this.triggeredAbility = triggeredAbility;
|
||||
}
|
||||
|
||||
private EscapesWithAbility(final EscapesWithAbility ability) {
|
||||
super(ability);
|
||||
this.counters = ability.counters;
|
||||
this.delayedTriggeredAbility = ability.delayedTriggeredAbility;
|
||||
this.triggeredAbility = ability.triggeredAbility;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -48,27 +51,42 @@ public class EscapesWithAbility extends EntersBattlefieldAbility {
|
|||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "{this} escapes with " + CardUtil.numberToText(counters, "a")
|
||||
+ " +1/+1 counter" + (counters > 1 ? 's' : "") + " on it."
|
||||
+ (this.delayedTriggeredAbility != null ? " " + this.delayedTriggeredAbility.getRule() : "");
|
||||
StringBuilder sb = new StringBuilder("{this} escapes with ");
|
||||
if (counters > 0) {
|
||||
sb.append(CardUtil.numberToText(counters, "a"));
|
||||
sb.append(" +1/+1 counter");
|
||||
sb.append((counters > 1 ? 's' : ""));
|
||||
sb.append(" on it.");
|
||||
}
|
||||
|
||||
if (triggeredAbility == null) {
|
||||
// Do nothing
|
||||
} else if (triggeredAbility instanceof DelayedTriggeredAbility) {
|
||||
sb.append(this.triggeredAbility.getRule());
|
||||
} else {
|
||||
sb.append("\"");
|
||||
sb.append(this.triggeredAbility.getRule());
|
||||
sb.append("\"");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class EscapesWithEffect extends OneShotEffect {
|
||||
|
||||
private final int counter;
|
||||
private final DelayedTriggeredAbility delayedTriggeredAbility;
|
||||
private final TriggeredAbility triggeredAbility;
|
||||
|
||||
EscapesWithEffect(int counter, DelayedTriggeredAbility delayedTriggeredAbility) {
|
||||
EscapesWithEffect(int counter, TriggeredAbility triggeredAbility) {
|
||||
super(Outcome.BoostCreature);
|
||||
this.counter = counter;
|
||||
this.delayedTriggeredAbility = delayedTriggeredAbility;
|
||||
this.triggeredAbility = triggeredAbility;
|
||||
}
|
||||
|
||||
private EscapesWithEffect(final EscapesWithEffect effect) {
|
||||
super(effect);
|
||||
this.counter = effect.counter;
|
||||
this.delayedTriggeredAbility = (effect.delayedTriggeredAbility == null ? null : effect.delayedTriggeredAbility.copy());
|
||||
this.triggeredAbility = (effect.triggeredAbility == null ? null : effect.triggeredAbility.copy());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -80,16 +98,26 @@ class EscapesWithEffect extends OneShotEffect {
|
|||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SpellAbility spellAbility = (SpellAbility) getValue(EntersBattlefieldEffect.SOURCE_CAST_SPELL_ABILITY);
|
||||
if (!(spellAbility instanceof EscapeAbility)
|
||||
|| !spellAbility.getSourceId().equals(source.getSourceId())
|
||||
|| permanent.getZoneChangeCounter(game) != spellAbility.getSourceObjectZoneChangeCounter()) {
|
||||
return false;
|
||||
}
|
||||
List<UUID> appliedEffects = (ArrayList<UUID>) this.getValue("appliedEffects");
|
||||
permanent.addCounters(CounterType.P1P1.createInstance(counter), source.getControllerId(), source, game, appliedEffects);
|
||||
if (this.delayedTriggeredAbility != null) {
|
||||
game.addDelayedTriggeredAbility(this.delayedTriggeredAbility, source);
|
||||
|
||||
if (counter > 0) {
|
||||
List<UUID> appliedEffects = (ArrayList<UUID>) this.getValue("appliedEffects");
|
||||
permanent.addCounters(CounterType.P1P1.createInstance(counter), source.getControllerId(), source, game, appliedEffects);
|
||||
}
|
||||
|
||||
if (this.triggeredAbility != null) {
|
||||
if (triggeredAbility instanceof DelayedTriggeredAbility) {
|
||||
game.addDelayedTriggeredAbility((DelayedTriggeredAbility) this.triggeredAbility, source);
|
||||
} else {
|
||||
ContinuousEffect gainsAbilityEffect = new GainAbilitySourceEffect(triggeredAbility);
|
||||
game.addEffect(gainsAbilityEffect, source);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@ public class SpellCastControllerTriggeredAbility extends TriggeredAbilityImpl {
|
|||
|
||||
// The source SPELL that triggered the ability will be set as target to effect
|
||||
protected boolean rememberSource;
|
||||
// Use it if you want remember CARD instead spell
|
||||
// Use it if you want to remember CARD instead spell
|
||||
protected boolean rememberSourceAsCard;
|
||||
// Trigger only for spells cast from this zone. Default is from any zone.
|
||||
private Zone fromZone = Zone.ALL;
|
||||
|
||||
public SpellCastControllerTriggeredAbility(Effect effect, boolean optional) {
|
||||
this(Zone.BATTLEFIELD, effect, StaticFilters.FILTER_SPELL_A, optional, false);
|
||||
|
|
@ -31,6 +33,11 @@ public class SpellCastControllerTriggeredAbility extends TriggeredAbilityImpl {
|
|||
this(effect, filter, optional, false);
|
||||
}
|
||||
|
||||
public SpellCastControllerTriggeredAbility(Effect effect, FilterSpell filter, boolean optional, Zone fromZone) {
|
||||
this(effect, filter, optional, false);
|
||||
this.fromZone = fromZone;
|
||||
}
|
||||
|
||||
public SpellCastControllerTriggeredAbility(Effect effect, FilterSpell filter, boolean optional, String rule) {
|
||||
this(effect, filter, optional, false);
|
||||
this.rule = rule;
|
||||
|
|
@ -49,7 +56,7 @@ public class SpellCastControllerTriggeredAbility extends TriggeredAbilityImpl {
|
|||
this.filter = filter;
|
||||
this.rememberSource = rememberSource;
|
||||
this.rememberSourceAsCard = rememberSourceAsCard;
|
||||
setTriggerPhrase("Whenever you cast " + filter.getMessage() + ", ");
|
||||
setTriggerPhrase("Whenever you cast " + filter.getMessage() + (fromZone != Zone.ALL ? "from your " + fromZone.toString().toLowerCase() : "") + ", ");
|
||||
}
|
||||
|
||||
public SpellCastControllerTriggeredAbility(final SpellCastControllerTriggeredAbility ability) {
|
||||
|
|
@ -58,6 +65,7 @@ public class SpellCastControllerTriggeredAbility extends TriggeredAbilityImpl {
|
|||
this.rule = ability.rule;
|
||||
this.rememberSource = ability.rememberSource;
|
||||
this.rememberSourceAsCard = ability.rememberSourceAsCard;
|
||||
this.fromZone = ability.fromZone;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -67,22 +75,20 @@ public class SpellCastControllerTriggeredAbility extends TriggeredAbilityImpl {
|
|||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
if (event.getPlayerId().equals(this.getControllerId())) {
|
||||
Spell spell = game.getStack().getSpell(event.getTargetId());
|
||||
if (filter.match(spell, getControllerId(), this, game)) {
|
||||
this.getEffects().setValue("spellCast", spell);
|
||||
if (rememberSource) {
|
||||
if (rememberSourceAsCard) {
|
||||
this.getEffects().setTargetPointer(new FixedTarget(spell.getCard().getId(), game));
|
||||
} else {
|
||||
this.getEffects().setTargetPointer(new FixedTarget(spell.getId(), game));
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!event.getPlayerId().equals(this.getControllerId())) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
Spell spell = game.getStack().getSpell(event.getTargetId());
|
||||
if (spell == null
|
||||
|| !filter.match(spell, getControllerId(), this, game)
|
||||
|| !(fromZone == Zone.ALL || fromZone == spell.getFromZone())) {
|
||||
return false;
|
||||
}
|
||||
this.getEffects().setValue("spellCast", spell);
|
||||
if (rememberSource) {
|
||||
this.getEffects().setTargetPointer(new FixedTarget(rememberSourceAsCard ? spell.getCard().getId() : spell.getId(), game));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -18,37 +18,60 @@ public class SpellCastOpponentTriggeredAbility extends TriggeredAbilityImpl {
|
|||
|
||||
protected FilterSpell filter;
|
||||
protected SetTargetPointer setTargetPointer;
|
||||
private final boolean onlyFromNonHand;
|
||||
|
||||
public SpellCastOpponentTriggeredAbility(Effect effect, boolean optional) {
|
||||
this(effect, StaticFilters.FILTER_SPELL_A, optional);
|
||||
this(effect, optional, false);
|
||||
}
|
||||
|
||||
public SpellCastOpponentTriggeredAbility(Effect effect, boolean optional, boolean onlyFromNonHand) {
|
||||
this(effect, StaticFilters.FILTER_SPELL_A, optional, onlyFromNonHand);
|
||||
}
|
||||
|
||||
public SpellCastOpponentTriggeredAbility(Effect effect, FilterSpell filter, boolean optional) {
|
||||
this(Zone.BATTLEFIELD, effect, filter, optional);
|
||||
this(effect, filter, optional, false);
|
||||
}
|
||||
|
||||
public SpellCastOpponentTriggeredAbility(Effect effect, FilterSpell filter, boolean optional, boolean onlyFromNonHand) {
|
||||
this(Zone.BATTLEFIELD, effect, filter, optional, onlyFromNonHand);
|
||||
}
|
||||
|
||||
public SpellCastOpponentTriggeredAbility(Zone zone, Effect effect, FilterSpell filter, boolean optional) {
|
||||
this(zone, effect, filter, optional, SetTargetPointer.NONE);
|
||||
this(zone, effect, filter, optional, false);
|
||||
}
|
||||
|
||||
public SpellCastOpponentTriggeredAbility(Zone zone, Effect effect, FilterSpell filter, boolean optional, boolean onlyFromNonHand) {
|
||||
this(zone, effect, filter, optional, SetTargetPointer.NONE, onlyFromNonHand);
|
||||
}
|
||||
|
||||
public SpellCastOpponentTriggeredAbility(Zone zone, Effect effect, FilterSpell filter, boolean optional, SetTargetPointer setTargetPointer) {
|
||||
this(zone, effect, filter, optional, setTargetPointer, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param zone
|
||||
* @param effect
|
||||
* @param filter
|
||||
* @param optional
|
||||
* @param setTargetPointer Supported: SPELL, PLAYER
|
||||
* @param zone The zone in which the source permanent has to be in for the ability to trigger
|
||||
* @param effect The effect to apply if condition is met
|
||||
* @param filter Filter for matching the spell cast
|
||||
* @param optional Whether the player can choose to apply the effect
|
||||
* @param onlyFromNonHand Whether to trigger only when spells are cast from not the hand
|
||||
* @param setTargetPointer Supported: SPELL, PLAYER
|
||||
*/
|
||||
public SpellCastOpponentTriggeredAbility(Zone zone, Effect effect, FilterSpell filter, boolean optional, SetTargetPointer setTargetPointer) {
|
||||
public SpellCastOpponentTriggeredAbility(Zone zone, Effect effect, FilterSpell filter, boolean optional, SetTargetPointer setTargetPointer, boolean onlyFromNonHand) {
|
||||
super(zone, effect, optional);
|
||||
this.filter = filter;
|
||||
this.setTargetPointer = setTargetPointer;
|
||||
setTriggerPhrase("Whenever an opponent casts " + filter.getMessage() + ", ");
|
||||
this.onlyFromNonHand = onlyFromNonHand;
|
||||
setTriggerPhrase("Whenever an opponent casts "
|
||||
+ filter.getMessage()
|
||||
+ (onlyFromNonHand ? " from anywhere other than their hand" : "")
|
||||
+ ", ");
|
||||
}
|
||||
|
||||
public SpellCastOpponentTriggeredAbility(final SpellCastOpponentTriggeredAbility ability) {
|
||||
super(ability);
|
||||
this.filter = ability.filter;
|
||||
this.setTargetPointer = ability.setTargetPointer;
|
||||
this.onlyFromNonHand = ability.onlyFromNonHand;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -65,6 +88,11 @@ public class SpellCastOpponentTriggeredAbility extends TriggeredAbilityImpl {
|
|||
if (!filter.match(spell, getControllerId(), this, game)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (onlyFromNonHand && spell.getFromZone() == Zone.HAND) {
|
||||
return false;
|
||||
}
|
||||
|
||||
getEffects().setValue("spellCast", spell);
|
||||
switch (setTargetPointer) {
|
||||
case NONE:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
/**
|
||||
* "Whenever you or a permanent you control becomes the target of a spell or ability an opponent controls,"
|
||||
* AND
|
||||
* "Whenever you become the target of a spell or ability an opponent controls,"
|
||||
*
|
||||
* @author Alex-Vasile
|
||||
*/
|
||||
public class TargetOfOpponentsSpellOrAbilityTriggeredAbility extends TriggeredAbilityImpl {
|
||||
|
||||
private Boolean onlyController = Boolean.FALSE;
|
||||
|
||||
public TargetOfOpponentsSpellOrAbilityTriggeredAbility(Effect effect) {
|
||||
this(effect, false);
|
||||
}
|
||||
|
||||
// NOTE: Using Boolean instead of boolean in order to have a second constructor with (Effect, "boolean") signature
|
||||
public TargetOfOpponentsSpellOrAbilityTriggeredAbility(Effect effect, Boolean onlyController) {
|
||||
this(effect, false, onlyController);
|
||||
}
|
||||
|
||||
public TargetOfOpponentsSpellOrAbilityTriggeredAbility(Effect effect, boolean optional) {
|
||||
this(effect, optional, Boolean.FALSE);
|
||||
}
|
||||
|
||||
public TargetOfOpponentsSpellOrAbilityTriggeredAbility(Effect effect, boolean optional, Boolean onlyController) {
|
||||
super(Zone.BATTLEFIELD, effect, optional);
|
||||
this.onlyController = onlyController;
|
||||
if (this.onlyController) {
|
||||
setTriggerPhrase("Whenever you become the target of a spell or ability an opponent controls, ");
|
||||
} else {
|
||||
setTriggerPhrase("Whenever you or a permanent you control becomes the target of a spell or ability an opponent controls, ");
|
||||
}
|
||||
}
|
||||
|
||||
private TargetOfOpponentsSpellOrAbilityTriggeredAbility(final TargetOfOpponentsSpellOrAbilityTriggeredAbility ability) {
|
||||
super(ability);
|
||||
this.onlyController = ability.onlyController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.TARGETED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
Player controller = game.getPlayer(this.getControllerId());
|
||||
Player targetter = game.getPlayer(event.getPlayerId());
|
||||
if (controller == null || targetter == null || controller.getId().equals(targetter.getId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if player was targeted
|
||||
if (controller.getId().equals(event.getTargetId())) {
|
||||
// Add target for effects which need it (e.g. the counter effect from AmuletOfSafekeeping)
|
||||
this.getEffects().setTargetPointer(new FixedTarget(event.getSourceId(), game));
|
||||
return true;
|
||||
}
|
||||
|
||||
// If only the controller is
|
||||
if (this.onlyController) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if permanent was targeted
|
||||
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId());
|
||||
if (permanent == null || !controller.getId().equals(permanent.getControllerId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add target for effects which need it (e.g. the counter effect from AmuletOfSafekeeping)
|
||||
this.getEffects().setTargetPointer(new FixedTarget(event.getSourceId(), game));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetOfOpponentsSpellOrAbilityTriggeredAbility copy() {
|
||||
return new TargetOfOpponentsSpellOrAbilityTriggeredAbility(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package mage.abilities.condition.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.counters.Counter;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
public enum SourceHasCountersCondition implements Condition {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = game.getPermanent(source.getSourceId());
|
||||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
return permanent
|
||||
.getCounters(game)
|
||||
.values()
|
||||
.stream()
|
||||
.mapToInt(Counter::getCount)
|
||||
.anyMatch(x -> x > 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package mage.abilities.costs.common;
|
|||
import mage.abilities.Ability;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.CostImpl;
|
||||
import mage.cards.Card;
|
||||
import mage.choices.Choice;
|
||||
import mage.choices.ChoiceImpl;
|
||||
import mage.constants.Outcome;
|
||||
|
|
@ -11,6 +12,9 @@ import mage.counters.CounterType;
|
|||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetCard;
|
||||
import mage.target.TargetObject;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
|
|
@ -23,19 +27,19 @@ import java.util.UUID;
|
|||
*/
|
||||
public class RemoveCounterCost extends CostImpl {
|
||||
|
||||
protected final TargetPermanent target;
|
||||
protected final Target target;
|
||||
private final CounterType counterTypeToRemove;
|
||||
protected final int countersToRemove;
|
||||
|
||||
public RemoveCounterCost(TargetPermanent target) {
|
||||
public RemoveCounterCost(Target target) {
|
||||
this(target, null);
|
||||
}
|
||||
|
||||
public RemoveCounterCost(TargetPermanent target, CounterType counterTypeToRemove) {
|
||||
public RemoveCounterCost(Target target, CounterType counterTypeToRemove) {
|
||||
this(target, counterTypeToRemove, 1);
|
||||
}
|
||||
|
||||
public RemoveCounterCost(TargetPermanent target, CounterType counterTypeToRemove, int countersToRemove) {
|
||||
public RemoveCounterCost(Target target, CounterType counterTypeToRemove, int countersToRemove) {
|
||||
this.target = target;
|
||||
this.counterTypeToRemove = counterTypeToRemove;
|
||||
this.countersToRemove = countersToRemove;
|
||||
|
|
@ -50,70 +54,94 @@ public class RemoveCounterCost extends CostImpl {
|
|||
this.counterTypeToRemove = cost.counterTypeToRemove;
|
||||
}
|
||||
|
||||
// TODO: TayamLuminousEnigmaCost can be simplified
|
||||
@Override
|
||||
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
|
||||
paid = false;
|
||||
int countersRemoved = 0;
|
||||
Player controller = game.getPlayer(controllerId);
|
||||
if (controller != null) {
|
||||
if (countersToRemove == 0) { // Can happen when used for X costs where X = 0;
|
||||
return paid = true;
|
||||
}
|
||||
target.clearChosen();
|
||||
if (target.choose(Outcome.UnboostCreature, controllerId, source.getSourceId(), source, game)) {
|
||||
for (UUID targetId : target.getTargets()) {
|
||||
Permanent permanent = game.getPermanent(targetId);
|
||||
if (permanent != null) {
|
||||
if (!permanent.getCounters(game).isEmpty() && (counterTypeToRemove == null || permanent.getCounters(game).containsKey(counterTypeToRemove))) {
|
||||
String counterName = null;
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
if (countersToRemove == 0) { // Can happen when used for X costs where X = 0;
|
||||
paid = true;
|
||||
return paid;
|
||||
}
|
||||
target.clearChosen();
|
||||
|
||||
if (counterTypeToRemove != null) {
|
||||
counterName = counterTypeToRemove.getName();
|
||||
} else if (permanent.getCounters(game).size() > 1 && counterTypeToRemove == null) {
|
||||
Choice choice = new ChoiceImpl(true);
|
||||
Set<String> choices = new HashSet<>();
|
||||
for (Counter counter : permanent.getCounters(game).values()) {
|
||||
if (permanent.getCounters(game).getCount(counter.getName()) > 0) {
|
||||
choices.add(counter.getName());
|
||||
}
|
||||
}
|
||||
choice.setChoices(choices);
|
||||
choice.setMessage("Choose a counter to remove from " + permanent.getLogName());
|
||||
if (!controller.choose(Outcome.UnboostCreature, choice, game)) {
|
||||
return false;
|
||||
}
|
||||
counterName = choice.getChoice();
|
||||
} else {
|
||||
for (Counter counter : permanent.getCounters(game).values()) {
|
||||
if (counter.getCount() > 0) {
|
||||
counterName = counter.getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (counterName != null && !counterName.isEmpty()) {
|
||||
int countersLeft = countersToRemove - countersRemoved;
|
||||
int countersOnPermanent = permanent.getCounters(game).getCount(counterName);
|
||||
int numberOfCountersSelected = 1;
|
||||
if (countersLeft > 1 && countersOnPermanent > 1) {
|
||||
numberOfCountersSelected = controller.getAmount(1, Math.min(countersLeft, countersOnPermanent),
|
||||
new StringBuilder("Remove how many counters from ").append(permanent.getIdName()).toString(), game);
|
||||
}
|
||||
permanent.removeCounters(counterName, numberOfCountersSelected, source, game);
|
||||
countersRemoved += numberOfCountersSelected;
|
||||
if (!game.isSimulation()) {
|
||||
game.informPlayers(new StringBuilder(controller.getLogName())
|
||||
.append(" removes ").append(numberOfCountersSelected == 1 ? "a" : numberOfCountersSelected).append(' ')
|
||||
.append(counterName).append(numberOfCountersSelected == 1 ? " counter from " : " counters from ")
|
||||
.append(permanent.getName()).toString());
|
||||
}
|
||||
if (countersRemoved == countersToRemove) {
|
||||
this.paid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Outcome outcome;
|
||||
if (target instanceof TargetPermanent) {
|
||||
outcome = Outcome.UnboostCreature;
|
||||
} else if (target instanceof TargetCard) { // For Mari, the Killing Quill
|
||||
outcome = Outcome.Neutral;
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Wrong target type provided for RemoveCounterCost. Provided " + target.getClass() + ". " +
|
||||
"From ability " + ability);
|
||||
}
|
||||
|
||||
if (!target.choose(outcome, controllerId, source.getSourceId(), source, game)) {
|
||||
return paid;
|
||||
}
|
||||
for (UUID targetId : target.getTargets()) {
|
||||
Card targetObject;
|
||||
if (target instanceof TargetPermanent) {
|
||||
targetObject = game.getPermanent(targetId);
|
||||
} else { // For Mari, the Killing Quill
|
||||
targetObject = game.getCard(targetId);
|
||||
}
|
||||
|
||||
if (targetObject == null
|
||||
|| targetObject.getCounters(game).isEmpty()
|
||||
|| !(counterTypeToRemove == null || targetObject.getCounters(game).containsKey(counterTypeToRemove))) {
|
||||
continue;
|
||||
}
|
||||
String counterName = null;
|
||||
|
||||
if (counterTypeToRemove != null) { // Counter type specified
|
||||
counterName = counterTypeToRemove.getName();
|
||||
} else if (targetObject.getCounters(game).size() == 1) { // Only one counter of creature
|
||||
for (Counter counter : targetObject.getCounters(game).values()) {
|
||||
if (counter.getCount() > 0) {
|
||||
counterName = counter.getName();
|
||||
}
|
||||
}
|
||||
} else { // Multiple counters, player much choose which type to remove from
|
||||
Choice choice = new ChoiceImpl(true);
|
||||
Set<String> choices = new HashSet<>();
|
||||
for (Counter counter : targetObject.getCounters(game).values()) {
|
||||
if (targetObject.getCounters(game).getCount(counter.getName()) > 0) {
|
||||
choices.add(counter.getName());
|
||||
}
|
||||
}
|
||||
choice.setChoices(choices);
|
||||
choice.setMessage("Choose a counter to remove from " + targetObject.getLogName());
|
||||
if (!controller.choose(Outcome.UnboostCreature, choice, game)) {
|
||||
return false;
|
||||
}
|
||||
counterName = choice.getChoice();
|
||||
}
|
||||
|
||||
if (counterName != null && !counterName.isEmpty()) {
|
||||
int countersLeft = countersToRemove - countersRemoved;
|
||||
int countersOnPermanent = targetObject.getCounters(game).getCount(counterName);
|
||||
int numberOfCountersSelected = 1;
|
||||
if (countersLeft > 1 && countersOnPermanent > 1) {
|
||||
numberOfCountersSelected = controller.getAmount(1, Math.min(countersLeft, countersOnPermanent),
|
||||
"Remove how many counters from " + targetObject.getIdName(), game);
|
||||
}
|
||||
targetObject.removeCounters(counterName, numberOfCountersSelected, source, game);
|
||||
countersRemoved += numberOfCountersSelected;
|
||||
if (!game.isSimulation()) {
|
||||
game.informPlayers(controller.getLogName() +
|
||||
" removes " + (numberOfCountersSelected == 1 ? "a" : numberOfCountersSelected) + ' ' +
|
||||
counterName + (numberOfCountersSelected == 1 ? " counter from " : " counters from ") +
|
||||
targetObject.getName());
|
||||
}
|
||||
if (countersRemoved == countersToRemove) {
|
||||
this.paid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,164 @@
|
|||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.Cards;
|
||||
import mage.cards.CardsImpl;
|
||||
import mage.constants.CommanderCardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterCreatureCard;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetCard;
|
||||
import mage.target.common.TargetCardInCommandZone;
|
||||
import mage.target.common.TargetCardInGraveyard;
|
||||
import mage.target.common.TargetCardInHand;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
/**
|
||||
* "Put a {filter} card from {zone 1} or {zone 2} onto the battlefield.
|
||||
*
|
||||
* @author TheElk801, Alex-Vasile
|
||||
*/
|
||||
public class PutCardFromOneOfTwoZonesOntoBattlefieldEffect extends OneShotEffect {
|
||||
|
||||
private final FilterCard filterCard;
|
||||
private final boolean tapped;
|
||||
private final Effect effectToApplyOnPermanent;
|
||||
private final Zone zone1;
|
||||
private final Zone zone2;
|
||||
|
||||
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect(FilterCard filterCard) {
|
||||
this(filterCard, false);
|
||||
}
|
||||
|
||||
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect(FilterCard filterCard, boolean tapped) {
|
||||
this(filterCard, tapped, null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param filterCard Filter used to filter which cards are valid choices. (no default)
|
||||
* @param tapped If the permanent should enter the battlefield tapped (default is False)
|
||||
* @param effectToApplyOnPermanent An effect to apply to the permanent after it enters (default null)
|
||||
* See "Swift Warkite" or "Nissa of Shadowed Boughs".
|
||||
* @param zone1 The first zone to pick from (default of HAND)
|
||||
* @param zone2 The second zone to pick from (defualt of GRAVEYARD)
|
||||
*/
|
||||
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect(FilterCard filterCard, boolean tapped, Effect effectToApplyOnPermanent, Zone zone1, Zone zone2) {
|
||||
super(filterCard instanceof FilterCreatureCard ? Outcome.PutCreatureInPlay : Outcome.PutCardInPlay);
|
||||
this.filterCard = filterCard;
|
||||
this.tapped = tapped;
|
||||
this.effectToApplyOnPermanent = effectToApplyOnPermanent;
|
||||
this.zone1 = zone1;
|
||||
this.zone2 = zone2;
|
||||
}
|
||||
|
||||
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect(FilterCard filterCard, boolean tapped, Effect effectToApplyOnPermanent) {
|
||||
this(filterCard, tapped, effectToApplyOnPermanent, Zone.HAND, Zone.GRAVEYARD);
|
||||
}
|
||||
|
||||
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect(FilterCard filterCard, Zone zone1, Zone zone2) {
|
||||
this(filterCard, false, null, zone1, zone2);
|
||||
}
|
||||
|
||||
private PutCardFromOneOfTwoZonesOntoBattlefieldEffect(final PutCardFromOneOfTwoZonesOntoBattlefieldEffect effect) {
|
||||
super(effect);
|
||||
this.filterCard = effect.filterCard;
|
||||
this.tapped = effect.tapped;
|
||||
this.zone1 = effect.zone1;
|
||||
this.zone2 = effect.zone2;
|
||||
if (effect.effectToApplyOnPermanent != null) {
|
||||
this.effectToApplyOnPermanent = effect.effectToApplyOnPermanent.copy();
|
||||
} else {
|
||||
this.effectToApplyOnPermanent = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Cards cardsInZone1 = getCardsFromZone(game, controller, zone1);
|
||||
Cards cardsInZone2 = getCardsFromZone(game, controller, zone2);
|
||||
|
||||
boolean cardsAvailableInZone1 = cardsInZone1.count(filterCard, game) > 0;
|
||||
boolean cardsAvailableInZone2 = cardsInZone2.count(filterCard, game) > 0;
|
||||
if (!cardsAvailableInZone1 && !cardsAvailableInZone2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean choose1stZone;
|
||||
if (cardsAvailableInZone1 && cardsAvailableInZone2) {
|
||||
choose1stZone = controller.chooseUse(outcome, "Where do you want to chose the card from?",
|
||||
null, zone1.name(), zone2.name(), source, game);
|
||||
} else {
|
||||
choose1stZone = cardsAvailableInZone1;
|
||||
}
|
||||
|
||||
Zone zone = choose1stZone ? zone1 : zone2;
|
||||
Cards cards = choose1stZone ? cardsInZone1 : cardsInZone2;
|
||||
TargetCard targetCard;
|
||||
|
||||
switch (zone) {
|
||||
case HAND:
|
||||
targetCard = new TargetCardInHand(filterCard);
|
||||
break;
|
||||
case GRAVEYARD:
|
||||
targetCard = new TargetCardInGraveyard(filterCard);
|
||||
break;
|
||||
case COMMAND:
|
||||
targetCard = new TargetCardInCommandZone(filterCard);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
controller.choose(outcome, cards, targetCard, game);
|
||||
Card card = game.getCard(targetCard.getFirstTarget());
|
||||
if (card == null || !controller.moveCards(card, Zone.BATTLEFIELD, source, game, tapped, false, false, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (effectToApplyOnPermanent != null) {
|
||||
effectToApplyOnPermanent.setTargetPointer(new FixedTarget(card.getId()));
|
||||
effectToApplyOnPermanent.apply(game, source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Cards getCardsFromZone(Game game, Player player, Zone zone) {
|
||||
switch (zone) {
|
||||
case HAND:
|
||||
return player.getHand();
|
||||
case COMMAND:
|
||||
return new CardsImpl(game.getCommanderCardsFromCommandZone(player, CommanderCardType.ANY));
|
||||
case GRAVEYARD:
|
||||
return player.getGraveyard();
|
||||
default:
|
||||
return new CardsImpl();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect copy() {
|
||||
return new PutCardFromOneOfTwoZonesOntoBattlefieldEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText(Mode mode) {
|
||||
return "you may put " + CardUtil.addArticle(this.filterCard.getMessage()) +
|
||||
" from your hand or graveyard onto the battlefield" +
|
||||
(this.tapped ? " tapped" : "") +
|
||||
(effectToApplyOnPermanent == null ? "" : ". " + effectToApplyOnPermanent.getText(mode));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
|
||||
/**
|
||||
* Used for "Return {this} to the battlefield attached to that creature at the beginning of the next end step" abilities.
|
||||
* E.g.g Gift of Immortality and Next of Kin.
|
||||
*
|
||||
* @author LevelX2, Alex-Vasile
|
||||
*/
|
||||
public class ReturnToBattlefieldAttachedEffect extends OneShotEffect {
|
||||
|
||||
public ReturnToBattlefieldAttachedEffect() {
|
||||
super(Outcome.PutCardInPlay);
|
||||
staticText = "Return {this} to the battlefield attached to that creature at the beginning of the next end step";
|
||||
}
|
||||
|
||||
public ReturnToBattlefieldAttachedEffect(final ReturnToBattlefieldAttachedEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source));
|
||||
Card aura = game.getCard(source.getSourceId());
|
||||
if (controller == null
|
||||
|| creature == null
|
||||
|| aura == null || game.getState().getZone(aura.getId()) != Zone.GRAVEYARD) {
|
||||
return false;
|
||||
}
|
||||
|
||||
game.getState().setValue("attachTo:" + aura.getId(), creature);
|
||||
controller.moveCards(aura, Zone.BATTLEFIELD, source, game);
|
||||
return creature.addAttachment(aura.getId(), source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReturnToBattlefieldAttachedEffect copy() {
|
||||
return new ReturnToBattlefieldAttachedEffect(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
package mage.abilities.effects.common.continuous;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.abilities.keyword.ReplicateAbility;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Layer;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubLayer;
|
||||
import mage.filter.FilterSpell;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.game.stack.StackObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
/**
|
||||
* @author LevelX2, Alex-Vasile
|
||||
*/
|
||||
public class EachSpellYouCastHasReplicateEffect extends ContinuousEffectImpl {
|
||||
|
||||
private final FilterSpell filter;
|
||||
private final Cost fixedNewCost;
|
||||
private final Map<UUID, ReplicateAbility> replicateAbilities = new HashMap<>();
|
||||
|
||||
public EachSpellYouCastHasReplicateEffect(FilterSpell filter) {
|
||||
this(filter, null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param filter Filter used for filtering spells
|
||||
* @param fixedNewCost Fixed new cost to pay as the replication cost
|
||||
*/
|
||||
public EachSpellYouCastHasReplicateEffect(FilterSpell filter, Cost fixedNewCost) {
|
||||
super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
|
||||
this.filter = filter;
|
||||
this.fixedNewCost = fixedNewCost;
|
||||
this.staticText = "Each " + this.filter.getMessage() + " you cast has replicate" +
|
||||
(this.fixedNewCost == null ? ". The replicate cost is equal to its mana cost" : ' ' + this.fixedNewCost.getText())
|
||||
+ ". <i>(When you cast it, copy it for each time you paid its replicate cost. You may choose new targets for the copies.)</i>";
|
||||
}
|
||||
|
||||
private EachSpellYouCastHasReplicateEffect(final EachSpellYouCastHasReplicateEffect effect) {
|
||||
super(effect);
|
||||
this.filter = effect.filter;
|
||||
this.fixedNewCost = effect.fixedNewCost;
|
||||
this.replicateAbilities.putAll(effect.replicateAbilities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = game.getPermanent(source.getSourceId());
|
||||
if (permanent == null
|
||||
|| !permanent.isControlledBy(source.getControllerId())) { // Verify that the controller of the permanent is the one who cast the spell
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean applied = false;
|
||||
|
||||
for (StackObject stackObject : game.getStack()) {
|
||||
if (!(stackObject instanceof Spell)
|
||||
|| stackObject.isCopy()
|
||||
|| !stackObject.isControlledBy(source.getControllerId())
|
||||
|| (fixedNewCost == null && stackObject.getManaCost().isEmpty())) { // If the spell has no mana cost, it cannot be played by this ability unless an fixed alternative cost (e.g. such as from Threefold Signal) is specified.
|
||||
continue;
|
||||
}
|
||||
Spell spell = (Spell) stackObject;
|
||||
if (filter.match(stackObject, game)) {
|
||||
Cost cost = fixedNewCost != null ? fixedNewCost.copy() : spell.getSpellAbility().getManaCosts().copy();
|
||||
ReplicateAbility replicateAbility = replicateAbilities.computeIfAbsent(spell.getId(), k -> new ReplicateAbility(cost));
|
||||
game.getState().addOtherAbility(spell.getCard(), replicateAbility, false); // Do not copy because paid and # of activations state is handled in the baility
|
||||
applied = true;
|
||||
}
|
||||
}
|
||||
if (game.getStack().isEmpty()) {
|
||||
replicateAbilities.clear();
|
||||
}
|
||||
|
||||
return applied;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EachSpellYouCastHasReplicateEffect copy() {
|
||||
return new EachSpellYouCastHasReplicateEffect(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package mage.abilities.effects.common.continuous;
|
|||
import mage.abilities.Ability;
|
||||
import mage.abilities.ActivatedAbility;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Layer;
|
||||
|
|
@ -24,6 +25,7 @@ public class GainControlTargetEffect extends ContinuousEffectImpl {
|
|||
protected UUID controllingPlayerId;
|
||||
private boolean fixedControl;
|
||||
private boolean firstControlChange = true;
|
||||
private final Condition condition;
|
||||
|
||||
public GainControlTargetEffect(Duration duration) {
|
||||
this(duration, false, null);
|
||||
|
|
@ -48,15 +50,21 @@ public class GainControlTargetEffect extends ContinuousEffectImpl {
|
|||
}
|
||||
|
||||
public GainControlTargetEffect(Duration duration, boolean fixedControl, UUID controllingPlayerId) {
|
||||
this(duration, fixedControl, controllingPlayerId, null);
|
||||
}
|
||||
|
||||
public GainControlTargetEffect(Duration duration, boolean fixedControl, UUID controllingPlayerId, Condition condition) {
|
||||
super(duration, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl);
|
||||
this.controllingPlayerId = controllingPlayerId;
|
||||
this.fixedControl = fixedControl;
|
||||
this.condition = condition;
|
||||
}
|
||||
|
||||
public GainControlTargetEffect(final GainControlTargetEffect effect) {
|
||||
super(effect);
|
||||
this.controllingPlayerId = effect.controllingPlayerId;
|
||||
this.fixedControl = effect.fixedControl;
|
||||
this.condition = effect.condition;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -106,6 +114,9 @@ public class GainControlTargetEffect extends ContinuousEffectImpl {
|
|||
// This does not handle correctly multiple targets at once
|
||||
discard();
|
||||
}
|
||||
if (condition != null && !condition.apply(game, source)) {
|
||||
discard();
|
||||
}
|
||||
}
|
||||
// no valid target exists and the controller is no longer in the game, effect can be discarded
|
||||
if (!oneTargetStillExists || !controller.isInGame()) {
|
||||
|
|
|
|||
|
|
@ -2,34 +2,55 @@ package mage.abilities.effects.common.counter;
|
|||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.choices.Choice;
|
||||
import mage.choices.ChoiceImpl;
|
||||
import mage.constants.Outcome;
|
||||
import mage.counters.Counter;
|
||||
import mage.counters.CounterType;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class AddCounterChoiceSourceEffect extends OneShotEffect {
|
||||
|
||||
private final CounterType counterType1;
|
||||
private final CounterType counterType2;
|
||||
private final List<CounterType> counterTypes;
|
||||
|
||||
public AddCounterChoiceSourceEffect(CounterType counterType1, CounterType counterType2) {
|
||||
public AddCounterChoiceSourceEffect(CounterType ... counterTypes) {
|
||||
super(Outcome.Benefit);
|
||||
this.counterType1 = counterType1;
|
||||
this.counterType2 = counterType2;
|
||||
staticText = "with your choice of a " + counterType1 + " counter or a " + counterType2 + " counter on it";
|
||||
this.counterTypes = Arrays.stream(counterTypes).collect(Collectors.toList());
|
||||
this.createStaticText();
|
||||
}
|
||||
|
||||
private AddCounterChoiceSourceEffect(final AddCounterChoiceSourceEffect effect) {
|
||||
super(effect);
|
||||
this.counterType1 = effect.counterType1;
|
||||
this.counterType2 = effect.counterType2;
|
||||
this.counterTypes = new ArrayList<>(effect.counterTypes);
|
||||
}
|
||||
|
||||
private void createStaticText() {
|
||||
switch (this.counterTypes.size()) {
|
||||
case 0:
|
||||
case 1:
|
||||
throw new IllegalArgumentException("AddCounterChoiceSourceEffect should be called with at least 2 " +
|
||||
"counter types, it was called with: " + this.counterTypes);
|
||||
case 2:
|
||||
this.staticText = "with your choice of a " + this.counterTypes.get(0) +
|
||||
" counter or a " + this.counterTypes.get(1) + " counter on it";
|
||||
break;
|
||||
default:
|
||||
List<String> strings = this.counterTypes.stream().map(CounterType::toString).collect(Collectors.toList());
|
||||
this.staticText = CardUtil.concatWithOr(strings);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -39,19 +60,30 @@ public class AddCounterChoiceSourceEffect extends OneShotEffect {
|
|||
if (player == null || permanent == null) {
|
||||
return false;
|
||||
}
|
||||
Counter counter;
|
||||
if (player.chooseUse(
|
||||
Outcome.Neutral, "Choose " + counterType1 + " or " + counterType2, null,
|
||||
cap(counterType1.getName()), cap(counterType2.getName()), source, game
|
||||
)) {
|
||||
counter = counterType1.createInstance();
|
||||
} else {
|
||||
counter = counterType2.createInstance();
|
||||
|
||||
Choice counterChoice = new ChoiceImpl();
|
||||
counterChoice.setMessage("Choose counter type");
|
||||
counterChoice.setChoices(
|
||||
this.counterTypes
|
||||
.stream()
|
||||
.map(counterType -> AddCounterChoiceSourceEffect.capitalize(counterType.getName()))
|
||||
.collect(Collectors.toSet())
|
||||
);
|
||||
|
||||
if (!player.choose(Outcome.Neutral, counterChoice, game)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CounterType counterChosen = CounterType.findByName(counterChoice.getChoice().toLowerCase(Locale.ENGLISH));
|
||||
if (counterChosen == null || !this.counterTypes.contains(counterChosen)) {
|
||||
return false;
|
||||
}
|
||||
Counter counter = counterChosen.createInstance();
|
||||
|
||||
return permanent.addCounters(counter, source.getControllerId(), source, game);
|
||||
}
|
||||
|
||||
private static final String cap(String string) {
|
||||
private static String capitalize(String string) {
|
||||
return string != null ? string.substring(0, 1).toUpperCase(Locale.ENGLISH) + string.substring(1) : null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,87 +1,95 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.abilities.common.delayed.ReflexiveTriggeredAbility;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.*;
|
||||
import mage.abilities.costs.common.SacrificeTargetCost;
|
||||
import mage.abilities.effects.common.CopySourceSpellEffect;
|
||||
import mage.abilities.effects.common.InfoEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.ComparisonType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.filter.predicate.mageobject.PowerPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.common.TargetControlledPermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
* @author TheElk801, Alex-Vasile
|
||||
*/
|
||||
public class CasualtyAbility extends StaticAbility {
|
||||
public class CasualtyAbility extends StaticAbility implements OptionalAdditionalSourceCosts {
|
||||
|
||||
public CasualtyAbility(Card card, int number) {
|
||||
super(Zone.ALL, new InfoEffect(
|
||||
"Casualty " + number + " <i>(As you cast this spell, " +
|
||||
"you may sacrifice a creature with power " + number +
|
||||
" or greater. When you do, copy this spell.)</i>"
|
||||
));
|
||||
card.getSpellAbility().addCost(new CasualtyCost(number));
|
||||
private static final String keywordText = "Casualty";
|
||||
private final String promptString;
|
||||
|
||||
protected OptionalAdditionalCost additionalCost;
|
||||
|
||||
private static TargetControlledPermanent makeFilter(int number) {
|
||||
FilterControlledPermanent filter = new FilterControlledCreaturePermanent(
|
||||
"creature with power " + number + " or greater"
|
||||
);
|
||||
filter.add(new PowerPredicate(ComparisonType.MORE_THAN, number - 1));
|
||||
return new TargetControlledPermanent(1, 1, filter, true);
|
||||
}
|
||||
|
||||
public CasualtyAbility(int number) {
|
||||
super(Zone.STACK, null);
|
||||
String reminderText = "As you cast this spell, you may sacrifice a creature with power " +
|
||||
number + " or greater. When you do, copy this spell.";
|
||||
this.promptString = "Sacrifice a creature with power " + number + " or greater?";
|
||||
this.additionalCost = new OptionalAdditionalCostImpl(keywordText, reminderText, new SacrificeTargetCost(makeFilter(number)));
|
||||
this.additionalCost.setRepeatable(false);
|
||||
this.setRuleAtTheTop(true);
|
||||
}
|
||||
|
||||
private CasualtyAbility(final CasualtyAbility ability) {
|
||||
super(ability);
|
||||
this.additionalCost = ability.additionalCost;
|
||||
this.promptString = ability.promptString;
|
||||
}
|
||||
|
||||
public void resetCasualty() {
|
||||
if (additionalCost != null) {
|
||||
additionalCost.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CasualtyAbility copy() {
|
||||
return new CasualtyAbility(this);
|
||||
}
|
||||
}
|
||||
|
||||
class CasualtyCost extends SacrificeTargetCost {
|
||||
|
||||
CasualtyCost(int number) {
|
||||
super(new TargetControlledPermanent(0, 1, makeFilter(number), true));
|
||||
this.text = "";
|
||||
}
|
||||
|
||||
private CasualtyCost(final CasualtyCost cost) {
|
||||
super(cost);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
|
||||
if (!super.pay(ability, game, source, controllerId, noMana, costToPay)) {
|
||||
return false;
|
||||
public void addOptionalAdditionalCosts(Ability ability, Game game) {
|
||||
if (!(ability instanceof SpellAbility)) {
|
||||
return;
|
||||
}
|
||||
if (!getPermanents().isEmpty()) {
|
||||
game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility(
|
||||
new CopySourceSpellEffect(), false, "when you do, copy this spell"
|
||||
), source);
|
||||
|
||||
Player player = game.getPlayer(ability.getControllerId());
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
return true;
|
||||
|
||||
this.resetCasualty();
|
||||
boolean canPay = additionalCost.canPay(ability, this, ability.getControllerId(), game);
|
||||
if (!canPay || !player.chooseUse(Outcome.Sacrifice, promptString, ability, game)) {
|
||||
return;
|
||||
}
|
||||
|
||||
additionalCost.activate();
|
||||
for (Cost cost : ((Costs<Cost>) additionalCost)) {
|
||||
ability.getCosts().add(cost.copy());
|
||||
}
|
||||
game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility(
|
||||
new CopySourceSpellEffect(), false, "when you do, copy this spell"
|
||||
), ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CasualtyCost copy() {
|
||||
return new CasualtyCost(this);
|
||||
}
|
||||
|
||||
private static FilterControlledPermanent makeFilter(int number) {
|
||||
FilterControlledPermanent filter = new FilterControlledCreaturePermanent(
|
||||
"creature with power " + number + " or greater"
|
||||
);
|
||||
filter.add(new PowerPredicate(ComparisonType.MORE_THAN, number - 1));
|
||||
return filter;
|
||||
public String getCastMessageSuffix() {
|
||||
return additionalCost == null ? "" : additionalCost.getCastSuffixMessage(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,17 +61,11 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona
|
|||
|
||||
@Override
|
||||
public boolean isActivated() {
|
||||
if (additionalCost != null) {
|
||||
return additionalCost.isActivated();
|
||||
}
|
||||
return false;
|
||||
return additionalCost != null && additionalCost.isActivated();
|
||||
}
|
||||
|
||||
public int getActivateCount() {
|
||||
if (additionalCost != null) {
|
||||
return additionalCost.getActivateCount();
|
||||
}
|
||||
return 0;
|
||||
return additionalCost == null ? 0 : additionalCost.getActivateCount();
|
||||
}
|
||||
|
||||
public void resetReplicate() {
|
||||
|
|
@ -82,36 +76,38 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona
|
|||
|
||||
@Override
|
||||
public void addOptionalAdditionalCosts(Ability ability, Game game) {
|
||||
if (ability instanceof SpellAbility) {
|
||||
Player player = game.getPlayer(ability.getControllerId());
|
||||
if (player != null) {
|
||||
this.resetReplicate();
|
||||
boolean again = true;
|
||||
while (player.canRespond()
|
||||
&& again) {
|
||||
String times = "";
|
||||
if (additionalCost.isRepeatable()) {
|
||||
int numActivations = additionalCost.getActivateCount();
|
||||
times = (numActivations + 1) + (numActivations == 0 ? " time " : " times ");
|
||||
}
|
||||
if (!(ability instanceof SpellAbility)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: add AI support to find max number of possible activations (from available mana)
|
||||
// canPay checks only single mana available, not total mana usage
|
||||
if (additionalCost.canPay(ability, this, ability.getControllerId(), game)
|
||||
&& player.chooseUse(/*Outcome.Benefit*/Outcome.AIDontUseIt,
|
||||
new StringBuilder("Pay ").append(times).append(
|
||||
additionalCost.getText(false)).append(" ?").toString(), ability, game)) {
|
||||
additionalCost.activate();
|
||||
for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext(); ) {
|
||||
Cost cost = (Cost) it.next();
|
||||
if (cost instanceof ManaCostsImpl) {
|
||||
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
|
||||
} else {
|
||||
ability.getCosts().add(cost.copy());
|
||||
}
|
||||
}
|
||||
Player player = game.getPlayer(ability.getControllerId());
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.resetReplicate();
|
||||
boolean again = true;
|
||||
while (player.canRespond() && again) {
|
||||
String times = "";
|
||||
if (additionalCost.isRepeatable()) {
|
||||
int numActivations = additionalCost.getActivateCount();
|
||||
times = (numActivations + 1) + (numActivations == 0 ? " time " : " times ");
|
||||
}
|
||||
String payPrompt = "Pay " + times + additionalCost.getText(false) + " ?";
|
||||
|
||||
// TODO: add AI support to find max number of possible activations (from available mana)
|
||||
// canPay checks only single mana available, not total mana usage
|
||||
boolean canPay = additionalCost.canPay(ability, this, ability.getControllerId(), game);
|
||||
if (!canPay || !player.chooseUse(/*Outcome.Benefit*/Outcome.AIDontUseIt, payPrompt, ability, game)) {
|
||||
again = false;
|
||||
} else {
|
||||
additionalCost.activate();
|
||||
for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext(); ) {
|
||||
Cost cost = (Cost) it.next();
|
||||
if (cost instanceof ManaCostsImpl) {
|
||||
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
|
||||
} else {
|
||||
again = false;
|
||||
ability.getCosts().add(cost.copy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -120,29 +116,16 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona
|
|||
|
||||
@Override
|
||||
public String getRule() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (additionalCost != null) {
|
||||
sb.append(additionalCost.getText(false));
|
||||
sb.append(' ').append(additionalCost.getReminderText());
|
||||
}
|
||||
return sb.toString();
|
||||
return additionalCost == null ? "" : additionalCost.getText(false) + ' ' + additionalCost.getReminderText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCastMessageSuffix() {
|
||||
if (additionalCost != null) {
|
||||
return additionalCost.getCastSuffixMessage(0);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
return additionalCost == null ? "" : additionalCost.getCastSuffixMessage(0);
|
||||
}
|
||||
|
||||
public String getReminderText() {
|
||||
if (additionalCost != null) {
|
||||
return additionalCost.getReminderText();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
return additionalCost == null ? "" : additionalCost.getReminderText();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,24 +152,26 @@ class ReplicateTriggeredAbility extends TriggeredAbilityImpl {
|
|||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
if (event.getSourceId().equals(this.sourceId)) {
|
||||
StackObject spell = game.getStack().getStackObject(this.sourceId);
|
||||
if (spell instanceof Spell) {
|
||||
Card card = ((Spell) spell).getCard();
|
||||
if (card != null) {
|
||||
for (Ability ability : card.getAbilities(game)) {
|
||||
if (ability instanceof ReplicateAbility) {
|
||||
if (ability.isActivated()) {
|
||||
for (Effect effect : this.getEffects()) {
|
||||
effect.setValue("ReplicateSpell", spell);
|
||||
effect.setValue("ReplicateCount", ((ReplicateAbility) ability).getActivateCount());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!event.getSourceId().equals(this.sourceId)) {
|
||||
return false;
|
||||
}
|
||||
StackObject spell = game.getStack().getStackObject(this.sourceId);
|
||||
if (!(spell instanceof Spell)) {
|
||||
return false;
|
||||
}
|
||||
Card card = ((Spell) spell).getCard();
|
||||
if (card == null) {
|
||||
return false;
|
||||
}
|
||||
for (Ability ability : card.getAbilities(game)) {
|
||||
if (!(ability instanceof ReplicateAbility) || !ability.isActivated()) {
|
||||
continue;
|
||||
}
|
||||
for (Effect effect : this.getEffects()) {
|
||||
effect.setValue("ReplicateSpell", spell);
|
||||
effect.setValue("ReplicateCount", ((ReplicateAbility) ability).getActivateCount());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -211,29 +196,26 @@ class ReplicateCopyEffect extends OneShotEffect {
|
|||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller != null) {
|
||||
Spell spell = (Spell) this.getValue("ReplicateSpell");
|
||||
int replicateCount = (Integer) this.getValue("ReplicateCount");
|
||||
if (spell != null
|
||||
&& replicateCount > 0) {
|
||||
// reset replicate now so the copies don't report x times Replicate
|
||||
Card card = game.getCard(spell.getSourceId());
|
||||
if (card != null) {
|
||||
for (Ability ability : card.getAbilities(game)) {
|
||||
if (ability instanceof ReplicateAbility) {
|
||||
if (ability.isActivated()) {
|
||||
((ReplicateAbility) ability).resetReplicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// create the copies
|
||||
spell.createCopyOnStack(game, source, source.getControllerId(), true, replicateCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
Spell spell = (Spell) this.getValue("ReplicateSpell");
|
||||
int replicateCount = (Integer) this.getValue("ReplicateCount");
|
||||
if (controller == null || spell == null || replicateCount == 0) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
// reset replicate now so the copies don't report x times Replicate
|
||||
Card card = game.getCard(spell.getSourceId());
|
||||
if (card == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Ability ability : card.getAbilities(game)) {
|
||||
if ((ability instanceof ReplicateAbility) && ability.isActivated()) {
|
||||
((ReplicateAbility) ability).resetReplicate();
|
||||
}
|
||||
}
|
||||
// create the copies
|
||||
spell.createCopyOnStack(game, source, source.getControllerId(), true, replicateCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -850,6 +850,21 @@ public final class StaticFilters {
|
|||
FILTER_CREATURE_TOKEN.setLockedFilter(true);
|
||||
}
|
||||
|
||||
public static final FilterCreaturePermanent FILTER_CONTROLLED_CREATURE_NON_TOKEN = new FilterCreaturePermanent("a nontoken creature you control");
|
||||
|
||||
static {
|
||||
FILTER_CONTROLLED_CREATURE_NON_TOKEN.add(TargetController.YOU.getControllerPredicate());
|
||||
FILTER_CONTROLLED_CREATURE_NON_TOKEN.add(TokenPredicate.FALSE);
|
||||
FILTER_CONTROLLED_CREATURE_NON_TOKEN.setLockedFilter(true);
|
||||
}
|
||||
|
||||
public static final FilterCreaturePermanent FILTER_CREATURE_NON_TOKEN = new FilterCreaturePermanent("a nontoken creature");
|
||||
|
||||
static {
|
||||
FILTER_CREATURE_NON_TOKEN.add(TokenPredicate.FALSE);
|
||||
FILTER_CREATURE_NON_TOKEN.setLockedFilter(true);
|
||||
}
|
||||
|
||||
public static final FilterControlledCreaturePermanent FILTER_A_CONTROLLED_CREATURE_P1P1 = new FilterControlledCreaturePermanent("a creature you control with a +1/+1 counter on it");
|
||||
|
||||
static {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package mage.filter.predicate.card;
|
||||
|
||||
import mage.cards.Card;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.game.Game;
|
||||
|
||||
/**
|
||||
* @author Plopman, Alex-Vasile
|
||||
*/
|
||||
public class CardManaCostLessThanControlledLandCountPredicate implements Predicate<Card> {
|
||||
|
||||
private static final String string = "card with mana value less than or equal to the number of lands you control";
|
||||
private static final CardManaCostLessThanControlledLandCountPredicate instance = new CardManaCostLessThanControlledLandCountPredicate();
|
||||
|
||||
private CardManaCostLessThanControlledLandCountPredicate() { }
|
||||
|
||||
public static CardManaCostLessThanControlledLandCountPredicate getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Card input, Game game) {
|
||||
return input.getManaValue() <= game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND, input.getOwnerId(), game).size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return string;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package mage.filter.predicate.mageobject;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.constants.ComparisonType;
|
||||
import mage.filter.predicate.IntComparePredicate;
|
||||
|
||||
/**
|
||||
* @author Alex-Vasile
|
||||
*/
|
||||
public class BasePowerPredicate extends IntComparePredicate<MageObject> {
|
||||
|
||||
public BasePowerPredicate(ComparisonType type, int value) {
|
||||
super(type, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getInputValue(MageObject input) {
|
||||
return input.getPower().getModifiedBaseValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Base power" + super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package mage.filter.predicate.mageobject;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.constants.ComparisonType;
|
||||
import mage.filter.predicate.IntComparePredicate;
|
||||
|
||||
public class BaseToughnessPredicate extends IntComparePredicate<MageObject> {
|
||||
|
||||
public BaseToughnessPredicate(ComparisonType type, int value) {
|
||||
super(type, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getInputValue(MageObject input) {
|
||||
return input.getToughness().getModifiedBaseValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Base toughness" + super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package mage.target.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.Cards;
|
||||
import mage.cards.CardsImpl;
|
||||
import mage.constants.CommanderCardType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.TargetEvent;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetCard;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Based on TargetCardInHand
|
||||
* @author Alex-Vasile
|
||||
*/
|
||||
public class TargetCardInCommandZone extends TargetCard {
|
||||
|
||||
public TargetCardInCommandZone(FilterCard filter) {
|
||||
super(1, 1, Zone.COMMAND, filter);
|
||||
}
|
||||
|
||||
public TargetCardInCommandZone(final TargetCardInCommandZone targetCardInCommandZone) {
|
||||
super(targetCardInCommandZone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetCardInCommandZone copy() {
|
||||
return new TargetCardInCommandZone(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
|
||||
Card card = game.getCard(id);
|
||||
return game.getState().getZone(id) == Zone.COMMAND
|
||||
&& game.getState().getPlayersInRange(getTargetController() == null ? playerId : getTargetController(), game).contains(game.getOwnerId(id))
|
||||
&& filter.match(card, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID id, Ability source, Game game) {
|
||||
return this.canTarget(source.getControllerId(), id, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
Player player = game.getPlayer(sourceControllerId);
|
||||
if (player == null) {
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
Cards cards = new CardsImpl(game.getCommanderCardsFromCommandZone(player, CommanderCardType.ANY));
|
||||
for (Card card : cards.getCards(filter, sourceControllerId, source, game)) {
|
||||
if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) {
|
||||
possibleTargets.add(card.getId());
|
||||
}
|
||||
}
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
|
||||
Player player = game.getPlayer(sourceControllerId);
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int possibletargets = 0;
|
||||
Cards cards = new CardsImpl(game.getCommanderCardsFromCommandZone(player, CommanderCardType.ANY));
|
||||
for (Card card : cards.getCards(filter, sourceControllerId, source, game)) {
|
||||
if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) {
|
||||
possibletargets++;
|
||||
if (possibletargets >= this.minNumberOfTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package mage.target.common;
|
||||
|
||||
import mage.filter.FilterOpponent;
|
||||
import mage.filter.FilterPlayer;
|
||||
import mage.target.TargetPlayer;
|
||||
|
||||
/**
|
||||
|
|
@ -16,7 +17,11 @@ public class TargetOpponent extends TargetPlayer {
|
|||
}
|
||||
|
||||
public TargetOpponent(boolean notTarget) {
|
||||
super(1, 1, notTarget, filter);
|
||||
this(1, 1, notTarget);
|
||||
}
|
||||
|
||||
public TargetOpponent(int minNumTargets, int maxNumTargets, boolean notTarget) {
|
||||
super(minNumTargets, maxNumTargets, notTarget, filter);
|
||||
}
|
||||
|
||||
private TargetOpponent(final TargetOpponent target) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import mage.abilities.Ability;
|
|||
import mage.abilities.Mode;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.Costs;
|
||||
import mage.abilities.costs.VariableCost;
|
||||
import mage.abilities.costs.mana.*;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
|
|
@ -61,7 +63,7 @@ public final class CardUtil {
|
|||
|
||||
public static final List<String> RULES_ERROR_INFO = ImmutableList.of("Exception occurred in rules generation");
|
||||
|
||||
private static final String SOURCE_EXILE_ZONE_TEXT = "SourceExileZone";
|
||||
public static final String SOURCE_EXILE_ZONE_TEXT = "SourceExileZone";
|
||||
|
||||
static final String[] numberStrings = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
|
||||
"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"};
|
||||
|
|
@ -1332,6 +1334,103 @@ public final class CardUtil {
|
|||
}
|
||||
}
|
||||
|
||||
public static void castSingle(Player player, Ability source, Game game, Card card) {
|
||||
castSingle(player, source, game, card, null);
|
||||
}
|
||||
|
||||
public static void castSingle(Player player, Ability source, Game game, Card card, ManaCostsImpl<ManaCost> manaCost) {
|
||||
// handle split-cards
|
||||
if (card instanceof SplitCard) {
|
||||
SplitCardHalf leftHalfCard = ((SplitCard) card).getLeftHalfCard();
|
||||
SplitCardHalf rightHalfCard = ((SplitCard) card).getRightHalfCard();
|
||||
if (manaCost != null) {
|
||||
// get additional cost if any
|
||||
Costs<Cost> additionalCostsLeft = leftHalfCard.getSpellAbility().getCosts();
|
||||
Costs<Cost> additionalCostsRight = rightHalfCard.getSpellAbility().getCosts();
|
||||
// set alternative cost and any additional cost
|
||||
player.setCastSourceIdWithAlternateMana(leftHalfCard.getId(), manaCost, additionalCostsLeft);
|
||||
player.setCastSourceIdWithAlternateMana(rightHalfCard.getId(), manaCost, additionalCostsRight);
|
||||
}
|
||||
// allow the card to be cast
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + leftHalfCard.getId(), Boolean.TRUE);
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), Boolean.TRUE);
|
||||
}
|
||||
|
||||
// handle MDFC
|
||||
if (card instanceof ModalDoubleFacesCard) {
|
||||
ModalDoubleFacesCardHalf leftHalfCard = ((ModalDoubleFacesCard) card).getLeftHalfCard();
|
||||
ModalDoubleFacesCardHalf rightHalfCard = ((ModalDoubleFacesCard) card).getRightHalfCard();
|
||||
if (manaCost != null) {
|
||||
// some MDFC cards are lands. IE: sea gate restoration
|
||||
if (!leftHalfCard.isLand(game)) {
|
||||
// get additional cost if any
|
||||
Costs<Cost> additionalCostsMDFCLeft = leftHalfCard.getSpellAbility().getCosts();
|
||||
// set alternative cost and any additional cost
|
||||
player.setCastSourceIdWithAlternateMana(leftHalfCard.getId(), manaCost, additionalCostsMDFCLeft);
|
||||
}
|
||||
if (!rightHalfCard.isLand(game)) {
|
||||
// get additional cost if any
|
||||
Costs<Cost> additionalCostsMDFCRight = rightHalfCard.getSpellAbility().getCosts();
|
||||
// set alternative cost and any additional cost
|
||||
player.setCastSourceIdWithAlternateMana(rightHalfCard.getId(), manaCost, additionalCostsMDFCRight);
|
||||
}
|
||||
}
|
||||
// allow the card to be cast
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + leftHalfCard.getId(), Boolean.TRUE);
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), Boolean.TRUE);
|
||||
}
|
||||
|
||||
// handle adventure cards
|
||||
if (card instanceof AdventureCard) {
|
||||
Card creatureCard = card.getMainCard();
|
||||
Card spellCard = ((AdventureCard) card).getSpellCard();
|
||||
if (manaCost != null) {
|
||||
// get additional cost if any
|
||||
Costs<Cost> additionalCostsCreature = creatureCard.getSpellAbility().getCosts();
|
||||
Costs<Cost> additionalCostsSpellCard = spellCard.getSpellAbility().getCosts();
|
||||
// set alternative cost and any additional cost
|
||||
player.setCastSourceIdWithAlternateMana(creatureCard.getId(), manaCost, additionalCostsCreature);
|
||||
player.setCastSourceIdWithAlternateMana(spellCard.getId(), manaCost, additionalCostsSpellCard);
|
||||
}
|
||||
// allow the card to be cast
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + creatureCard.getId(), Boolean.TRUE);
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + spellCard.getId(), Boolean.TRUE);
|
||||
}
|
||||
|
||||
// normal card
|
||||
if (manaCost != null) {
|
||||
// get additional cost if any
|
||||
Costs<Cost> additionalCostsNormalCard = card.getSpellAbility().getCosts();
|
||||
player.setCastSourceIdWithAlternateMana(card.getMainCard().getId(), manaCost, additionalCostsNormalCard);
|
||||
}
|
||||
|
||||
// cast it
|
||||
player.cast(player.chooseAbilityForCast(card.getMainCard(), game, false),
|
||||
game, false, new ApprovingObject(source, game));
|
||||
|
||||
// turn off effect after cast on every possible card-face
|
||||
if (card instanceof SplitCard) {
|
||||
SplitCardHalf leftHalfCard = ((SplitCard) card).getLeftHalfCard();
|
||||
SplitCardHalf rightHalfCard = ((SplitCard) card).getRightHalfCard();
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + leftHalfCard.getId(), null);
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), null);
|
||||
}
|
||||
if (card instanceof ModalDoubleFacesCard) {
|
||||
ModalDoubleFacesCardHalf leftHalfCard = ((ModalDoubleFacesCard) card).getLeftHalfCard();
|
||||
ModalDoubleFacesCardHalf rightHalfCard = ((ModalDoubleFacesCard) card).getRightHalfCard();
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + leftHalfCard.getId(), null);
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), null);
|
||||
}
|
||||
if (card instanceof AdventureCard) {
|
||||
Card creatureCard = card.getMainCard();
|
||||
Card spellCard = ((AdventureCard) card).getSpellCard();
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + creatureCard.getId(), null);
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + spellCard.getId(), null);
|
||||
}
|
||||
// turn off effect on a normal card
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pay life in effects
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package mage.watchers.common;
|
||||
|
||||
import mage.cards.Card;
|
||||
import mage.constants.WatcherScope;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.token.Token;
|
||||
import mage.util.CardUtil;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
|
|
@ -15,7 +17,9 @@ import java.util.UUID;
|
|||
*/
|
||||
public class CreatedTokenWatcher extends Watcher {
|
||||
|
||||
// Player ID to Number of tokens created
|
||||
private final Map<UUID, Integer> playerMap = new HashMap<>();
|
||||
private final Map<UUID, Map<Class<? extends Token>, Integer>> tokenCreatedMap = new HashMap<>();
|
||||
|
||||
public CreatedTokenWatcher() {
|
||||
super(WatcherScope.GAME);
|
||||
|
|
@ -25,12 +29,19 @@ public class CreatedTokenWatcher extends Watcher {
|
|||
public void watch(GameEvent event, Game game) {
|
||||
if (event.getType() == GameEvent.EventType.CREATED_TOKEN) {
|
||||
playerMap.compute(event.getPlayerId(), CardUtil::setOrIncrementValue);
|
||||
|
||||
tokenCreatedMap.putIfAbsent(event.getPlayerId(), new HashMap<>());
|
||||
Class<? extends Token> tokenClazz = ((Token) game.getPermanent(event.getTargetId())).getClass();
|
||||
Map<Class<? extends Token>, Integer> playersTokens = tokenCreatedMap.getOrDefault(event.getPlayerId(), new HashMap<>());
|
||||
playersTokens.compute(tokenClazz, CardUtil::setOrIncrementValue);
|
||||
tokenCreatedMap.put(event.getPlayerId(), playersTokens);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
playerMap.clear();
|
||||
tokenCreatedMap.clear();
|
||||
}
|
||||
|
||||
public static boolean checkPlayer(UUID playerId, Game game) {
|
||||
|
|
@ -44,4 +55,13 @@ public class CreatedTokenWatcher extends Watcher {
|
|||
.playerMap
|
||||
.getOrDefault(playerId, 0);
|
||||
}
|
||||
|
||||
public static int getTypeCreatedCountByPlayer(UUID playerId, Class<? extends Token> tokenCLazz, Game game) {
|
||||
return game
|
||||
.getState()
|
||||
.getWatcher(CreatedTokenWatcher.class)
|
||||
.tokenCreatedMap
|
||||
.getOrDefault(playerId, new HashMap<>())
|
||||
.getOrDefault(tokenCLazz, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ import mage.game.Game;
|
|||
import mage.game.events.GameEvent;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author LevelX2
|
||||
|
|
@ -18,6 +16,12 @@ public class DamageDoneWatcher extends Watcher {
|
|||
// which object did how much damage during the turn
|
||||
private final Map<MageObjectReference, Integer> damagingObjects;
|
||||
|
||||
// which object received how much damage during the turn
|
||||
private final Map<MageObjectReference, Integer> damagedObjects;
|
||||
|
||||
// Which object damaged which player(s)
|
||||
private final Map<MageObjectReference, Integer> objectsToPlayersDamaged;
|
||||
|
||||
public Map<MageObjectReference, Integer> getDamagingObjects() {
|
||||
return damagingObjects;
|
||||
}
|
||||
|
|
@ -26,13 +30,13 @@ public class DamageDoneWatcher extends Watcher {
|
|||
return damagedObjects;
|
||||
}
|
||||
|
||||
// which object received how much damage during the turn
|
||||
private final Map<MageObjectReference, Integer> damagedObjects;
|
||||
|
||||
|
||||
public DamageDoneWatcher() {
|
||||
super(WatcherScope.GAME);
|
||||
this.damagingObjects = new HashMap<>();
|
||||
this.damagedObjects = new HashMap<>();
|
||||
this.objectsToPlayersDamaged = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -47,6 +51,11 @@ public class DamageDoneWatcher extends Watcher {
|
|||
MageObjectReference damageTargetRef = new MageObjectReference(event.getTargetId(), game);
|
||||
damagedObjects.putIfAbsent(damageTargetRef, 0);
|
||||
damagedObjects.compute(damageTargetRef, (k, damage) -> damage + event.getAmount());
|
||||
|
||||
if (game.getPlayer(event.getTargetId()) != null) {
|
||||
objectsToPlayersDamaged.putIfAbsent(damageSourceRef, 0);
|
||||
objectsToPlayersDamaged.compute(damageSourceRef, (k, numPlayers) -> numPlayers + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -56,6 +65,7 @@ public class DamageDoneWatcher extends Watcher {
|
|||
super.reset();
|
||||
damagingObjects.clear();
|
||||
damagedObjects.clear();
|
||||
objectsToPlayersDamaged.clear();
|
||||
}
|
||||
|
||||
public int damageDoneBy(UUID objectId, int zoneChangeCounter, Game game) {
|
||||
|
|
@ -73,4 +83,9 @@ public class DamageDoneWatcher extends Watcher {
|
|||
return damagedObjects.containsKey(mor);
|
||||
}
|
||||
|
||||
public boolean damagedAPlayer(UUID objectId, int zoneChangeCounter, Game game) {
|
||||
MageObjectReference mor = new MageObjectReference(objectId, zoneChangeCounter, game);
|
||||
return objectsToPlayersDamaged.containsKey(mor);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue