* Library of Leng - fixed that it triggers on discarded cards from cost part instead only effect part (#3047);

This commit is contained in:
Oleg Agafonov 2019-07-08 08:31:55 +04:00
parent d6c99437f3
commit 79d40754ed
3 changed files with 57 additions and 41 deletions

View file

@ -1,17 +1,11 @@
package mage.cards.l;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.cards.*;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
@ -22,14 +16,15 @@ import mage.game.events.GameEvent.EventType;
import mage.game.events.ZoneChangeEvent;
import mage.players.Player;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class LibraryOfLeng extends CardImpl {
public LibraryOfLeng(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{1}");
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}");
// You have no maximum hand size.
Effect effect = new MaximumHandSizeControllerEffect(Integer.MAX_VALUE, Duration.WhileOnBattlefield, MaximumHandSizeControllerEffect.HandSizeModification.SET);
@ -79,14 +74,15 @@ class LibraryOfLengEffect extends ReplacementEffectImpl {
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getType() == EventType.DISCARD_CARD) {
// rules:
// You cant use the Library of Leng ability to place a discarded card on top of your library when you discard a card as a cost,
// because costs arent effects. (2004-10-04)
if (event.getType() == EventType.DISCARD_CARD && event.getFlag()) {
return event.getPlayerId().equals(source.getControllerId());
}
if (event.getType() == EventType.ZONE_CHANGE) {
if (event.getTargetId().equals(cardId) && game.getState().getZoneChangeCounter(event.getTargetId()) == zoneChangeCounter) {
if (((ZoneChangeEvent) event).getFromZone() == Zone.HAND && ((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD) {
return true;
}
return ((ZoneChangeEvent) event).getFromZone() == Zone.HAND && ((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD;
}
}
return false;

View file

@ -21,6 +21,7 @@ public class GameEvent implements Serializable {
// flags:
// for counters: event is result of effect (+1 from planeswalkers is cost, not effect)
// for combat damage: event is preventable damage
// for discard: event is result of effect (1) or result of cost (0)
protected boolean flag;
protected String data;
protected Zone zone;

View file

@ -792,20 +792,23 @@ public abstract class PlayerImpl implements Player, Serializable {
about the discarded card, that cost payment is illegal; the game returns to
the moment before the cost was paid (see rule 717, "Handling Illegal Actions").
*/
if (card != null
&& !game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DISCARD_CARD, card.getId(), source == null ? null : source.getSourceId(), playerId), source)) {
// write info to game log first so game log infos from triggered or replacement effects follow in the game log
if (!game.isSimulation()) {
game.informPlayers(getLogName() + " discards " + card.getLogName());
if (card != null) {
GameEvent gameEvent = GameEvent.getEvent(GameEvent.EventType.DISCARD_CARD, card.getId(), source == null ? null : source.getSourceId(), playerId);
gameEvent.setFlag(source != null); // event from effect or from cost (source == null)
if (!game.replaceEvent(gameEvent, source)) {
// write info to game log first so game log infos from triggered or replacement effects follow in the game log
if (!game.isSimulation()) {
game.informPlayers(getLogName() + " discards " + card.getLogName());
}
/* If a card is discarded while Rest in Peace is on the battlefield, abilities that function
* when a card is discarded (such as madness) still work, even though that card never reaches
* a graveyard. In addition, spells or abilities that check the characteristics of a discarded
* card (such as Chandra Ablaze's first ability) can find that card in exile. */
card.moveToZone(Zone.GRAVEYARD, source == null ? null : source.getSourceId(), game, false);
// So discard is also successful if card is moved to another zone by replacement effect!
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DISCARDED_CARD, card.getId(), source == null ? null : source.getSourceId(), playerId));
return true;
}
/* If a card is discarded while Rest in Peace is on the battlefield, abilities that function
* when a card is discarded (such as madness) still work, even though that card never reaches
* a graveyard. In addition, spells or abilities that check the characteristics of a discarded
* card (such as Chandra Ablaze's first ability) can find that card in exile. */
card.moveToZone(Zone.GRAVEYARD, source == null ? null : source.getSourceId(), game, false);
// So discard is also successful if card is moved to another zone by replacement effect!
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DISCARDED_CARD, card.getId(), source == null ? null : source.getSourceId(), playerId));
return true;
}
return false;
}
@ -1014,7 +1017,8 @@ public abstract class PlayerImpl implements Player, Serializable {
}
@Override
public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs) {
public void setCastSourceIdWithAlternateMana(UUID
sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs) {
castSourceIdWithAlternateMana = sourceId;
castSourceIdManaCosts = manaCosts;
castSourceIdCosts = costs;
@ -1046,7 +1050,8 @@ public abstract class PlayerImpl implements Player, Serializable {
}
@Override
public boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, MageObjectReference reference) {
public boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, MageObjectReference
reference) {
if (card == null) {
return false;
}
@ -1063,7 +1068,8 @@ public abstract class PlayerImpl implements Player, Serializable {
}
@Override
public boolean cast(SpellAbility originalAbility, Game game, boolean noMana, MageObjectReference permittingObject) {
public boolean cast(SpellAbility originalAbility, Game game, boolean noMana, MageObjectReference
permittingObject) {
if (game == null || originalAbility == null) {
return false;
}
@ -1426,7 +1432,8 @@ public abstract class PlayerImpl implements Player, Serializable {
// Get the usable activated abilities for a *single card object*, that is, either a card or half of a split card.
// Also called on the whole split card but only passing the fuse ability and other whole-split-card shared abilities
// as candidates.
private void getUseableActivatedAbilitiesHalfImpl(MageObject object, Zone zone, Game game, Abilities<Ability> candidateAbilites, LinkedHashMap<UUID, ActivatedAbility> output) {
private void getUseableActivatedAbilitiesHalfImpl(MageObject object, Zone zone, Game
game, Abilities<Ability> candidateAbilites, LinkedHashMap<UUID, ActivatedAbility> output) {
boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game);
ManaOptions availableMana = null;
// ManaOptions availableMana = getManaAvailable(game); // can only be activated if mana calculation works flawless otherwise player can't play spells they could play if calculation would work correctly
@ -1496,7 +1503,8 @@ public abstract class PlayerImpl implements Player, Serializable {
}
@Override
public LinkedHashMap<UUID, ActivatedAbility> getUseableActivatedAbilities(MageObject object, Zone zone, Game game) {
public LinkedHashMap<UUID, ActivatedAbility> getUseableActivatedAbilities(MageObject object, Zone zone, Game
game) {
LinkedHashMap<UUID, ActivatedAbility> useable = new LinkedHashMap<>();
if (object instanceof StackAbility) { // It may not be possible to activate abilities of stack abilities
return useable;
@ -1515,7 +1523,8 @@ public abstract class PlayerImpl implements Player, Serializable {
}
// Adds special abilities that are given to non permanents by continuous effects
private void getOtherUseableActivatedAbilities(MageObject object, Zone zone, Game game, Map<UUID, ActivatedAbility> useable) {
private void getOtherUseableActivatedAbilities(MageObject object, Zone zone, Game
game, Map<UUID, ActivatedAbility> useable) {
Abilities<ActivatedAbility> otherAbilities = game.getState().getActivatedOtherAbilities(object.getId(), zone);
if (otherAbilities != null) {
boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game);
@ -1561,7 +1570,8 @@ public abstract class PlayerImpl implements Player, Serializable {
}
}
protected LinkedHashMap<UUID, ActivatedManaAbilityImpl> getUseableManaAbilities(MageObject object, Zone zone, Game game) {
protected LinkedHashMap<UUID, ActivatedManaAbilityImpl> getUseableManaAbilities(MageObject object, Zone
zone, Game game) {
LinkedHashMap<UUID, ActivatedManaAbilityImpl> useable = new LinkedHashMap<>();
boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game);
for (ActivatedManaAbilityImpl ability : object.getAbilities().getActivatedManaAbilities(zone)) {
@ -1813,7 +1823,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) {
private List<Permanent> getPermanentsThatCanBeUntapped(Game
game, 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) {
@ -1996,12 +2008,14 @@ public abstract class PlayerImpl implements Player, Serializable {
}
@Override
public int damage(int damage, UUID sourceId, Game game, boolean combatDamage, boolean preventable, List<UUID> appliedEffects) {
public int damage(int damage, UUID sourceId, Game game, boolean combatDamage, boolean preventable, List<
UUID> appliedEffects) {
return doDamage(damage, sourceId, game, combatDamage, preventable, appliedEffects);
}
@SuppressWarnings({"null", "ConstantConditions"})
private int doDamage(int damage, UUID sourceId, Game game, boolean combatDamage, boolean preventable, List<UUID> appliedEffects) {
private int doDamage(int damage, UUID sourceId, Game game, boolean combatDamage, boolean preventable, List<
UUID> appliedEffects) {
if (damage > 0) {
if (canDamage(game.getObject(sourceId), game)) {
GameEvent event = new DamagePlayerEvent(playerId, sourceId, playerId, damage, preventable, combatDamage);
@ -2503,7 +2517,8 @@ public abstract class PlayerImpl implements Player, Serializable {
}
@Override
public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId, boolean triggerEvents) {
public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId,
boolean triggerEvents) {
//20091005 - 701.14c
Library searchedLibrary = null;
String searchInfo = null;
@ -2745,7 +2760,8 @@ public abstract class PlayerImpl implements Player, Serializable {
* or NilRoll
*/
@Override
public PlanarDieRoll rollPlanarDie(Game game, ArrayList<UUID> appliedEffects, int numberChaosSides, int numberPlanarSides) {
public PlanarDieRoll rollPlanarDie(Game game, ArrayList<UUID> appliedEffects, int numberChaosSides,
int numberPlanarSides) {
int result = RandomUtil.nextInt(9) + 1;
PlanarDieRoll roll = PlanarDieRoll.NIL_ROLL;
if (numberChaosSides + numberPlanarSides > 9) {
@ -2965,7 +2981,8 @@ public abstract class PlayerImpl implements Player, Serializable {
return false;
}
protected boolean canPlayCardByAlternateCost(Card sourceObject, ManaOptions available, Ability ability, Game game) {
protected boolean canPlayCardByAlternateCost(Card sourceObject, ManaOptions available, Ability ability, Game
game) {
if (sourceObject != null && !(sourceObject instanceof Permanent)) {
for (Ability alternateSourceCostsAbility : sourceObject.getAbilities()) {
// if cast for noMana no Alternative costs are allowed
@ -3029,7 +3046,8 @@ public abstract class PlayerImpl implements Player, Serializable {
return false;
}
protected boolean canLandPlayAlternateSourceCostsAbility(Card sourceObject, ManaOptions available, Ability ability, Game game) {
protected boolean canLandPlayAlternateSourceCostsAbility(Card sourceObject, ManaOptions available, Ability
ability, Game game) {
if (!(sourceObject instanceof Permanent)) {
Ability sourceAbility = null;
for (Ability landAbility : sourceObject.getAbilities()) {
@ -3064,7 +3082,8 @@ public abstract class PlayerImpl implements Player, Serializable {
return false;
}
private void getPlayableFromGraveyardCard(Game game, Card card, Abilities<Ability> candidateAbilities, ManaOptions availableMana, List<Ability> output) {
private void getPlayableFromGraveyardCard(Game game, Card
card, Abilities<Ability> candidateAbilities, ManaOptions availableMana, List<Ability> output) {
MageObjectReference permittingObject = game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, card.getSpellAbility(), this.getId(), game);
for (ActivatedAbility ability : candidateAbilities.getActivatedAbilities(Zone.ALL)) {
boolean possible = false;