forked from External/mage
- WIP: AI and multi targets, human and X=0 use cases, human and impossible targets use cases;
- improved stability and shared logic (related to #13606, #11134, #11666, continue from a53eb66b58, close #13617, close #13613);
- improved test logs and debug info to show more target info on errors;
- improved test framework to support multiple addTarget calls;
- improved test framework to find bad commands order for targets (related to #11666);
- fixed game freezes on auto-choice usages with disconnected or under control players (related to #11285);
- gui, game: fixed that player doesn't mark avatar as selected/green in "up to" targeting;
- gui, game: fixed small font in some popup messages on big screens (related to #969);
- gui, game: added min targets info for target selection dialog;
- for devs: added new cheat option to call and test any game dialog (define own dialogs, targets, etc in HumanDialogsTester);
- for devs: now tests require complete an any or up to target selection by addTarget + TestPlayer.TARGET_SKIP or setChoice + TestPlayer.CHOICE_SKIP (if not all max/possible targets used);
- for devs: added detail targets info for activate/trigger/cast, can be useful to debug unit tests, auto-choose or AI (see DebugUtil.GAME_SHOW_CHOOSE_TARGET_LOGS)
146 lines
5 KiB
Java
146 lines
5 KiB
Java
package mage.abilities.effects.common;
|
|
|
|
import mage.abilities.Ability;
|
|
import mage.abilities.dynamicvalue.DynamicValue;
|
|
import mage.abilities.dynamicvalue.common.StaticValue;
|
|
import mage.abilities.effects.OneShotEffect;
|
|
import mage.constants.Outcome;
|
|
import mage.filter.FilterPermanent;
|
|
import mage.game.Game;
|
|
import mage.game.permanent.Permanent;
|
|
import mage.players.Player;
|
|
import mage.target.common.TargetSacrifice;
|
|
import mage.util.CardUtil;
|
|
|
|
import java.util.*;
|
|
|
|
/**
|
|
* @author BetaSteward_at_googlemail.com, JayDi85
|
|
*/
|
|
public class SacrificeAllEffect extends OneShotEffect {
|
|
|
|
private final DynamicValue amount;
|
|
private final FilterPermanent filter;
|
|
private final boolean onlyOpponents;
|
|
|
|
private static final String VALUE_KEY = "sacrificeAllEffect_permanentsList";
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
protected SacrificeAllEffect(DynamicValue amount, FilterPermanent filter, boolean onlyOpponents) {
|
|
super(Outcome.Sacrifice);
|
|
this.amount = amount;
|
|
this.filter = filter;
|
|
this.onlyOpponents = onlyOpponents;
|
|
setText();
|
|
}
|
|
|
|
protected SacrificeAllEffect(final SacrificeAllEffect effect) {
|
|
super(effect);
|
|
this.amount = effect.amount;
|
|
this.filter = effect.filter.copy();
|
|
this.onlyOpponents = effect.onlyOpponents;
|
|
}
|
|
|
|
@Override
|
|
public SacrificeAllEffect copy() {
|
|
return new SacrificeAllEffect(this);
|
|
}
|
|
|
|
@Override
|
|
public boolean apply(Game game, Ability source) {
|
|
int num = amount.calculate(game, source, this);
|
|
if (num < 1) {
|
|
return false;
|
|
}
|
|
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) {
|
|
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);
|
|
target.choose(Outcome.Sacrifice, player.getId(), source, game);
|
|
perms.addAll(target.getTargets());
|
|
}
|
|
|
|
List<Permanent> sacraficedPermanents = new ArrayList<>();
|
|
for (UUID permID : perms) {
|
|
Permanent permanent = game.getPermanent(permID);
|
|
if (permanent != null && permanent.sacrifice(source, game)) {
|
|
sacraficedPermanents.add(permanent.copy());
|
|
}
|
|
}
|
|
saveSacrificedPermanentsList(source.getSourceId(), game, sacraficedPermanents);
|
|
|
|
return true;
|
|
}
|
|
|
|
public static void saveSacrificedPermanentsList(UUID sourceObjectId, Game game, List<Permanent> list) {
|
|
game.getState().setValue(CardUtil.getCardZoneString(VALUE_KEY, sourceObjectId, game), list);
|
|
}
|
|
|
|
/**
|
|
* Get detailed list of sacrificed permanents
|
|
*
|
|
* @param previous if you need to look in detailed list on battlefield, then use previous param to find data from a stack moment
|
|
*/
|
|
public static List<Permanent> getSacrificedPermanentsList(UUID sourceObjectId, Game game, boolean previous) {
|
|
return (List<Permanent>) game.getState().getValue(CardUtil.getCardZoneString(VALUE_KEY, sourceObjectId, game, previous));
|
|
}
|
|
|
|
private void setText() {
|
|
StringBuilder sb = new StringBuilder();
|
|
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());
|
|
}
|
|
if (!filter.getMessage().contains("with")) {
|
|
sb.append(" of their choice");
|
|
}
|
|
staticText = sb.toString();
|
|
}
|
|
|
|
}
|