Merge remote-tracking branch 'upstream/master'

This commit is contained in:
CCTV-1
2020-01-21 17:22:43 +08:00
21 changed files with 86 additions and 119 deletions

View File

@@ -2389,7 +2389,7 @@ public class ComputerUtilCombat {
restDamage = target.staticReplaceDamage(restDamage, source, isCombat);
// Predict replacement effects
for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final ReplacementEffect re : ca.getReplacementEffects()) {
Map<String, String> params = re.getMapParams();
if (!re.getMode().equals(ReplacementType.DamageDone) || !params.containsKey("PreventionEffect")) {

View File

@@ -23,6 +23,7 @@ import forge.util.TextUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
public class SpellAbilityPicker {
private Game game;
@@ -307,7 +308,7 @@ public class SpellAbilityPicker {
if (conditions == null) {
return true;
}
List<PhaseType> phases = conditions.getPhases();
Set<PhaseType> phases = conditions.getPhases();
return phases.isEmpty() || phases.contains(PhaseType.MAIN1);
}

View File

@@ -385,7 +385,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
}
// CantTarget static abilities
for (final Card ca : getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
for (final Card ca : getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (stAb.applyAbility("CantAttach", attach, this)) {
return false;

View File

@@ -1,5 +1,8 @@
package forge.game;
import java.util.EnumSet;
import java.util.Set;
import com.google.common.base.Enums;
import com.google.common.base.Function;
import forge.StaticData;
@@ -149,4 +152,15 @@ public enum GameType {
public static GameType smartValueOf(String name) {
return Enums.getIfPresent(GameType.class, name).orNull();
}
public static Set<GameType> listValueOf(final String values) {
final Set<GameType> result = EnumSet.noneOf(GameType.class);
for (final String s : values.split(",")) {
GameType g = GameType.smartValueOf(s);
if (g != null) {
result.add(g);
}
}
return result;
}
}

View File

@@ -1202,7 +1202,7 @@ public class Card extends GameEntity implements Comparable<Card> {
public final boolean canReceiveCounters(final CounterType type) {
// CantPutCounter static abilities
for (final Card ca : getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
for (final Card ca : getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (stAb.applyAbility("CantPutCounter", this, type)) {
return false;
@@ -2032,7 +2032,7 @@ public class Card extends GameEntity implements Comparable<Card> {
// CantBlockBy static abilities
if (game != null && isCreature() && isInZone(ZoneType.Battlefield)) {
for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
if (equals(ca)) {
continue;
}
@@ -4757,7 +4757,7 @@ public class Card extends GameEntity implements Comparable<Card> {
}
public final boolean canDamagePrevented(final boolean isCombat) {
CardCollection list = new CardCollection(getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command")));
CardCollection list = new CardCollection(getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES));
list.add(this);
for (final Card ca : list) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
@@ -4829,7 +4829,7 @@ public class Card extends GameEntity implements Comparable<Card> {
}
// Prevent Damage static abilities
for (final Card ca : getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
for (final Card ca : getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
restDamage = stAb.applyAbility("PreventDamage", source, this, restDamage, isCombat, isTest);
}
@@ -5580,9 +5580,8 @@ public class Card extends GameEntity implements Comparable<Card> {
}
// CantTarget static abilities
for (final Card ca : getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
final Iterable<StaticAbility> staticAbilities = ca.getStaticAbilities();
for (final StaticAbility stAb : staticAbilities) {
for (final Card ca : getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (stAb.applyAbility("CantTarget", this, sa)) {
return false;
}

View File

@@ -226,9 +226,8 @@ public class CombatUtil {
}
// CantAttack static abilities
for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
final FCollectionView<StaticAbility> staticAbilities = ca.getStaticAbilities();
for (final StaticAbility stAb : staticAbilities) {
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (stAb.applyAbility("CantAttack", attacker, defender)) {
return false;
}
@@ -276,9 +275,8 @@ public class CombatUtil {
final Cost attackCost = new Cost(ManaCost.ZERO, true);
boolean hasCost = false;
// Sort abilities to apply them in proper order
for (final Card card : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
final FCollectionView<StaticAbility> staticAbilities = card.getStaticAbilities();
for (final StaticAbility stAb : staticAbilities) {
for (final Card card : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : card.getStaticAbilities()) {
final Cost additionalCost = stAb.getAttackCost(attacker, defender);
if (null != additionalCost) {
attackCost.add(additionalCost);
@@ -1007,7 +1005,7 @@ public class CombatUtil {
}
// CantBlockBy static abilities
for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (stAb.applyAbility("CantBlockBy", attacker, blocker)) {
return false;

View File

@@ -48,7 +48,6 @@ import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.util.CollectionSuppliers;
import forge.util.TextUtil;
import forge.util.collect.FCollectionView;
import forge.util.maps.HashMapOfLists;
import forge.util.maps.MapOfLists;
import org.apache.commons.lang3.time.StopWatch;
@@ -761,10 +760,8 @@ public class PhaseHandler implements java.io.Serializable {
Cost blockCost = new Cost(ManaCost.ZERO, true);
// Sort abilities to apply them in proper order
boolean noCost = true;
List<ZoneType> checkZones = ZoneType.listValueOf("Battlefield,Command");
for (Card card : game.getCardsIn(checkZones)) {
final FCollectionView<StaticAbility> staticAbilities = card.getStaticAbilities();
for (final StaticAbility stAb : staticAbilities) {
for (Card card : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : card.getStaticAbilities()) {
Cost c1 = stAb.getBlockCost(blocker, attacker);
if (c1 != null) {
blockCost.add(c1);

View File

@@ -2,10 +2,11 @@ package forge.game.phase;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
public enum PhaseType {
@@ -82,32 +83,21 @@ public enum PhaseType {
throw new IllegalArgumentException("No element named " + value + " in enum PhaseType");
}
public static List<PhaseType> listValueOf(final String values) {
final List<PhaseType> result = new ArrayList<>();
for (final String s : values.split("[, ]+")) {
result.add(PhaseType.smartValueOf(s));
}
return result;
}
/**
* TODO: Write javadoc for this method.
* @param string
* @return
*/
public static List<PhaseType> parseRange(String values) {
final List<PhaseType> result = new ArrayList<>();
public static Set<PhaseType> parseRange(String values) {
final Set<PhaseType> result = EnumSet.noneOf(PhaseType.class);
for (final String s : values.split(",")) {
int idxArrow = s.indexOf("->");
if (idxArrow >= 0) {
PhaseType from = PhaseType.smartValueOf(s.substring(0, idxArrow));
String sTo = s.substring(idxArrow + 2);
PhaseType to = StringUtils.isBlank(sTo) ? PhaseType.CLEANUP : PhaseType.smartValueOf(sTo);
int iFrom = ALL_PHASES.indexOf(from);
int iTo = ALL_PHASES.indexOf(to);
for (int i = iFrom; i <= iTo; i++) {
result.add(ALL_PHASES.get(i));
}
result.addAll(EnumSet.range(from, to));
}
else {
result.add(PhaseType.smartValueOf(s));

View File

@@ -650,9 +650,8 @@ public class Player extends GameEntity implements Comparable<Player> {
int restDamage = damage;
// Prevent Damage static abilities
for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
final Iterable<StaticAbility> staticAbilities = ca.getStaticAbilities();
for (final StaticAbility stAb : staticAbilities) {
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
restDamage = stAb.applyAbility("PreventDamage", source, this, restDamage, isCombat, isTest);
}
}
@@ -888,7 +887,7 @@ public class Player extends GameEntity implements Comparable<Player> {
public final boolean canReceiveCounters(final CounterType type) {
// CantPutCounter static abilities
for (final Card ca : getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
for (final Card ca : getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (stAb.applyAbility("CantPutCounter", this, type)) {
return false;
@@ -1187,7 +1186,7 @@ public class Player extends GameEntity implements Comparable<Player> {
public final boolean canBeTargetedBy(final SpellAbility sa) {
// CantTarget static abilities
for (final Card ca : getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
for (final Card ca : getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (stAb.applyAbility("CantTarget", this, sa)) {
return false;
@@ -1723,9 +1722,8 @@ public class Player extends GameEntity implements Comparable<Player> {
}
// CantBeCast static abilities
for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
final Iterable<StaticAbility> staticAbilities = ca.getStaticAbilities();
for (final StaticAbility stAb : staticAbilities) {
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (stAb.applyAbility("CantPlayLand", land, this)) {
return false;
}

View File

@@ -149,17 +149,9 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
}
if (params.containsKey("ActivePhases")) {
boolean isPhase = false;
List<PhaseType> aPhases = PhaseType.parseRange(params.get("ActivePhases"));
final PhaseType currPhase = game.getPhaseHandler().getPhase();
for (final PhaseType s : aPhases) {
if (s == currPhase) {
isPhase = true;
break;
}
if (!PhaseType.parseRange(params.get("ActivePhases")).contains(game.getPhaseHandler().getPhase())) {
return false;
}
return isPhase;
}
return meetsCommonRequirements(params);

View File

@@ -88,7 +88,7 @@ public abstract class AbilityActivated extends SpellAbility implements java.io.S
final Card c = this.getHostCard();
// CantBeActivated static abilities
for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
final FCollectionView<StaticAbility> staticAbilities = ca.getStaticAbilities();
for (final StaticAbility stAb : staticAbilities) {
if (stAb.applyAbility("CantBeActivated", c, this)) {

View File

@@ -191,7 +191,7 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
Player activator = getActivatingPlayer();
final Game game = activator.getGame();
// CantBeCast static abilities
final CardCollection allp = new CardCollection(game.getCardsIn(ZoneType.listValueOf("Battlefield,Command")));
final CardCollection allp = new CardCollection(game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES));
allp.add(source);
for (final Card ca : allp) {
final FCollectionView<StaticAbility> staticAbilities = ca.getStaticAbilities();

View File

@@ -17,7 +17,6 @@
*/
package forge.game.spellability;
import com.google.common.collect.Lists;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.GameObject;
@@ -146,12 +145,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
}
if (params.containsKey("ConditionGameTypes")) {
String[] gameTypeNames = params.get("ConditionGameTypes").split(",");
List<GameType> gameTypes = Lists.newArrayList();
for (String name : gameTypeNames) {
gameTypes.add(GameType.smartValueOf(name));
}
this.setGameTypes(gameTypes);
this.setGameTypes(GameType.listValueOf(params.get("ConditionGameTypes")));
}
if (params.containsKey("ConditionChosenColor")) {
@@ -315,23 +309,13 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
}
if (this.getPhases().size() > 0) {
boolean isPhase = false;
final PhaseType currPhase = phase.getPhase();
for (final PhaseType s : this.getPhases()) {
if (s == currPhase) {
isPhase = true;
break;
}
}
if (!isPhase) {
if (!this.getPhases().contains(phase.getPhase())) {
return false;
}
}
if (this.getGameTypes().size() > 0) {
GameType currGameType = sa.getHostCard().getGame().getRules().getGameType();
if (!getGameTypes().contains(currGameType)) {
if (!getGameTypes().contains(game.getRules().getGameType())) {
return false;
}
}

View File

@@ -20,7 +20,6 @@ package forge.game.spellability;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import forge.game.Game;
import forge.game.GameType;
import forge.game.ability.AbilityUtils;
@@ -144,12 +143,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
}
if (params.containsKey("ActivationGameTypes")) {
String[] gameTypeNames = params.get("ActivationGameTypes").split(",");
List<GameType> gameTypes = Lists.newArrayList();
for (String name : gameTypeNames) {
gameTypes.add(GameType.smartValueOf(name));
}
this.setGameTypes(gameTypes);
this.setGameTypes(GameType.listValueOf(params.get("ActivationGameTypes")));
}
if (params.containsKey("ActivationCardsInHand")) {
@@ -313,16 +307,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
}
if (this.getPhases().size() > 0) {
boolean isPhase = false;
final PhaseType currPhase = game.getPhaseHandler().getPhase();
for (final PhaseType s : this.getPhases()) {
if (s == currPhase) {
isPhase = true;
break;
}
}
if (!isPhase) {
if (!this.getPhases().contains(game.getPhaseHandler().getPhase())) {
return false;
}
}

View File

@@ -17,9 +17,10 @@
*/
package forge.game.spellability;
import java.util.List;
import java.util.EnumSet;
import java.util.Set;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import forge.game.GameType;
import forge.game.phase.PhaseType;
@@ -56,7 +57,8 @@ public class SpellAbilityVariables implements Cloneable {
*/
public void setVariables(SpellAbilityVariables sav) {
this.zone = sav.getZone();
this.phases = Lists.newArrayList(sav.getPhases());
this.phases = Sets.newEnumSet(sav.getPhases(), PhaseType.class);
this.gameTypes = Sets.newEnumSet(sav.getGameTypes(), GameType.class);
this.sorcerySpeed = sav.isSorcerySpeed();
this.instantSpeed = sav.isInstantSpeed();
this.anyPlayer = sav.isAnyPlayer();
@@ -97,10 +99,10 @@ public class SpellAbilityVariables implements Cloneable {
private ZoneType zone = ZoneType.Battlefield;
/** The phases. */
private List<PhaseType> phases = Lists.newArrayList();
private Set<PhaseType> phases = EnumSet.noneOf(PhaseType.class);
/** The GameTypes */
private List<GameType> gameTypes = Lists.newArrayList();
private Set<GameType> gameTypes = EnumSet.noneOf(GameType.class);
/** The b sorcery speed. */
private boolean sorcerySpeed = false;
@@ -386,7 +388,7 @@ public class SpellAbilityVariables implements Cloneable {
* @param phases
* a {@link java.lang.String} object.
*/
public final void setPhases(final List<PhaseType> phases) {
public final void setPhases(final Set<PhaseType> phases) {
this.phases.addAll(phases);
}
@@ -397,7 +399,7 @@ public class SpellAbilityVariables implements Cloneable {
*
* @param gameTypes
*/
public final void setGameTypes(final List<GameType> gameTypes) {
public final void setGameTypes(final Set<GameType> gameTypes) {
this.gameTypes.clear();
this.gameTypes.addAll(gameTypes);
}
@@ -698,7 +700,7 @@ public class SpellAbilityVariables implements Cloneable {
*
* @return the phases
*/
public final List<PhaseType> getPhases() {
public final Set<PhaseType> getPhases() {
return this.phases;
}
@@ -707,7 +709,7 @@ public class SpellAbilityVariables implements Cloneable {
*
* @return the phases
*/
public final List<GameType> getGameTypes() {
public final Set<GameType> getGameTypes() {
return this.gameTypes;
}

View File

@@ -623,8 +623,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
}
if (hasParam("Phases")) {
List<PhaseType> phases = PhaseType.parseRange(getParam("Phases"));
if (!phases.contains(ph.getPhase())) {
if (!PhaseType.parseRange(getParam("Phases")).contains(ph.getPhase())) {
return false;
}
}

View File

@@ -37,6 +37,7 @@ import forge.game.zone.ZoneType;
import java.util.*;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import forge.util.TextUtil;
@@ -104,7 +105,7 @@ public abstract class Trigger extends TriggerReplacementBase {
}
private List<PhaseType> validPhases;
private Set<PhaseType> validPhases;
/**
* <p>
@@ -558,7 +559,7 @@ public abstract class Trigger extends TriggerReplacementBase {
}
if (validPhases != null) {
copy.setTriggerPhases(Lists.newArrayList(validPhases));
copy.setTriggerPhases(Sets.newEnumSet(validPhases, PhaseType.class));
}
copy.setActiveZone(validHostZones);
return copy;
@@ -568,7 +569,7 @@ public abstract class Trigger extends TriggerReplacementBase {
return hasParam("Static"); // && params.get("Static").equals("True") [always true if present]
}
public void setTriggerPhases(List<PhaseType> phases) {
public void setTriggerPhases(Set<PhaseType> phases) {
validPhases = phases;
}

View File

@@ -65,7 +65,7 @@ public class PlayerZoneBattlefield extends PlayerZone {
if (trigger) {
// ETBTapped static abilities
for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (stAb.applyAbility("ETBTapped", c)) {
// it enters the battlefield this way, and should

View File

@@ -295,4 +295,8 @@ public class ImageCache {
return output;
}
public static boolean isDefaultImage(BufferedImage image) {
return _defaultImage.equals(image);
}
}

View File

@@ -17,16 +17,8 @@
*/
package forge.gui;
import java.awt.BorderLayout;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import javax.swing.JPanel;
import forge.GuiBase;
import forge.ImageCache;
import forge.util.ImageFetcher;
import forge.ImageKeys;
import forge.game.card.CardView.CardStateView;
import forge.item.InventoryItem;
@@ -37,6 +29,13 @@ import forge.toolbox.CardFaceSymbols;
import forge.toolbox.imaging.FImagePanel;
import forge.toolbox.imaging.FImagePanel.AutoSizeImageMode;
import forge.toolbox.imaging.FImageUtil;
import forge.util.ImageFetcher;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
/**
* Displays image associated with a card or inventory item.
@@ -111,7 +110,11 @@ public final class CardPicturePanel extends JPanel implements ImageFetcher.Callb
if (displayed instanceof InventoryItem) {
final InventoryItem item = (InventoryItem) displayed;
return ImageCache.getOriginalImage(item.getImageKey(false), true);
BufferedImage image = ImageCache.getOriginalImage(item.getImageKey(false), true);
if (ImageCache.isDefaultImage(image) && item instanceof PaperCard) {
GuiBase.getInterface().getImageFetcher().fetchImage(item.getImageKey(false), this);
}
return image;
} else if (displayed instanceof CardStateView) {
CardStateView card = (CardStateView) displayed;
BufferedImage image = ImageCache.getOriginalImage(card.getImageKey(), false);

View File

@@ -4,6 +4,6 @@ Types:Land Island
R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplaceWith$ LandTapped | Description$ CARDNAME enters the battlefield tapped unless you control three or more other Islands.
SVar:LandTapped:DB$ Tap | Defined$ Self | ETB$ True | ConditionPresent$ Island.YouCtrl+Other | ConditionCompare$ LT3 | SubAbility$ MoveToPlay
SVar:MoveToPlay:DB$ ChangeZone | Defined$ Self | Origin$ All | Destination$ Battlefield
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self+untapped | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may put target instant or sorcery card from your graveyard on top of your library.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self+untapped | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield untapped, you may put target instant or sorcery card from your graveyard on top of your library.
SVar:TrigChange:DB$ ChangeZone | TgtPrompt$ Choose target instant or sorcery card in your graveyard | ValidTgts$ Instant.YouOwn,Sorcery.YouOwn | Origin$ Graveyard | Destination$ Library
Oracle:({T}: Add {U}.)\nMystic Sanctuary enters the battlefield tapped unless you control three or more other Islands.\nWhen Mystic Sanctuary enters the battlefield untapped, you may put target instant or sorcery card from your graveyard on top of your library.