dPChHand = CardLists.getValidCards(dPHand, valid.split(","), source.getController(), source, sa);
+ CardCollectionView dPChHand = CardLists.getValidCards(dPHand, valid.split(","), source.getController(), source, sa);
dPChHand = CardLists.filter(dPChHand, Presets.NON_TOKEN);
+ if (dPChHand.size() > 1 && p.getGame().isGraveyardOrdered()) {
+ dPChHand = GameActionUtil.orderCardsByTheirOwners(p.getGame(), dPChHand, ZoneType.Graveyard);
+ }
+
// Reveal cards that will be discarded?
for (final Card c : dPChHand) {
p.discard(c, sa);
@@ -263,6 +293,10 @@ public class DiscardEffect extends SpellAbilityEffect {
CardCollectionView toBeDiscarded = validCards.isEmpty() ? null : chooser.getController().chooseCardsToDiscardFrom(p, sa, validCards, min, max);
+ if (toBeDiscarded.size() > 1 && p.getGame().isGraveyardOrdered()) {
+ toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(p.getGame(), toBeDiscarded, ZoneType.Graveyard);
+ }
+
if (toBeDiscarded != null) {
if (mode.startsWith("Reveal") ) {
p.getController().reveal(toBeDiscarded, ZoneType.Hand, p,
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java
index 77992501..34956a33 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java
@@ -38,7 +38,7 @@ public class ReorderZoneEffect extends SpellAbilityEffect {
p.getZone(zone).setCards(list);
}
else {
- p.getController().orderMoveToZoneList(list, zone);
+ p.getController().orderMoveToZoneList(list, zone, sa);
}
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java
index be9958b0..45fdd9cc 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java
@@ -1,13 +1,10 @@
package forge.game.ability.effects;
import forge.game.Game;
+import forge.game.GameActionUtil;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
-import forge.game.card.Card;
-import forge.game.card.CardCollectionView;
-import forge.game.card.CardLists;
-import forge.game.card.CardPredicates;
-import forge.game.card.CardUtil;
+import forge.game.card.*;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
@@ -62,6 +59,10 @@ public class SacrificeAllEffect extends SpellAbilityEffect {
card.clearRemembered();
}
+ if (list.size() > 1 && game.isGraveyardOrdered()) {
+ list = GameActionUtil.orderCardsByTheirOwners(game, list, ZoneType.Graveyard);
+ }
+
for (Card sac : list) {
final Card lKICopy = CardUtil.getLKICopy(sac);
if (game.getAction().sacrifice(sac, sa) != null && remSacrificed) {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java
index f6b108b2..32af45a1 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java
@@ -1,16 +1,12 @@
package forge.game.ability.effects;
+import com.google.common.collect.Maps;
import forge.card.mana.ManaCost;
import forge.game.Game;
+import forge.game.GameActionUtil;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
-import forge.game.card.Card;
-import forge.game.card.CardCollection;
-import forge.game.card.CardCollectionView;
-import forge.game.card.CardLists;
-import forge.game.card.CardPredicates;
-import forge.game.card.CardUtil;
-import forge.game.card.CounterType;
+import forge.game.card.*;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.player.PlayerController.ManaPaymentPurpose;
@@ -18,14 +14,11 @@ import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
+import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
-import org.apache.commons.lang3.StringUtils;
-
-import com.google.common.collect.Maps;
-
public class SacrificeEffect extends SpellAbilityEffect {
@Override
@@ -136,6 +129,10 @@ public class SacrificeEffect extends SpellAbilityEffect {
}
}
+ if (choosenToSacrifice.size() > 1 && game.isGraveyardOrdered()) {
+ choosenToSacrifice = GameActionUtil.orderCardsByTheirOwners(game, choosenToSacrifice, ZoneType.Graveyard);
+ }
+
for (Card sac : choosenToSacrifice) {
final Card lKICopy = CardUtil.getLKICopy(sac);
boolean wasSacrificed = !destroy && game.getAction().sacrifice(sac, sa) != null;
diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java
index 6c204487..26bf8ec9 100644
--- a/forge-game/src/main/java/forge/game/player/Player.java
+++ b/forge-game/src/main/java/forge/game/player/Player.java
@@ -17,60 +17,19 @@
*/
package forge.game.player;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Random;
-import java.util.TreeMap;
-import java.util.concurrent.ConcurrentSkipListMap;
-
-import forge.util.TextUtil;
-import org.apache.commons.lang3.tuple.ImmutablePair;
-
import com.google.common.base.Function;
import com.google.common.base.Predicates;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
+import com.google.common.collect.*;
import forge.LobbyPlayer;
import forge.card.MagicColor;
-import forge.game.Game;
-import forge.game.GameEntity;
-import forge.game.GameLogEntryType;
-import forge.game.GameStage;
-import forge.game.GameType;
-import forge.game.GlobalRuleChange;
+import forge.game.*;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.DetachedCardEffect;
-import forge.game.card.Card;
-import forge.game.card.CardCollection;
-import forge.game.card.CardCollectionView;
-import forge.game.card.CardDamageMap;
-import forge.game.card.CardFactoryUtil;
-import forge.game.card.CardLists;
-import forge.game.card.CardPredicates;
+import forge.game.card.*;
import forge.game.card.CardPredicates.Presets;
-import forge.game.card.CardUtil;
-import forge.game.card.CounterType;
-import forge.game.event.GameEventCardSacrificed;
-import forge.game.event.GameEventLandPlayed;
-import forge.game.event.GameEventMulligan;
-import forge.game.event.GameEventPlayerControl;
-import forge.game.event.GameEventPlayerCounters;
-import forge.game.event.GameEventPlayerDamaged;
-import forge.game.event.GameEventPlayerLivesChanged;
-import forge.game.event.GameEventPlayerPoisoned;
-import forge.game.event.GameEventPlayerStatsChanged;
-import forge.game.event.GameEventScry;
-import forge.game.event.GameEventShuffle;
+import forge.game.event.*;
import forge.game.keyword.KeywordCollection;
import forge.game.keyword.KeywordCollection.KeywordCollectionView;
import forge.game.keyword.KeywordsChange;
@@ -93,8 +52,14 @@ import forge.item.PaperCard;
import forge.util.Aggregates;
import forge.util.Lang;
import forge.util.MyRandom;
+import forge.util.TextUtil;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentSkipListMap;
/**
*
@@ -1591,12 +1556,22 @@ public class Player extends GameEntity implements Comparable {
for (int i = 0; i < max; i++) {
if (bottom) {
- milled.add(game.getAction().moveTo(destination, lib.getLast(), null));
+ milled.add(lib.get(lib.size() - i - 1));
}
else {
- milled.add(game.getAction().moveTo(destination, lib.getFirst(), null));
+ milled.add(lib.get(i));
}
}
+
+ CardCollectionView milledView = milled;
+ if (destination == ZoneType.Graveyard && milled.size() > 1 && game.isGraveyardOrdered()) {
+ milledView = GameActionUtil.orderCardsByTheirOwners(game, milled, ZoneType.Graveyard);
+ }
+
+ for (Card m : milledView) {
+ game.getAction().moveTo(destination, m, null);
+ }
+
return milled;
}
diff --git a/forge-game/src/main/java/forge/game/player/PlayerController.java b/forge-game/src/main/java/forge/game/player/PlayerController.java
index b2e08fa1..fcb2ee1d 100644
--- a/forge-game/src/main/java/forge/game/player/PlayerController.java
+++ b/forge-game/src/main/java/forge/game/player/PlayerController.java
@@ -1,16 +1,8 @@
package forge.game.player;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.commons.lang3.tuple.ImmutablePair;
-import org.apache.commons.lang3.tuple.Pair;
-
import com.google.common.base.Predicate;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
-
import forge.LobbyPlayer;
import forge.card.ColorSet;
import forge.card.ICardFace;
@@ -22,28 +14,25 @@ import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.GameOutcome.AnteResult;
import forge.game.GameType;
-import forge.game.card.Card;
-import forge.game.card.CardCollection;
-import forge.game.card.CardCollectionView;
-import forge.game.card.CardShields;
-import forge.game.card.CardView;
-import forge.game.card.CounterType;
+import forge.game.card.*;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostPartMana;
import forge.game.mana.Mana;
import forge.game.replacement.ReplacementEffect;
-import forge.game.spellability.AbilitySub;
-import forge.game.spellability.OptionalCostValue;
-import forge.game.spellability.SpellAbility;
-import forge.game.spellability.SpellAbilityStackInstance;
-import forge.game.spellability.TargetChoices;
+import forge.game.spellability.*;
import forge.game.trigger.WrappedAbility;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
-import forge.util.collect.FCollectionView;
import forge.util.ITriggerEvent;
+import forge.util.collect.FCollectionView;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
/**
* A prototype for player controller class
@@ -149,7 +138,10 @@ public abstract class PlayerController {
public abstract void notifyOfValue(SpellAbility saSource, GameObject realtedTarget, String value);
public abstract ImmutablePair arrangeForScry(CardCollection topN);
public abstract boolean willPutCardOnTop(Card c);
- public abstract CardCollectionView orderMoveToZoneList(CardCollectionView cards, ZoneType destinationZone);
+ public final CardCollectionView orderMoveToZoneList(CardCollectionView cards, ZoneType destinationZone) {
+ return orderMoveToZoneList(cards, destinationZone, null);
+ }
+ public abstract CardCollectionView orderMoveToZoneList(CardCollectionView cards, ZoneType destinationZone, SpellAbility source);
/** p = target player, validCards - possible discards, min cards to discard */
public abstract CardCollectionView chooseCardsToDiscardFrom(Player playerDiscard, SpellAbility sa, CardCollection validCards, int min, int max);
diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java
index 4ef999e5..92919626 100644
--- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java
+++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java
@@ -120,6 +120,7 @@ public enum CSubmenuPreferences implements ICDoc {
lstControls.add(Pair.of(view.getCbEscapeEndsTurn(), FPref.UI_ALLOW_ESC_TO_END_TURN));
lstControls.add(Pair.of(view.getCbDetailedPaymentDesc(), FPref.UI_DETAILED_SPELLDESC_IN_PROMPT));
lstControls.add(Pair.of(view.getCbPreselectPrevAbOrder(), FPref.UI_PRESELECT_PREVIOUS_ABILITY_ORDER));
+ lstControls.add(Pair.of(view.getCbAllowOrderingGraveyard(), FPref.UI_ALLOW_ORDER_GRAVEYARD_WHEN_NEEDED));
lstControls.add(Pair.of(view.getCbShowStormCount(), FPref.UI_SHOW_STORM_COUNT_IN_PROMPT));
lstControls.add(Pair.of(view.getCbFilterLandsByColorId(), FPref.UI_FILTER_LANDS_BY_COLOR_IDENTITY));
diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java
index ceea3a02..af63ac34 100644
--- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java
+++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java
@@ -87,6 +87,7 @@ public enum VSubmenuPreferences implements IVSubmenu {
private final JCheckBox cbCompactPrompt = new OptionsCheckBox("Compact Prompt");
private final JCheckBox cbEscapeEndsTurn = new OptionsCheckBox("Use Escape Key to End Turn");
private final JCheckBox cbPreselectPrevAbOrder = new OptionsCheckBox("Preselect Last Order of Abilities");
+ private final JCheckBox cbAllowOrderingGraveyard = new OptionsCheckBox("Allow Ordering Graveyard if Needed");
private final JCheckBox cbHideReminderText = new OptionsCheckBox("Hide Reminder Text");
private final JCheckBox cbOpenPacksIndiv = new OptionsCheckBox("Open Packs Individually");
private final JCheckBox cbTokensInSeparateRow = new OptionsCheckBox("Display Tokens in a Separate Row");
@@ -188,6 +189,9 @@ public enum VSubmenuPreferences implements IVSubmenu {
pnlPrefs.add(cbPreselectPrevAbOrder, titleConstraints);
pnlPrefs.add(new NoteLabel("When enabled, preselects the last defined simultaneous ability order in the ordering dialog."), descriptionConstraints);
+ pnlPrefs.add(cbAllowOrderingGraveyard, titleConstraints);
+ pnlPrefs.add(new NoteLabel("When enabled, allows to choose the order of cards going to graveyard when playing with cards for which it matters (for example, Volrath's Shapeshifter)."), descriptionConstraints);
+
pnlPrefs.add(cbpAutoYieldMode, comboBoxConstraints);
pnlPrefs.add(new NoteLabel("Defines the granularity level of auto-yields (per unique ability or per unique card)."), descriptionConstraints);
@@ -684,6 +688,10 @@ public enum VSubmenuPreferences implements IVSubmenu {
return cbPreselectPrevAbOrder;
}
+ public final JCheckBox getCbAllowOrderingGraveyard() {
+ return cbAllowOrderingGraveyard;
+ }
+
/** @return {@link forge.toolbox.FLabel} */
public FLabel getBtnReset() {
return btnReset;
diff --git a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java
index d7bc7baa..57b8913c 100644
--- a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java
+++ b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java
@@ -1,19 +1,9 @@
package forge.gamesimulationtests.util;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.commons.lang3.tuple.ImmutablePair;
-import org.apache.commons.lang3.tuple.Pair;
-import org.testng.collections.Lists;
-
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
-
import forge.LobbyPlayer;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilMana;
@@ -33,12 +23,7 @@ import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.GameType;
import forge.game.ability.AbilityUtils;
-import forge.game.card.Card;
-import forge.game.card.CardCollection;
-import forge.game.card.CardCollectionView;
-import forge.game.card.CardShields;
-import forge.game.card.CardView;
-import forge.game.card.CounterType;
+import forge.game.card.*;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.Cost;
@@ -46,34 +31,29 @@ import forge.game.cost.CostPart;
import forge.game.cost.CostPartMana;
import forge.game.mana.Mana;
import forge.game.mana.ManaCostBeingPaid;
-import forge.game.player.DelayedReveal;
-import forge.game.player.Player;
-import forge.game.player.PlayerActionConfirmMode;
-import forge.game.player.PlayerController;
-import forge.game.player.PlayerView;
+import forge.game.player.*;
import forge.game.replacement.ReplacementEffect;
-import forge.game.spellability.AbilitySub;
-import forge.game.spellability.OptionalCostValue;
-import forge.game.spellability.Spell;
-import forge.game.spellability.SpellAbility;
-import forge.game.spellability.SpellAbilityStackInstance;
-import forge.game.spellability.TargetChoices;
+import forge.game.spellability.*;
import forge.game.trigger.WrappedAbility;
import forge.game.zone.ZoneType;
import forge.gamesimulationtests.util.card.CardSpecification;
import forge.gamesimulationtests.util.card.CardSpecificationHandler;
import forge.gamesimulationtests.util.player.PlayerSpecification;
import forge.gamesimulationtests.util.player.PlayerSpecificationHandler;
-import forge.gamesimulationtests.util.playeractions.ActivateAbilityAction;
-import forge.gamesimulationtests.util.playeractions.CastSpellFromHandAction;
-import forge.gamesimulationtests.util.playeractions.DeclareAttackersAction;
-import forge.gamesimulationtests.util.playeractions.DeclareBlockersAction;
-import forge.gamesimulationtests.util.playeractions.PlayerActions;
+import forge.gamesimulationtests.util.playeractions.*;
import forge.item.PaperCard;
import forge.player.HumanPlay;
-import forge.util.collect.FCollectionView;
import forge.util.ITriggerEvent;
import forge.util.MyRandom;
+import forge.util.collect.FCollectionView;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.testng.collections.Lists;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
/**
* Default harmless implementation for tests.
@@ -265,7 +245,7 @@ public class PlayerControllerForTests extends PlayerController {
}
@Override
- public CardCollectionView orderMoveToZoneList(CardCollectionView cards, ZoneType destinationZone) {
+ public CardCollectionView orderMoveToZoneList(CardCollectionView cards, ZoneType destinationZone, SpellAbility source) {
return cards;
}
diff --git a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java
index 020a22a9..e418b703 100644
--- a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java
+++ b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java
@@ -1,7 +1,6 @@
package forge.screens.settings;
import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment;
-
import forge.Forge;
import forge.Graphics;
import forge.ai.AiProfileUtil;
@@ -134,6 +133,10 @@ public class SettingsPage extends TabPage {
"Preselect Last Order of Abilities",
"When enabled, preselects the last defined simultaneous ability order in the ordering dialog."),
1);
+ lstSettings.addItem(new BooleanSetting(FPref.UI_ALLOW_ORDER_GRAVEYARD_WHEN_NEEDED,
+ "Allow Ordering Graveyard if Needed",
+ "When enabled, allows to order cards going to graveyard when playing with cards for which it matters."),
+ 1);
lstSettings.addItem(new CustomSelectSetting(FPref.UI_AUTO_YIELD_MODE,
"Auto-Yield",
"Defines the granularity level of auto-yields (yield to each unique ability or to each unique card).",
diff --git a/forge-gui/release-files/CHANGES.txt b/forge-gui/release-files/CHANGES.txt
index 4e43238f..aa5caab7 100644
--- a/forge-gui/release-files/CHANGES.txt
+++ b/forge-gui/release-files/CHANGES.txt
@@ -1,6 +1,9 @@
- Ixalan -
All 279 cards of the newly released Ixalan set are available in Forge. We've done our best to fix the issues that you reported with the cards in the pre-release version. If you still see anything wrong with the new cards, don't hesitate to report!
+- Allow Ordering Graveyard if Needed (option) -
+A new option is available in Forge that makes the game offer you to order the cards as they go into graveyard if, for example, several cards are destroyed, sacrificed or milled at the same time. When enabled, this option only takes effect in case there is at least one card in at least one player's library that cares about the order of cards in graveyard (currently the following cards are marked as caring about graveyard order: Nether Shadow, Spinning Darkness, Corpse Dance, Shallow Grave, Phyrexian Furnace, Krovikan Horror, Volrath's Shapeshifter, Ashen Ghoul, Phyrexian Grimoire, Nature's Kiss, Soldevi Digger, Guiding Spirit, Barrow Ghoul, Circling Vultures, Zombie Scavengers, Necratog, Mistmoon Griffin, Bone Dancer). Note that this option does not affect cards that reorder the graveyard as a part of their effect (Fossil Find). If this option is disabled, then no ordering is performed for cards like Volrath's Shapeshifter and the cards go into graveyards in whatever order the game automatically determines them to do so (this is the original Forge behavior). This mechanism is not perfect yet (please report cases in which you were not allowed to order cards in the graveyard, as well as any strange behavior in corner cases, e.g. when some permanents are indestructible, etc.). This option is disabled by default.
+
- Desktop Forge: Personal Card Ratings in Quest Mode -
In Desktop Forge, it is now possible to assign personal ratings (from 1 star to 5 stars) to cards in Quest Mode by right clicking them and choosing the relevant context menu entry. It is then possible to filter cards by rating in both the deck editor and the quest shop, which should simplify managing bigger inventories. This patch was provided by Seravy.
diff --git a/forge-gui/res/cardsfolder/a/ashen_ghoul.txt b/forge-gui/res/cardsfolder/a/ashen_ghoul.txt
index 2e72cb4b..11e477fa 100644
--- a/forge-gui/res/cardsfolder/a/ashen_ghoul.txt
+++ b/forge-gui/res/cardsfolder/a/ashen_ghoul.txt
@@ -4,5 +4,6 @@ Types:Creature Zombie
PT:3/1
K:Haste
A:AB$ ChangeZone | Cost$ B | Defined$ Self | Origin$ Graveyard | Destination$ Battlefield | ActivationZone$ Graveyard | ActivationPhases$ Upkeep | PlayerTurn$ True | IsPresent$ Creature.YouOwn+Above | PresentZone$ Graveyard | PresentCompare$ GE3 | SpellDescription$ Return CARDNAME from your graveyard to the battlefield. Activate this ability only during your upkeep and only if three or more creature cards are above CARDNAME.
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/ashen_ghoul.jpg
Oracle:Haste\n{B}: Return Ashen Ghoul from your graveyard to the battlefield. Activate this ability only during your upkeep and only if three or more creature cards are above Ashen Ghoul.
diff --git a/forge-gui/res/cardsfolder/b/barrow_ghoul.txt b/forge-gui/res/cardsfolder/b/barrow_ghoul.txt
index 9e5e0cd8..95059b4f 100644
--- a/forge-gui/res/cardsfolder/b/barrow_ghoul.txt
+++ b/forge-gui/res/cardsfolder/b/barrow_ghoul.txt
@@ -5,5 +5,6 @@ PT:4/4
K:UpkeepCost:ExileFromGrave<1/Card.TopGraveyardCreature>
SVar:NeedsToPlayVar:Y GE2
SVar:Y:Count$TypeInYourYard.Creature
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/barrow_ghoul.jpg
Oracle:At the beginning of your upkeep, sacrifice Barrow Ghoul unless you exile the top creature card of your graveyard.
diff --git a/forge-gui/res/cardsfolder/b/bone_dancer.txt b/forge-gui/res/cardsfolder/b/bone_dancer.txt
index 541cb345..0f5622b1 100644
--- a/forge-gui/res/cardsfolder/b/bone_dancer.txt
+++ b/forge-gui/res/cardsfolder/b/bone_dancer.txt
@@ -5,5 +5,6 @@ PT:2/2
T:Mode$ AttackerUnblocked | ValidCard$ Card.Self | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ DBChangeZone | TriggerDescription$ Whenever CARDNAME attacks and isn't blocked, you may put the top creature card of defending player's graveyard onto the battlefield under your control. If you do, CARDNAME assigns no combat damage this turn.
SVar:DBChangeZone:DB$ChangeZoneAll | Cost$ 0 | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Creature.TopGraveyardCreature+DefenderCtrl | GainControl$ True | ChangeNum$ 1 | SubAbility$ DBNoCombatDamage
SVar:DBNoCombatDamage:DB$Pump | Defined$ Self | KW$ HIDDEN CARDNAME assigns no combat damage
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/bone_dancer.jpg
Oracle:Whenever Bone Dancer attacks and isn't blocked, you may put the top creature card of defending player's graveyard onto the battlefield under your control. If you do, Bone Dancer assigns no combat damage this turn.
diff --git a/forge-gui/res/cardsfolder/c/circling_vultures.txt b/forge-gui/res/cardsfolder/c/circling_vultures.txt
index 8f2ff229..611198e8 100644
--- a/forge-gui/res/cardsfolder/c/circling_vultures.txt
+++ b/forge-gui/res/cardsfolder/c/circling_vultures.txt
@@ -6,5 +6,6 @@ K:Flying
K:UpkeepCost:ExileFromGrave<1/Card.TopGraveyardCreature>
A:ST$ Discard | Cost$ 0 | Mode$ Defined | DefinedCards$ Self | Optional$ True | DiscardMessage$ Do you want discard this card? | ActivationZone$ Hand | InstantSpeed$ True | SpellDescription$ You may discard CARDNAME any time you could cast an instant.
SVar:RemAIDeck:True
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/circling_vultures.jpg
Oracle:Flying\nYou may discard Circling Vultures any time you could cast an instant.\nAt the beginning of your upkeep, sacrifice Circling Vultures unless you exile the top creature card of your graveyard.
diff --git a/forge-gui/res/cardsfolder/c/corpse_dance.txt b/forge-gui/res/cardsfolder/c/corpse_dance.txt
index 4d070147..d5af017d 100644
--- a/forge-gui/res/cardsfolder/c/corpse_dance.txt
+++ b/forge-gui/res/cardsfolder/c/corpse_dance.txt
@@ -6,5 +6,6 @@ A:SP$ ChangeZone | Cost$ 2 B | Origin$ Graveyard | Destination$ Battlefield | Ch
SVar:DBPump:DB$ Animate | Keywords$ Haste | sVars$ SneakAttackEOT | Defined$ Remembered | AtEOT$ Exile | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:SneakAttackEOT:SVar:EndOfTurnLeavePlay:True
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/corpse_dance.jpg
Oracle:Buyback {2} (You may pay an additional {2} as you cast this spell. If you do, put this card into your hand as it resolves.)\nReturn the top creature card of your graveyard to the battlefield. That creature gains haste until end of turn. Exile it at the beginning of the next end step.
diff --git a/forge-gui/res/cardsfolder/g/guiding_spirit.txt b/forge-gui/res/cardsfolder/g/guiding_spirit.txt
index 449e542b..4f3a102f 100644
--- a/forge-gui/res/cardsfolder/g/guiding_spirit.txt
+++ b/forge-gui/res/cardsfolder/g/guiding_spirit.txt
@@ -5,5 +5,6 @@ PT:1/2
K:Flying
A:AB$ ChangeZoneAll | Cost$ T | ValidTgts$ Player | TgtPrompt$ Select target player | Origin$ Graveyard | Destination$ Library | ChangeType$ Card.TopGraveyardCreature | SpellDescription$ If the top card of target player's graveyard is a creature card, put that card on top of that player's library.
SVar:RemAIDeck:True
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/guiding_spirit.jpg
Oracle:Flying\n{T}: If the top card of target player's graveyard is a creature card, put that card on top of that player's library.
diff --git a/forge-gui/res/cardsfolder/k/krovikan_horror.txt b/forge-gui/res/cardsfolder/k/krovikan_horror.txt
index feeffd39..8f688b17 100644
--- a/forge-gui/res/cardsfolder/k/krovikan_horror.txt
+++ b/forge-gui/res/cardsfolder/k/krovikan_horror.txt
@@ -7,5 +7,6 @@ SVar:TrigReturn:AB$ChangeZone | Cost$ 0 | Defined$ Self | Origin$ Graveyard | De
A:AB$ DealDamage | Cost$ 1 Sac<1/Creature> | ValidTgts$ Creature,Player | TgtPrompt$ Select target creature or player | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to target creature or player.
SVar:DiscardMe:3
SVar:SacMe:1
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/krovikan_horror.jpg
Oracle:At the beginning of the end step, if Krovikan Horror is in your graveyard with a creature card directly above it, you may return Krovikan Horror to your hand.\n{1}, Sacrifice a creature: Krovikan Horror deals 1 damage to target creature or player.
diff --git a/forge-gui/res/cardsfolder/m/mistmoon_griffin.txt b/forge-gui/res/cardsfolder/m/mistmoon_griffin.txt
index 6fd6b843..f646e1e8 100644
--- a/forge-gui/res/cardsfolder/m/mistmoon_griffin.txt
+++ b/forge-gui/res/cardsfolder/m/mistmoon_griffin.txt
@@ -6,5 +6,6 @@ K:Flying
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigExileMe | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, exile CARDNAME, then return the top creature card of your graveyard to the battlefield.
SVar:TrigExileMe:AB$ ChangeZone | Cost$ 0 | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Exile | SubAbility$ DBReturnCreature
SVar:DBReturnCreature:DB$ ChangeZoneAll | ChangeType$ Creature.YouOwn+TopGraveyardCreature | Origin$ Graveyard | Destination$ Battlefield
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/mistmoon_griffin.jpg
Oracle:Flying\nWhen Mistmoon Griffin dies, exile Mistmoon Griffin, then return the top creature card of your graveyard to the battlefield.
diff --git a/forge-gui/res/cardsfolder/n/natures_kiss.txt b/forge-gui/res/cardsfolder/n/natures_kiss.txt
index 9bdbe4cd..7d595d35 100644
--- a/forge-gui/res/cardsfolder/n/natures_kiss.txt
+++ b/forge-gui/res/cardsfolder/n/natures_kiss.txt
@@ -6,5 +6,6 @@ A:SP$ Attach | Cost$ 1 G | ValidTgts$ Creature | AILogic$ Pump
A:AB$ Pump | Cost$ 1 ExileFromGrave<1/Card.TopGraveyard> | Defined$ Enchanted | NumAtt$ +1 | NumDef$ +1 | CostDesc$ {1}, Exile the top card of your graveyard: | SpellDescription$ Enchanted creature gets +1/+1 until end of turn.
SVar:RemAIDeck:True
SVar:NonStackingAttachEffect:True
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/natures_kiss.jpg
Oracle:Enchant creature\n{1}, Exile the top card of your graveyard: Enchanted creature gets +1/+1 until end of turn.
diff --git a/forge-gui/res/cardsfolder/n/necratog.txt b/forge-gui/res/cardsfolder/n/necratog.txt
index 8a67eca7..74289c7f 100644
--- a/forge-gui/res/cardsfolder/n/necratog.txt
+++ b/forge-gui/res/cardsfolder/n/necratog.txt
@@ -3,5 +3,6 @@ ManaCost:1 B B
Types:Creature Atog
PT:1/2
A:AB$ Pump | Cost$ ExileFromGrave<1/Card.TopGraveyardCreature> | CostDesc$ Exile the top creature card of your graveyard: | NumAtt$ +2 | NumDef$ +2 | SpellDescription$ CARDNAME gets +2/+2 until end of turn.
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/necratog.jpg
Oracle:Exile the top creature card of your graveyard: Necratog gets +2/+2 until end of turn.
diff --git a/forge-gui/res/cardsfolder/n/nether_shadow.txt b/forge-gui/res/cardsfolder/n/nether_shadow.txt
index bcdd25b4..df37838e 100644
--- a/forge-gui/res/cardsfolder/n/nether_shadow.txt
+++ b/forge-gui/res/cardsfolder/n/nether_shadow.txt
@@ -6,5 +6,6 @@ K:Haste
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Graveyard | IsPresent$ Creature.YouOwn+Above | PresentZone$ Graveyard | PresentCompare$ GE3 | Execute$ TrigReturn | OptionalDecider$ You | TriggerDescription$ At the beginning of your upkeep, if CARDNAME is in your graveyard with three or more creature cards above it, you may put CARDNAME onto the battlefield.
SVar:TrigReturn:AB$ChangeZone | Cost$ 0 | Defined$ Self | Origin$ Graveyard | Destination$ Battlefield
SVar:DiscardMe:2
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/nether_shadow.jpg
Oracle:Haste\nAt the beginning of your upkeep, if Nether Shadow is in your graveyard with three or more creature cards above it, you may put Nether Shadow onto the battlefield.
diff --git a/forge-gui/res/cardsfolder/p/phyrexian_furnace.txt b/forge-gui/res/cardsfolder/p/phyrexian_furnace.txt
index 50d58b62..d27aeca6 100644
--- a/forge-gui/res/cardsfolder/p/phyrexian_furnace.txt
+++ b/forge-gui/res/cardsfolder/p/phyrexian_furnace.txt
@@ -5,5 +5,6 @@ A:AB$ ChangeZoneAll | Cost$ T | ValidTgts$ Player | TgtPrompt$ Select target pla
A:AB$ ChangeZone | Cost$ 1 Sac<1/CARDNAME> | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SubAbility$ DBDraw | SpellDescription$ Exile target card from a graveyard. Draw a card.
SVar:DBDraw:DB$Draw | NumCards$ 1
SVar:RemRandomDeck:True
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/phyrexian_furnace.jpg
Oracle:{T}: Exile the bottom card of target player's graveyard.\n{1}, Sacrifice Phyrexian Furnace: Exile target card from a graveyard. Draw a card.
diff --git a/forge-gui/res/cardsfolder/p/phyrexian_grimoire.txt b/forge-gui/res/cardsfolder/p/phyrexian_grimoire.txt
index fd395fe1..28484119 100644
--- a/forge-gui/res/cardsfolder/p/phyrexian_grimoire.txt
+++ b/forge-gui/res/cardsfolder/p/phyrexian_grimoire.txt
@@ -7,5 +7,6 @@ SVar:DBExile:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Graveyard | Destinat
SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Graveyard | Destination$ Hand | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:RemAIDeck:True
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/phyrexian_grimoire.jpg
Oracle:{4}, {T}: Target opponent chooses one of the top two cards of your graveyard. Exile that card and put the other one into your hand.
diff --git a/forge-gui/res/cardsfolder/s/shallow_grave.txt b/forge-gui/res/cardsfolder/s/shallow_grave.txt
index 480b9e96..f4e474bb 100644
--- a/forge-gui/res/cardsfolder/s/shallow_grave.txt
+++ b/forge-gui/res/cardsfolder/s/shallow_grave.txt
@@ -5,5 +5,6 @@ A:SP$ ChangeZone | Cost$ 1 B | Origin$ Graveyard | Destination$ Battlefield | Ch
SVar:DBPump:DB$ Animate | Keywords$ Haste | sVars$ SneakAttackEOT | Defined$ Remembered | AtEOT$ Exile | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:SneakAttackEOT:SVar:EndOfTurnLeavePlay:True
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/shallow_grave.jpg
Oracle:Return the top creature card of your graveyard to the battlefield. That creature gains haste until end of turn. Exile it at the beginning of the next end step.
diff --git a/forge-gui/res/cardsfolder/s/soldevi_digger.txt b/forge-gui/res/cardsfolder/s/soldevi_digger.txt
index 296d521f..cd1017f5 100644
--- a/forge-gui/res/cardsfolder/s/soldevi_digger.txt
+++ b/forge-gui/res/cardsfolder/s/soldevi_digger.txt
@@ -3,5 +3,6 @@ ManaCost:2
Types:Artifact
A:AB$ ChangeZone | Cost$ 2 | Origin$ Graveyard | Destination$ Library | LibraryPosition$ -1 | Hidden$ True | Mandatory$ True | ChangeType$ Card.TopGraveyard+YouOwn | SpellDescription$ Put the top card of your graveyard on the bottom of your library. | StackDescription$ Put the top card of your graveyard on the bottom of your library.
SVar:RemAIDeck:True
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/soldevi_digger.jpg
Oracle:{2}: Put the top card of your graveyard on the bottom of your library.
diff --git a/forge-gui/res/cardsfolder/s/spinning_darkness.txt b/forge-gui/res/cardsfolder/s/spinning_darkness.txt
index 285ff824..2b6a8d5e 100644
--- a/forge-gui/res/cardsfolder/s/spinning_darkness.txt
+++ b/forge-gui/res/cardsfolder/s/spinning_darkness.txt
@@ -5,5 +5,6 @@ SVar:AltCost:Cost$ ExileFromGrave<3/Card.Black+FromTopGrave> | Description$ You
A:SP$ DealDamage | Cost$ 4 B B | ValidTgts$ Creature.nonBlack | TgtPrompt$ Select target nonblack creature | NumDmg$ 3 | SubAbility$ DBGainLife | SpellDescription$ CARDNAME deals 3 damage to target nonblack creature. You gain 3 life.
SVar:DBGainLife:DB$ GainLife | LifeAmount$ 3
SVar:RemAIDeck:True
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/spinning_darkness.jpg
Oracle:You may exile the top three black cards of your graveyard rather than pay Spinning Darkness's mana cost.\nSpinning Darkness deals 3 damage to target nonblack creature. You gain 3 life.
diff --git a/forge-gui/res/cardsfolder/v/volraths_shapeshifter.txt b/forge-gui/res/cardsfolder/v/volraths_shapeshifter.txt
index e34aee10..9a5aaf51 100644
--- a/forge-gui/res/cardsfolder/v/volraths_shapeshifter.txt
+++ b/forge-gui/res/cardsfolder/v/volraths_shapeshifter.txt
@@ -4,5 +4,6 @@ Types:Creature Shapeshifter
PT:0/1
A:AB$ Discard | Cost$ 2 | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | AILogic$ VolrathsShapeshifter | SpellDescription$ Discard a card.
S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Battlefield | GainTextOf$ Creature.TopGraveyard+YouCtrl | GainedTextHasThisStaticAbility$ True | Description$ ORIGINALTEXTONLY:As long as the top card of your graveyard is a creature card, CARDNAME has the full text of that card and has the text "{2}: Discard a card." (CARDNAME has that card's name, mana cost, color, types, abilities, power, and toughness.)
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/volraths_shapeshifter.jpg
Oracle:As long as the top card of your graveyard is a creature card, Volrath's Shapeshifter has the full text of that card and has the text "{2}: Discard a card." (Volrath's Shapeshifter has that card's name, mana cost, color, types, abilities, power, and toughness.)
\ No newline at end of file
diff --git a/forge-gui/res/cardsfolder/z/zombie_scavengers.txt b/forge-gui/res/cardsfolder/z/zombie_scavengers.txt
index cdf6a0a8..3a83611b 100644
--- a/forge-gui/res/cardsfolder/z/zombie_scavengers.txt
+++ b/forge-gui/res/cardsfolder/z/zombie_scavengers.txt
@@ -3,5 +3,6 @@ ManaCost:2 B
Types:Creature Zombie
PT:3/1
A:AB$ Regenerate | Cost$ ExileFromGrave<1/Card.TopGraveyardCreature> | CostDesc$ Exile the top creature card of your graveyard: | SpellDescription$ Regenerate CARDNAME.
+SVar:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/zombie_scavengers.jpg
Oracle:Exile the top creature card of your graveyard: Regenerate Zombie Scavengers.
diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java
index bf0a2be7..09d9ef47 100644
--- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java
+++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java
@@ -687,7 +687,15 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
}
@Override
- public CardCollectionView orderMoveToZoneList(final CardCollectionView cards, final ZoneType destinationZone) {
+ public CardCollectionView orderMoveToZoneList(final CardCollectionView cards, final ZoneType destinationZone, final SpellAbility source) {
+ if (source == null || source.getApi() != ApiType.ReorderZone) {
+ if (destinationZone == ZoneType.Graveyard
+ && !FModel.getPreferences().getPrefBoolean(FPref.UI_ALLOW_ORDER_GRAVEYARD_WHEN_NEEDED) || !game.isGraveyardOrdered()) {
+ // Ordering not necessary
+ return cards;
+ }
+ }
+
List choices;
tempShowCards(cards);
switch (destinationZone) {
diff --git a/forge-gui/src/main/java/forge/properties/ForgePreferences.java b/forge-gui/src/main/java/forge/properties/ForgePreferences.java
index 519c4d45..32484c5e 100644
--- a/forge-gui/src/main/java/forge/properties/ForgePreferences.java
+++ b/forge-gui/src/main/java/forge/properties/ForgePreferences.java
@@ -97,6 +97,7 @@ public class ForgePreferences extends PreferencesStore {
UI_ROTATE_PLANE_OR_PHENOMENON("false"),
UI_DYNAMIC_PLANECHASE_BG("false"),
UI_DISABLE_IMAGES_EFFECT_CARDS("false"),
+ UI_ALLOW_ORDER_GRAVEYARD_WHEN_NEEDED ("false"),
UI_FOR_TOUCHSCREN("false"),