mirror of
https://github.com/magefree/mage.git
synced 2025-12-28 22:42:03 -08:00
Merge origin/master
This commit is contained in:
commit
89ce87e16e
57 changed files with 2814 additions and 1336 deletions
|
|
@ -215,14 +215,20 @@ public abstract class AbilityImpl implements Ability {
|
|||
else {
|
||||
game.addEffect((ContinuousEffect) effect, this);
|
||||
}
|
||||
/**
|
||||
* All restrained trigger events are fired now.
|
||||
* To restrain the events is mainly neccessary because of the movement of multiple object at once.
|
||||
* If the event is fired directly as one object moved, other objects are not already in the correct zone
|
||||
* to check for their effects. (e.g. Valakut, the Molten Pinnacle)
|
||||
*/
|
||||
game.getState().handleSimultaneousEvent(game);
|
||||
game.resetShortLivingLKI();
|
||||
/**
|
||||
* game.applyEffects() has to be done at least for every effect that moves cards/permanent between zones,
|
||||
* so Static effects work as intened if dependant from the moved objects zone it is in
|
||||
* Otherwise for example were static abilities with replacement effects deactivated to late
|
||||
* Example: {@link org.mage.test.cards.replacement.DryadMilitantTest#testDiesByDestroy testDiesByDestroy}
|
||||
*/
|
||||
// game.applyEffects();
|
||||
// some effects must be applied before next effect is resolved, because effect is dependend.
|
||||
if (effect.applyEffectsAfter()) {
|
||||
game.applyEffects();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
*/
|
||||
package mage.abilities.common;
|
||||
|
||||
import mage.constants.CardType;
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
|
@ -18,12 +18,19 @@ import mage.game.permanent.Permanent;
|
|||
*/
|
||||
public class BecomesTappedCreatureControlledTriggeredAbility extends TriggeredAbilityImpl{
|
||||
|
||||
FilterControlledCreaturePermanent filter;
|
||||
|
||||
public BecomesTappedCreatureControlledTriggeredAbility(Effect effect, boolean optional) {
|
||||
this(effect, optional, new FilterControlledCreaturePermanent("a creature you control"));
|
||||
}
|
||||
public BecomesTappedCreatureControlledTriggeredAbility(Effect effect, boolean optional, FilterControlledCreaturePermanent filter) {
|
||||
super(Zone.BATTLEFIELD, effect, optional);
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public BecomesTappedCreatureControlledTriggeredAbility(final BecomesTappedCreatureControlledTriggeredAbility ability) {
|
||||
super(ability);
|
||||
this.filter = ability.filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -39,15 +46,11 @@ public class BecomesTappedCreatureControlledTriggeredAbility extends TriggeredAb
|
|||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
Permanent permanent = game.getPermanent(event.getTargetId());
|
||||
if (permanent != null && permanent.getControllerId().equals(this.controllerId)
|
||||
&& permanent.getCardType().contains(CardType.CREATURE)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return permanent != null && filter.match(permanent, getSourceId(), getControllerId(), game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "When a creature you control becomes tapped, " + super.getRule();
|
||||
return "When " + filter.getMessage() + " becomes tapped, " + super.getRule();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,8 +32,10 @@ import mage.constants.Outcome;
|
|||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.filter.Filter;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.game.stack.StackObject;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
|
|
@ -43,21 +45,27 @@ public class ChooseNewTargetsTargetEffect extends OneShotEffect {
|
|||
|
||||
private boolean forceChange;
|
||||
private boolean onlyOneTarget;
|
||||
|
||||
private FilterPermanent filterNewTarget;
|
||||
|
||||
public ChooseNewTargetsTargetEffect() {
|
||||
this(false, false);
|
||||
}
|
||||
public ChooseNewTargetsTargetEffect(boolean forceChange, boolean onlyOneTarget) {
|
||||
this(forceChange, onlyOneTarget, null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param forceChange forces the user to choose another target (only targets with maxtargets = 1 supported)
|
||||
* @param onlyOneTarget only one target can be selected for the change
|
||||
* @param filterNewTarget restriction to the new target
|
||||
*/
|
||||
|
||||
public ChooseNewTargetsTargetEffect(boolean forceChange, boolean onlyOneTarget) {
|
||||
public ChooseNewTargetsTargetEffect(boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget) {
|
||||
super(Outcome.Benefit);
|
||||
this.forceChange = forceChange;
|
||||
this.onlyOneTarget = onlyOneTarget;
|
||||
this.filterNewTarget = filterNewTarget;
|
||||
}
|
||||
|
||||
public ChooseNewTargetsTargetEffect(final ChooseNewTargetsTargetEffect effect) {
|
||||
|
|
@ -68,9 +76,9 @@ public class ChooseNewTargetsTargetEffect extends OneShotEffect {
|
|||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Spell spell = game.getStack().getSpell(source.getFirstTarget());
|
||||
if (spell != null) {
|
||||
return spell.chooseNewTargets(game, source.getControllerId(), forceChange, onlyOneTarget);
|
||||
StackObject stackObject = game.getStack().getStackObject(source.getFirstTarget());
|
||||
if (stackObject != null) {
|
||||
return stackObject.chooseNewTargets(game, source.getControllerId(), forceChange, onlyOneTarget, filterNewTarget);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -82,6 +90,9 @@ public class ChooseNewTargetsTargetEffect extends OneShotEffect {
|
|||
|
||||
@Override
|
||||
public String getText(Mode mode) {
|
||||
if (staticText != null && !staticText.isEmpty()) {
|
||||
return staticText;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (forceChange) {
|
||||
sb.append("change the target of target ");
|
||||
|
|
|
|||
|
|
@ -95,9 +95,10 @@ public class AddCountersTargetEffect extends OneShotEffect {
|
|||
permanent.addCounters(newCounter, game);
|
||||
int numberAdded = permanent.getCounters().getCount(counter.getName()) - before;
|
||||
affectedTargets ++;
|
||||
if (!game.isSimulation())
|
||||
if (!game.isSimulation()) {
|
||||
game.informPlayers(sourceObject.getLogName() +": "+ controller.getName()+ " puts " +
|
||||
numberAdded + " " + counter.getName().toLowerCase() + " counter on " + permanent.getLogName());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Player player = game.getPlayer(uuid);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class ShadowAbility extends EvasionAbility implements MageSingleton {
|
|||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Shadow";
|
||||
return "Shadow <i>(This creature can block or be blocked by only creatures with shadow.)</i>";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -53,10 +53,7 @@ class ShadowEffect extends RestrictionEffect implements MageSingleton {
|
|||
|
||||
@Override
|
||||
public boolean applies(Permanent permanent, Ability source, Game game) {
|
||||
if (permanent.getAbilities().containsKey(ShadowAbility.getInstance().getId())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return permanent.getAbilities().containsKey(ShadowAbility.getInstance().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -66,11 +63,8 @@ class ShadowEffect extends RestrictionEffect implements MageSingleton {
|
|||
|
||||
@Override
|
||||
public boolean canBeBlocked(Permanent attacker, Permanent blocker, Ability source, Game game) {
|
||||
if (blocker.getAbilities().containsKey(ShadowAbility.getInstance().getId())
|
||||
|| game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_SHADOW, source, blocker.getControllerId(), game)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return blocker.getAbilities().containsKey(ShadowAbility.getInstance().getId())
|
||||
|| game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_SHADOW, source, blocker.getControllerId(), game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -60,7 +60,7 @@ public enum CardRepository {
|
|||
// raise this if db structure was changed
|
||||
private static final long CARD_DB_VERSION = 37;
|
||||
// raise this if new cards were added to the server
|
||||
private static final long CARD_CONTENT_VERSION = 12;
|
||||
private static final long CARD_CONTENT_VERSION = 13;
|
||||
|
||||
private final Random random = new Random();
|
||||
private Dao<CardInfo, Object> cardDao;
|
||||
|
|
|
|||
|
|
@ -177,7 +177,16 @@ public class GameEvent {
|
|||
ADD_COUNTER, COUNTER_ADDED,
|
||||
ADD_COUNTERS, COUNTERS_ADDED,
|
||||
COUNTER_REMOVED,
|
||||
LOSE_CONTROL, LOST_CONTROL,
|
||||
LOSE_CONTROL,
|
||||
|
||||
/* LOST_CONTROL
|
||||
targetId id of the creature that lost control
|
||||
sourceId id of the creature that lost control
|
||||
playerId player that controlles the creature before
|
||||
amount not used for this event
|
||||
flag not used for this event
|
||||
*/
|
||||
LOST_CONTROL,
|
||||
GAIN_CONTROL, GAINED_CONTROL,
|
||||
CREATE_TOKEN,
|
||||
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ public class PermanentToken extends PermanentImpl {
|
|||
if (!game.replaceEvent(new ZoneChangeEvent(this, sourceId, this.getControllerId(), Zone.BATTLEFIELD, Zone.EXILED))) {
|
||||
game.rememberLKI(objectId, Zone.BATTLEFIELD, this);
|
||||
if (game.getPlayer(controllerId).removeFromBattlefield(this, game)) {
|
||||
game.fireEvent(new ZoneChangeEvent(this, sourceId, this.getControllerId(), Zone.BATTLEFIELD, Zone.EXILED));
|
||||
game.addSimultaneousEvent(new ZoneChangeEvent(this, sourceId, this.getControllerId(), Zone.BATTLEFIELD, Zone.EXILED));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ 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;
|
||||
|
|
@ -81,6 +82,7 @@ public class Spell implements StackObject, Card {
|
|||
private UUID controllerId;
|
||||
private boolean copiedSpell;
|
||||
private boolean faceDown;
|
||||
private boolean countered;
|
||||
|
||||
public Spell(Card card, SpellAbility ability, UUID controllerId, Zone fromZone) {
|
||||
this.card = card;
|
||||
|
|
@ -99,6 +101,7 @@ public class Spell implements StackObject, Card {
|
|||
}
|
||||
this.controllerId = controllerId;
|
||||
this.fromZone = fromZone;
|
||||
this.countered = false;
|
||||
}
|
||||
|
||||
public Spell(final Spell spell) {
|
||||
|
|
@ -198,8 +201,8 @@ public class Spell implements StackObject, Card {
|
|||
result |= spellAbility.resolve(game);
|
||||
}
|
||||
}
|
||||
game.getState().handleSimultaneousEvent(game);
|
||||
game.resetShortLivingLKI();
|
||||
// game.getState().handleSimultaneousEvent(game);
|
||||
// game.resetShortLivingLKI();
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
|
@ -319,7 +322,7 @@ public class Spell implements StackObject, Card {
|
|||
* @return
|
||||
*/
|
||||
public boolean chooseNewTargets(Game game, UUID playerId) {
|
||||
return chooseNewTargets(game, playerId, false, false);
|
||||
return chooseNewTargets(game, playerId, false, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -377,13 +380,13 @@ public class Spell implements StackObject, Card {
|
|||
*
|
||||
* @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 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
|
||||
*/
|
||||
public boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget) {
|
||||
@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();
|
||||
|
|
@ -393,7 +396,7 @@ public class Spell implements StackObject, Card {
|
|||
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, game);
|
||||
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()) {
|
||||
|
|
@ -424,7 +427,7 @@ public class Spell implements StackObject, Card {
|
|||
* @param game
|
||||
* @return
|
||||
*/
|
||||
private Target chooseNewTarget(Player player, SpellAbility spellAbility, Mode mode, Target target, boolean forceChange, Game game) {
|
||||
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()) {
|
||||
|
|
@ -443,6 +446,14 @@ public class Spell implements StackObject, Card {
|
|||
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 {
|
||||
|
|
@ -473,6 +484,12 @@ public class Spell implements StackObject, Card {
|
|||
} 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);
|
||||
|
|
@ -506,6 +523,7 @@ public class Spell implements StackObject, Card {
|
|||
|
||||
@Override
|
||||
public void counter(UUID sourceId, Game game) {
|
||||
this.countered = true;
|
||||
if (!isCopiedSpell()) {
|
||||
card.moveToZone(Zone.GRAVEYARD, sourceId, game, false);
|
||||
}
|
||||
|
|
@ -987,4 +1005,8 @@ public class Spell implements StackObject, Card {
|
|||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
public boolean isCountered() {
|
||||
return countered;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,9 +97,10 @@ public class SpellStack extends ArrayDeque<StackObject> {
|
|||
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER, objectId, sourceId, stackObject.getControllerId()))) {
|
||||
if ( stackObject instanceof Spell ) {
|
||||
game.rememberLKI(objectId, Zone.STACK, (Spell)stackObject);
|
||||
} else {
|
||||
this.remove(stackObject);
|
||||
}
|
||||
this.remove(stackObject);
|
||||
stackObject.counter(sourceId, game); // tries to move to graveyard
|
||||
stackObject.counter(sourceId, game);
|
||||
if (!game.isSimulation())
|
||||
game.informPlayers(counteredObjectName + " is countered by " + sourceObject.getLogName());
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTERED, objectId, sourceId, stackObject.getControllerId()));
|
||||
|
|
|
|||
|
|
@ -54,10 +54,15 @@ 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.watchers.Watcher;
|
||||
|
||||
/**
|
||||
|
|
@ -535,4 +540,197 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.getName();
|
||||
}
|
||||
} else {
|
||||
targetNames = object.getName();
|
||||
}
|
||||
return targetNames;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ package mage.game.stack;
|
|||
import java.util.UUID;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.Controllable;
|
||||
import mage.game.Game;
|
||||
|
||||
|
|
@ -41,6 +42,7 @@ public interface StackObject extends MageObject, Controllable {
|
|||
void counter(UUID sourceId, Game game);
|
||||
Ability getStackAbility();
|
||||
int getConvertedManaCost();
|
||||
boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget);
|
||||
@Override
|
||||
StackObject copy();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,4 +104,8 @@ public interface Target extends Serializable {
|
|||
UUID getFirstTarget();
|
||||
|
||||
Target copy();
|
||||
}
|
||||
|
||||
// some targets are choosen from players that are not the controller of the ability (e.g. Pandemonium)
|
||||
void setTargetController(UUID playerId);
|
||||
UUID getTargetController();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ public abstract class TargetImpl implements Target {
|
|||
// is the target handled as targeted spell/ability (notTarget = true is used for not targeted effects like e.g. sacrifice)
|
||||
protected boolean notTarget = false;
|
||||
protected boolean atRandom = false;
|
||||
protected UUID targetController = null; // if null the ability controller is the targetController
|
||||
|
||||
@Override
|
||||
public abstract TargetImpl copy();
|
||||
|
|
@ -84,6 +85,7 @@ public abstract class TargetImpl implements Target {
|
|||
this.zoneChangeCounters.putAll(target.zoneChangeCounters);
|
||||
this.atRandom = target.atRandom;
|
||||
this.notTarget = target.notTarget;
|
||||
this.targetController = target.targetController;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -421,5 +423,15 @@ public abstract class TargetImpl implements Target {
|
|||
this.atRandom = atRandom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTargetController(UUID playerId) {
|
||||
this.targetController = playerId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getTargetController() {
|
||||
return targetController;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,11 @@ public class Targets extends ArrayList<Target> {
|
|||
}
|
||||
while (!isChosen()) {
|
||||
Target target = this.getUnchosen().get(0);
|
||||
if (!target.chooseTarget(outcome, playerId, source, game)) {
|
||||
UUID targetController = playerId;
|
||||
if (target.getTargetController() != null) { // some targets can have controller different than ability controller
|
||||
targetController = target.getTargetController();
|
||||
}
|
||||
if (!target.chooseTarget(outcome, targetController, source, game)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ public class CardUtil {
|
|||
* @param increaseCount
|
||||
*/
|
||||
public static void increaseCost(Ability ability, int increaseCount) {
|
||||
adjustCost(ability, -increaseCount);
|
||||
adjustAbilityCost(ability, -increaseCount);
|
||||
adjustAlternativeCosts(ability, -increaseCount);
|
||||
}
|
||||
|
||||
|
|
@ -143,7 +143,7 @@ public class CardUtil {
|
|||
* @param reduceCount
|
||||
*/
|
||||
public static void reduceCost(Ability ability, int reduceCount) {
|
||||
adjustCost(ability, reduceCount);
|
||||
adjustAbilityCost(ability, reduceCount);
|
||||
adjustAlternativeCosts(ability, reduceCount);
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ public class CardUtil {
|
|||
* @param reduceCount
|
||||
*/
|
||||
public static void adjustCost(SpellAbility spellAbility, int reduceCount) {
|
||||
CardUtil.adjustCost((Ability) spellAbility, reduceCount);
|
||||
CardUtil.adjustAbilityCost((Ability) spellAbility, reduceCount);
|
||||
adjustAlternativeCosts(spellAbility, reduceCount);
|
||||
}
|
||||
|
||||
|
|
@ -208,7 +208,7 @@ public class CardUtil {
|
|||
* @param ability
|
||||
* @param reduceCount
|
||||
*/
|
||||
private static void adjustCost(Ability ability, int reduceCount) {
|
||||
public static void adjustAbilityCost(Ability ability, int reduceCount) {
|
||||
ManaCosts<ManaCost> adjustedCost = adjustCost(ability.getManaCostsToPay(), reduceCount);
|
||||
ability.getManaCostsToPay().clear();
|
||||
ability.getManaCostsToPay().addAll(adjustedCost);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue