mirror of
https://github.com/magefree/mage.git
synced 2025-12-25 21:12:04 -08:00
- Reworked SourceOnBattlefieldControlUnchangedCondition checking now the LOST_CONTROL event which solves the problem with the old code to not be able to detect all controller changes of layered changeController effects when applied later.
- Simplified and fixed some problems of the handling of the "Until end of your next turn" duration.
- Fixed that some continous effects changed controller but shouldn't dependant from their duration type. Controller chnage will now done duration type dependant.
(that change fixes #6581 in a more general way undoing the effect specific changes of 2e8ece1dbd).
This commit is contained in:
parent
25802dc105
commit
1e36b39434
30 changed files with 534 additions and 469 deletions
|
|
@ -1,31 +1,43 @@
|
|||
|
||||
package mage.abilities.condition.common;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.watchers.common.LostControlWatcher;
|
||||
|
||||
/**
|
||||
* This condition remembers controller on the first apply.
|
||||
* As long as this controller keeps unchanged and the source is
|
||||
* on the battlefield, the condition is true.
|
||||
* This condition checks if ever since first call of the apply method the
|
||||
* controller of the source has changed
|
||||
*
|
||||
* Monitoring the LOST_CONTROL event has the advantage that also all layered
|
||||
* effects can correctly check for controller change because comparing old and
|
||||
* new controller during their apply time does not take into account layered
|
||||
* cahnge control effects that will be applied later.
|
||||
*
|
||||
* This condition needs the LostControlWatcher, so be sure to add it to the card
|
||||
* that uses the condition
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class SourceOnBattlefieldControlUnchangedCondition implements Condition {
|
||||
|
||||
private UUID controllerId;
|
||||
|
||||
private Long checkingSince;
|
||||
private int startingZoneChangeCounter;
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
if (controllerId == null) {
|
||||
controllerId = source.getControllerId();
|
||||
if (checkingSince == null) {
|
||||
checkingSince = System.currentTimeMillis() - 1;
|
||||
startingZoneChangeCounter = game.getState().getZoneChangeCounter(source.getSourceId());
|
||||
}
|
||||
Permanent permanent = game.getBattlefield().getPermanent(source.getSourceId());
|
||||
return (permanent != null && Objects.equals(controllerId, source.getControllerId()));
|
||||
if (game.getState().getZoneChangeCounter(source.getSourceId()) > startingZoneChangeCounter) {
|
||||
return false;
|
||||
}
|
||||
LostControlWatcher watcher = game.getState().getWatcher(LostControlWatcher.class);
|
||||
if (watcher != null) {
|
||||
return checkingSince > watcher.getOrderOfLastLostControl(source.getSourceId());
|
||||
}
|
||||
throw new UnsupportedOperationException("LostControlWatcher not found!");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
package mage.abilities.effects;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.constants.DependencyType;
|
||||
|
|
@ -9,11 +13,6 @@ import mage.constants.SubLayer;
|
|||
import mage.game.Game;
|
||||
import mage.target.targetpointer.TargetPointer;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
|
|
@ -67,8 +66,6 @@ public interface ContinuousEffect extends Effect {
|
|||
|
||||
UUID getStartingController();
|
||||
|
||||
void incYourTurnNumPlayed();
|
||||
|
||||
boolean isYourNextTurn(Game game);
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.effects;
|
||||
|
||||
import java.util.*;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.CompoundAbility;
|
||||
|
|
@ -19,8 +20,6 @@ import mage.game.stack.StackObject;
|
|||
import mage.players.Player;
|
||||
import mage.target.targetpointer.TargetPointer;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||
*/
|
||||
|
|
@ -47,9 +46,9 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
|
|||
protected boolean characterDefining = false;
|
||||
|
||||
// until your next turn or until end of your next turn
|
||||
private UUID startingControllerId; // player to checkss turns (can't different with real controller ability)
|
||||
private boolean startingTurnWasActive;
|
||||
private int yourTurnNumPlayed = 0; // turnes played after effect was created
|
||||
private UUID startingControllerId; // player to check for turn duration (can't different with real controller ability)
|
||||
private boolean startingTurnWasActive; // effect started during related players turn and related players turn was already active
|
||||
private int effectStartingOnTurn = 0; // turn the effect started
|
||||
|
||||
public ContinuousEffectImpl(Duration duration, Outcome outcome) {
|
||||
super(outcome);
|
||||
|
|
@ -79,7 +78,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
|
|||
this.temporary = effect.temporary;
|
||||
this.startingControllerId = effect.startingControllerId;
|
||||
this.startingTurnWasActive = effect.startingTurnWasActive;
|
||||
this.yourTurnNumPlayed = effect.yourTurnNumPlayed;
|
||||
this.effectStartingOnTurn = effect.effectStartingOnTurn;
|
||||
this.dependencyTypes = effect.dependencyTypes;
|
||||
this.dependendToTypes = effect.dependendToTypes;
|
||||
this.characterDefining = effect.characterDefining;
|
||||
|
|
@ -191,23 +190,13 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
|
|||
this.startingControllerId = startingController;
|
||||
this.startingTurnWasActive = activePlayerId != null
|
||||
&& activePlayerId.equals(startingController); // you can't use "game" for active player cause it's called from tests/cheat too
|
||||
this.yourTurnNumPlayed = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incYourTurnNumPlayed() {
|
||||
yourTurnNumPlayed++;
|
||||
this.effectStartingOnTurn = game.getTurnNum();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isYourNextTurn(Game game) {
|
||||
if (this.startingTurnWasActive) {
|
||||
return yourTurnNumPlayed == 1
|
||||
&& game.isActivePlayer(startingControllerId);
|
||||
} else {
|
||||
return yourTurnNumPlayed == 0
|
||||
&& game.isActivePlayer(startingControllerId);
|
||||
}
|
||||
return effectStartingOnTurn < game.getTurnNum()
|
||||
&& game.isActivePlayer(startingControllerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -367,6 +356,9 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
|
|||
/**
|
||||
* Auto-generates dependencies on different effects (what's apply first and
|
||||
* what's apply second)
|
||||
*
|
||||
* @param abilityToGain
|
||||
* @param filterToSearch
|
||||
*/
|
||||
public void generateGainAbilityDependencies(Ability abilityToGain, Filter filterToSearch) {
|
||||
this.addDependencyType(DependencyType.AddingAbility);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
package mage.abilities.effects;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
|
|
@ -26,11 +30,6 @@ import mage.target.common.TargetCardInHand;
|
|||
import mage.util.CardUtil;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
|
|
@ -163,28 +162,17 @@ public class ContinuousEffects implements Serializable {
|
|||
spliceCardEffects.removeInactiveEffects(game);
|
||||
}
|
||||
|
||||
public synchronized void incYourTurnNumPlayed(Game game) {
|
||||
layeredEffects.incYourTurnNumPlayed(game);
|
||||
continuousRuleModifyingEffects.incYourTurnNumPlayed(game);
|
||||
replacementEffects.incYourTurnNumPlayed(game);
|
||||
preventionEffects.incYourTurnNumPlayed(game);
|
||||
requirementEffects.incYourTurnNumPlayed(game);
|
||||
restrictionEffects.incYourTurnNumPlayed(game);
|
||||
for (ContinuousEffectsList asThoughtlist : asThoughEffectsMap.values()) {
|
||||
asThoughtlist.incYourTurnNumPlayed(game);
|
||||
}
|
||||
costModificationEffects.incYourTurnNumPlayed(game);
|
||||
spliceCardEffects.incYourTurnNumPlayed(game);
|
||||
}
|
||||
|
||||
public synchronized List<ContinuousEffect> getLayeredEffects(Game game) {
|
||||
return getLayeredEffects(game, "main");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return effects list ordered by timestamps (timestamps are automaticity generates from new/old lists on same layer)
|
||||
* Return effects list ordered by timestamps (timestamps are automaticity
|
||||
* generates from new/old lists on same layer)
|
||||
*
|
||||
* @param timestampGroupName workaround to fix broken timestamps on effect's add/remove between different layers
|
||||
* @param game
|
||||
* @param timestampGroupName workaround to fix broken timestamps on effect's
|
||||
* add/remove between different layers
|
||||
* @return effects list ordered by timestamp
|
||||
*/
|
||||
public synchronized List<ContinuousEffect> getLayeredEffects(Game game, String timestampGroupName) {
|
||||
|
|
@ -229,8 +217,10 @@ public class ContinuousEffects implements Serializable {
|
|||
* "actual" meaning it becomes turned on that is defined by
|
||||
* Ability.#isInUseableZone(Game, boolean) method in
|
||||
* #getLayeredEffects(Game).
|
||||
* <p>
|
||||
* It must be called with different timestamp group name (otherwise sort order will be changed for add/remove effects, see Urborg and Bloodmoon test)
|
||||
*
|
||||
* It must be called with different timestamp group name (otherwise sort
|
||||
* order will be changed for add/remove effects, see Urborg and Bloodmoon
|
||||
* test)
|
||||
*
|
||||
* @param layerEffects
|
||||
*/
|
||||
|
|
@ -359,7 +349,7 @@ public class ContinuousEffects implements Serializable {
|
|||
}
|
||||
// boolean checkLKI = event.getType().equals(EventType.ZONE_CHANGE) || event.getType().equals(EventType.DESTROYED_PERMANENT);
|
||||
//get all applicable transient Replacement effects
|
||||
for (Iterator<ReplacementEffect> iterator = replacementEffects.iterator(); iterator.hasNext(); ) {
|
||||
for (Iterator<ReplacementEffect> iterator = replacementEffects.iterator(); iterator.hasNext();) {
|
||||
ReplacementEffect effect = iterator.next();
|
||||
if (!effect.checksEventType(event, game)) {
|
||||
continue;
|
||||
|
|
@ -392,7 +382,7 @@ public class ContinuousEffects implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
for (Iterator<PreventionEffect> iterator = preventionEffects.iterator(); iterator.hasNext(); ) {
|
||||
for (Iterator<PreventionEffect> iterator = preventionEffects.iterator(); iterator.hasNext();) {
|
||||
PreventionEffect effect = iterator.next();
|
||||
if (!effect.checksEventType(event, game)) {
|
||||
continue;
|
||||
|
|
@ -768,8 +758,8 @@ public class ContinuousEffects implements Serializable {
|
|||
* @param event
|
||||
* @param targetAbility ability the event is attached to. can be null.
|
||||
* @param game
|
||||
* @param silentMode true if the event does not really happen but it's
|
||||
* checked if the event would be replaced
|
||||
* @param silentMode true if the event does not really happen but it's
|
||||
* checked if the event would be replaced
|
||||
* @return
|
||||
*/
|
||||
public boolean preventedByRuleModification(GameEvent event, Ability targetAbility, Game game, boolean silentMode) {
|
||||
|
|
@ -817,7 +807,7 @@ public class ContinuousEffects implements Serializable {
|
|||
do {
|
||||
Map<ReplacementEffect, Set<Ability>> rEffects = getApplicableReplacementEffects(event, game);
|
||||
// Remove all consumed effects (ability dependant)
|
||||
for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext(); ) {
|
||||
for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext();) {
|
||||
ReplacementEffect entry = it1.next();
|
||||
if (consumed.containsKey(entry.getId()) /*&& !(entry instanceof CommanderReplacementEffect) */) { // 903.9.
|
||||
Set<UUID> consumedAbilitiesIds = consumed.get(entry.getId());
|
||||
|
|
@ -1002,7 +992,7 @@ public class ContinuousEffects implements Serializable {
|
|||
.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> dependentTo.contains(entry.getKey().getId())
|
||||
&& entry.getValue().contains(effect.getId()))
|
||||
&& entry.getValue().contains(effect.getId()))
|
||||
.forEach(entry -> {
|
||||
entry.getValue().remove(effect.getId());
|
||||
dependentTo.remove(entry.getKey().getId());
|
||||
|
|
@ -1036,7 +1026,7 @@ public class ContinuousEffects implements Serializable {
|
|||
continue;
|
||||
}
|
||||
// check if waiting effects can be applied now
|
||||
for (Iterator<Map.Entry<ContinuousEffect, Set<UUID>>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext(); ) {
|
||||
for (Iterator<Map.Entry<ContinuousEffect, Set<UUID>>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext();) {
|
||||
Map.Entry<ContinuousEffect, Set<UUID>> entry = iterator.next();
|
||||
if (!appliedEffects.containsAll(entry.getValue())) { // all dependent to effects are applied now so apply the effect itself
|
||||
continue;
|
||||
|
|
@ -1283,18 +1273,19 @@ public class ContinuousEffects implements Serializable {
|
|||
}
|
||||
|
||||
private void setControllerForEffect(ContinuousEffectsList<?> effects, UUID sourceId, UUID controllerId) {
|
||||
for (Effect effect : effects) {
|
||||
Set<Ability> abilities = effects.getAbility(effect.getId());
|
||||
for (Ability ability : abilities) {
|
||||
if (ability.getSourceId() != null) {
|
||||
if (ability.getSourceId().equals(sourceId)) {
|
||||
ability.setControllerId(controllerId);
|
||||
for (ContinuousEffect effect : effects) {
|
||||
if (!effect.getDuration().isFixedController()) {
|
||||
Set<Ability> abilities = effects.getAbility(effect.getId());
|
||||
for (Ability ability : abilities) {
|
||||
if (ability.getSourceId() != null) {
|
||||
if (ability.getSourceId().equals(sourceId)) {
|
||||
ability.setControllerId(controllerId);
|
||||
}
|
||||
} else if (ability.getZone() != Zone.COMMAND) {
|
||||
logger.fatal("Continuous effect for ability with no sourceId Ability: " + ability);
|
||||
}
|
||||
} else if (ability.getZone() != Zone.COMMAND) {
|
||||
logger.fatal("Continuous effect for ability with no sourceId Ability: " + ability);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.effects;
|
||||
|
||||
import java.util.*;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.MageSingleton;
|
||||
|
|
@ -10,8 +11,6 @@ import mage.game.Game;
|
|||
import mage.players.Player;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @param <T>
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
|
|
@ -47,7 +46,7 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
|
|||
public void removeEndOfTurnEffects(Game game) {
|
||||
// calls every turn on cleanup step (only end of turn duration)
|
||||
// rules 514.2
|
||||
for (Iterator<T> i = this.iterator(); i.hasNext(); ) {
|
||||
for (Iterator<T> i = this.iterator(); i.hasNext();) {
|
||||
T entry = i.next();
|
||||
boolean canRemove = false;
|
||||
switch (entry.getDuration()) {
|
||||
|
|
@ -67,7 +66,7 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
|
|||
|
||||
public void removeEndOfCombatEffects() {
|
||||
|
||||
for (Iterator<T> i = this.iterator(); i.hasNext(); ) {
|
||||
for (Iterator<T> i = this.iterator(); i.hasNext();) {
|
||||
T entry = i.next();
|
||||
if (entry.getDuration() == Duration.EndOfCombat) {
|
||||
i.remove();
|
||||
|
|
@ -77,7 +76,7 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
|
|||
}
|
||||
|
||||
public void removeInactiveEffects(Game game) {
|
||||
for (Iterator<T> i = this.iterator(); i.hasNext(); ) {
|
||||
for (Iterator<T> i = this.iterator(); i.hasNext();) {
|
||||
T entry = i.next();
|
||||
if (isInactive(entry, game)) {
|
||||
i.remove();
|
||||
|
|
@ -86,15 +85,6 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
|
|||
}
|
||||
}
|
||||
|
||||
public void incYourTurnNumPlayed(Game game) {
|
||||
for (Iterator<T> i = this.iterator(); i.hasNext(); ) {
|
||||
T entry = i.next();
|
||||
if (game.isActivePlayer(entry.getStartingController())) {
|
||||
entry.incYourTurnNumPlayed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInactive(T effect, Game game) {
|
||||
// ends all inactive effects -- calls on player leave or apply new effect
|
||||
if (game.getState().isGameOver()) {
|
||||
|
|
@ -109,9 +99,8 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
|
|||
those objects are exiled. This is not a state-based action. It happens as soon as the player leaves the game.
|
||||
If the player who left the game had priority at the time they left, priority passes to the next player in turn
|
||||
order who’s still in the game.
|
||||
*/
|
||||
*/
|
||||
// objects removes doing in player.leave() call... effects removes is here
|
||||
|
||||
Set<Ability> set = effectAbilityMap.get(effect.getId());
|
||||
if (set == null) {
|
||||
logger.debug("No abilities for effect found: " + effect.toString());
|
||||
|
|
@ -224,7 +213,7 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
|
|||
abilities.removeAll(abilitiesToRemove);
|
||||
}
|
||||
if (abilities == null || abilities.isEmpty()) {
|
||||
for (Iterator<T> iterator = this.iterator(); iterator.hasNext(); ) {
|
||||
for (Iterator<T> iterator = this.iterator(); iterator.hasNext();) {
|
||||
ContinuousEffect effect = iterator.next();
|
||||
if (effect.getId().equals(effectIdToRemove)) {
|
||||
iterator.remove();
|
||||
|
|
|
|||
|
|
@ -1,32 +1,23 @@
|
|||
package mage.abilities.effects.common.combat;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.RestrictionEffect;
|
||||
import mage.constants.Duration;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class CantAttackYouEffect extends RestrictionEffect {
|
||||
|
||||
UUID controllerId;
|
||||
|
||||
public CantAttackYouEffect(Duration duration) {
|
||||
super(duration);
|
||||
}
|
||||
|
||||
public CantAttackYouEffect(Duration duration, UUID controllerId) {
|
||||
super(duration);
|
||||
this.controllerId = controllerId;
|
||||
}
|
||||
|
||||
public CantAttackYouEffect(final CantAttackYouEffect effect) {
|
||||
super(effect);
|
||||
this.controllerId = effect.controllerId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -44,9 +35,6 @@ public class CantAttackYouEffect extends RestrictionEffect {
|
|||
if (defenderId == null) {
|
||||
return true;
|
||||
}
|
||||
if (controllerId == null) {
|
||||
controllerId = source.getControllerId();
|
||||
}
|
||||
return !defenderId.equals(controllerId);
|
||||
return !defenderId.equals(source.getControllerId());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,18 +41,18 @@ public class GoadTargetEffect extends OneShotEffect {
|
|||
Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source));
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (targetCreature != null && controller != null) {
|
||||
// TODO: Allow goad to target controller, current AttacksIfAbleTargetEffect is not support it
|
||||
// TODO: Allow goad to target controller, current AttacksIfAbleTargetEffect does not support it
|
||||
// https://github.com/magefree/mage/issues/5283
|
||||
/*
|
||||
If the creature doesn’t meet any of the above exceptions and can attack, it must attack a player other than
|
||||
the controller of the spell or ability that goaded it if able. It the creature can’t attack any of those
|
||||
the controller of the spell or ability that goaded it if able. If the creature can’t attack any of those
|
||||
players but could otherwise attack, it must attack an opposing planeswalker (controlled by any opponent)
|
||||
or the player that goaded it. (2016-08-23)
|
||||
*/
|
||||
ContinuousEffect effect = new AttacksIfAbleTargetEffect(Duration.UntilYourNextTurn);
|
||||
effect.setTargetPointer(new FixedTarget(getTargetPointer().getFirst(game, source)));
|
||||
game.addEffect(effect, source);
|
||||
effect = new CantAttackYouEffect(Duration.UntilYourNextTurn, source.getControllerId()); // remember current controller
|
||||
effect = new CantAttackYouEffect(Duration.UntilYourNextTurn); // remember current controller
|
||||
effect.setTargetPointer(new FixedTarget(getTargetPointer().getFirst(game, source)));
|
||||
game.addEffect(effect, source);
|
||||
game.informPlayers(controller.getLogName() + " is goading " + targetCreature.getLogName());
|
||||
|
|
|
|||
|
|
@ -4,25 +4,27 @@ package mage.constants;
|
|||
* @author North
|
||||
*/
|
||||
public enum Duration {
|
||||
OneUse("", true),
|
||||
EndOfGame("for the rest of the game", false),
|
||||
WhileOnBattlefield("", false),
|
||||
WhileOnStack("", false),
|
||||
WhileInGraveyard("", false),
|
||||
EndOfTurn("until end of turn", true),
|
||||
UntilYourNextTurn("until your next turn", true),
|
||||
UntilEndOfYourNextTurn("until the end of your next turn", true),
|
||||
UntilSourceLeavesBattlefield("until {source} leaves the battlefield", true), // supported for continuous layered effects
|
||||
EndOfCombat("until end of combat", true),
|
||||
EndOfStep("until end of phase step", true),
|
||||
Custom("", true);
|
||||
OneUse("", true, true),
|
||||
EndOfGame("for the rest of the game", false, false),
|
||||
WhileOnBattlefield("", false, false),
|
||||
WhileOnStack("", false, true),
|
||||
WhileInGraveyard("", false, false),
|
||||
EndOfTurn("until end of turn", true, true),
|
||||
UntilYourNextTurn("until your next turn", true, true),
|
||||
UntilEndOfYourNextTurn("until the end of your next turn", true, true),
|
||||
UntilSourceLeavesBattlefield("until {source} leaves the battlefield", true, false), // supported for continuous layered effects
|
||||
EndOfCombat("until end of combat", true, true),
|
||||
EndOfStep("until end of phase step", true, true),
|
||||
Custom("", true, true);
|
||||
|
||||
private final String text;
|
||||
private final boolean onlyValidIfNoZoneChange; // defines if an effect lasts only if the source has not chnaged zone since init of the effect
|
||||
private final boolean onlyValidIfNoZoneChange; // defines if an effect lasts only if the source has not changed zone since init of the effect
|
||||
private final boolean fixedController; // has the controller of the effect to change, if the controller of the source changes
|
||||
|
||||
Duration(String text, boolean onlyValidIfNoZoneChange) {
|
||||
Duration(String text, boolean onlyValidIfNoZoneChange, boolean fixedController) {
|
||||
this.text = text;
|
||||
this.onlyValidIfNoZoneChange = onlyValidIfNoZoneChange;
|
||||
this.fixedController = fixedController;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -34,4 +36,7 @@ public enum Duration {
|
|||
return onlyValidIfNoZoneChange;
|
||||
}
|
||||
|
||||
public boolean isFixedController() {
|
||||
return fixedController;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
package mage.game;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import mage.MageException;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.*;
|
||||
|
|
@ -67,11 +71,6 @@ import mage.util.functions.ApplyToPermanent;
|
|||
import mage.watchers.common.*;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public abstract class GameImpl implements Game, Serializable {
|
||||
|
||||
private static final int ROLLBACK_TURNS_MAX = 4;
|
||||
|
|
@ -1549,7 +1548,7 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
/**
|
||||
* @param emblem
|
||||
* @param sourceObject
|
||||
* @param toPlayerId controller and owner of the emblem
|
||||
* @param toPlayerId controller and owner of the emblem
|
||||
*/
|
||||
@Override
|
||||
public void addEmblem(Emblem emblem, MageObject sourceObject, UUID toPlayerId) {
|
||||
|
|
@ -1567,8 +1566,8 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
/**
|
||||
* @param plane
|
||||
* @param sourceObject
|
||||
* @param toPlayerId controller and owner of the plane (may only be one per
|
||||
* game..)
|
||||
* @param toPlayerId controller and owner of the plane (may only be one per
|
||||
* game..)
|
||||
* @return boolean - whether the plane was added successfully or not
|
||||
*/
|
||||
@Override
|
||||
|
|
@ -1804,7 +1803,7 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
break;
|
||||
}
|
||||
// triggered abilities that don't use the stack have to be executed first (e.g. Banisher Priest Return exiled creature
|
||||
for (Iterator<TriggeredAbility> it = abilities.iterator(); it.hasNext(); ) {
|
||||
for (Iterator<TriggeredAbility> it = abilities.iterator(); it.hasNext();) {
|
||||
TriggeredAbility triggeredAbility = it.next();
|
||||
if (!triggeredAbility.isUsesStack()) {
|
||||
state.removeTriggeredAbility(triggeredAbility);
|
||||
|
|
@ -1917,8 +1916,8 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
*/
|
||||
boolean usePowerInsteadOfToughnessForDamageLethality = usePowerInsteadOfToughnessForDamageLethalityFilters.stream()
|
||||
.anyMatch(filter -> filter.match(perm, this));
|
||||
int lethalDamageThreshold = usePowerInsteadOfToughnessForDamageLethality ?
|
||||
// Zilortha, Strength Incarnate, 2020-04-17: A creature with 0 power isn’t destroyed unless it has at least 1 damage marked on it.
|
||||
int lethalDamageThreshold = usePowerInsteadOfToughnessForDamageLethality
|
||||
? // Zilortha, Strength Incarnate, 2020-04-17: A creature with 0 power isn’t destroyed unless it has at least 1 damage marked on it.
|
||||
Math.max(perm.getPower().getValue(), 1) : perm.getToughness().getValue();
|
||||
if (lethalDamageThreshold <= perm.getDamage() || perm.isDeathtouched()) {
|
||||
if (perm.destroy(null, this, false)) {
|
||||
|
|
@ -2249,7 +2248,6 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
}
|
||||
|
||||
//TODO: implement the rest
|
||||
|
||||
return somethingHappened;
|
||||
}
|
||||
|
||||
|
|
@ -2557,7 +2555,7 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
}
|
||||
//20100423 - 800.4a
|
||||
Set<Card> toOutside = new HashSet<>();
|
||||
for (Iterator<Permanent> it = getBattlefield().getAllPermanents().iterator(); it.hasNext(); ) {
|
||||
for (Iterator<Permanent> it = getBattlefield().getAllPermanents().iterator(); it.hasNext();) {
|
||||
Permanent perm = it.next();
|
||||
if (perm.isOwnedBy(playerId)) {
|
||||
if (perm.getAttachedTo() != null) {
|
||||
|
|
@ -2595,14 +2593,14 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
}
|
||||
}
|
||||
}
|
||||
for(Card card : toOutside) {
|
||||
for (Card card : toOutside) {
|
||||
rememberLKI(card.getId(), Zone.BATTLEFIELD, card);
|
||||
}
|
||||
// needed to send event that permanent leaves the battlefield to allow non stack effects to execute
|
||||
player.moveCards(toOutside, Zone.OUTSIDE, null, this);
|
||||
// triggered abilities that don't use the stack have to be executed
|
||||
List<TriggeredAbility> abilities = state.getTriggered(player.getId());
|
||||
for (Iterator<TriggeredAbility> it = abilities.iterator(); it.hasNext(); ) {
|
||||
for (Iterator<TriggeredAbility> it = abilities.iterator(); it.hasNext();) {
|
||||
TriggeredAbility triggeredAbility = it.next();
|
||||
if (!triggeredAbility.isUsesStack()) {
|
||||
state.removeTriggeredAbility(triggeredAbility);
|
||||
|
|
@ -2622,7 +2620,7 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
|
||||
// Remove cards from the player in all exile zones
|
||||
for (ExileZone exile : this.getExile().getExileZones()) {
|
||||
for (Iterator<UUID> it = exile.iterator(); it.hasNext(); ) {
|
||||
for (Iterator<UUID> it = exile.iterator(); it.hasNext();) {
|
||||
Card card = this.getCard(it.next());
|
||||
if (card != null && card.isOwnedBy(playerId)) {
|
||||
it.remove();
|
||||
|
|
@ -2632,7 +2630,7 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
|
||||
//Remove all commander/emblems/plane the player controls
|
||||
boolean addPlaneAgain = false;
|
||||
for (Iterator<CommandObject> it = this.getState().getCommand().iterator(); it.hasNext(); ) {
|
||||
for (Iterator<CommandObject> it = this.getState().getCommand().iterator(); it.hasNext();) {
|
||||
CommandObject obj = it.next();
|
||||
if (obj.isControlledBy(playerId)) {
|
||||
if (obj instanceof Emblem) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
package mage.game;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import static java.util.Collections.emptyList;
|
||||
import java.util.stream.Collectors;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.*;
|
||||
import mage.abilities.effects.ContinuousEffect;
|
||||
|
|
@ -35,12 +39,6 @@ import mage.util.ThreadLocalStringBuilder;
|
|||
import mage.watchers.Watcher;
|
||||
import mage.watchers.Watchers;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
* <p>
|
||||
|
|
@ -179,8 +177,8 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
this.copiedCards.putAll(state.copiedCards);
|
||||
this.permanentOrderNumber = state.permanentOrderNumber;
|
||||
this.applyEffectsCounter = state.applyEffectsCounter;
|
||||
state.usePowerInsteadOfToughnessForDamageLethalityFilters.forEach((uuid, filter) ->
|
||||
this.usePowerInsteadOfToughnessForDamageLethalityFilters.put(uuid, filter.copy()));
|
||||
state.usePowerInsteadOfToughnessForDamageLethalityFilters.forEach((uuid, filter)
|
||||
-> this.usePowerInsteadOfToughnessForDamageLethalityFilters.put(uuid, filter.copy()));
|
||||
}
|
||||
|
||||
public void restoreForRollBack(GameState state) {
|
||||
|
|
@ -226,8 +224,8 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
this.copiedCards = state.copiedCards;
|
||||
this.permanentOrderNumber = state.permanentOrderNumber;
|
||||
this.applyEffectsCounter = state.applyEffectsCounter;
|
||||
state.usePowerInsteadOfToughnessForDamageLethalityFilters.forEach((uuid, filter) ->
|
||||
this.usePowerInsteadOfToughnessForDamageLethalityFilters.put(uuid, filter.copy()));
|
||||
state.usePowerInsteadOfToughnessForDamageLethalityFilters.forEach((uuid, filter)
|
||||
-> this.usePowerInsteadOfToughnessForDamageLethalityFilters.put(uuid, filter.copy()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -605,7 +603,6 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
delayed.removeEndOfTurnAbilities(game);
|
||||
exile.cleanupEndOfTurnZones(game);
|
||||
game.applyEffects();
|
||||
effects.incYourTurnNumPlayed(game);
|
||||
}
|
||||
|
||||
public void addEffect(ContinuousEffect effect, Ability source) {
|
||||
|
|
@ -623,7 +620,6 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
// public void addMessage(String message) {
|
||||
// this.messages.add(message);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Returns a list of all players of the game ignoring range or if a player
|
||||
* has lost or left the game.
|
||||
|
|
@ -797,7 +793,7 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
for (Map.Entry<ZoneChangeData, List<GameEvent>> entry : eventsByKey.entrySet()) {
|
||||
Set<Card> movedCards = new LinkedHashSet<>();
|
||||
Set<PermanentToken> movedTokens = new LinkedHashSet<>();
|
||||
for (Iterator<GameEvent> it = entry.getValue().iterator(); it.hasNext(); ) {
|
||||
for (Iterator<GameEvent> it = entry.getValue().iterator(); it.hasNext();) {
|
||||
GameEvent event = it.next();
|
||||
ZoneChangeEvent castEvent = (ZoneChangeEvent) event;
|
||||
UUID targetId = castEvent.getTargetId();
|
||||
|
|
@ -1030,7 +1026,7 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
* @param attachedTo
|
||||
* @param ability
|
||||
* @param copyAbility copies non MageSingleton abilities before adding to
|
||||
* state
|
||||
* state
|
||||
*/
|
||||
public void addOtherAbility(Card attachedTo, Ability ability, boolean copyAbility) {
|
||||
Ability newAbility;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package mage.game.combat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.RequirementEffect;
|
||||
|
|
@ -33,9 +35,6 @@ import mage.util.Copyable;
|
|||
import mage.util.trace.TraceUtil;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
|
|
@ -346,8 +345,8 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
if (game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_ATTACKERS, attackingPlayerId, attackingPlayerId))
|
||||
|| (!canBand && !canBandWithOther)
|
||||
|| !player.chooseUse(Outcome.Benefit,
|
||||
"Do you wish to " + (isBanded ? "band " + attacker.getLogName()
|
||||
+ " with another " : "form a band with " + attacker.getLogName() + " and an ")
|
||||
"Do you wish to " + (isBanded ? "band " + attacker.getLogName()
|
||||
+ " with another " : "form a band with " + attacker.getLogName() + " and an ")
|
||||
+ "attacking creature?", null, game)) {
|
||||
break;
|
||||
}
|
||||
|
|
@ -412,7 +411,8 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
if (!isBanded) {
|
||||
return;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(player.getLogName()).append(" formed a band with ").append((attacker.getBandedCards().size() + 1) + " creatures: ");
|
||||
StringBuilder sb = new StringBuilder(player.getLogName()).append(" formed a band with ")
|
||||
.append(attacker.getBandedCards().size()).append(1).append(" creatures: ");
|
||||
sb.append(attacker.getLogName());
|
||||
for (UUID id : attacker.getBandedCards()) {
|
||||
sb.append(", ");
|
||||
|
|
@ -565,7 +565,7 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
* Handle the blocker selection process
|
||||
*
|
||||
* @param blockController player that controls how to block, if null the
|
||||
* defender is the controller
|
||||
* defender is the controller
|
||||
* @param game
|
||||
*/
|
||||
public void selectBlockers(Player blockController, Game game) {
|
||||
|
|
@ -744,15 +744,15 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
}
|
||||
|
||||
/**
|
||||
* 509.1c The defending player checks each creature they control to
|
||||
* see whether it's affected by any requirements (effects that say a
|
||||
* creature must block, or that it must block if some condition is met). If
|
||||
* the number of requirements that are being obeyed is fewer than the
|
||||
* maximum possible number of requirements that could be obeyed without
|
||||
* disobeying any restrictions, the declaration of blockers is illegal. If a
|
||||
* creature can't block unless a player pays a cost, that player is not
|
||||
* required to pay that cost, even if blocking with that creature would
|
||||
* increase the number of requirements being obeyed.
|
||||
* 509.1c The defending player checks each creature they control to see
|
||||
* whether it's affected by any requirements (effects that say a creature
|
||||
* must block, or that it must block if some condition is met). If the
|
||||
* number of requirements that are being obeyed is fewer than the maximum
|
||||
* possible number of requirements that could be obeyed without disobeying
|
||||
* any restrictions, the declaration of blockers is illegal. If a creature
|
||||
* can't block unless a player pays a cost, that player is not required to
|
||||
* pay that cost, even if blocking with that creature would increase the
|
||||
* number of requirements being obeyed.
|
||||
* <p>
|
||||
* <p>
|
||||
* Example: A player controls one creature that "blocks if able" and another
|
||||
|
|
@ -1379,7 +1379,7 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
* @param playerId
|
||||
* @param game
|
||||
* @param solveBanding check whether also add creatures banded with
|
||||
* attackerId
|
||||
* attackerId
|
||||
*/
|
||||
public void addBlockingGroup(UUID blockerId, UUID attackerId, UUID playerId, Game game, boolean solveBanding) {
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package mage.players;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import mage.ConditionalMana;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectReference;
|
||||
|
|
@ -65,10 +68,6 @@ import mage.util.GameLog;
|
|||
import mage.util.RandomUtil;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public abstract class PlayerImpl implements Player, Serializable {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(PlayerImpl.class);
|
||||
|
|
@ -612,9 +611,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
&& this.hasOpponent(sourceControllerId, game)
|
||||
&& game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) == null
|
||||
&& abilities.stream()
|
||||
.filter(HexproofBaseAbility.class::isInstance)
|
||||
.map(HexproofBaseAbility.class::cast)
|
||||
.anyMatch(ability -> ability.checkObject(source, game))) {
|
||||
.filter(HexproofBaseAbility.class::isInstance)
|
||||
.map(HexproofBaseAbility.class::cast)
|
||||
.anyMatch(ability -> ability.checkObject(source, game))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -654,7 +653,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
game.informPlayers(getLogName() + " discards down to "
|
||||
+ this.maxHandSize
|
||||
+ (this.maxHandSize == 1
|
||||
? " hand card" : " hand cards"));
|
||||
? " hand card" : " hand cards"));
|
||||
}
|
||||
discard(hand.size() - this.maxHandSize, false, null, game);
|
||||
}
|
||||
|
|
@ -803,7 +802,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
GameEvent gameEvent = GameEvent.getEvent(GameEvent.EventType.DISCARD_CARD,
|
||||
card.getId(), source == null
|
||||
? null : source.getSourceId(), playerId);
|
||||
? null : source.getSourceId(), playerId);
|
||||
gameEvent.setFlag(source != null); // event from effect or from cost (source == null)
|
||||
if (game.replaceEvent(gameEvent, source)) {
|
||||
return false;
|
||||
|
|
@ -1345,6 +1344,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean activateAbility(ActivatedAbility ability, Game game) {
|
||||
if (ability == null) {
|
||||
return false;
|
||||
}
|
||||
boolean result;
|
||||
if (ability instanceof PassAbility) {
|
||||
pass(game);
|
||||
|
|
@ -1502,14 +1504,13 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
@Override
|
||||
public LinkedHashMap<UUID, ActivatedAbility> getPlayableActivatedAbilities(MageObject object, Zone zone, Game game) {
|
||||
LinkedHashMap<UUID, ActivatedAbility> useable = new LinkedHashMap<>();
|
||||
// It may not be possible to activate abilities of stack abilities
|
||||
if (object instanceof StackAbility || object == null) {
|
||||
return useable;
|
||||
}
|
||||
boolean previousState = game.inCheckPlayableState();
|
||||
game.setCheckPlayableState(true);
|
||||
try {
|
||||
// It may not be possible to activate abilities of stack abilities
|
||||
if (object instanceof StackAbility) {
|
||||
return useable;
|
||||
}
|
||||
|
||||
// collect and filter playable activated abilities
|
||||
// GUI: user clicks on card, but it must activate ability from any card's parts (main, left, right)
|
||||
UUID needId1, needId2, needId3;
|
||||
|
|
@ -1810,9 +1811,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
private List<Permanent> getPermanentsThatCanBeUntapped(Game game,
|
||||
List<Permanent> canBeUntapped,
|
||||
RestrictionUntapNotMoreThanEffect handledEffect,
|
||||
Map<Entry<RestrictionUntapNotMoreThanEffect, Set<Ability>>, Integer> notMoreThanEffectsUsage) {
|
||||
List<Permanent> canBeUntapped,
|
||||
RestrictionUntapNotMoreThanEffect handledEffect,
|
||||
Map<Entry<RestrictionUntapNotMoreThanEffect, Set<Ability>>, Integer> notMoreThanEffectsUsage) {
|
||||
List<Permanent> leftForUntap = new ArrayList<>();
|
||||
// select permanents that can still be untapped
|
||||
for (Permanent permanent : canBeUntapped) {
|
||||
|
|
@ -2521,7 +2522,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId,
|
||||
boolean triggerEvents) {
|
||||
boolean triggerEvents) {
|
||||
//20091005 - 701.14c
|
||||
Library searchedLibrary = null;
|
||||
String searchInfo = null;
|
||||
|
|
@ -2699,7 +2700,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
(event.isWinnable() ? "(You called " + event.getChosenName() + ")" : null),
|
||||
"Heads", "Tails", source, game
|
||||
));
|
||||
} else event.setResult(canChooseHeads);
|
||||
} else {
|
||||
event.setResult(canChooseHeads);
|
||||
}
|
||||
game.informPlayers(getLogName() + " chose to keep " + CardUtil.booleanToFlipName(event.getResult()));
|
||||
}
|
||||
if (event.isWinnable()) {
|
||||
|
|
@ -2721,7 +2724,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
/**
|
||||
* @param game
|
||||
* @param appliedEffects
|
||||
* @param numSides Number of sides the dice has
|
||||
* @param numSides Number of sides the dice has
|
||||
* @return the number that the player rolled
|
||||
*/
|
||||
@Override
|
||||
|
|
@ -2758,16 +2761,16 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
/**
|
||||
* @param game
|
||||
* @param appliedEffects
|
||||
* @param numberChaosSides The number of chaos sides the planar die
|
||||
* currently has (normally 1 but can be 5)
|
||||
* @param numberChaosSides The number of chaos sides the planar die
|
||||
* currently has (normally 1 but can be 5)
|
||||
* @param numberPlanarSides The number of chaos sides the planar die
|
||||
* currently has (normally 1)
|
||||
* currently has (normally 1)
|
||||
* @return the outcome that the player rolled. Either ChaosRoll, PlanarRoll
|
||||
* or NilRoll
|
||||
*/
|
||||
@Override
|
||||
public PlanarDieRoll rollPlanarDie(Game game, List<UUID> appliedEffects, int numberChaosSides,
|
||||
int numberPlanarSides) {
|
||||
int numberPlanarSides) {
|
||||
int result = RandomUtil.nextInt(9) + 1;
|
||||
PlanarDieRoll roll = PlanarDieRoll.NIL_ROLL;
|
||||
if (numberChaosSides + numberPlanarSides > 9) {
|
||||
|
|
@ -2924,14 +2927,14 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
/**
|
||||
* @param ability
|
||||
* @param available if null, it won't be checked if enough mana is available
|
||||
* @param available if null, it won't be checked if enough mana is available
|
||||
* @param sourceObject
|
||||
* @param game
|
||||
* @return
|
||||
*/
|
||||
protected boolean canPlay(ActivatedAbility ability, ManaOptions available, MageObject sourceObject, Game game) {
|
||||
if (!(ability instanceof ActivatedManaAbilityImpl)) {
|
||||
ActivatedAbility copy = ability.copy(); // copy is needed because cost reduction effects modify e.g. the mana to activate/cast the ability
|
||||
ActivatedAbility copy = ability.copy(); // Copy is needed because cost reduction effects modify e.g. the mana to activate/cast the ability
|
||||
if (!copy.canActivate(playerId, game).canActivate()) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -3117,7 +3120,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
protected boolean canLandPlayAlternateSourceCostsAbility(Card sourceObject, ManaOptions available, Ability ability, Game game) {
|
||||
if (!(sourceObject instanceof Permanent)) {
|
||||
if (sourceObject != null && !(sourceObject instanceof Permanent)) {
|
||||
Ability sourceAbility = sourceObject.getAbilities().stream()
|
||||
.filter(landAbility -> landAbility.getAbilityType() == AbilityType.PLAY_LAND)
|
||||
.findFirst().orElse(null);
|
||||
|
|
@ -3158,7 +3161,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
private void getPlayableFromCardAll(Game game, Zone fromZone, Card card, ManaOptions availableMana, List<ActivatedAbility> output) {
|
||||
if (fromZone == null) {
|
||||
if (fromZone == null || card == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -3187,7 +3190,6 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
boolean isPlayLand = (ability instanceof PlayLandAbility);
|
||||
|
||||
// as original controller
|
||||
|
||||
// play land restrictions
|
||||
if (isPlayLand && game.getContinuousEffects().preventedByRuleModification(
|
||||
GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, ability.getSourceId(),
|
||||
|
|
@ -3221,7 +3223,6 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|| (fromZone == Zone.GRAVEYARD && canPlayCardsFromGraveyard());
|
||||
|
||||
// as affected controller
|
||||
|
||||
UUID savedControllerId = ability.getControllerId();
|
||||
ability.setControllerId(this.getId());
|
||||
try {
|
||||
|
|
@ -3620,7 +3621,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean canPaySacrificeCost(Permanent permanent, UUID sourceId,
|
||||
UUID controllerId, Game game
|
||||
UUID controllerId, Game game
|
||||
) {
|
||||
return sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, sourceId, controllerId, game);
|
||||
}
|
||||
|
|
@ -3773,8 +3774,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCards(Card card, Zone toZone,
|
||||
Ability source, Game game,
|
||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||
Ability source, Game game,
|
||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||
) {
|
||||
Set<Card> cardList = new HashSet<>();
|
||||
if (card != null) {
|
||||
|
|
@ -3785,22 +3786,22 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCards(Cards cards, Zone toZone,
|
||||
Ability source, Game game
|
||||
Ability source, Game game
|
||||
) {
|
||||
return moveCards(cards.getCards(game), toZone, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveCards(Set<Card> cards, Zone toZone,
|
||||
Ability source, Game game
|
||||
Ability source, Game game
|
||||
) {
|
||||
return moveCards(cards, toZone, source, game, false, false, false, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveCards(Set<Card> cards, Zone toZone,
|
||||
Ability source, Game game,
|
||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||
Ability source, Game game,
|
||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||
) {
|
||||
if (cards.isEmpty()) {
|
||||
return true;
|
||||
|
|
@ -3902,8 +3903,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardsToExile(Card card, Ability source,
|
||||
Game game, boolean withName, UUID exileId,
|
||||
String exileZoneName
|
||||
Game game, boolean withName, UUID exileId,
|
||||
String exileZoneName
|
||||
) {
|
||||
Set<Card> cards = new HashSet<>();
|
||||
cards.add(card);
|
||||
|
|
@ -3912,8 +3913,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardsToExile(Set<Card> cards, Ability source,
|
||||
Game game, boolean withName, UUID exileId,
|
||||
String exileZoneName
|
||||
Game game, boolean withName, UUID exileId,
|
||||
String exileZoneName
|
||||
) {
|
||||
if (cards.isEmpty()) {
|
||||
return true;
|
||||
|
|
@ -3929,14 +3930,14 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
|
||||
Game game
|
||||
Game game
|
||||
) {
|
||||
return this.moveCardToHandWithInfo(card, sourceId, game, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
|
||||
Game game, boolean withName
|
||||
Game game, boolean withName
|
||||
) {
|
||||
boolean result = false;
|
||||
Zone fromZone = game.getState().getZone(card.getId());
|
||||
|
|
@ -3961,7 +3962,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public Set<Card> moveCardsToGraveyardWithInfo(Set<Card> allCards, Ability source,
|
||||
Game game, Zone fromZone
|
||||
Game game, Zone fromZone
|
||||
) {
|
||||
UUID sourceId = source == null ? null : source.getSourceId();
|
||||
Set<Card> movedCards = new LinkedHashSet<>();
|
||||
|
|
@ -3969,7 +3970,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
// identify cards from one owner
|
||||
Cards cards = new CardsImpl();
|
||||
UUID ownerId = null;
|
||||
for (Iterator<Card> it = allCards.iterator(); it.hasNext(); ) {
|
||||
for (Iterator<Card> it = allCards.iterator(); it.hasNext();) {
|
||||
Card card = it.next();
|
||||
if (cards.isEmpty()) {
|
||||
ownerId = card.getOwnerId();
|
||||
|
|
@ -4032,7 +4033,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId,
|
||||
Game game, Zone fromZone
|
||||
Game game, Zone fromZone
|
||||
) {
|
||||
if (card == null) {
|
||||
return false;
|
||||
|
|
@ -4061,8 +4062,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardToLibraryWithInfo(Card card, UUID sourceId,
|
||||
Game game, Zone fromZone,
|
||||
boolean toTop, boolean withName
|
||||
Game game, Zone fromZone,
|
||||
boolean toTop, boolean withName
|
||||
) {
|
||||
if (card == null) {
|
||||
return false;
|
||||
|
|
@ -4127,7 +4128,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardToExileWithInfo(Card card, UUID exileId, String exileName, UUID sourceId,
|
||||
Game game, Zone fromZone, boolean withName) {
|
||||
Game game, Zone fromZone, boolean withName) {
|
||||
if (card == null) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -4150,7 +4151,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName()
|
||||
+ (card.isCopy() ? " (Copy)" : "") : "a card face down") + ' '
|
||||
+ (fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH)
|
||||
+ ' ' : "") + "to the exile zone");
|
||||
+ ' ' : "") + "to the exile zone");
|
||||
|
||||
}
|
||||
result = true;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package mage.watchers.common;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import mage.constants.WatcherScope;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class LostControlWatcher extends Watcher {
|
||||
|
||||
private final Map<UUID, Long> lastLostControl = new HashMap<>();
|
||||
|
||||
public LostControlWatcher() {
|
||||
super(WatcherScope.GAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void watch(GameEvent event, Game game) {
|
||||
if (event.getType() == GameEvent.EventType.LOST_CONTROL) {
|
||||
lastLostControl.put(event.getTargetId(), System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
lastLostControl.clear();
|
||||
}
|
||||
|
||||
public long getOrderOfLastLostControl(UUID sourceId) {
|
||||
return lastLostControl.getOrDefault(sourceId, new Long(0));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue