mirror of
https://github.com/magefree/mage.git
synced 2026-01-09 12:22:10 -08:00
Rework sacrifice effects to support "can't be sacrificed" (#11587)
* add TargetSacrifice and canBeSacrificed
* SacrificeTargetCost refactor, now uses TargetSacrifice, constructors simplified, subclasses aligned
* fix text errors introduced by refactor
* refactor SacrificeEffect, SacrificeAllEffect, SacrificeOpponentsEffect
* cleanup keyword abilities involving sacrifice
* fix a bunch of custom effect classes involving sacrifice
* fix test choices
* update Assault Suit implementation
* fix filter check arguments
* add documentation to refactored common classes
* [CLB] Implement Jon Irenicus, Shattered One
* implement "{this} can't be sacrificed"
* add tests for Assault Suit and Jon Irenicus
* refactor out PlayerToRightGainsControlOfSourceEffect
* implement [LTC] Hithlain Rope
* add choose hint to all TargetSacrifice
---------
Co-authored-by: Evan Kranzler <theelk801@gmail.com>
Co-authored-by: PurpleCrowbar <26198472+PurpleCrowbar@users.noreply.github.com>
This commit is contained in:
parent
f28c5c4fc5
commit
9b3ff32a33
699 changed files with 1837 additions and 1619 deletions
|
|
@ -50,10 +50,7 @@ public class SacrificeAllCost extends CostImpl implements SacrificeCost {
|
|||
if (ability.getAbilityType() == AbilityType.ACTIVATED || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) {
|
||||
if (((ActivatedAbilityImpl) ability).getActivatorId() != null) {
|
||||
activator = ((ActivatedAbilityImpl) ability).getActivatorId();
|
||||
} else {
|
||||
// Aktivator not filled?
|
||||
activator = controllerId;
|
||||
}
|
||||
} // else, Activator not filled?
|
||||
}
|
||||
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, controllerId, game)) {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,14 @@ public class SacrificeAttachmentCost extends UseAttachedCost implements Sacrific
|
|||
return paid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
|
||||
if (!super.canPay(ability, source, controllerId, game)) {
|
||||
return false;
|
||||
}
|
||||
return game.getPermanent(source.getSourceId()).canBeSacrificed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SacrificeAttachmentCost copy() {
|
||||
return new SacrificeAttachmentCost(this);
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ import mage.abilities.costs.CostImpl;
|
|||
import mage.abilities.costs.SacrificeCost;
|
||||
import mage.constants.AbilityType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetControlledPermanent;
|
||||
import mage.target.common.TargetSacrifice;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -24,20 +26,32 @@ public class SacrificeTargetCost extends CostImpl implements SacrificeCost {
|
|||
|
||||
private final List<Permanent> permanents = new ArrayList<>();
|
||||
|
||||
public SacrificeTargetCost(FilterControlledPermanent filter) {
|
||||
this(new TargetControlledPermanent(filter));
|
||||
/**
|
||||
* Sacrifice a permanent matching the filter:
|
||||
* @param filter can be generic, will automatically add article and sacrifice predicates
|
||||
*/
|
||||
public SacrificeTargetCost(FilterPermanent filter) {
|
||||
this(1, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sacrifice N permanents matching the filter:
|
||||
* @param filter can be generic, will automatically add sacrifice predicates
|
||||
*/
|
||||
public SacrificeTargetCost(int numToSac, FilterPermanent filter) {
|
||||
this(new TargetSacrifice(numToSac, filter));
|
||||
}
|
||||
|
||||
// remove once merge complete
|
||||
@Deprecated
|
||||
public SacrificeTargetCost(TargetControlledPermanent target) {
|
||||
throw new UnsupportedOperationException("Wrong code usage, refactor to TargetSacrifice");
|
||||
}
|
||||
|
||||
public SacrificeTargetCost(TargetSacrifice target) {
|
||||
this.addTarget(target);
|
||||
target.withNotTarget(true); // sacrifice is never targeted
|
||||
target.setRequired(false); // can be canceled
|
||||
this.text = "sacrifice " + makeText(target);
|
||||
target.setTargetName(target.getTargetName() + " (to sacrifice)");
|
||||
}
|
||||
|
||||
public SacrificeTargetCost(TargetControlledPermanent target, boolean noText) {
|
||||
this.addTarget(target);
|
||||
}
|
||||
|
||||
public SacrificeTargetCost(SacrificeTargetCost cost) {
|
||||
|
|
@ -70,6 +84,9 @@ public class SacrificeTargetCost extends CostImpl implements SacrificeCost {
|
|||
return paid;
|
||||
}
|
||||
|
||||
/**
|
||||
* For storing additional info upon selecting permanents to sacrifice
|
||||
*/
|
||||
protected void addSacrificeTarget(Game game, Permanent permanent) {
|
||||
permanents.add(permanent.copy());
|
||||
}
|
||||
|
|
@ -80,18 +97,15 @@ public class SacrificeTargetCost extends CostImpl implements SacrificeCost {
|
|||
if (ability.getAbilityType() == AbilityType.ACTIVATED || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) {
|
||||
if (((ActivatedAbilityImpl) ability).getActivatorId() != null) {
|
||||
activator = ((ActivatedAbilityImpl) ability).getActivatorId();
|
||||
} else {
|
||||
// Activator not filled?
|
||||
activator = controllerId;
|
||||
}
|
||||
} // else, Activator not filled?
|
||||
}
|
||||
|
||||
int validTargets = 0;
|
||||
int neededtargets = this.getTargets().get(0).getNumberOfTargets();
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(((TargetControlledPermanent) this.getTargets().get(0)).getFilter(), controllerId, game)) {
|
||||
int neededTargets = this.getTargets().get(0).getNumberOfTargets();
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(((TargetPermanent) this.getTargets().get(0)).getFilter(), controllerId, source, game)) {
|
||||
if (game.getPlayer(activator).canPaySacrificeCost(permanent, source, controllerId, game)) {
|
||||
validTargets++;
|
||||
if (validTargets >= neededtargets) {
|
||||
if (validTargets >= neededTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -112,7 +126,7 @@ public class SacrificeTargetCost extends CostImpl implements SacrificeCost {
|
|||
return permanents;
|
||||
}
|
||||
|
||||
private static String makeText(TargetControlledPermanent target) {
|
||||
private static String makeText(TargetSacrifice target) {
|
||||
if (target.getMinNumberOfTargets() != target.getMaxNumberOfTargets()) {
|
||||
return target.getTargetName();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,28 +5,27 @@ import mage.abilities.costs.Cost;
|
|||
import mage.abilities.costs.SacrificeCost;
|
||||
import mage.abilities.costs.VariableCostImpl;
|
||||
import mage.abilities.costs.VariableCostType;
|
||||
import mage.filter.Filter;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.target.common.TargetControlledPermanent;
|
||||
import mage.target.common.TargetSacrifice;
|
||||
|
||||
/**
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class SacrificeXTargetCost extends VariableCostImpl implements SacrificeCost {
|
||||
|
||||
protected final FilterControlledPermanent filter;
|
||||
protected final FilterPermanent filter;
|
||||
private final int minValue;
|
||||
|
||||
public SacrificeXTargetCost(FilterControlledPermanent filter) {
|
||||
public SacrificeXTargetCost(FilterPermanent filter) {
|
||||
this(filter, false);
|
||||
}
|
||||
|
||||
public SacrificeXTargetCost(FilterControlledPermanent filter, boolean useAsAdditionalCost) {
|
||||
public SacrificeXTargetCost(FilterPermanent filter, boolean useAsAdditionalCost) {
|
||||
this(filter, useAsAdditionalCost, 0);
|
||||
}
|
||||
|
||||
public SacrificeXTargetCost(FilterControlledPermanent filter, boolean useAsAdditionalCost, int minValue) {
|
||||
public SacrificeXTargetCost(FilterPermanent filter, boolean useAsAdditionalCost, int minValue) {
|
||||
super(useAsAdditionalCost ? VariableCostType.ADDITIONAL : VariableCostType.NORMAL,
|
||||
filter.getMessage() + " to sacrifice");
|
||||
this.text = (useAsAdditionalCost ? "as an additional cost to cast this spell, sacrifice " : "Sacrifice ") + xText + ' ' + filter.getMessage();
|
||||
|
|
@ -57,11 +56,10 @@ public class SacrificeXTargetCost extends VariableCostImpl implements SacrificeC
|
|||
|
||||
@Override
|
||||
public Cost getFixedCostsFromAnnouncedValue(int xValue) {
|
||||
TargetControlledPermanent target = new TargetControlledPermanent(xValue, xValue, filter, true);
|
||||
return new SacrificeTargetCost(target);
|
||||
return new SacrificeTargetCost(new TargetSacrifice(xValue, filter));
|
||||
}
|
||||
|
||||
public Filter getFilter() {
|
||||
public FilterPermanent getFilter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import mage.game.events.GameEvent;
|
|||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.common.TargetControlledPermanent;
|
||||
import mage.target.common.TargetSacrifice;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -88,8 +88,7 @@ public class DevourEffect extends ReplacementEffectImpl {
|
|||
}
|
||||
filter.add(AnotherPredicate.instance);
|
||||
|
||||
Target target = new TargetControlledPermanent(1, Integer.MAX_VALUE, filter, true);
|
||||
target.setRequired(false);
|
||||
Target target = new TargetSacrifice(1, Integer.MAX_VALUE, filter);
|
||||
if (!target.canChoose(source.getControllerId(), source, game)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.continuous.GainControlTargetEffect;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author xenohedron
|
||||
*/
|
||||
public class PlayerToRightGainsControlOfSourceEffect extends OneShotEffect {
|
||||
|
||||
public PlayerToRightGainsControlOfSourceEffect() {
|
||||
super(Outcome.Detriment);
|
||||
this.staticText = "the player to your right gains control of {this}";
|
||||
}
|
||||
|
||||
protected PlayerToRightGainsControlOfSourceEffect(final PlayerToRightGainsControlOfSourceEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerToRightGainsControlOfSourceEffect copy() {
|
||||
return new PlayerToRightGainsControlOfSourceEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
||||
if (permanent == null) {
|
||||
return true;
|
||||
}
|
||||
UUID playerToRightId = game
|
||||
.getState()
|
||||
.getPlayersInRange(source.getControllerId(), game)
|
||||
.stream()
|
||||
.reduce((u1, u2) -> u2)
|
||||
.orElse(null);
|
||||
if (playerToRightId == null) {
|
||||
return false;
|
||||
}
|
||||
game.addEffect(new GainControlTargetEffect(
|
||||
Duration.Custom, true, playerToRightId
|
||||
).setTargetPointer(new FixedTarget(permanent, game)), source);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,15 +5,15 @@ import mage.abilities.dynamicvalue.DynamicValue;
|
|||
import mage.abilities.dynamicvalue.common.StaticValue;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.common.TargetControlledPermanent;
|
||||
import mage.target.common.TargetSacrifice;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -21,21 +21,42 @@ import java.util.UUID;
|
|||
*/
|
||||
public class SacrificeAllEffect extends OneShotEffect {
|
||||
|
||||
protected DynamicValue amount;
|
||||
protected FilterControlledPermanent filter;
|
||||
private final DynamicValue amount;
|
||||
private final FilterPermanent filter;
|
||||
private final boolean onlyOpponents;
|
||||
|
||||
public SacrificeAllEffect(FilterControlledPermanent filter) {
|
||||
/**
|
||||
* Each player sacrifices a permanent
|
||||
* @param filter can be generic, will automatically add article and necessary sacrifice predicates
|
||||
*/
|
||||
public SacrificeAllEffect(FilterPermanent filter) {
|
||||
this(1, filter);
|
||||
}
|
||||
|
||||
public SacrificeAllEffect(int amount, FilterControlledPermanent filter) {
|
||||
/**
|
||||
* Each player sacrifices N permanents
|
||||
* @param filter can be generic, will automatically add necessary sacrifice predicates
|
||||
*/
|
||||
public SacrificeAllEffect(int amount, FilterPermanent filter) {
|
||||
this(StaticValue.get(amount), filter);
|
||||
}
|
||||
|
||||
public SacrificeAllEffect(DynamicValue amount, FilterControlledPermanent filter) {
|
||||
/**
|
||||
* Each player sacrifices X permanents
|
||||
* @param filter can be generic, will automatically add necessary sacrifice predicates
|
||||
*/
|
||||
public SacrificeAllEffect(DynamicValue amount, FilterPermanent filter) {
|
||||
this(amount, filter, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal use for this and SacrificeOpponentsEffect
|
||||
*/
|
||||
protected SacrificeAllEffect(DynamicValue amount, FilterPermanent filter, boolean onlyOpponents) {
|
||||
super(Outcome.Sacrifice);
|
||||
this.amount = amount;
|
||||
this.filter = filter;
|
||||
this.onlyOpponents = onlyOpponents;
|
||||
setText();
|
||||
}
|
||||
|
||||
|
|
@ -43,6 +64,7 @@ public class SacrificeAllEffect extends OneShotEffect {
|
|||
super(effect);
|
||||
this.amount = effect.amount;
|
||||
this.filter = effect.filter.copy();
|
||||
this.onlyOpponents = effect.onlyOpponents;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -52,24 +74,27 @@ public class SacrificeAllEffect extends OneShotEffect {
|
|||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller == null) {
|
||||
int num = amount.calculate(game, source, this);
|
||||
if (num < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<UUID> perms = new ArrayList<>();
|
||||
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
|
||||
Set<UUID> perms = new HashSet<>();
|
||||
for (UUID playerId : onlyOpponents ?
|
||||
game.getOpponents(source.getControllerId()) :
|
||||
game.getState().getPlayersInRange(source.getControllerId(), game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
int numTargets = Math.min(amount.calculate(game, source, this), game.getBattlefield().countAll(filter, player.getId(), game));
|
||||
TargetControlledPermanent target = new TargetControlledPermanent(numTargets, numTargets, filter, true);
|
||||
if (target.canChoose(player.getId(), source, game)) {
|
||||
while (!target.isChosen() && player.canRespond()) {
|
||||
player.choose(Outcome.Sacrifice, target, source, game);
|
||||
}
|
||||
perms.addAll(target.getTargets());
|
||||
}
|
||||
if (player == null) {
|
||||
continue;
|
||||
}
|
||||
int numTargets = Math.min(num, game.getBattlefield().count(TargetSacrifice.makeFilter(filter), player.getId(), source, game));
|
||||
if (numTargets < 1) {
|
||||
continue;
|
||||
}
|
||||
TargetSacrifice target = new TargetSacrifice(numTargets, filter);
|
||||
while (!target.isChosen() && target.canChoose(player.getId(), source, game) && player.canRespond()) {
|
||||
player.choose(Outcome.Sacrifice, target, source, game);
|
||||
}
|
||||
perms.addAll(target.getTargets());
|
||||
}
|
||||
for (UUID permID : perms) {
|
||||
Permanent permanent = game.getPermanent(permID);
|
||||
|
|
@ -82,17 +107,20 @@ public class SacrificeAllEffect extends OneShotEffect {
|
|||
|
||||
private void setText() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("each player sacrifices ");
|
||||
if (amount.toString().equals("X")) {
|
||||
sb.append(amount.toString());
|
||||
sb.append(' ');
|
||||
sb.append(filter.getMessage());
|
||||
} else if (amount.toString().equals("1")) {
|
||||
sb.append(CardUtil.addArticle(filter.getMessage()));
|
||||
} else {
|
||||
sb.append(CardUtil.numberToText(amount.toString(), "a"));
|
||||
sb.append(' ');
|
||||
sb.append(filter.getMessage());
|
||||
sb.append(onlyOpponents ? "each opponent sacrifices " : "each player sacrifices ");
|
||||
switch (amount.toString()) {
|
||||
case "X":
|
||||
sb.append(amount.toString());
|
||||
sb.append(' ');
|
||||
sb.append(filter.getMessage());
|
||||
break;
|
||||
case "1":
|
||||
sb.append(CardUtil.addArticle(filter.getMessage()));
|
||||
break;
|
||||
default:
|
||||
sb.append(CardUtil.numberToText(amount.toString(), "a"));
|
||||
sb.append(' ');
|
||||
sb.append(filter.getMessage());
|
||||
}
|
||||
staticText = sb.toString();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,10 @@ import mage.abilities.dynamicvalue.common.StaticValue;
|
|||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.predicate.permanent.ControllerIdPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetSacrifice;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
|
@ -21,14 +19,20 @@ import java.util.UUID;
|
|||
*/
|
||||
public class SacrificeEffect extends OneShotEffect {
|
||||
|
||||
private FilterPermanent filter;
|
||||
private String preText;
|
||||
private final FilterPermanent filter;
|
||||
private final String preText;
|
||||
private DynamicValue count;
|
||||
|
||||
/**
|
||||
* Target player sacrifices N permanents matching the filter
|
||||
*/
|
||||
public SacrificeEffect(FilterPermanent filter, int count, String preText) {
|
||||
this(filter, StaticValue.get(count), preText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Target player sacrifices X permanents matching the filter
|
||||
*/
|
||||
public SacrificeEffect(FilterPermanent filter, DynamicValue count, String preText) {
|
||||
super(Outcome.Sacrifice);
|
||||
this.filter = filter;
|
||||
|
|
@ -49,26 +53,24 @@ public class SacrificeEffect extends OneShotEffect {
|
|||
boolean applied = false;
|
||||
for (UUID playerId : targetPointer.getTargets(game, source)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
FilterPermanent newFilter = filter.copy(); // filter can be static, so it's important to copy here
|
||||
newFilter.add(new ControllerIdPredicate(player.getId()));
|
||||
int amount = count.calculate(game, source, this);
|
||||
int realCount = game.getBattlefield().countAll(newFilter, player.getId(), game);
|
||||
amount = Math.min(amount, realCount);
|
||||
Target target = new TargetPermanent(amount, amount, newFilter, true);
|
||||
if (amount > 0 && target.canChoose(player.getId(), source, game)) {
|
||||
while (!target.isChosen()
|
||||
&& target.canChoose(player.getId(), source, game)
|
||||
&& player.canRespond()) {
|
||||
player.chooseTarget(Outcome.Sacrifice, target, source, game);
|
||||
}
|
||||
for (int idx = 0; idx < target.getTargets().size(); idx++) {
|
||||
Permanent permanent = game.getPermanent(target.getTargets().get(idx));
|
||||
if (permanent != null
|
||||
&& permanent.sacrifice(source, game)) {
|
||||
applied = true;
|
||||
}
|
||||
}
|
||||
if (player == null) {
|
||||
continue;
|
||||
}
|
||||
int amount = Math.min(
|
||||
count.calculate(game, source, this),
|
||||
game.getBattlefield().count(TargetSacrifice.makeFilter(filter), player.getId(), source, game)
|
||||
);
|
||||
if (amount < 1) {
|
||||
continue;
|
||||
}
|
||||
TargetSacrifice target = new TargetSacrifice(amount, filter);
|
||||
while (!target.isChosen() && target.canChoose(player.getId(), source, game) && player.canRespond()) {
|
||||
player.choose(Outcome.Sacrifice, target, source, game);
|
||||
}
|
||||
for (UUID targetId : target.getTargets()) {
|
||||
Permanent permanent = game.getPermanent(targetId);
|
||||
if (permanent != null && permanent.sacrifice(source, game)) {
|
||||
applied = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +1,37 @@
|
|||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.dynamicvalue.common.StaticValue;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.TargetController;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* All opponents have to sacrifice [amount] permanents that match the [filter].
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class SacrificeOpponentsEffect extends OneShotEffect {
|
||||
|
||||
protected DynamicValue amount;
|
||||
protected FilterPermanent filter;
|
||||
public class SacrificeOpponentsEffect extends SacrificeAllEffect {
|
||||
|
||||
/**
|
||||
* Each opponent sacrifices a permanent
|
||||
* @param filter can be generic, will automatically add article and necessary sacrifice predicates
|
||||
*/
|
||||
public SacrificeOpponentsEffect(FilterPermanent filter) {
|
||||
this(1, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Each opponent sacrifices N permanents
|
||||
* @param filter can be generic, will automatically add necessary sacrifice predicates
|
||||
*/
|
||||
public SacrificeOpponentsEffect(int amount, FilterPermanent filter) {
|
||||
this(StaticValue.get(amount), filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Each opponent sacrifices X permanents
|
||||
* @param filter can be generic, will automatically add necessary sacrifice predicates
|
||||
*/
|
||||
public SacrificeOpponentsEffect(DynamicValue amount, FilterPermanent filter) {
|
||||
super(Outcome.Sacrifice);
|
||||
this.amount = amount;
|
||||
this.filter = filter.copy();
|
||||
this.filter.add(TargetController.YOU.getControllerPredicate());
|
||||
super(amount, filter, true);
|
||||
}
|
||||
|
||||
protected SacrificeOpponentsEffect(final SacrificeOpponentsEffect effect) {
|
||||
super(effect);
|
||||
this.amount = effect.amount;
|
||||
this.filter = effect.filter.copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -54,48 +39,4 @@ public class SacrificeOpponentsEffect extends OneShotEffect {
|
|||
return new SacrificeOpponentsEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
List<UUID> perms = new ArrayList<>();
|
||||
for (UUID playerId : game.getOpponents(source.getControllerId())) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
int numTargets = Math.min(amount.calculate(game, source, this), game.getBattlefield().countAll(filter, player.getId(), game));
|
||||
if (numTargets > 0) {
|
||||
TargetPermanent target = new TargetPermanent(numTargets, numTargets, filter, true);
|
||||
if (target.canChoose(player.getId(), source, game)) {
|
||||
player.chooseTarget(Outcome.Sacrifice, target, source, game);
|
||||
perms.addAll(target.getTargets());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (UUID permID : perms) {
|
||||
Permanent permanent = game.getPermanent(permID);
|
||||
if (permanent != null) {
|
||||
permanent.sacrifice(source, game);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText(Mode mode) {
|
||||
if (staticText != null && !staticText.isEmpty()) {
|
||||
return staticText;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("each opponent sacrifices ");
|
||||
switch (amount.toString()) {
|
||||
case "X":
|
||||
sb.append(amount.toString()).append(' ');
|
||||
break;
|
||||
case "1":
|
||||
sb.append(CardUtil.addArticle(filter.getMessage()));
|
||||
break;
|
||||
default:
|
||||
sb.append(CardUtil.numberToText(amount.toString())).append(' ').append(filter.getMessage());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,21 @@
|
|||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.dynamicvalue.common.StaticValue;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.TargetController;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetSacrifice;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.ManaUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -60,45 +55,41 @@ public class SacrificeOpponentsUnlessPayEffect extends OneShotEffect {
|
|||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
List<UUID> permsToSacrifice = new ArrayList<>();
|
||||
filter.add(TargetController.YOU.getControllerPredicate());
|
||||
Set<UUID> permsToSacrifice = new HashSet<>();
|
||||
|
||||
for (UUID playerId : game.getOpponents(source.getControllerId())) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player != null) {
|
||||
Cost costToPay = cost.copy();
|
||||
String costValueMessage = costToPay.getText();
|
||||
String message = ((costToPay instanceof ManaCost) ? "Pay " : "") + costValueMessage + '?';
|
||||
Cost costToPay = cost.copy();
|
||||
String costValueMessage = costToPay.getText();
|
||||
String message = ((costToPay instanceof ManaCost) ? "Pay " : "") + costValueMessage + '?';
|
||||
|
||||
costToPay.clearPaid();
|
||||
if (!(player.chooseUse(Outcome.Benefit, message, source, game)
|
||||
&& costToPay.pay(source, game, source, player.getId(), false, null))) {
|
||||
game.informPlayers(player.getLogName() + " chooses not to pay " + costValueMessage + " to prevent the sacrifice effect");
|
||||
costToPay.clearPaid();
|
||||
if (!(player.chooseUse(Outcome.Benefit, message, source, game)
|
||||
&& costToPay.pay(source, game, source, player.getId(), false, null))) {
|
||||
game.informPlayers(player.getLogName() + " chooses not to pay " + costValueMessage + " to prevent the sacrifice effect");
|
||||
|
||||
int numTargets = Math.min(1, game.getBattlefield().countAll(filter, player.getId(), game));
|
||||
if (numTargets > 0) {
|
||||
TargetPermanent target = new TargetPermanent(numTargets, numTargets, filter, true);
|
||||
|
||||
if (target.canChoose(player.getId(), source, game)) {
|
||||
player.chooseTarget(Outcome.Sacrifice, target, source, game);
|
||||
permsToSacrifice.addAll(target.getTargets());
|
||||
}
|
||||
if (game.getBattlefield().count(TargetSacrifice.makeFilter(filter), player.getId(), source, game) > 0) {
|
||||
TargetSacrifice target = new TargetSacrifice(1, filter);
|
||||
if (target.canChoose(player.getId(), source, game)) {
|
||||
player.choose(Outcome.Sacrifice, target, source, game);
|
||||
permsToSacrifice.addAll(target.getTargets());
|
||||
}
|
||||
} else {
|
||||
game.informPlayers(player.getLogName() + " chooses to pay " + costValueMessage + " to prevent the sacrifice effect");
|
||||
}
|
||||
} else {
|
||||
game.informPlayers(player.getLogName() + " chooses to pay " + costValueMessage + " to prevent the sacrifice effect");
|
||||
}
|
||||
}
|
||||
|
||||
for (UUID permID : permsToSacrifice) {
|
||||
Permanent permanent = game.getPermanent(permID);
|
||||
|
||||
if (permanent != null) {
|
||||
permanent.sacrifice(source, game);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
|
||||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.constants.Outcome;
|
||||
|
|
@ -20,6 +18,7 @@ public class SacrificeSourceUnlessConditionEffect extends OneShotEffect {
|
|||
public SacrificeSourceUnlessConditionEffect(Condition condition) {
|
||||
super(Outcome.Sacrifice);
|
||||
this.condition = condition;
|
||||
this.staticText = "sacrifice {this} unless " + condition.toString();
|
||||
}
|
||||
|
||||
protected SacrificeSourceUnlessConditionEffect(final SacrificeSourceUnlessConditionEffect effect) {
|
||||
|
|
@ -46,13 +45,4 @@ public class SacrificeSourceUnlessConditionEffect extends OneShotEffect {
|
|||
return new SacrificeSourceUnlessConditionEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText(Mode mode) {
|
||||
if (staticText != null && !staticText.isEmpty()) {
|
||||
return staticText;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder("sacrifice {this} unless ");
|
||||
sb.append(condition.toString());
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package mage.abilities.effects.common.continuous;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Layer;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubLayer;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
/**
|
||||
* @author xenohedron
|
||||
*/
|
||||
public class CantBeSacrificedSourceEffect extends ContinuousEffectImpl {
|
||||
|
||||
public CantBeSacrificedSourceEffect() {
|
||||
super(Duration.WhileOnBattlefield, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit);
|
||||
staticText = "{this} can't be sacrificed";
|
||||
}
|
||||
|
||||
protected CantBeSacrificedSourceEffect(final CantBeSacrificedSourceEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CantBeSacrificedSourceEffect copy() {
|
||||
return new CantBeSacrificedSourceEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = game.getPermanentEntering(source.getSourceId());
|
||||
if (permanent == null) {
|
||||
permanent = source.getSourcePermanentIfItStillExists(game);
|
||||
}
|
||||
if (permanent == null) {
|
||||
discard();
|
||||
return false;
|
||||
}
|
||||
permanent.setCanBeSacrificed(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,20 +1,14 @@
|
|||
|
||||
package mage.abilities.keyword;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.constants.Outcome;
|
||||
import mage.abilities.effects.common.SacrificeEffect;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.common.TargetControlledPermanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -29,16 +23,17 @@ import java.util.UUID;
|
|||
*/
|
||||
public class AnnihilatorAbility extends TriggeredAbilityImpl {
|
||||
|
||||
int count;
|
||||
String rule;
|
||||
|
||||
public AnnihilatorAbility(int count) {
|
||||
super(Zone.BATTLEFIELD, new AnnihilatorEffect(count), false);
|
||||
this.count = count;
|
||||
super(Zone.BATTLEFIELD, new SacrificeEffect(StaticFilters.FILTER_CONTROLLED_PERMANENTS, count, ""), false);
|
||||
this.rule = "Annihilator " + count + " <i>(Whenever this creature attacks, defending player sacrifices "
|
||||
+ (count == 1 ? "a permanent" : CardUtil.numberToText(count) + " permanents") + ".)</i>";
|
||||
}
|
||||
|
||||
protected AnnihilatorAbility(final AnnihilatorAbility ability) {
|
||||
super(ability);
|
||||
this.count = ability.count;
|
||||
this.rule = ability.rule;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -52,9 +47,7 @@ public class AnnihilatorAbility extends TriggeredAbilityImpl {
|
|||
UUID defendingPlayerId = game.getCombat().getDefendingPlayerId(sourceId, game);
|
||||
if (defendingPlayerId != null) {
|
||||
// the id has to be set here because the source can be leave battlefield
|
||||
getEffects().forEach((effect) -> {
|
||||
effect.setValue("defendingPlayerId", defendingPlayerId);
|
||||
});
|
||||
getEffects().setTargetPointer(new FixedTarget(defendingPlayerId));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -63,8 +56,7 @@ public class AnnihilatorAbility extends TriggeredAbilityImpl {
|
|||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Annihilator " + count + " <i>(Whenever this creature attacks, defending player sacrifices "
|
||||
+ (count == 1 ? "a permanent" : CardUtil.numberToText(count) + " permanents") + ".)</i>";
|
||||
return rule;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -73,52 +65,3 @@ public class AnnihilatorAbility extends TriggeredAbilityImpl {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
class AnnihilatorEffect extends OneShotEffect {
|
||||
|
||||
private final int count;
|
||||
|
||||
AnnihilatorEffect(int count) {
|
||||
super(Outcome.Sacrifice);
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
AnnihilatorEffect(AnnihilatorEffect effect) {
|
||||
super(effect);
|
||||
this.count = effect.count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
UUID defendingPlayerId = (UUID) getValue("defendingPlayerId");
|
||||
Player player = null;
|
||||
if (defendingPlayerId != null) {
|
||||
player = game.getPlayer(defendingPlayerId);
|
||||
}
|
||||
if (player != null) {
|
||||
int amount = Math.min(count, game.getBattlefield().countAll(new FilterControlledPermanent(), player.getId(), game));
|
||||
if (amount > 0) {
|
||||
Target target = new TargetControlledPermanent(amount, amount, new FilterControlledPermanent(), true);
|
||||
if (target.canChoose(player.getId(), source, game)) {
|
||||
while (player.canRespond()
|
||||
&& target.canChoose(player.getId(), source, game)
|
||||
&& !target.isChosen()) {
|
||||
player.choose(Outcome.Sacrifice, target, source, game);
|
||||
}
|
||||
target.getTargets().stream()
|
||||
.map(game::getPermanent)
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(permanent -> permanent.sacrifice(source, game));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnihilatorEffect copy() {
|
||||
return new AnnihilatorEffect(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ 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 mage.target.common.TargetSacrifice;
|
||||
|
||||
/**
|
||||
* @author TheElk801, Alex-Vasile
|
||||
|
|
@ -28,12 +28,12 @@ public class CasualtyAbility extends StaticAbility implements OptionalAdditional
|
|||
|
||||
protected OptionalAdditionalCost additionalCost;
|
||||
|
||||
private static TargetControlledPermanent makeFilter(int number) {
|
||||
private static TargetSacrifice 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);
|
||||
return new TargetSacrifice(1, filter);
|
||||
}
|
||||
|
||||
public CasualtyAbility(int number) {
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@ import mage.cards.Card;
|
|||
import mage.constants.Outcome;
|
||||
import mage.constants.SpellAbilityType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||
import mage.filter.predicate.permanent.CanBeSacrificedPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
import mage.target.common.TargetSacrifice;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
|
@ -30,6 +32,11 @@ public class EmergeAbility extends SpellAbility {
|
|||
private final ManaCosts<ManaCost> emergeCost;
|
||||
public static final String EMERGE_ACTIVATION_CREATURE_REFERENCE = "emergeActivationMOR";
|
||||
|
||||
private static final FilterPermanent SAC_FILTER = new FilterControlledCreaturePermanent();
|
||||
static {
|
||||
SAC_FILTER.add(CanBeSacrificedPredicate.instance);
|
||||
}
|
||||
|
||||
public EmergeAbility(Card card, String emergeString) {
|
||||
super(card.getSpellAbility());
|
||||
this.emergeCost = new ManaCostsImpl<>(emergeString);
|
||||
|
|
@ -56,7 +63,7 @@ public class EmergeAbility extends SpellAbility {
|
|||
Player controller = game.getPlayer(this.getControllerId());
|
||||
if (controller != null) {
|
||||
for (Permanent creature : game.getBattlefield().getActivePermanents(
|
||||
new FilterControlledCreaturePermanent(), this.getControllerId(), this, game)) {
|
||||
SAC_FILTER, this.getControllerId(), this, game)) {
|
||||
ManaCost costToPay = CardUtil.reduceCost(emergeCost.copy(), creature.getManaValue());
|
||||
if (costToPay.canPay(this, this, this.getControllerId(), game)) {
|
||||
return new ActivationStatus(new ApprovingObject(this, game));
|
||||
|
|
@ -91,7 +98,8 @@ public class EmergeAbility extends SpellAbility {
|
|||
public boolean activate(Game game, boolean noMana) {
|
||||
Player controller = game.getPlayer(this.getControllerId());
|
||||
if (controller != null) {
|
||||
TargetPermanent target = new TargetControlledCreaturePermanent(new FilterControlledCreaturePermanent("creature to sacrifice for emerge"));
|
||||
TargetSacrifice target = new TargetSacrifice(StaticFilters.FILTER_PERMANENT_A_CREATURE);
|
||||
target.withChooseHint("to sacrifice for emerge");
|
||||
if (controller.choose(Outcome.Sacrifice, target, this, game)) {
|
||||
Permanent creature = game.getPermanent(target.getFirstTarget());
|
||||
if (creature != null) {
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ import mage.abilities.Ability;
|
|||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetSacrifice;
|
||||
|
||||
/**
|
||||
* Exploit is the signature ability of the blue-black Silumgar clan. When a creature with exploit
|
||||
|
|
@ -71,14 +71,13 @@ class ExploitEffect extends OneShotEffect {
|
|||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller != null) {
|
||||
Target target = new TargetPermanent(1, 1, new FilterControlledCreaturePermanent("creature to exploit"), true);
|
||||
Target target = new TargetSacrifice(StaticFilters.FILTER_PERMANENT_A_CREATURE);
|
||||
target.withChooseHint("to exploit");
|
||||
if (target.canChoose(controller.getId(), source, game)) {
|
||||
controller.chooseTarget(Outcome.Sacrifice, target, source, game);
|
||||
controller.choose(Outcome.Sacrifice, target, source, game);
|
||||
Permanent permanent = game.getPermanent(target.getFirstTarget());
|
||||
if (permanent != null) {
|
||||
if (permanent.sacrifice(source, game)) {
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.EXPLOITED_CREATURE, permanent.getId(), source, controller.getId()));
|
||||
}
|
||||
if (permanent != null && (permanent.sacrifice(source, game))) {
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.EXPLOITED_CREATURE, permanent.getId(), source, controller.getId()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import mage.game.Game;
|
|||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetSacrifice;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.GameLog;
|
||||
|
|
@ -175,8 +175,8 @@ class OfferingAsThoughEffect extends AsThoughEffectImpl {
|
|||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player != null
|
||||
&& player.chooseUse(Outcome.Benefit, "Offer a " + filter.getMessage() + " to cast " + spellToCast.getName() + '?', source, game)) {
|
||||
Target target = new TargetPermanent(1, 1, filter, true);
|
||||
player.chooseTarget(Outcome.Sacrifice, target, source, game);
|
||||
Target target = new TargetSacrifice(filter);
|
||||
player.choose(Outcome.Sacrifice, target, source, game);
|
||||
if (!target.isChosen()) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package mage.filter.predicate.permanent;
|
||||
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public enum CanBeSacrificedPredicate implements Predicate<Permanent> {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public boolean apply(Permanent input, Game game) {
|
||||
return input.canBeSacrificed();
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ import mage.players.Player;
|
|||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetControlledPermanent;
|
||||
import mage.target.common.TargetDiscard;
|
||||
import mage.target.common.TargetSacrifice;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
|
|
@ -162,7 +163,7 @@ class OublietteEffect extends OneShotEffect {
|
|||
}
|
||||
}
|
||||
|
||||
class OublietteTarget extends TargetControlledPermanent {
|
||||
class OublietteTarget extends TargetSacrifice {
|
||||
|
||||
private static final CardTypeAssignment cardTypeAssigner = new CardTypeAssignment(
|
||||
CardType.ARTIFACT,
|
||||
|
|
@ -176,7 +177,7 @@ class OublietteTarget extends TargetControlledPermanent {
|
|||
}
|
||||
|
||||
OublietteTarget(int numTargets) {
|
||||
super(numTargets, numTargets, filter, true);
|
||||
super(numTargets, filter);
|
||||
}
|
||||
|
||||
private OublietteTarget(final OublietteTarget target) {
|
||||
|
|
@ -263,4 +264,3 @@ class SandfallCellEffect extends OneShotEffect {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -101,6 +101,10 @@ public interface Permanent extends Card, Controllable {
|
|||
|
||||
boolean isProtectedBy(UUID playerId);
|
||||
|
||||
void setCanBeSacrificed(boolean canBeSacrificed);
|
||||
|
||||
boolean canBeSacrificed();
|
||||
|
||||
void setCardNumber(String cid);
|
||||
|
||||
void setExpansionSetCode(String expansionSetCode);
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
protected boolean manifested = false;
|
||||
protected boolean morphed = false;
|
||||
protected boolean ringBearerFlag = false;
|
||||
protected boolean canBeSacrificed = true;
|
||||
protected int classLevel = 1;
|
||||
protected final Set<UUID> goadingPlayers = new HashSet<>();
|
||||
protected UUID originalControllerId;
|
||||
|
|
@ -176,6 +177,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.manifested = permanent.manifested;
|
||||
this.createOrder = permanent.createOrder;
|
||||
this.prototyped = permanent.prototyped;
|
||||
this.canBeSacrificed = permanent.canBeSacrificed;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -215,6 +217,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.goadingPlayers.clear();
|
||||
this.loyaltyActivationsAvailable = 1;
|
||||
this.legendRuleApplies = true;
|
||||
this.canBeSacrificed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -1361,7 +1364,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
@Override
|
||||
public boolean sacrifice(Ability source, Game game) {
|
||||
//20091005 - 701.13
|
||||
if (isPhasedIn() && !game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.SACRIFICE_PERMANENT, objectId, source, controllerId))) {
|
||||
if (isPhasedIn() && canBeSacrificed && !game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.SACRIFICE_PERMANENT, objectId, source, controllerId))) {
|
||||
// Commander replacement effect or Rest in Peace (exile instead of graveyard) in play does not prevent successful sacrifice
|
||||
// so the return value of the moveToZone is not taken into account here
|
||||
moveToZone(Zone.GRAVEYARD, source, game, false);
|
||||
|
|
@ -1773,6 +1776,16 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
return protectorId != null && protectorId.equals(playerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCanBeSacrificed(boolean canBeSacrificed) {
|
||||
this.canBeSacrificed = canBeSacrificed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeSacrificed() {
|
||||
return canBeSacrificed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPairedCard(MageObjectReference pairedCard) {
|
||||
this.pairedPermanent = pairedCard;
|
||||
|
|
|
|||
|
|
@ -4437,7 +4437,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean canPaySacrificeCost(Permanent permanent, Ability source, UUID controllerId, Game game) {
|
||||
return sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, controllerId, source, game);
|
||||
return permanent.canBeSacrificed() &&
|
||||
(sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, controllerId, source, game));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
49
Mage/src/main/java/mage/target/common/TargetSacrifice.java
Normal file
49
Mage/src/main/java/mage/target/common/TargetSacrifice.java
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package mage.target.common;
|
||||
|
||||
import mage.constants.TargetController;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.predicate.permanent.CanBeSacrificedPredicate;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class TargetSacrifice extends TargetPermanent {
|
||||
|
||||
public TargetSacrifice(FilterPermanent filter) {
|
||||
this(1, filter);
|
||||
}
|
||||
|
||||
public TargetSacrifice(int numTargets, FilterPermanent filter) {
|
||||
this(numTargets, numTargets, filter);
|
||||
}
|
||||
|
||||
public TargetSacrifice(int minNumTargets, int maxNumTargets, FilterPermanent filter) {
|
||||
super(minNumTargets, maxNumTargets, makeFilter(filter), true);
|
||||
this.withChooseHint("to sacrifice");
|
||||
}
|
||||
|
||||
protected TargetSacrifice(final TargetSacrifice target) {
|
||||
super(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetSacrifice copy() {
|
||||
return new TargetSacrifice(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new filter with necessary constraints for sacrificing
|
||||
* @param filter input generic filter
|
||||
* @return new filter with "you control" and CanBeSacrificedPredicate added
|
||||
*/
|
||||
public static FilterPermanent makeFilter(FilterPermanent filter) {
|
||||
FilterPermanent newFilter = filter.copy();
|
||||
newFilter.add(TargetController.YOU.getControllerPredicate());
|
||||
newFilter.add(CanBeSacrificedPredicate.instance);
|
||||
if (filter.getMessage().contains(" you control")) {
|
||||
newFilter.setMessage(filter.getMessage().replace(" you control", ""));
|
||||
}
|
||||
return newFilter;
|
||||
}
|
||||
}
|
||||
|
|
@ -21,11 +21,11 @@ import java.util.stream.Collectors;
|
|||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class TargetControlledCreatureEachColor extends TargetControlledPermanent {
|
||||
public class TargetSacrificeCreatureEachColor extends TargetSacrifice {
|
||||
|
||||
private final ColorAssignment colorAssigner;
|
||||
|
||||
private static final FilterControlledPermanent makeFilter(String colors) {
|
||||
private static FilterControlledPermanent makeFilter(String colors) {
|
||||
List<ObjectColor> objectColors
|
||||
= Arrays.stream(colors.split(""))
|
||||
.map(ObjectColor::new)
|
||||
|
|
@ -42,12 +42,12 @@ public class TargetControlledCreatureEachColor extends TargetControlledPermanent
|
|||
return filter;
|
||||
}
|
||||
|
||||
public TargetControlledCreatureEachColor(String colors) {
|
||||
public TargetSacrificeCreatureEachColor(String colors) {
|
||||
super(colors.length(), makeFilter(colors));
|
||||
colorAssigner = new ColorAssignment(colors.split(""));
|
||||
}
|
||||
|
||||
private TargetControlledCreatureEachColor(final TargetControlledCreatureEachColor target) {
|
||||
private TargetSacrificeCreatureEachColor(final TargetSacrificeCreatureEachColor target) {
|
||||
super(target);
|
||||
this.colorAssigner = target.colorAssigner;
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ public class TargetControlledCreatureEachColor extends TargetControlledPermanent
|
|||
}
|
||||
|
||||
@Override
|
||||
public TargetControlledCreatureEachColor copy() {
|
||||
return new TargetControlledCreatureEachColor(this);
|
||||
public TargetSacrificeCreatureEachColor copy() {
|
||||
return new TargetSacrificeCreatureEachColor(this);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue