mirror of
https://github.com/correl/mage.git
synced 2024-11-14 19:19:32 +00:00
* Fixed target change handling (e.g. with opponent filter - fixes #574). Added some tests.
This commit is contained in:
parent
af1892a6e7
commit
1b690e5c8c
22 changed files with 531 additions and 484 deletions
|
@ -204,11 +204,16 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
if (log.isDebugEnabled()) {
|
||||
log.debug("chooseTarget: " + outcome.toString() + ":" + target.toString());
|
||||
}
|
||||
UUID opponentId = game.getOpponents(playerId).iterator().next();
|
||||
// sometimes a target aelection can be made from a player that does not control the ability
|
||||
UUID abilityControllerId = playerId;
|
||||
if (target.getTargetController() != null && target.getAbilityController() != null) {
|
||||
abilityControllerId = target.getAbilityController();
|
||||
}
|
||||
UUID opponentId = game.getOpponents(abilityControllerId).iterator().next();
|
||||
if (target instanceof TargetPlayer) {
|
||||
if (outcome.isGood()) {
|
||||
if (target.canTarget(playerId, game)) {
|
||||
target.add(playerId, game);
|
||||
if (target.canTarget(abilityControllerId, game)) {
|
||||
target.add(abilityControllerId, game);
|
||||
return true;
|
||||
}
|
||||
if (target.isRequired(sourceId, game)) {
|
||||
|
@ -223,8 +228,8 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
return true;
|
||||
}
|
||||
if (target.isRequired(sourceId, game)) {
|
||||
if (target.canTarget(playerId, game)) {
|
||||
target.add(playerId, game);
|
||||
if (target.canTarget(abilityControllerId, game)) {
|
||||
target.add(abilityControllerId, game);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -257,12 +262,12 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
}
|
||||
if (target instanceof TargetControlledPermanent) {
|
||||
List<Permanent> targets;
|
||||
targets = threats(playerId, sourceId, ((TargetControlledPermanent) target).getFilter(), game, target.getTargets());
|
||||
targets = threats(abilityControllerId, sourceId, ((TargetControlledPermanent) target).getFilter(), game, target.getTargets());
|
||||
if (!outcome.isGood()) {
|
||||
Collections.reverse(targets);
|
||||
}
|
||||
for (Permanent permanent : targets) {
|
||||
if (((TargetControlledPermanent) target).canTarget(playerId, permanent.getId(), sourceId, game, false) && !target.getTargets().contains(permanent.getId())) {
|
||||
if (((TargetControlledPermanent) target).canTarget(abilityControllerId, permanent.getId(), sourceId, game, false) && !target.getTargets().contains(permanent.getId())) {
|
||||
target.add(permanent.getId(), game);
|
||||
return true;
|
||||
}
|
||||
|
@ -275,13 +280,13 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
targets = threats(null, sourceId, ((TargetPermanent) target).getFilter(), game, target.getTargets());
|
||||
} else {
|
||||
if (outcome.isGood()) {
|
||||
targets = threats(playerId, sourceId, ((TargetPermanent) target).getFilter(), game, target.getTargets());
|
||||
targets = threats(abilityControllerId, sourceId, ((TargetPermanent) target).getFilter(), game, target.getTargets());
|
||||
} else {
|
||||
targets = threats(opponentId, sourceId, ((TargetPermanent) target).getFilter(), game, target.getTargets());
|
||||
}
|
||||
}
|
||||
for (Permanent permanent : targets) {
|
||||
if (((TargetPermanent) target).canTarget(playerId, permanent.getId(), null, game) && !target.getTargets().contains(permanent.getId())) {
|
||||
if (((TargetPermanent) target).canTarget(abilityControllerId, permanent.getId(), null, game) && !target.getTargets().contains(permanent.getId())) {
|
||||
target.add(permanent.getId(), game);
|
||||
return true;
|
||||
}
|
||||
|
@ -309,13 +314,13 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
List<Permanent> targets;
|
||||
TargetCreatureOrPlayer t = ((TargetCreatureOrPlayer) target);
|
||||
if (outcome.isGood()) {
|
||||
targets = threats(playerId, sourceId, ((FilterCreatureOrPlayer) t.getFilter()).getCreatureFilter(), game, target.getTargets());
|
||||
targets = threats(abilityControllerId, sourceId, ((FilterCreatureOrPlayer) t.getFilter()).getCreatureFilter(), game, target.getTargets());
|
||||
} else {
|
||||
targets = threats(opponentId, sourceId, ((FilterCreatureOrPlayer) t.getFilter()).getCreatureFilter(), game, target.getTargets());
|
||||
}
|
||||
for (Permanent permanent : targets) {
|
||||
List<UUID> alreadyTargetted = target.getTargets();
|
||||
if (t.canTarget(playerId, permanent.getId(), null, game)) {
|
||||
if (t.canTarget(abilityControllerId, permanent.getId(), null, game)) {
|
||||
if (alreadyTargetted != null && !alreadyTargetted.contains(permanent.getId())) {
|
||||
target.add(permanent.getId(), game);
|
||||
return true;
|
||||
|
@ -323,8 +328,8 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
}
|
||||
}
|
||||
if (outcome.isGood()) {
|
||||
if (target.canTarget(playerId, null, game)) {
|
||||
target.add(playerId, game);
|
||||
if (target.canTarget(abilityControllerId, null, game)) {
|
||||
target.add(abilityControllerId, game);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
|
@ -341,7 +346,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
if (target instanceof TargetPermanentOrPlayer) {
|
||||
List<Permanent> targets;
|
||||
TargetPermanentOrPlayer t = ((TargetPermanentOrPlayer) target);
|
||||
List<Permanent> ownedTargets = threats(playerId, sourceId, ((FilterPermanentOrPlayer) t.getFilter()).getPermanentFilter(), game, target.getTargets());;
|
||||
List<Permanent> ownedTargets = threats(abilityControllerId, sourceId, ((FilterPermanentOrPlayer) t.getFilter()).getPermanentFilter(), game, target.getTargets());;
|
||||
List<Permanent> opponentTargets = threats(opponentId, sourceId, ((FilterPermanentOrPlayer) t.getFilter()).getPermanentFilter(), game, target.getTargets());
|
||||
if (outcome.isGood()) {
|
||||
targets = ownedTargets;
|
||||
|
@ -358,8 +363,8 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
}
|
||||
}
|
||||
if (outcome.isGood()) {
|
||||
if (target.canTarget(playerId, null, game)) {
|
||||
target.add(playerId, game);
|
||||
if (target.canTarget(abilityControllerId, null, game)) {
|
||||
target.add(abilityControllerId, game);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
|
@ -375,8 +380,8 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
target.add(opponentId, game);
|
||||
return true;
|
||||
}
|
||||
if (target.canTarget(playerId, null, game)) {
|
||||
target.add(playerId, game);
|
||||
if (target.canTarget(abilityControllerId, null, game)) {
|
||||
target.add(abilityControllerId, game);
|
||||
return true;
|
||||
}
|
||||
if (outcome.isGood()) { // no other valid targets so use a permanent
|
||||
|
@ -415,7 +420,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
|
||||
if (target instanceof TargetCardInYourGraveyard) {
|
||||
List<UUID> alreadyTargetted = target.getTargets();
|
||||
List<Card> cards = new ArrayList<>(game.getPlayer(playerId).getGraveyard().getCards(game));
|
||||
List<Card> cards = new ArrayList<>(game.getPlayer(abilityControllerId).getGraveyard().getCards(game));
|
||||
while(!cards.isEmpty()) {
|
||||
Card card = pickTarget(cards, outcome, target, null, game);
|
||||
if (card != null && alreadyTargetted != null && !alreadyTargetted.contains(card.getId())) {
|
||||
|
@ -428,7 +433,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
if (target instanceof TargetSource) {
|
||||
Set<UUID> targets;
|
||||
TargetSource t = ((TargetSource) target);
|
||||
targets = t.possibleTargets(sourceId, playerId, game);
|
||||
targets = t.possibleTargets(sourceId, abilityControllerId, game);
|
||||
for (UUID targetId : targets) {
|
||||
MageObject targetObject = game.getObject(targetId);
|
||||
if (targetObject != null) {
|
||||
|
@ -455,28 +460,33 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
if (log.isDebugEnabled()) {
|
||||
log.debug("chooseTarget: " + outcome.toString() + ":" + target.toString());
|
||||
}
|
||||
UUID opponentId = game.getOpponents(playerId).iterator().next();
|
||||
// sometimes a target selection can be made from a player that does not control the ability
|
||||
UUID abilityControllerId = playerId;
|
||||
if (target.getAbilityController() != null) {
|
||||
abilityControllerId = target.getAbilityController();
|
||||
}
|
||||
UUID opponentId = game.getOpponents(abilityControllerId).iterator().next();
|
||||
if (target instanceof TargetPlayer) {
|
||||
if (outcome.isGood()) {
|
||||
if (target.canTarget(playerId, playerId, source, game)) {
|
||||
if (target.canTarget(abilityControllerId, abilityControllerId, source, game)) {
|
||||
target.addTarget(playerId, source, game);
|
||||
return true;
|
||||
}
|
||||
if (target.isRequired(source)) {
|
||||
if (target.canTarget(playerId, opponentId, source, game)) {
|
||||
if (target.canTarget(abilityControllerId, opponentId, source, game)) {
|
||||
target.addTarget(opponentId, source, game);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (target.canTarget(playerId, opponentId, source, game)) {
|
||||
if (target.canTarget(abilityControllerId, opponentId, source, game)) {
|
||||
target.addTarget(opponentId, source, game);
|
||||
return true;
|
||||
}
|
||||
if (target.isRequired(source)) {
|
||||
if (target.canTarget(playerId, playerId, source, game)) {
|
||||
target.addTarget(playerId, source, game);
|
||||
if (target.canTarget(abilityControllerId, abilityControllerId, source, game)) {
|
||||
target.addTarget(abilityControllerId, source, game);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -526,12 +536,12 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
}
|
||||
if (target instanceof TargetControlledPermanent) {
|
||||
List<Permanent> targets;
|
||||
targets = threats(playerId, source.getSourceId(), ((TargetControlledPermanent)target).getFilter(), game, target.getTargets());
|
||||
targets = threats(abilityControllerId, source.getSourceId(), ((TargetControlledPermanent)target).getFilter(), game, target.getTargets());
|
||||
if (!outcome.isGood()) {
|
||||
Collections.reverse(targets);
|
||||
}
|
||||
for (Permanent permanent: targets) {
|
||||
if (((TargetControlledPermanent)target).canTarget(playerId, permanent.getId(), source, game)) {
|
||||
if (((TargetControlledPermanent)target).canTarget(abilityControllerId, permanent.getId(), source, game)) {
|
||||
target.addTarget(permanent.getId(), source, game);
|
||||
if (target.getNumberOfTargets() <= target.getTargets().size() && (!outcome.isGood() || target.getMaxNumberOfTargets() <= target.getTargets().size())) {
|
||||
return true;
|
||||
|
@ -545,7 +555,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
List<Permanent> targets;
|
||||
boolean outcomeTargets = true;
|
||||
if (outcome.isGood()) {
|
||||
targets = threats(playerId, source == null?null:source.getSourceId(), ((TargetPermanent)target).getFilter(), game, target.getTargets());
|
||||
targets = threats(abilityControllerId, source == null?null:source.getSourceId(), ((TargetPermanent)target).getFilter(), game, target.getTargets());
|
||||
}
|
||||
else {
|
||||
targets = threats(opponentId, source == null?null:source.getSourceId(), ((TargetPermanent)target).getFilter(), game, target.getTargets());
|
||||
|
@ -557,7 +567,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
//targets = game.getBattlefield().getActivePermanents(((TargetPermanent)target).getFilter(), playerId, game);
|
||||
}
|
||||
for (Permanent permanent: targets) {
|
||||
if (((TargetPermanent)target).canTarget(playerId, permanent.getId(), source, game)) {
|
||||
if (((TargetPermanent)target).canTarget(abilityControllerId, permanent.getId(), source, game)) {
|
||||
target.addTarget(permanent.getId(), source, game);
|
||||
if (!outcomeTargets || target.getMaxNumberOfTargets() <= target.getTargets().size()) {
|
||||
return true;
|
||||
|
@ -570,7 +580,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
List<Permanent> targets;
|
||||
TargetCreatureOrPlayer t = ((TargetCreatureOrPlayer)target);
|
||||
if (outcome.isGood()) {
|
||||
targets = threats(playerId, source.getSourceId(), ((FilterCreatureOrPlayer)t.getFilter()).getCreatureFilter(), game, target.getTargets());
|
||||
targets = threats(abilityControllerId, source.getSourceId(), ((FilterCreatureOrPlayer)t.getFilter()).getCreatureFilter(), game, target.getTargets());
|
||||
}
|
||||
else {
|
||||
targets = threats(opponentId, source.getSourceId(), ((FilterCreatureOrPlayer)t.getFilter()).getCreatureFilter(), game, target.getTargets());
|
||||
|
@ -578,8 +588,8 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
|
||||
if (targets.isEmpty()) {
|
||||
if (outcome.isGood()) {
|
||||
if (target.canTarget(playerId, source, game)) {
|
||||
target.addTarget(playerId, source, game);
|
||||
if (target.canTarget(abilityControllerId, source, game)) {
|
||||
target.addTarget(abilityControllerId, source, game);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -596,7 +606,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
}
|
||||
for (Permanent permanent : targets) {
|
||||
List<UUID> alreadyTargetted = target.getTargets();
|
||||
if (t.canTarget(playerId, permanent.getId(), source, game)) {
|
||||
if (t.canTarget(abilityControllerId, permanent.getId(), source, game)) {
|
||||
if (alreadyTargetted != null && !alreadyTargetted.contains(permanent.getId())) {
|
||||
target.addTarget(permanent.getId(), source, game);
|
||||
return true;
|
||||
|
@ -605,8 +615,8 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
}
|
||||
|
||||
if (outcome.isGood()) {
|
||||
if (target.canTarget(playerId, source, game)) {
|
||||
target.addTarget(playerId, source, game);
|
||||
if (target.canTarget(abilityControllerId, source, game)) {
|
||||
target.addTarget(abilityControllerId, source, game);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -634,7 +644,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
return false;
|
||||
}
|
||||
if (target instanceof TargetCardInLibrary) {
|
||||
List<Card> cards = new ArrayList<>(game.getPlayer(playerId).getLibrary().getCards(game));
|
||||
List<Card> cards = new ArrayList<>(game.getPlayer(abilityControllerId).getLibrary().getCards(game));
|
||||
Card card = pickTarget(cards, outcome, target, source, game);
|
||||
if (card != null) {
|
||||
target.addTarget(card.getId(), source, game);
|
||||
|
@ -643,7 +653,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
return false;
|
||||
}
|
||||
if (target instanceof TargetCardInYourGraveyard) {
|
||||
List<Card> cards = new ArrayList<>(game.getPlayer(playerId).getGraveyard().getCards(game));
|
||||
List<Card> cards = new ArrayList<>(game.getPlayer(abilityControllerId).getGraveyard().getCards(game));
|
||||
while(!target.isChosen() && !cards.isEmpty()) {
|
||||
Card card = pickTarget(cards, outcome, target, source, game);
|
||||
if (card != null) {
|
||||
|
@ -680,7 +690,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
List<Permanent> targets;
|
||||
boolean outcomeTargets = true;
|
||||
if (outcome.isGood()) {
|
||||
targets = threats(playerId, source == null?null:source.getSourceId(), ((TargetSpellOrPermanent)target).getPermanentFilter(), game, target.getTargets());
|
||||
targets = threats(abilityControllerId, source == null?null:source.getSourceId(), ((TargetSpellOrPermanent)target).getPermanentFilter(), game, target.getTargets());
|
||||
}
|
||||
else {
|
||||
targets = threats(opponentId, source == null?null:source.getSourceId(), ((TargetSpellOrPermanent)target).getPermanentFilter(), game, target.getTargets());
|
||||
|
@ -692,7 +702,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
//targets = game.getBattlefield().getActivePermanents(((TargetPermanent)target).getFilter(), playerId, game);
|
||||
}
|
||||
for (Permanent permanent: targets) {
|
||||
if (((TargetPermanent)target).canTarget(playerId, permanent.getId(), source, game)) {
|
||||
if (((TargetPermanent)target).canTarget(abilityControllerId, permanent.getId(), source, game)) {
|
||||
target.addTarget(permanent.getId(), source, game);
|
||||
if (!outcomeTargets || target.getMaxNumberOfTargets() <= target.getTargets().size()) {
|
||||
return true;
|
||||
|
@ -715,7 +725,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
}
|
||||
if (target instanceof TargetCardInOpponentsGraveyard) {
|
||||
List<Card> cards = new ArrayList<>();
|
||||
for (UUID uuid: game.getOpponents(playerId)) {
|
||||
for (UUID uuid: game.getOpponents(abilityControllerId)) {
|
||||
Player player = game.getPlayer(uuid);
|
||||
if (player != null) {
|
||||
cards.addAll(player.getGraveyard().getCards(game));
|
||||
|
|
|
@ -235,11 +235,15 @@ public class HumanPlayer extends PlayerImpl {
|
|||
@Override
|
||||
public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game, Map<String, Serializable> options) {
|
||||
updateGameStatePriority("choose(5)", game);
|
||||
UUID abilityControllerId = playerId;
|
||||
if (target.getTargetController() != null && target.getAbilityController() != null) {
|
||||
abilityControllerId = target.getAbilityController();
|
||||
}
|
||||
if (options == null) {
|
||||
options = new HashMap<>();
|
||||
}
|
||||
while (!abort) {
|
||||
Set<UUID> targetIds = target.possibleTargets(sourceId, playerId, game);
|
||||
Set<UUID> targetIds = target.possibleTargets(sourceId, abilityControllerId, game);
|
||||
if (targetIds == null || targetIds.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -251,14 +255,14 @@ public class HumanPlayer extends PlayerImpl {
|
|||
List<UUID> chosen = target.getTargets();
|
||||
options.put("chosen", (Serializable)chosen);
|
||||
|
||||
game.fireSelectTargetEvent(playerId, target.getMessage(), targetIds, required, getOptions(target, options));
|
||||
game.fireSelectTargetEvent(getId(), target.getMessage(), targetIds, required, getOptions(target, options));
|
||||
waitForResponse(game);
|
||||
if (response.getUUID() != null) {
|
||||
if (!targetIds.contains(response.getUUID())) {
|
||||
continue;
|
||||
}
|
||||
if (target instanceof TargetPermanent) {
|
||||
if (((TargetPermanent)target).canTarget(playerId, response.getUUID(), sourceId, game, false)) {
|
||||
if (((TargetPermanent)target).canTarget(abilityControllerId, response.getUUID(), sourceId, game, false)) {
|
||||
target.add(response.getUUID(), game);
|
||||
if(target.doneChosing()){
|
||||
return true;
|
||||
|
@ -306,13 +310,17 @@ public class HumanPlayer extends PlayerImpl {
|
|||
@Override
|
||||
public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) {
|
||||
updateGameStatePriority("chooseTarget", game);
|
||||
UUID abilityControllerId = playerId;
|
||||
if (target.getAbilityController() != null) {
|
||||
abilityControllerId = target.getAbilityController();
|
||||
}
|
||||
while (!abort) {
|
||||
Set<UUID> possibleTargets = target.possibleTargets(source==null?null:source.getSourceId(), playerId, game);
|
||||
Set<UUID> possibleTargets = target.possibleTargets(source==null?null:source.getSourceId(), abilityControllerId, game);
|
||||
boolean required = target.isRequired(source);
|
||||
if (possibleTargets.isEmpty() || target.getTargets().size() >= target.getNumberOfTargets()) {
|
||||
required = false;
|
||||
}
|
||||
game.fireSelectTargetEvent(playerId, target.getMessage(), possibleTargets, required, getOptions(target, null));
|
||||
game.fireSelectTargetEvent(getId(), target.getMessage(), possibleTargets, required, getOptions(target, null));
|
||||
waitForResponse(game);
|
||||
if (response.getUUID() != null) {
|
||||
if (target.getTargets().contains(response.getUUID())) {
|
||||
|
@ -320,7 +328,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
continue;
|
||||
}
|
||||
if (possibleTargets.contains(response.getUUID())) {
|
||||
if (target.canTarget(playerId, response.getUUID(), source, game)) {
|
||||
if (target.canTarget(abilityControllerId, response.getUUID(), source, game)) {
|
||||
target.addTarget(response.getUUID(), source, game);
|
||||
if(target.doneChosing()){
|
||||
return true;
|
||||
|
|
|
@ -41,6 +41,7 @@ import java.util.UUID;
|
|||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.game.events.EntersTheBattlefieldEvent;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.players.Player;
|
||||
|
||||
/**
|
||||
* @author noxx
|
||||
|
@ -126,8 +127,9 @@ class TreacherousPitDwellerEffect extends ContinuousEffectImpl {
|
|||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = (Permanent) source.getSourceObjectIfItStillExists(game);
|
||||
if (permanent != null) {
|
||||
return permanent.changeControllerId(source.getFirstTarget(), game);
|
||||
Player targetOpponent = game.getPlayer(source.getFirstTarget());
|
||||
if (permanent != null && targetOpponent != null) {
|
||||
return permanent.changeControllerId(targetOpponent.getId(), game);
|
||||
} else {
|
||||
discard();
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ public class MonasteryMentor extends CardImpl {
|
|||
|
||||
// Prowess
|
||||
this.addAbility(new ProwessAbility());
|
||||
|
||||
// Whenever you cast a noncreature spell, put a 1/1 white Monk creature token with prowess onto the battlefield.
|
||||
this.addAbility(new SpellCastControllerTriggeredAbility(new CreateTokenEffect(new MonasteryMentorToken()), filter, false));
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ public class WhisperwoodElemental extends CardImpl {
|
|||
Effect effect = new GainAbilityControlledEffect(abilityToGain, Duration.EndOfTurn, filter);
|
||||
effect.setText("Until end of turn, face-up, nontoken creatures you control gain \"When this creature dies, manifest the top card of your library.\"");
|
||||
this.addAbility(new SimpleActivatedAbility(
|
||||
Zone.BATTLEFIELD, effect, new SacrificeSourceCost()));
|
||||
Zone.ALL, effect, new SacrificeSourceCost()));
|
||||
}
|
||||
|
||||
public WhisperwoodElemental(final WhisperwoodElemental card) {
|
||||
|
|
|
@ -60,10 +60,10 @@ public class Misdirection extends CardImpl {
|
|||
super(ownerId, 87, "Misdirection", Rarity.RARE, new CardType[]{CardType.INSTANT}, "{3}{U}{U}");
|
||||
this.expansionSetCode = "MMQ";
|
||||
|
||||
|
||||
// You may exile a blue card from your hand rather than pay Misdirection's mana cost.
|
||||
FilterOwnedCard filterCardInHand = new FilterOwnedCard("a blue card from your hand");
|
||||
filterCardInHand.add(new ColorPredicate(ObjectColor.BLUE));
|
||||
|
||||
// the exile cost can never be paid with the card itself
|
||||
filterCardInHand.add(Predicates.not(new CardIdPredicate(this.getId())));
|
||||
this.addAbility(new AlternativeCostSourceAbility(new ExileFromHandCost(new TargetCardInHand(filterCardInHand))));
|
||||
|
|
|
@ -51,7 +51,6 @@ public class CityOfSolitude extends CardImpl {
|
|||
super(ownerId, 52, "City of Solitude", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}");
|
||||
this.expansionSetCode = "VIS";
|
||||
|
||||
|
||||
// Players can cast spells and activate abilities only during their own turns.
|
||||
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CityOfSolitudeEffect()));
|
||||
}
|
||||
|
@ -87,11 +86,6 @@ class CityOfSolitudeEffect extends ContinuousRuleModifyingEffectImpl {
|
|||
return !game.getActivePlayerId().equals(event.getPlayerId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CityOfSolitudeEffect copy() {
|
||||
return new CityOfSolitudeEffect(this);
|
||||
|
|
|
@ -362,4 +362,40 @@ public class ManifestTest extends CardTestPlayerBase {
|
|||
assertPermanentCount(playerB, "", 1);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Whisperwood Elemental - Its sacrifice ability doesn't work..
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testWhisperwoodElemental() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||
// Seismic Rupture deals 2 damage to each creature without flying.
|
||||
addCard(Zone.HAND, playerA, "Seismic Rupture", 1);
|
||||
|
||||
// At the beginning of your end step, manifest the top card of your library.
|
||||
// Sacrifice Whisperwood Elemental: Until end of turn, face-up, nontoken creatures you control gain "When this creature dies, manifest the top card of your library."
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Whisperwood Elemental", 1);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 2);
|
||||
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Sacrifice");
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Seismic Rupture");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
// no life gain
|
||||
assertLife(playerA, 20);
|
||||
assertLife(playerB, 20);
|
||||
|
||||
assertGraveyardCount(playerA, "Seismic Rupture", 1);
|
||||
assertGraveyardCount(playerB, "Whisperwood Elemental", 1);
|
||||
assertGraveyardCount(playerB, "Silvercoat Lion", 2);
|
||||
|
||||
assertPermanentCount(playerB, "", 2);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,14 +31,20 @@ public class TargetOpponentGainsControlTest extends CardTestPlayerBase {
|
|||
@Test
|
||||
public void testChangeControlEffectFromTwoCards() {
|
||||
addCard(Zone.HAND, playerA, "Lightning Bolt", 3);
|
||||
addCard(Zone.HAND, playerA, "Unhallowed Pact", 3);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Treacherous Pit-Dweller");
|
||||
// Enchant creature
|
||||
// When enchanted creature dies, return that card to the battlefield under your control.
|
||||
addCard(Zone.HAND, playerA, "Unhallowed Pact", 1); // {2}{B}
|
||||
// Undying
|
||||
// When Treacherous Pit-Dweller enters the battlefield from a graveyard, target opponent gains control of it.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Treacherous Pit-Dweller"); // 4/3
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Treacherous Pit-Dweller");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Unhallowed Pact", "Treacherous Pit-Dweller");
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", "Treacherous Pit-Dweller");
|
||||
castSpell(1, PhaseStep.UPKEEP, playerA, "Lightning Bolt", "Treacherous Pit-Dweller"); // comes back with undying
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Unhallowed Pact", "Treacherous Pit-Dweller");
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", "Treacherous Pit-Dweller"); // Treacherous Pit-Dweller is now 5/4
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", "Treacherous Pit-Dweller");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
|
@ -46,8 +52,10 @@ public class TargetOpponentGainsControlTest extends CardTestPlayerBase {
|
|||
|
||||
// went to graveyard
|
||||
assertGraveyardCount(playerA, "Unhallowed Pact", 1);
|
||||
assertGraveyardCount(playerA, "Lightning Bolt", 3);
|
||||
|
||||
// returned back
|
||||
assertPermanentCount(playerA, "Treacherous Pit-Dweller", 1);
|
||||
assertGraveyardCount(playerA, "Treacherous Pit-Dweller", 0);
|
||||
assertPermanentCount(playerB, "Treacherous Pit-Dweller", 1); // opponent gets it because ETB of Dweller resolves always last
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,74 @@ public class MisdirectionTest extends CardTestPlayerBase {
|
|||
assertGraveyardCount(playerA, "Rakshasa's Secret", 1);
|
||||
assertGraveyardCount(playerB, "Misdirection", 1);
|
||||
assertHandCount(playerB, "Silvercoat Lion", 0);
|
||||
|
||||
}
|
||||
|
||||
// check to change target permanent creature legal to to a creature the opponent of the spell controller controls
|
||||
@Test
|
||||
public void testChangePublicExecution() {
|
||||
// Destroy target creature an opponent controls. Each other creature that player controls gets -2/-0 until end of turn.
|
||||
addCard(Zone.HAND, playerA, "Public Execution");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6);
|
||||
/*
|
||||
Misdirection {3}{U}{U}
|
||||
Instant
|
||||
You may exile a blue card from your hand rather than pay Misdirection's mana cost.
|
||||
Change the target of target spell with a single target.
|
||||
*/
|
||||
addCard(Zone.HAND, playerB, "Misdirection");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Custodian of the Trove", 1); // 4/3
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Island", 5);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Public Execution", "Pillarfield Ox");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Misdirection", "Public Execution", "Public Execution");
|
||||
addTarget(playerB, "Custodian of the Trove");
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, "Public Execution", 1);
|
||||
assertGraveyardCount(playerB, "Misdirection", 1);
|
||||
|
||||
assertGraveyardCount(playerB, "Custodian of the Trove",1);
|
||||
assertPermanentCount(playerB, "Pillarfield Ox", 1);
|
||||
assertPowerToughness(playerB, "Pillarfield Ox", 0, 4);
|
||||
|
||||
}
|
||||
|
||||
// check to change target permanent creature not legal to to a creature the your opponent controls
|
||||
@Test
|
||||
public void testChangePublicExecution2() {
|
||||
// Destroy target creature an opponent controls. Each other creature that player controls gets -2/-0 until end of turn.
|
||||
addCard(Zone.HAND, playerA, "Public Execution");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Keeper of the Lens", 1);
|
||||
/*
|
||||
Misdirection {3}{U}{U}
|
||||
Instant
|
||||
You may exile a blue card from your hand rather than pay Misdirection's mana cost.
|
||||
Change the target of target spell with a single target.
|
||||
*/
|
||||
addCard(Zone.HAND, playerB, "Misdirection");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Custodian of the Trove", 1); // 4/3
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Island", 5);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Public Execution", "Custodian of the Trove");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Misdirection", "Public Execution", "Public Execution");
|
||||
addTarget(playerB, "Keeper of the Lens");
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, "Public Execution", 1);
|
||||
assertGraveyardCount(playerB, "Misdirection", 1);
|
||||
assertPermanentCount(playerA, "Keeper of the Lens", 1);
|
||||
|
||||
assertPermanentCount(playerB, "Pillarfield Ox", 1);
|
||||
assertPowerToughness(playerB, "Pillarfield Ox", 0, 4);
|
||||
|
||||
assertGraveyardCount(playerB, "Custodian of the Trove",1);
|
||||
|
||||
}
|
||||
}
|
|
@ -65,4 +65,32 @@ public class SpellCastTriggerTest extends CardTestPlayerBase {
|
|||
assertPowerToughness(playerA, "Sunscorch Regent", 5, 4);
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Monastery Mentor triggers are causing a "rollback" error.
|
||||
*/
|
||||
@Test
|
||||
public void testMonasteryMentor() {
|
||||
// Prowess (Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn.)
|
||||
// Whenever you cast a noncreature spell, put a 1/1 white Monk creature token with prowess onto the battlefield.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Monastery Mentor", 1);
|
||||
|
||||
addCard(Zone.HAND, playerA, "Lightning Bolt", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20);
|
||||
assertLife(playerB, 14);
|
||||
|
||||
assertGraveyardCount(playerA, "Lightning Bolt", 2);
|
||||
assertPermanentCount(playerA, "Monk", 2);
|
||||
assertPowerToughness(playerA, "Monk", 2, 2);
|
||||
assertPowerToughness(playerA, "Monk", 1, 1);
|
||||
|
||||
assertPowerToughness(playerA, "Monastery Mentor", 4, 4);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -389,6 +389,10 @@ public class TestPlayer extends ComputerPlayer {
|
|||
@Override
|
||||
public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) {
|
||||
if (!targets.isEmpty()) {
|
||||
UUID abilityControllerId = playerId;
|
||||
if (target.getTargetController() != null && target.getAbilityController() != null) {
|
||||
abilityControllerId = target.getAbilityController();
|
||||
}
|
||||
if ((target instanceof TargetPermanent) || (target instanceof TargetPermanentOrPlayer)) {
|
||||
for (String targetDefinition: targets) {
|
||||
String[] targetList = targetDefinition.split("\\^");
|
||||
|
@ -408,7 +412,7 @@ public class TestPlayer extends ComputerPlayer {
|
|||
}
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents((FilterPermanent)target.getFilter(), game)) {
|
||||
if (permanent.getName().equals(targetName) || (permanent.getName()+"-"+permanent.getExpansionSetCode()).equals(targetName)) {
|
||||
if (((TargetPermanent)target).canTarget(source == null ? this.getId(): source.getControllerId(), permanent.getId(), source, game) && !target.getTargets().contains(permanent.getId())) {
|
||||
if (((TargetPermanent)target).canTarget(abilityControllerId, permanent.getId(), source, game) && !target.getTargets().contains(permanent.getId())) {
|
||||
if ((permanent.isCopy() && !originOnly) || (!permanent.isCopy() && !copyOnly )) {
|
||||
target.add(permanent.getId(), game);
|
||||
targetFound = true;
|
||||
|
@ -446,7 +450,7 @@ public class TestPlayer extends ComputerPlayer {
|
|||
for (String targetName: targetList) {
|
||||
for (Card card: this.getHand().getCards(((TargetCardInHand)target).getFilter(), game)) {
|
||||
if (card.getName().equals(targetName) || (card.getName()+"-"+card.getExpansionSetCode()).equals(targetName)) {
|
||||
if (((TargetCardInHand)target).canTarget(this.getId(), card.getId(), source, game) && !target.getTargets().contains(card.getId())) {
|
||||
if (((TargetCardInHand)target).canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) {
|
||||
target.add(card.getId(), game);
|
||||
targetFound = true;
|
||||
break;
|
||||
|
|
|
@ -537,4 +537,6 @@ public interface Ability extends Controllable, Serializable {
|
|||
*/
|
||||
|
||||
MageObject getSourceObjectIfItStillExists(Game game);
|
||||
|
||||
String getTargetDescription(Targets targets, Game game);
|
||||
}
|
||||
|
|
|
@ -1074,6 +1074,7 @@ public abstract class AbilityImpl implements Ability {
|
|||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTargetDescription(Targets targets, Game game) {
|
||||
return getTargetDescriptionForLog(targets, game);
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@ public class ChooseNewTargetsTargetEffect extends OneShotEffect {
|
|||
super(effect);
|
||||
this.forceChange = effect.forceChange;
|
||||
this.onlyOneTarget = effect.onlyOneTarget;
|
||||
this.filterNewTarget = effect.filterNewTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -37,39 +37,33 @@ import mage.Mana;
|
|||
import mage.ObjectColor;
|
||||
import mage.abilities.Abilities;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.costs.AlternativeSourceCosts;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.dynamicvalue.common.StaticValue;
|
||||
import mage.abilities.keyword.BestowAbility;
|
||||
import mage.abilities.keyword.MorphAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.SplitCard;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Rarity;
|
||||
import mage.constants.SpellAbilityType;
|
||||
import mage.constants.Zone;
|
||||
import mage.counters.Counter;
|
||||
import mage.counters.Counters;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.ZoneChangeEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.PermanentCard;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetAmount;
|
||||
import mage.util.GameLog;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class Spell implements StackObject, Card {
|
||||
public class Spell extends StackObjImpl implements Card {
|
||||
|
||||
private final List<Card> spellCards = new ArrayList<>();
|
||||
private final List<SpellAbility> spellAbilities = new ArrayList<>();
|
||||
|
@ -313,213 +307,8 @@ public class Spell implements StackObject, Card {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose new targets for the spell
|
||||
*
|
||||
* @param game
|
||||
* @param playerId Player UUID who changes the targets.
|
||||
* @return
|
||||
*/
|
||||
public boolean chooseNewTargets(Game game, UUID playerId) {
|
||||
return chooseNewTargets(game, playerId, false, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 114.6. Some effects allow a player to change the target(s) of a spell or
|
||||
* ability, and other effects allow a player to choose new targets for a
|
||||
* spell or ability.
|
||||
*
|
||||
* 114.6a If an effect allows a player to "change the
|
||||
* target(s)" of a spell or ability, each target can be changed only to
|
||||
* another legal target. If a target can't be changed to another legal
|
||||
* target, the original target is unchanged, even if the original target is
|
||||
* itself illegal by then. If all the targets aren't changed to other legal
|
||||
* targets, none of them are changed.
|
||||
*
|
||||
* 114.6b If an effect allows a player to "change a target" of a
|
||||
* spell or ability, the process described in rule 114.6a
|
||||
* is followed, except that only one of those targets may be changed
|
||||
* (rather than all of them or none of them).
|
||||
*
|
||||
* 114.6c If an effect allows a
|
||||
* player to "change any targets" of a spell or ability, the process
|
||||
* described in rule 114.6a is followed, except that any number of those
|
||||
* targets may be changed (rather than all of them or none of them).
|
||||
*
|
||||
* 114.6d If an effect allows a player to "choose new targets" for a spell or
|
||||
* ability, the player may leave any number of the targets unchanged, even
|
||||
* if those targets would be illegal. If the player chooses to change some
|
||||
* or all of the targets, the new targets must be legal and must not cause
|
||||
* any unchanged targets to become illegal.
|
||||
*
|
||||
* 114.6e When changing targets or
|
||||
* choosing new targets for a spell or ability, only the final set of
|
||||
* targets is evaluated to determine whether the change is legal.
|
||||
*
|
||||
* Example: Arc Trail is a sorcery that reads "Arc Trail deals 2 damage to
|
||||
* target creature or player and 1 damage to another target creature or
|
||||
* player." The current targets of Arc Trail are Runeclaw Bear and Llanowar
|
||||
* Elves, in that order. You cast Redirect, an instant that reads "You may
|
||||
* choose new targets for target spell," targeting Arc Trail. You can change
|
||||
* the first target to Llanowar Elves and change the second target to
|
||||
* Runeclaw Bear.
|
||||
*
|
||||
* 114.7. Modal spells and abilities may have different targeting
|
||||
* requirements for each mode. An effect that allows a player to change the
|
||||
* target(s) of a modal spell or ability, or to choose new targets for a
|
||||
* modal spell or ability, doesn't allow that player to change its mode.
|
||||
* (See rule 700.2.)
|
||||
*
|
||||
* 706.10c Some effects copy a spell or ability and state that its
|
||||
* controller may choose new targets for the copy. The player may leave any
|
||||
* number of the targets unchanged, even if those targets would be illegal.
|
||||
* If the player chooses to change some or all of the targets, the new
|
||||
* targets must be legal. Once the player has decided what the copy's
|
||||
* targets will be, the copy is put onto the stack with those targets.
|
||||
*
|
||||
* @param game
|
||||
* @param playerId - player that can/has to change the taregt of the spell
|
||||
* @param forceChange - does only work for targets with maximum of one targetId
|
||||
* @param onlyOneTarget - 114.6b one target must be changed to another target
|
||||
* @param filterNewTarget restriction for the new target, if null nothing is cheched
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
StringBuilder newTargetDescription = new StringBuilder();
|
||||
// Fused split spells or spells where "Splice on Arcane" was used can have more than one ability
|
||||
for (SpellAbility spellAbility : spellAbilities) {
|
||||
// Some spells can have more than one mode
|
||||
for (UUID modeId : spellAbility.getModes().getSelectedModes()) {
|
||||
Mode mode = spellAbility.getModes().get(modeId);
|
||||
for (Target target : mode.getTargets()) {
|
||||
Target newTarget = chooseNewTarget(player, spellAbility, mode, target, forceChange, filterNewTarget, game);
|
||||
// clear the old target and copy all targets from new target
|
||||
target.clearChosen();
|
||||
for (UUID targetId : newTarget.getTargets()) {
|
||||
target.addTarget(targetId, newTarget.getTargetAmount(targetId), spellAbility, game, false);
|
||||
}
|
||||
|
||||
}
|
||||
newTargetDescription.append(getSpellAbility().getTargetDescription(mode.getTargets(), game));
|
||||
}
|
||||
|
||||
}
|
||||
if (newTargetDescription.length() > 0 && !game.isSimulation()) {
|
||||
game.informPlayers(this.getName() + " is now " + newTargetDescription.toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the change of one target instance of a mode
|
||||
*
|
||||
* @param player - player that can choose the new target
|
||||
* @param spellAbility
|
||||
* @param mode
|
||||
* @param target
|
||||
* @param forceChange
|
||||
* @param game
|
||||
* @return
|
||||
*/
|
||||
private Target chooseNewTarget(Player player, SpellAbility spellAbility, Mode mode, Target target, boolean forceChange, FilterPermanent filterNewTarget, Game game) {
|
||||
Target newTarget = target.copy();
|
||||
newTarget.clearChosen();
|
||||
for (UUID targetId : target.getTargets()) {
|
||||
String targetNames = getNamesOftargets(targetId, game);
|
||||
// change the target?
|
||||
if (targetNames != null
|
||||
&& (forceChange || player.chooseUse(mode.getEffects().get(0).getOutcome(), "Change this target: " + targetNames + "?", game))) {
|
||||
// choose exactly one other target
|
||||
if (forceChange && target.possibleTargets(this.getSourceId(), getControllerId(), game).size() > 1) { // controller of spell must be used (e.g. TargetOpponent)
|
||||
int iteration = 0;
|
||||
do {
|
||||
if (iteration > 0 && !game.isSimulation()) {
|
||||
game.informPlayer(player, "You may only select exactly one target that must be different from the origin target!");
|
||||
}
|
||||
iteration++;
|
||||
newTarget.clearChosen();
|
||||
// TODO: Distinction between "spell controller" and "player that can change the target" - here player is used for both
|
||||
newTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), player.getId(), spellAbility, game);
|
||||
// check target restriction
|
||||
if (newTarget.getFirstTarget() != null && filterNewTarget != null) {
|
||||
Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget());
|
||||
if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) {
|
||||
game.informPlayer(player, "Target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")");
|
||||
newTarget.clearChosen();
|
||||
}
|
||||
}
|
||||
} while (player.isInGame() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1));
|
||||
// choose a new target
|
||||
} else {
|
||||
// build a target definition with exactly one possible target to select that replaces old target
|
||||
Target tempTarget = target.copy();
|
||||
if (target instanceof TargetAmount) {
|
||||
((TargetAmount)tempTarget).setAmountDefinition(new StaticValue(target.getTargetAmount(targetId)));
|
||||
}
|
||||
tempTarget.setMinNumberOfTargets(1);
|
||||
tempTarget.setMaxNumberOfTargets(1);
|
||||
boolean again;
|
||||
do {
|
||||
again = false;
|
||||
tempTarget.clearChosen();
|
||||
if (!tempTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), player.getId(), spellAbility, game)) {
|
||||
if (player.chooseUse(Outcome.Benefit, "No target object selected. Reset to original target?", game)) {
|
||||
// use previous target no target was selected
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), spellAbility, game, false);
|
||||
} else {
|
||||
again = true;
|
||||
}
|
||||
} else {
|
||||
// if possible add the alternate Target - it may not be included in the old definition nor in the already selected targets of the new definition
|
||||
if (newTarget.getTargets().contains(tempTarget.getFirstTarget()) || target.getTargets().contains(tempTarget.getFirstTarget())) {
|
||||
if(player.isHuman()) {
|
||||
game.informPlayer(player, "This target was already selected from origin spell. You can only keep this target!");
|
||||
again = true;
|
||||
} else {
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), spellAbility, game, false);
|
||||
}
|
||||
} else if (newTarget.getFirstTarget() != null && filterNewTarget != null) {
|
||||
Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget());
|
||||
if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) {
|
||||
game.informPlayer(player, "This target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")");
|
||||
again = true;
|
||||
}
|
||||
} else {
|
||||
// valid target was selected, add it to the new target definition
|
||||
newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(targetId), spellAbility, game, false);
|
||||
}
|
||||
}
|
||||
} while (again && player.isInGame());
|
||||
}
|
||||
}
|
||||
// keep the target
|
||||
else {
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), spellAbility, game, false);
|
||||
}
|
||||
}
|
||||
return newTarget;
|
||||
}
|
||||
|
||||
|
||||
private String getNamesOftargets(UUID targetId, Game game) {
|
||||
MageObject object = game.getObject(targetId);
|
||||
String name = null;
|
||||
if (object == null) {
|
||||
Player targetPlayer = game.getPlayer(targetId);
|
||||
if (targetPlayer != null) {
|
||||
name = targetPlayer.getLogName();
|
||||
}
|
||||
} else {
|
||||
name = object.getName();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void counter(UUID sourceId, Game game) {
|
||||
|
|
|
@ -54,15 +54,10 @@ import mage.target.Targets;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import mage.abilities.dynamicvalue.common.StaticValue;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.AbilityWord;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetAmount;
|
||||
import mage.util.GameLog;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
|
@ -70,7 +65,7 @@ import mage.watchers.Watcher;
|
|||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class StackAbility implements StackObject, Ability {
|
||||
public class StackAbility extends StackObjImpl implements Ability {
|
||||
|
||||
private static List<CardType> emptyCardType = new ArrayList<>();
|
||||
private static List<String> emptyString = new ArrayList<>();
|
||||
|
@ -551,197 +546,9 @@ public class StackAbility implements StackObject, Ability {
|
|||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 114.6. Some effects allow a player to change the target(s) of a spell or
|
||||
* ability, and other effects allow a player to choose new targets for a
|
||||
* spell or ability.
|
||||
*
|
||||
* 114.6a If an effect allows a player to "change the
|
||||
* target(s)" of a spell or ability, each target can be changed only to
|
||||
* another legal target. If a target can't be changed to another legal
|
||||
* target, the original target is unchanged, even if the original target is
|
||||
* itself illegal by then. If all the targets aren't changed to other legal
|
||||
* targets, none of them are changed.
|
||||
*
|
||||
* 114.6b If an effect allows a player to "change a target" of a
|
||||
* spell or ability, the process described in rule 114.6a
|
||||
* is followed, except that only one of those targets may be changed
|
||||
* (rather than all of them or none of them).
|
||||
*
|
||||
* 114.6c If an effect allows a
|
||||
* player to "change any targets" of a spell or ability, the process
|
||||
* described in rule 114.6a is followed, except that any number of those
|
||||
* targets may be changed (rather than all of them or none of them).
|
||||
*
|
||||
* 114.6d If an effect allows a player to "choose new targets" for a spell or
|
||||
* ability, the player may leave any number of the targets unchanged, even
|
||||
* if those targets would be illegal. If the player chooses to change some
|
||||
* or all of the targets, the new targets must be legal and must not cause
|
||||
* any unchanged targets to become illegal.
|
||||
*
|
||||
* 114.6e When changing targets or
|
||||
* choosing new targets for a spell or ability, only the final set of
|
||||
* targets is evaluated to determine whether the change is legal.
|
||||
*
|
||||
* Example: Arc Trail is a sorcery that reads "Arc Trail deals 2 damage to
|
||||
* target creature or player and 1 damage to another target creature or
|
||||
* player." The current targets of Arc Trail are Runeclaw Bear and Llanowar
|
||||
* Elves, in that order. You cast Redirect, an instant that reads "You may
|
||||
* choose new targets for target spell," targeting Arc Trail. You can change
|
||||
* the first target to Llanowar Elves and change the second target to
|
||||
* Runeclaw Bear.
|
||||
*
|
||||
* 114.7. Modal spells and abilities may have different targeting
|
||||
* requirements for each mode. An effect that allows a player to change the
|
||||
* target(s) of a modal spell or ability, or to choose new targets for a
|
||||
* modal spell or ability, doesn't allow that player to change its mode.
|
||||
* (See rule 700.2.)
|
||||
*
|
||||
* 706.10c Some effects copy a spell or ability and state that its
|
||||
* controller may choose new targets for the copy. The player may leave any
|
||||
* number of the targets unchanged, even if those targets would be illegal.
|
||||
* If the player chooses to change some or all of the targets, the new
|
||||
* targets must be legal. Once the player has decided what the copy's
|
||||
* targets will be, the copy is put onto the stack with those targets.
|
||||
*
|
||||
* @param game
|
||||
* @param playerId - player that can/has to change the target of the ability
|
||||
* @param forceChange - does only work for targets with maximum of one targetId
|
||||
* @param onlyOneTarget - 114.6b one target must be changed to another target
|
||||
* @param filterNewTarget restriction for the new target, if null nothing is cheched
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
StringBuilder newTargetDescription = new StringBuilder();
|
||||
// Some abilities can have more than one mode
|
||||
for (UUID modeId : ability.getModes().getSelectedModes()) {
|
||||
Mode mode = ability.getModes().get(modeId);
|
||||
for (Target target : mode.getTargets()) {
|
||||
Target newTarget = chooseNewTarget(player, getStackAbility(), mode, target, forceChange, filterNewTarget, game);
|
||||
// clear the old target and copy all targets from new target
|
||||
target.clearChosen();
|
||||
for (UUID targetId : newTarget.getTargets()) {
|
||||
target.addTarget(targetId, newTarget.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
|
||||
}
|
||||
newTargetDescription.append(((AbilityImpl)ability).getTargetDescription(mode.getTargets(), game));
|
||||
}
|
||||
|
||||
if (newTargetDescription.length() > 0 && !game.isSimulation()) {
|
||||
game.informPlayers(this.getName() + " is now " + newTargetDescription.toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
public String getTargetDescription(Targets targets, Game game) {
|
||||
return getAbilities().get(0).getTargetDescription(targets, game);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the change of one target instance of a mode
|
||||
*
|
||||
* @param player - player that can choose the new target
|
||||
* @param ability
|
||||
* @param mode
|
||||
* @param target
|
||||
* @param forceChange
|
||||
* @param game
|
||||
* @return
|
||||
*/
|
||||
private Target chooseNewTarget(Player player, Ability ability, Mode mode, Target target, boolean forceChange, FilterPermanent filterNewTarget, Game game) {
|
||||
Target newTarget = target.copy();
|
||||
newTarget.clearChosen();
|
||||
for (UUID targetId : target.getTargets()) {
|
||||
String targetNames = getNamesOfTargets(targetId, game);
|
||||
// change the target?
|
||||
if (targetNames != null
|
||||
&& (forceChange || player.chooseUse(mode.getEffects().get(0).getOutcome(), "Change this target: " + targetNames + "?", game))) {
|
||||
// choose exactly one other target
|
||||
if (forceChange && target.possibleTargets(this.getSourceId(), getControllerId(), game).size() > 1) { // controller of ability must be used (e.g. TargetOpponent)
|
||||
int iteration = 0;
|
||||
do {
|
||||
if (iteration > 0 && !game.isSimulation()) {
|
||||
game.informPlayer(player, "You may only select exactly one target that must be different from the origin target!");
|
||||
}
|
||||
iteration++;
|
||||
newTarget.clearChosen();
|
||||
// TODO: Distinction between "ability controller" and "player that can change the target" - here player is used for both
|
||||
newTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), player.getId(), ability, game);
|
||||
// check target restriction
|
||||
if (newTarget.getFirstTarget() != null && filterNewTarget != null) {
|
||||
Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget());
|
||||
if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) {
|
||||
game.informPlayer(player, "Target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")");
|
||||
newTarget.clearChosen();
|
||||
}
|
||||
}
|
||||
} while (player.isInGame() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1));
|
||||
// choose a new target
|
||||
} else {
|
||||
// build a target definition with exactly one possible target to select that replaces old target
|
||||
Target tempTarget = target.copy();
|
||||
if (target instanceof TargetAmount) {
|
||||
((TargetAmount)tempTarget).setAmountDefinition(new StaticValue(target.getTargetAmount(targetId)));
|
||||
}
|
||||
tempTarget.setMinNumberOfTargets(1);
|
||||
tempTarget.setMaxNumberOfTargets(1);
|
||||
boolean again;
|
||||
do {
|
||||
again = false;
|
||||
tempTarget.clearChosen();
|
||||
if (!tempTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), player.getId(), ability, game)) {
|
||||
if (player.chooseUse(Outcome.Benefit, "No target object selected. Reset to original target?", game)) {
|
||||
// use previous target no target was selected
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false);
|
||||
} else {
|
||||
again = true;
|
||||
}
|
||||
} else {
|
||||
// if possible add the alternate Target - it may not be included in the old definition nor in the already selected targets of the new definition
|
||||
if (newTarget.getTargets().contains(tempTarget.getFirstTarget()) || target.getTargets().contains(tempTarget.getFirstTarget())) {
|
||||
if (player.isHuman()) {
|
||||
game.informPlayer(player, "This target was already selected from origin ability. You can only keep this target!");
|
||||
again = true;
|
||||
} else {
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
} else if (newTarget.getFirstTarget() != null && filterNewTarget != null) {
|
||||
Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget());
|
||||
if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) {
|
||||
game.informPlayer(player, "This target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")");
|
||||
again = true;
|
||||
}
|
||||
} else {
|
||||
// valid target was selected, add it to the new target definition
|
||||
newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
}
|
||||
} while (again && player.isInGame());
|
||||
}
|
||||
}
|
||||
// keep the target
|
||||
else {
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
}
|
||||
return newTarget;
|
||||
}
|
||||
|
||||
|
||||
private String getNamesOfTargets(UUID targetId, Game game) {
|
||||
MageObject object = game.getObject(targetId);
|
||||
String targetNames = null;
|
||||
if (object == null) {
|
||||
Player targetPlayer = game.getPlayer(targetId);
|
||||
if (targetPlayer != null) {
|
||||
targetNames = targetPlayer.getLogName();
|
||||
}
|
||||
} else {
|
||||
targetNames = object.getName();
|
||||
}
|
||||
return targetNames;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
260
Mage/src/mage/game/stack/StackObjImpl.java
Normal file
260
Mage/src/mage/game/stack/StackObjImpl.java
Normal file
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package mage.game.stack;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Abilities;
|
||||
import mage.abilities.AbilitiesImpl;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.dynamicvalue.common.StaticValue;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetAmount;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public abstract class StackObjImpl implements StackObject {
|
||||
|
||||
/**
|
||||
* Choose new targets for a stack Object
|
||||
*
|
||||
* @param game
|
||||
* @param playerId Player UUID who changes the targets.
|
||||
* @return
|
||||
*/
|
||||
public boolean chooseNewTargets(Game game, UUID playerId) {
|
||||
return chooseNewTargets(game, playerId, false, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 114.6. Some effects allow a player to change the target(s) of a spell or
|
||||
* ability, and other effects allow a player to choose new targets for a
|
||||
* spell or ability.
|
||||
*
|
||||
* 114.6a If an effect allows a player to "change the
|
||||
* target(s)" of a spell or ability, each target can be changed only to
|
||||
* another legal target. If a target can't be changed to another legal
|
||||
* target, the original target is unchanged, even if the original target is
|
||||
* itself illegal by then. If all the targets aren't changed to other legal
|
||||
* targets, none of them are changed.
|
||||
*
|
||||
* 114.6b If an effect allows a player to "change a target" of a
|
||||
* spell or ability, the process described in rule 114.6a
|
||||
* is followed, except that only one of those targets may be changed
|
||||
* (rather than all of them or none of them).
|
||||
*
|
||||
* 114.6c If an effect allows a
|
||||
* player to "change any targets" of a spell or ability, the process
|
||||
* described in rule 114.6a is followed, except that any number of those
|
||||
* targets may be changed (rather than all of them or none of them).
|
||||
*
|
||||
* 114.6d If an effect allows a player to "choose new targets" for a spell or
|
||||
* ability, the player may leave any number of the targets unchanged, even
|
||||
* if those targets would be illegal. If the player chooses to change some
|
||||
* or all of the targets, the new targets must be legal and must not cause
|
||||
* any unchanged targets to become illegal.
|
||||
*
|
||||
* 114.6e When changing targets or
|
||||
* choosing new targets for a spell or ability, only the final set of
|
||||
* targets is evaluated to determine whether the change is legal.
|
||||
*
|
||||
* Example: Arc Trail is a sorcery that reads "Arc Trail deals 2 damage to
|
||||
* target creature or player and 1 damage to another target creature or
|
||||
* player." The current targets of Arc Trail are Runeclaw Bear and Llanowar
|
||||
* Elves, in that order. You cast Redirect, an instant that reads "You may
|
||||
* choose new targets for target spell," targeting Arc Trail. You can change
|
||||
* the first target to Llanowar Elves and change the second target to
|
||||
* Runeclaw Bear.
|
||||
*
|
||||
* 114.7. Modal spells and abilities may have different targeting
|
||||
* requirements for each mode. An effect that allows a player to change the
|
||||
* target(s) of a modal spell or ability, or to choose new targets for a
|
||||
* modal spell or ability, doesn't allow that player to change its mode.
|
||||
* (See rule 700.2.)
|
||||
*
|
||||
* 706.10c Some effects copy a spell or ability and state that its
|
||||
* controller may choose new targets for the copy. The player may leave any
|
||||
* number of the targets unchanged, even if those targets would be illegal.
|
||||
* If the player chooses to change some or all of the targets, the new
|
||||
* targets must be legal. Once the player has decided what the copy's
|
||||
* targets will be, the copy is put onto the stack with those targets.
|
||||
*
|
||||
* @param game
|
||||
* @param targetControllerId - player that can/has to change the target of the spell
|
||||
* @param forceChange - does only work for targets with maximum of one targetId
|
||||
* @param onlyOneTarget - 114.6b one target must be changed to another target
|
||||
* @param filterNewTarget restriction for the new target, if null nothing is cheched
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean chooseNewTargets(Game game, UUID targetControllerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget) {
|
||||
Player targetController = game.getPlayer(targetControllerId);
|
||||
if (targetController != null) {
|
||||
StringBuilder newTargetDescription = new StringBuilder();
|
||||
// Fused split spells or spells where "Splice on Arcane" was used can have more than one ability
|
||||
Abilities<Ability> objectAbilities = new AbilitiesImpl<>();
|
||||
if (this instanceof Spell) {
|
||||
objectAbilities.addAll(((Spell)this).getSpellAbilities());
|
||||
} else {
|
||||
objectAbilities.addAll(getAbilities());
|
||||
}
|
||||
for (Ability ability : objectAbilities) {
|
||||
// Some spells can have more than one mode
|
||||
for (UUID modeId : ability.getModes().getSelectedModes()) {
|
||||
Mode mode = ability.getModes().get(modeId);
|
||||
for (Target target : mode.getTargets()) {
|
||||
Target newTarget = chooseNewTarget(targetController, ability, mode, target, forceChange, filterNewTarget, game);
|
||||
// clear the old target and copy all targets from new target
|
||||
target.clearChosen();
|
||||
for (UUID targetId : newTarget.getTargets()) {
|
||||
target.addTarget(targetId, newTarget.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
|
||||
}
|
||||
newTargetDescription.append(ability.getTargetDescription(mode.getTargets(), game));
|
||||
}
|
||||
|
||||
}
|
||||
if (newTargetDescription.length() > 0 && !game.isSimulation()) {
|
||||
game.informPlayers(this.getLogName() + " is now " + newTargetDescription.toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the change of one target instance of a mode
|
||||
*
|
||||
* @param targetController - player that can choose the new target
|
||||
* @param ability
|
||||
* @param mode
|
||||
* @param target
|
||||
* @param forceChange
|
||||
* @param game
|
||||
* @return
|
||||
*/
|
||||
private Target chooseNewTarget(Player targetController, Ability ability, Mode mode, Target target, boolean forceChange, FilterPermanent filterNewTarget, Game game) {
|
||||
Target newTarget = target.copy();
|
||||
if (!targetController.getId().equals(getControllerId())) {
|
||||
newTarget.setTargetController(targetController.getId()); // target controller for the change is different from spell controller
|
||||
newTarget.setAbilityController(getControllerId());
|
||||
}
|
||||
newTarget.clearChosen();
|
||||
for (UUID targetId : target.getTargets()) {
|
||||
String targetNames = getNamesOftargets(targetId, game);
|
||||
// change the target?
|
||||
if (targetNames != null
|
||||
&& (forceChange || targetController.chooseUse(mode.getEffects().get(0).getOutcome(), "Change this target: " + targetNames + "?", game))) {
|
||||
Set<UUID> possibleTargets = target.possibleTargets(this.getSourceId(), getControllerId(), game);
|
||||
// choose exactly one other target - already targeted objects are not counted
|
||||
if (forceChange && possibleTargets != null && possibleTargets.size() > 1) { // controller of spell must be used (e.g. TargetOpponent)
|
||||
int iteration = 0;
|
||||
do {
|
||||
if (iteration > 0 && !game.isSimulation()) {
|
||||
game.informPlayer(targetController, "You may only select exactly one target that must be different from the origin target!");
|
||||
}
|
||||
iteration++;
|
||||
newTarget.clearChosen();
|
||||
|
||||
newTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), getControllerId(), ability, game);
|
||||
// check target restriction
|
||||
if (newTarget.getFirstTarget() != null && filterNewTarget != null) {
|
||||
Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget());
|
||||
if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) {
|
||||
game.informPlayer(targetController, "Target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")");
|
||||
newTarget.clearChosen();
|
||||
}
|
||||
}
|
||||
} while (targetController.isInGame() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1));
|
||||
// choose a new target
|
||||
} else {
|
||||
// build a target definition with exactly one possible target to select that replaces old target
|
||||
Target tempTarget = target.copy();
|
||||
if (target instanceof TargetAmount) {
|
||||
((TargetAmount)tempTarget).setAmountDefinition(new StaticValue(target.getTargetAmount(targetId)));
|
||||
}
|
||||
tempTarget.setMinNumberOfTargets(1);
|
||||
tempTarget.setMaxNumberOfTargets(1);
|
||||
if (!targetController.getId().equals(getControllerId())) {
|
||||
tempTarget.setTargetController(targetController.getId());
|
||||
tempTarget.setAbilityController(getControllerId());
|
||||
}
|
||||
boolean again;
|
||||
do {
|
||||
again = false;
|
||||
tempTarget.clearChosen();
|
||||
if (!tempTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), getControllerId(), ability, game)) {
|
||||
if (targetController.chooseUse(Outcome.Benefit, "No target object selected. Reset to original target?", game)) {
|
||||
// use previous target no target was selected
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false);
|
||||
} else {
|
||||
again = true;
|
||||
}
|
||||
} else {
|
||||
// if possible add the alternate Target - it may not be included in the old definition nor in the already selected targets of the new definition
|
||||
if (newTarget.getTargets().contains(tempTarget.getFirstTarget()) || target.getTargets().contains(tempTarget.getFirstTarget())) {
|
||||
if(targetController.isHuman()) {
|
||||
game.informPlayer(targetController, "This target was already selected from origin spell. You can only keep this target!");
|
||||
again = true;
|
||||
} else {
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
} else if (!target.canTarget(getControllerId(), tempTarget.getFirstTarget(), ability, game)) {
|
||||
if(targetController.isHuman()) {
|
||||
game.informPlayer(targetController, "This target is not valid!");
|
||||
again = true;
|
||||
} else {
|
||||
// keep the old
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
} else if (newTarget.getFirstTarget() != null && filterNewTarget != null) {
|
||||
Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget());
|
||||
if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) {
|
||||
game.informPlayer(targetController, "This target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")");
|
||||
again = true;
|
||||
}
|
||||
} else {
|
||||
// valid target was selected, add it to the new target definition
|
||||
newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
}
|
||||
} while (again && targetController.isInGame());
|
||||
}
|
||||
}
|
||||
// keep the target
|
||||
else {
|
||||
newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false);
|
||||
}
|
||||
}
|
||||
return newTarget;
|
||||
}
|
||||
|
||||
|
||||
private String getNamesOftargets(UUID targetId, Game game) {
|
||||
MageObject object = game.getObject(targetId);
|
||||
String name = null;
|
||||
if (object == null) {
|
||||
Player targetPlayer = game.getPlayer(targetId);
|
||||
if (targetPlayer != null) {
|
||||
name = targetPlayer.getLogName();
|
||||
}
|
||||
} else {
|
||||
name = object.getName();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
|
@ -130,7 +130,6 @@ import mage.target.common.TargetCardInLibrary;
|
|||
import mage.target.common.TargetDiscard;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.GameLog;
|
||||
import mage.watchers.common.BloodthirstWatcher;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
|
@ -690,7 +689,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
@Override
|
||||
public Cards discard(int amount, boolean random, Ability source, Game game) {
|
||||
Cards discardedCards = new CardsImpl();
|
||||
if (amount >= this.getHand().size()) {
|
||||
if (this.getHand().size() == 1) {
|
||||
discardedCards.addAll(this.getHand());
|
||||
while (this.getHand().size() > 0) {
|
||||
discard(this.getHand().get(this.getHand().iterator().next(), game), source, game);
|
||||
|
|
|
@ -38,6 +38,7 @@ import java.io.Serializable;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.players.Player;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -108,4 +109,7 @@ public interface Target extends Serializable {
|
|||
// some targets are choosen from players that are not the controller of the ability (e.g. Pandemonium)
|
||||
void setTargetController(UUID playerId);
|
||||
UUID getTargetController();
|
||||
void setAbilityController(UUID playerId);
|
||||
UUID getAbilityController();
|
||||
Player getTargetController(Game game, UUID playerId);
|
||||
}
|
||||
|
|
|
@ -117,10 +117,9 @@ public abstract class TargetAmount extends TargetImpl {
|
|||
if (!amountWasSet) {
|
||||
setAmount(source, game);
|
||||
}
|
||||
Player player = game.getPlayer(playerId);
|
||||
chosen = remainingAmount == 0;
|
||||
while (remainingAmount > 0) {
|
||||
if (!player.chooseTargetAmount(outcome, this, source, game)) {
|
||||
if (!getTargetController(game, playerId).chooseTargetAmount(outcome, this, source, game)) {
|
||||
return chosen;
|
||||
}
|
||||
chosen = remainingAmount == 0;
|
||||
|
|
|
@ -61,6 +61,7 @@ public abstract class TargetImpl implements Target {
|
|||
protected boolean notTarget = false;
|
||||
protected boolean atRandom = false;
|
||||
protected UUID targetController = null; // if null the ability controller is the targetController
|
||||
protected UUID abilityController = null; // only used if target controller != ability controller
|
||||
|
||||
@Override
|
||||
public abstract TargetImpl copy();
|
||||
|
@ -86,6 +87,7 @@ public abstract class TargetImpl implements Target {
|
|||
this.atRandom = target.atRandom;
|
||||
this.notTarget = target.notTarget;
|
||||
this.targetController = target.targetController;
|
||||
this.abilityController = target.abilityController;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -110,6 +112,12 @@ public abstract class TargetImpl implements Target {
|
|||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
String suffix = "";
|
||||
if (targetController != null) {
|
||||
// Hint for the selecting player that the targets must be valid from the point of the ability controller
|
||||
// e.g. select opponent text may be misleading otherwise
|
||||
suffix = " (target controlling!)";
|
||||
}
|
||||
if (getMaxNumberOfTargets() != 1) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Select ").append(targetName);
|
||||
|
@ -118,12 +126,13 @@ public abstract class TargetImpl implements Target {
|
|||
} else {
|
||||
sb.append(" (").append(targets.size()).append(")");
|
||||
}
|
||||
sb.append(suffix);
|
||||
return sb.toString();
|
||||
}
|
||||
if (targetName.startsWith("another") || targetName.startsWith("a ") || targetName.startsWith("an ")) {
|
||||
return "Select " + targetName;
|
||||
return "Select " + targetName + suffix;
|
||||
}
|
||||
return "Select a " + targetName;
|
||||
return "Select a " + targetName + suffix;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -298,7 +307,6 @@ public abstract class TargetImpl implements Target {
|
|||
|
||||
@Override
|
||||
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
while (!isChosen() && !doneChosing()) {
|
||||
chosen = targets.size() >= getNumberOfTargets();
|
||||
if (isRandom()) {
|
||||
|
@ -316,7 +324,7 @@ public abstract class TargetImpl implements Target {
|
|||
return chosen;
|
||||
}
|
||||
} else {
|
||||
if (!player.chooseTarget(outcome, this, source, game)) {
|
||||
if (!getTargetController(game, playerId).chooseTarget(outcome, this, source, game)) {
|
||||
return chosen;
|
||||
}
|
||||
}
|
||||
|
@ -433,5 +441,23 @@ public abstract class TargetImpl implements Target {
|
|||
return targetController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAbilityController(UUID playerId) {
|
||||
this.abilityController = playerId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getAbilityController() {
|
||||
return abilityController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getTargetController(Game game, UUID playerId) {
|
||||
if (getTargetController() != null) {
|
||||
return game.getPlayer(getTargetController());
|
||||
} else {
|
||||
return game.getPlayer(playerId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue